fido2-1.2.0/0000775000175000017500000000000014741676716012106 5ustar winniewinniefido2-1.2.0/COPYING0000644000175000017500000000243014721556664013133 0ustar winniewinnieCopyright (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-1.2.0/NEWS0000644000175000017500000002141614721556664012604 0ustar winniewinnie* Version 1.2.0 (released 2024-11-27) ** Improved extension handling: Several new extensions are now supported, both for Fido2Client and WindowsClient. Extension APIs have been redesigned, and old APIs have been deprecated, slated for removal in version 2.0. *** Disable hmac-secret extension by default, preferring prf. ** Improved (de-)serialization of dataclasses to/from JSON-friendly dicts. ** Fido2Client: *** Support allowCredentials/excludeCredentials of arbitrary length. *** Handle PUAT_REQUIRED by re-attempting with PIN/UV. ** Allow localhost (and subdomains) to use http:// in RP ID verification by default. ** NFC: Support for Authenticators that return SW=61XX on SELECT. ** USB: Improve connection recovery and use more specific exceptions for errors. ** Fix: Handle residentKey=preferred properly. ** Fix: Handle Authentictors that do not pass extensions in GetInfo. * Version 1.1.3 (released 2024-03-13) ** Fix USB HID issue on MacOS that sometimes caused a pause while waiting for a timeout. ** Fix argument to CredProp extension where an enum value was required instead of also allowing a string. ** Fix parsing of some key types (ES384, ES512) causing signature verification to fail. ** Deprecation: Calling websafe_decode with a bytes argument instead of str. This will raise a TypeError in the next major version of the library. * Version 1.1.2 (released 2023-07-06) ** Fix ClientPin usage for Authenticators that do not support passing a PIN. ** Fix: Handle correct CTAP response codes in authenticatorSelection. * Version 1.1.1 (released 2023-04-05) ** Add community provided support for NetBSD. ** Bugfix: Don't set length for largeBlob when offset is 0. ** Bugfix: Remove print statement in webauthn parsing. * Version 1.1.0 (released 2022-10-17) ** Bugfix: Fix name of "crossOrigin" in CollectedClientData.create(). ** Bugfix: Some incorrect type hints in the MDS3 classes were fixed. ** Stricter checking of dataclass field types. ** Add support for JSON-serialization of WebAuthn data classes. This changes the objects dict representation to align with new additions in the WebAuthn specification. As this may break compatibility, the new behavior requires explicit opt-in until python-fido2 2.0 is released. ** Update server example to use JSON serialization. ** Server: Add support for passing RegistrationResponse/AuthenticationResponse (or their deserialized JSON data) to register_complete/authenticate_complete. ** Add new "hybrid" AuthenticatorTransport. ** Add new AuthenticatorData flags, and use 2-letter names as in the WebAuthn spec (long names are still available as aliases). * Version 1.0.0 (released 2022-06-08) ** First stable release. * Version 1.0.0rc1 (released 2022-05-02) ** Release Candidate 1 of first stable release. ** Require Python 3.7 or later. ** APIs have updated to align with WebAuthn level 2. ** Several CTAP 2.1 features have been implemented. * Version 0.9.3 (released 2021-11-09) ** Bugfix: Linux - Don't fail device discovery when hidraw doesn't support HIDIOCGRAWUNIQ (Linux kernels before 5.6). * Version 0.9.2 (released 2021-10-14) ** Support the latest Windows webauthn.h API (included in Windows 11). ** Add product name and serial number to HidDescriptors. ** Remove the need for the uhid-freebsd dependency on FreeBSD. * Version 0.9.1 (released 2021-02-03) ** Add new CTAP error codes and improve handling of unknown codes. * Version 0.9.0 (released 2021-01-20) ** Server: Attestation is now done in two parts (to align better with the spec): First, type-specific validation is done to provide a trust chain. Second, validation of the trust chain is done. ** Client: API changes to better support extensions. *** Fido2Client can be configured with Ctap2Extensions to support. *** Client.make_credential now returns a AuthenticatorAttestationResponse, which holds the AttestationObject and ClientData, as well as any client extension results for the credential. *** Client.get_assertion now returns an AssertionSelection object, which is used to select between multiple assertions, resulting in an AuthenticatorAssertionResponse, which holds the ClientData, assertion values, as well as any client extension results for the assertion. ** Renames: The CTAP1 and CTAP2 classes have been renamed to Ctap1 and Ctap2, respectively. The old names currently work, but will be removed in the future. ** ClientPin: The ClientPin API has been restructured to support multiple PIN protocols, UV tokens, and token permissions. ** CTAP 2.1 PRE: Several new features have been added for CTAP 2.1, including Credential Management, Bio Enrollment, Large Blobs, and Authenticator Config. ** HID: The platform specific HID code has been revamped and cleaned up. * Version 0.8.1 (released 2019-11-25) ** Bugfix: WindowsClient.make_credential error when resident key requirement is unspecified. * Version 0.8.0 (released 2019-11-25) ** New fido2.webauthn classes modeled after the W3C WebAuthn spec introduced. ** CTAP2 send_cbor/make_credential/get_assertion and U2fClient request/authenticate `timeout` arguments replaced with `event` used to cancel a request. ** Fido2Client: *** make_credential/get_assertion now take WebAuthn options objects. *** timeout is now provided in ms in WebAuthn options objects. Event based cancelation also available by passing an Event. ** Fido2Server: *** ATTESTATION, USER_VERIFICATION, and AUTHENTICATOR_ATTACHMENT enums have been replaced with fido2.webauthn classes. *** RelyingParty has been replaced with PublicKeyCredentialRpEntity, and name is no longer optional. *** Options returned by register_begin/authenticate_begin now omit unspecified values if they are optional, instead of filling in default values. *** Fido2Server.allowed_algorithms now contains a list of PublicKeyCredentialParameters instead of algorithm identifiers. *** Fido2Server.timeout is now in ms and of type int. ** Support native WebAuthn API on Windows through WindowsClient. * Version 0.7.3 (released 2019-10-24) ** Bugfix: Workaround for size of int on Python 2 on Windows. * Version 0.7.2 (released 2019-10-24) ** Support for the TPM attestation format. ** Allow passing custom challenges to register/authenticate in Fido2Server. ** Bugfix: CTAP2 CANCEL command response handling fixed. ** Bugfix: Fido2Client fix handling of empty allow_list. ** Bugfix: Fix typo in CTAP2.get_assertions() causing it to fail. * Version 0.7.1 (released 2019-09-20) ** Support for FreeBSD. ** Enforce canonical CBOR on Authenticator responses by default. ** PCSC: Support extended APDUs. ** Server: Verify that UP flag is set. ** U2FFido2Server: Implement AppID exclusion extension. ** U2FFido2Server: Allow custom U2F facet verification. ** Bugfix: U2FFido2Server.authenticate_complete now returns the result. * Version 0.7.0 (released 2019-06-17) ** Add support for NFC devices using PCSC. ** Add support for the hmac-secret Authenticator extension. ** Honor max credential ID length and number of credentials to Authenticator. ** Add close() method to CTAP devices to explicitly release their resources. * Version 0.6.0 (released 2019-05-10) ** Don't fail if CTAP2 Info contains unknown fields. ** Replace cbor loads/dumps functions with encode/decode/decode_from. ** Server: Add support for AuthenticatorAttachment. ** Server: Add support for more key algorithms. ** Client: Expose CTAP2 Info object as Fido2Client.info. * Version 0.5.0 (released 2018-12-21) ** Changes to server classes, some backwards breaking. ** Add ability to authenticate U2F credentials by using the appid extension. ** Make verification of attestation more explicit. ** Add support for Android SafetyNet attestation. ** Make it easier to work with U2F/CTAP1 data formats. * Version 0.4.0 (released 2018-09-27) ** Add classes for implementing a server. ** Various small changes, some affecting backwards compatibility. * Version 0.3.0 (released 2018-04-13) ** Add conversion between string/int keys for AttestationObject. ** Replace internal Exceptions with built-in types. ** Bugfix: Don't use TimeoutError which isn't available on Python 2. * Version 0.2.2 (released 2018-04-11) ** Bugfix: Better handling of unplugged devices on MacOS and avoid leaking threads. * Version 0.2.1 (released 2018-04-10) ** Add server example. ** Parse AttestationObjects that use string keys (Webauthn). ** Fix bug in handling packets with the wrong channel id. * Version 0.2.0 (released 2018-04-05) ** Changed name of project to python-fido2 to better reflect its scope. ** Added attestation and assertion verification methods. ** A lot of name changes, moved classes, etc. ** New example for multi-device use. * Version 0.1.0 (released 2018-03-16) ** First beta release. fido2-1.2.0/README.adoc0000644000175000017500000000764014721556664013675 0ustar winniewinnie== python-fido2 image:https://github.com/Yubico/python-fido2/workflows/build/badge.svg["Github actions build", link="https://github.com/Yubico/python-fido2/actions"] Provides library functionality for communicating with a FIDO device over USB as well as verifying attestation and assertion signatures. This library aims to support the FIDO U2F and FIDO 2 protocols for communicating with a USB authenticator via the Client-to-Authenticator Protocol (CTAP 1 and 2). In addition to this low-level device access, classes defined in the `fido2.client` and `fido2.server` modules implement higher level operations which are useful when interfacing with an Authenticator, or when implementing WebAuthn support for a Relying Party. For usage, see the `examples/` directory. === References These links related to WebAuthn and FIDO2 can help you get started: * Yubico WebAuthn/FIDO2 guide: https://developers.yubico.com/FIDO2/ * W3C WebAuthn specification: https://www.w3.org/TR/webauthn/ * FIDO specifications: https://fidoalliance.org/specifications/download/ === License This project, with the exception of the files mentioned below, is licensed under the BSD 2-clause license. See the _COPYING_ file for the full license text. This project contains source code from pyu2f (https://github.com/google/pyu2f) which is licensed under the Apache License, version 2.0. These files are located in `fido2/hid/`. See http://www.apache.org/licenses/LICENSE-2.0, or the _COPYING.APLv2_ file for the full license text. This project also bundles the public suffix list (https://publicsuffix.org) which is licensed under the Mozilla Public License, version 2.0. This file is stored as `fido2/public_suffix_list.dat`. See https://mozilla.org/MPL/2.0/, or the _COPYING.MPLv2_ file for the full license text. === Requirements fido2 is compatible with Python 3.7 and later, and is tested on Windows, MacOS, and Linux. Support for OpenBSD, FreeBSD, and NetBSD is provided as-is and relies on community contributions. === Installation fido2 is installable by running the following command: pip install fido2 To install the dependencies required for communication with NFC authenticators, instead use: pip install fido2[pcsc] Under Windows 10 (1903 or later) access to FIDO devices is restricted and requires running as Administrator. This library can still be used when running as non-administrator, via the `fido.client.WindowsClient` class. An example of this is included in the file `examples/credential.py`. Under Linux you will need to add a Udev rule to be able to access the FIDO device, or run as root. For example, the Udev rule may contain the following: ---- #Udev rule for allowing HID access to Yubico devices for FIDO support. KERNEL=="hidraw*", SUBSYSTEM=="hidraw", \ MODE="0664", GROUP="plugdev", ATTRS{idVendor}=="1050" ---- There may be a package already available for your distribution that does this for you, see: https://support.yubico.com/hc/en-us/articles/360013708900-Using-Your-U2F-YubiKey-with-Linux Under FreeBSD you will either need to run as root or add rules for your device to /etc/devd.conf, which can be automated by installing security/u2f-devd: # pkg install u2f-devd ==== Dependencies This project depends on Cryptography. For instructions on installing this dependency, see https://cryptography.io/en/latest/installation/. NFC support is optionally available via PC/SC, using the pyscard library. For instructions on installing this dependency, see https://github.com/LudovicRousseau/pyscard/blob/master/INSTALL.md. === Development For development of the library we use https://python-poetry.org/[poetry]. To set up the dev environment, run this command in the root directory of the repository: poetry install We also use https://pre-commit.com/[pre-commit] to run some scans on the code prior to committing. ==== Running tests While many tests can run on their own, some require a connected U2F or FIDO2 device to run. poetry run pytest fido2-1.2.0/pyproject.toml0000644000175000017500000000332414721556664015017 0ustar winniewinnie[tool.poetry] name = "fido2" version = "1.2.0" description = "FIDO2/WebAuthn library for implementing clients and servers." authors = ["Dain Nilsson "] homepage = "https://github.com/Yubico/python-fido2" repository = "https://github.com/Yubico/python-fido2" keywords = ["fido2", "webauthn", "ctap", "u2f"] classifiers = [ "License :: OSI Approved :: BSD License", "License :: OSI Approved :: Apache Software License", "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Intended Audience :: System Administrators", "Topic :: Internet", "Topic :: Security :: Cryptography", "Topic :: Software Development :: Libraries :: Python Modules" ] include = [ { path = "COPYING", format = "sdist"}, { path = "COPYING.MPLv2", format = "sdist"}, { path = "COPYING.APLv2", format = "sdist"}, { path = "NEWS", format = "sdist"}, { path = "README.adoc", format = "sdist"}, { path= "tests/", format = "sdist"}, { path= "examples/", format = "sdist"}, ] [tool.poetry.dependencies] python = "^3.8" cryptography = ">=2.6, !=35, <45" pyscard = {version = "^1.9 || ^2", optional = true} [tool.poetry.extras] pcsc = ["pyscard"] [tool.poetry.dev-dependencies] pytest = "^7.0" Sphinx = {version = "^8.1", python = ">=3.10"} sphinx-rtd-theme = {version = "^3.0.1", python = ">=3.10"} sphinx-autoapi = {version = "^3.3.3", python = ">=3.10"} [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.pytest.ini_options] testpaths = ["tests"] fido2-1.2.0/COPYING.MPLv20000644000175000017500000004052614721556664014042 0ustar winniewinnieMozilla 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-1.2.0/PKG-INFO0000644000175000017500000000265000000000000013122 0ustar winniewinnieMetadata-Version: 2.1 Name: fido2 Version: 1.2.0 Summary: FIDO2/WebAuthn library for implementing clients and servers. Home-page: https://github.com/Yubico/python-fido2 Keywords: fido2,webauthn,ctap,u2f Author: Dain Nilsson Author-email: dain@yubico.com Requires-Python: >=3.8,<4.0 Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: License :: OSI Approved :: BSD License Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) Classifier: Operating System :: MacOS Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Topic :: Internet Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Software Development :: Libraries :: Python Modules Provides-Extra: pcsc Requires-Dist: cryptography (>=2.6,!=35,<45) Requires-Dist: pyscard (>=1.9,<3) ; extra == "pcsc" Project-URL: Repository, https://github.com/Yubico/python-fido2 fido2-1.2.0/tests/0000775000175000017500000000000014741676716013250 5ustar winniewinniefido2-1.2.0/tests/test_client.py0000644000175000017500000002266014721556664016140 0ustar winniewinnie# coding=utf-8 # Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import unittest from unittest import mock from fido2 import cbor from fido2.utils import sha256 from fido2.hid import CAPABILITY from fido2.ctap import CtapError from fido2.ctap1 import RegistrationData from fido2.ctap2 import Info, AttestationResponse from fido2.client import ClientError, Fido2Client from fido2.webauthn import ( PublicKeyCredentialCreationOptions, AttestationObject, CollectedClientData, ) APP_ID = "https://foo.example.com" REG_DATA = RegistrationData( bytes.fromhex( "0504b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6d9402a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c253082013c3081e4a003020102020a47901280001155957352300a06082a8648ce3d0403023017311530130603550403130c476e756262792050696c6f74301e170d3132303831343138323933325a170d3133303831343138323933325a3031312f302d0603550403132650696c6f74476e756262792d302e342e312d34373930313238303030313135353935373335323059301306072a8648ce3d020106082a8648ce3d030107034200048d617e65c9508e64bcc5673ac82a6799da3c1446682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4199edd7862f23abaf0203b4b8911ba0569994e101300a06082a8648ce3d0403020347003044022060cdb6061e9c22262d1aac1d96d8c70829b2366531dda268832cb836bcd30dfa0220631b1459f09e6330055722c8d89b7f48883b9089b88d60d1d9795902b30410df304502201471899bcc3987e62e8202c9b39c33c19033f7340352dba80fcab017db9230e402210082677d673d891933ade6f617e5dbde2e247e70423fd5ad7804a6d3d3961ef871" # noqa E501 ) ) rp = {"id": "example.com", "name": "Example RP"} user = {"id": b"user_id", "name": "A. User"} challenge = b"Y2hhbGxlbmdl" _INFO_NO_PIN = bytes.fromhex( "a60182665532465f5632684649444f5f325f3002826375766d6b686d61632d7365637265740350f8a011f38c0a4d15800617111f9edc7d04a462726bf5627570f564706c6174f469636c69656e7450696ef4051904b0068101" # noqa E501 ) _MC_RESP = bytes.fromhex( "a301667061636b6564025900c40021f5fc0b85cd22e60623bcd7d1ca48948909249b4776eb515154e57b66ae12410000001cf8a011f38c0a4d15800617111f9edc7d0040fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b15783a5010203262001215820643566c206dd00227005fa5de69320616ca268043a38f08bde2e9dc45a5cafaf225820171353b2932434703726aae579fa6542432861fe591e481ea22d63997e1a529003a363616c67266373696758483046022100cc1ef43edf07de8f208c21619c78a565ddcf4150766ad58781193be8e0a742ed022100f1ed7c7243e45b7d8e5bda6b1abf10af7391789d1ef21b70bd69fed48dba4cb163783563815901973082019330820138a003020102020900859b726cb24b4c29300a06082a8648ce3d0403023047310b300906035504061302555331143012060355040a0c0b59756269636f205465737431223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e301e170d3136313230343131353530305a170d3236313230323131353530305a3047310b300906035504061302555331143012060355040a0c0b59756269636f205465737431223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3059301306072a8648ce3d020106082a8648ce3d03010703420004ad11eb0e8852e53ad5dfed86b41e6134a18ec4e1af8f221a3c7d6e636c80ea13c3d504ff2e76211bb44525b196c44cb4849979cf6f896ecd2bb860de1bf4376ba30d300b30090603551d1304023000300a06082a8648ce3d0403020349003046022100e9a39f1b03197525f7373e10ce77e78021731b94d0c03f3fda1fd22db3d030e7022100c4faec3445a820cf43129cdb00aabefd9ae2d874f9c5d343cb2f113da23723f3" # noqa E501 ) class TestFido2Client(unittest.TestCase): def test_ctap1_info(self): dev = mock.Mock() dev.capabilities = 0 client = Fido2Client(dev, APP_ID) self.assertEqual(client.info.versions, ["U2F_V2"]) self.assertEqual(client.info.pin_uv_protocols, []) @mock.patch("fido2.client.Ctap2") def test_make_credential_wrong_app_id(self, PatchedCtap2): dev = mock.Mock() dev.capabilities = CAPABILITY.CBOR ctap2 = mock.MagicMock() ctap2.get_info.return_value = Info.from_dict(cbor.decode(_INFO_NO_PIN)) PatchedCtap2.return_value = ctap2 client = Fido2Client(dev, APP_ID) try: client.make_credential( PublicKeyCredentialCreationOptions( {"id": "bar.example.com", "name": "Invalid RP"}, user, challenge, [{"type": "public-key", "alg": -7}], ) ) self.fail("make_credential did not raise error") except ClientError as e: self.assertEqual(e.code, ClientError.ERR.BAD_REQUEST) @mock.patch("fido2.client.Ctap2") def test_make_credential_existing_key(self, PatchedCtap2): dev = mock.Mock() dev.capabilities = CAPABILITY.CBOR ctap2 = mock.MagicMock() ctap2.get_info.return_value = Info.from_dict(cbor.decode(_INFO_NO_PIN)) ctap2.info = ctap2.get_info() ctap2.make_credential.side_effect = CtapError(CtapError.ERR.CREDENTIAL_EXCLUDED) PatchedCtap2.return_value = ctap2 client = Fido2Client(dev, APP_ID) try: client.make_credential( PublicKeyCredentialCreationOptions( rp, user, challenge, [{"type": "public-key", "alg": -7}], authenticator_selection={"userVerification": "discouraged"}, ) ) self.fail("make_credential did not raise error") except ClientError as e: self.assertEqual(e.code, ClientError.ERR.DEVICE_INELIGIBLE) ctap2.make_credential.assert_called_once() @mock.patch("fido2.client.Ctap2") def test_make_credential_ctap2(self, PatchedCtap2): dev = mock.Mock() dev.capabilities = CAPABILITY.CBOR ctap2 = mock.MagicMock() ctap2.get_info.return_value = Info.from_dict(cbor.decode(_INFO_NO_PIN)) ctap2.info = ctap2.get_info() ctap2.make_credential.return_value = AttestationResponse.from_dict( cbor.decode(_MC_RESP) ) PatchedCtap2.return_value = ctap2 client = Fido2Client(dev, APP_ID) response = client.make_credential( PublicKeyCredentialCreationOptions( rp, user, challenge, [{"type": "public-key", "alg": -7}], timeout=1000, authenticator_selection={"userVerification": "discouraged"}, ) ) self.assertIsInstance(response.attestation_object, AttestationObject) self.assertIsInstance(response.client_data, CollectedClientData) ctap2.make_credential.assert_called_with( response.client_data.hash, rp, user, [{"type": "public-key", "alg": -7}], None, None, None, None, None, None, event=mock.ANY, on_keepalive=mock.ANY, ) self.assertEqual(response.client_data.origin, APP_ID) self.assertEqual(response.client_data.type, "webauthn.create") self.assertEqual(response.client_data.challenge, challenge) def test_make_credential_ctap1(self): dev = mock.Mock() dev.capabilities = 0 # No CTAP2 client = Fido2Client(dev, APP_ID) ctap1_mock = mock.MagicMock() ctap1_mock.get_version.return_value = "U2F_V2" ctap1_mock.register.return_value = REG_DATA client._backend.ctap1 = ctap1_mock response = client.make_credential( PublicKeyCredentialCreationOptions( rp, user, challenge, [{"type": "public-key", "alg": -7}] ) ) self.assertIsInstance(response.attestation_object, AttestationObject) self.assertIsInstance(response.client_data, CollectedClientData) client_data = response.client_data ctap1_mock.register.assert_called_with( client_data.hash, sha256(rp["id"].encode()) ) self.assertEqual(client_data.origin, APP_ID) self.assertEqual(client_data.type, "webauthn.create") self.assertEqual(client_data.challenge, challenge) self.assertEqual(response.attestation_object.fmt, "fido-u2f") fido2-1.2.0/tests/test_webauthn.py0000644000175000017500000002772514721556664016506 0ustar winniewinnie# Copyright (c) 2019 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from fido2.webauthn import ( Aaguid, AuthenticatorSelectionCriteria, CollectedClientData, ResidentKeyRequirement, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity, PublicKeyCredentialParameters, PublicKeyCredentialDescriptor, PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions, ) from fido2.utils import websafe_encode import unittest import json class TestAaguid(unittest.TestCase): def test_aaguid(self): bs = b"\1" * 16 a = Aaguid(bs) assert a assert a == bs assert bs == a def test_aaguid_none(self): a = Aaguid(b"\0" * 16) assert not a assert a == Aaguid.NONE assert Aaguid.NONE == a def test_aaguid_wrong_length(self): with self.assertRaises(ValueError): Aaguid(b"1234") with self.assertRaises(ValueError): Aaguid.fromhex("11" * 15) with self.assertRaises(ValueError): Aaguid(b"\2" * 17) def test_aaguid_parse(self): a = Aaguid.parse("00000000-0000-0000-0000-000000000000") assert a == Aaguid.NONE b = Aaguid.parse("01020304-0102-0304-0506-010203040506") assert b == Aaguid.fromhex("01020304010203040506010203040506") assert b == Aaguid(bytes.fromhex("01020304010203040506010203040506")) class TestWebAuthnDataTypes(unittest.TestCase): def test_collected_client_data(self): o = CollectedClientData( b'{"type":"webauthn.create","challenge":"cdySOP-1JI4J_BpOeO9ut25rlZJueF16aO6auTTYAis","origin":"https://demo.yubico.com","crossOrigin":false}' # noqa ) assert o.type == "webauthn.create" assert o.origin == "https://demo.yubico.com" assert o.challenge == bytes.fromhex( "71dc9238ffb5248e09fc1a4e78ef6eb76e6b95926e785d7a68ee9ab934d8022b" ) assert o.cross_origin is False assert ( o.b64 == "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiY2R5U09QLTFKSTRKX0JwT2VPOXV0MjVybFpKdWVGMTZhTzZhdVRUWUFpcyIsIm9yaWdpbiI6Imh0dHBzOi8vZGVtby55dWJpY28uY29tIiwiY3Jvc3NPcmlnaW4iOmZhbHNlfQ" # noqa ) assert o.hash == bytes.fromhex( "8b20a0b904b4747aacae71d55bf60b4eb2583f7e639f55f40baac23c2600c178" ) assert o == CollectedClientData.create( "webauthn.create", "cdySOP-1JI4J_BpOeO9ut25rlZJueF16aO6auTTYAis", "https://demo.yubico.com", ) o = CollectedClientData.create( "webauthn.create", "cdySOP-1JI4J_BpOeO9ut25rlZJueF16aO6auTTYAis", "https://demo.yubico.com", True, ) assert o.cross_origin is True def test_authenticator_selection_criteria(self): o = AuthenticatorSelectionCriteria( "platform", require_resident_key=True, user_verification="required" ) self.assertEqual( dict(o), { "authenticatorAttachment": "platform", "requireResidentKey": True, "residentKey": "required", "userVerification": "required", }, ) self.assertEqual(o.authenticator_attachment, "platform") self.assertEqual(o.require_resident_key, True) self.assertEqual(o.user_verification, "required") self.assertIsNone( AuthenticatorSelectionCriteria( authenticator_attachment="invalid" ).authenticator_attachment ) self.assertIsNone( AuthenticatorSelectionCriteria( user_verification="invalid" ).user_verification ) self.assertEqual( AuthenticatorSelectionCriteria(resident_key="invalid").resident_key, "discouraged", ) o = AuthenticatorSelectionCriteria() self.assertEqual(o.resident_key, "discouraged") self.assertEqual(o.require_resident_key, False) self.assertIsNone(o.authenticator_attachment) self.assertIsNone(o.user_verification) o = AuthenticatorSelectionCriteria(require_resident_key=True) self.assertEqual(o.resident_key, ResidentKeyRequirement.REQUIRED) self.assertEqual(o.require_resident_key, True) o = AuthenticatorSelectionCriteria(resident_key=False) self.assertEqual(o.require_resident_key, False) o = AuthenticatorSelectionCriteria(resident_key="required") self.assertEqual(o.resident_key, ResidentKeyRequirement.REQUIRED) self.assertEqual(o.require_resident_key, True) o = AuthenticatorSelectionCriteria(resident_key="preferred") self.assertEqual(o.resident_key, ResidentKeyRequirement.PREFERRED) self.assertEqual(o.require_resident_key, False) o = AuthenticatorSelectionCriteria(resident_key="discouraged") self.assertEqual(o.resident_key, ResidentKeyRequirement.DISCOURAGED) self.assertEqual(o.require_resident_key, False) def test_rp_entity(self): o = PublicKeyCredentialRpEntity("Example", "example.com") self.assertEqual(o, {"id": "example.com", "name": "Example"}) self.assertEqual(o.id, "example.com") self.assertEqual(o.name, "Example") with self.assertRaises(TypeError): PublicKeyCredentialRpEntity(id="example.com") with self.assertRaises(TypeError): PublicKeyCredentialRpEntity() def test_user_entity(self): o = PublicKeyCredentialUserEntity("Example", b"user", display_name="Display") self.assertEqual( o, { "id": websafe_encode(b"user"), "name": "Example", "displayName": "Display", }, ) self.assertEqual(o.id, b"user") self.assertEqual(o.name, "Example") self.assertEqual(o.display_name, "Display") with self.assertRaises(TypeError): PublicKeyCredentialUserEntity(name=b"user") with self.assertRaises(TypeError): PublicKeyCredentialUserEntity() def test_parameters(self): o = PublicKeyCredentialParameters("public-key", -7) self.assertEqual(o, {"type": "public-key", "alg": -7}) self.assertEqual(o.type, "public-key") self.assertEqual(o.alg, -7) p = PublicKeyCredentialParameters("invalid-type", -7) assert p.type is None with self.assertRaises(TypeError): PublicKeyCredentialParameters("public-key") with self.assertRaises(TypeError): PublicKeyCredentialParameters() def test_descriptor(self): o = PublicKeyCredentialDescriptor("public-key", b"credential_id") self.assertEqual( o, {"type": "public-key", "id": websafe_encode(b"credential_id")} ) self.assertEqual(o.type, "public-key") self.assertEqual(o.id, b"credential_id") self.assertIsNone(o.transports) o = PublicKeyCredentialDescriptor( "public-key", b"credential_id", ["usb", "nfc"] ) self.assertEqual( o, { "type": "public-key", "id": websafe_encode(b"credential_id"), "transports": ["usb", "nfc"], }, ) self.assertEqual(o.transports, ["usb", "nfc"]) PublicKeyCredentialDescriptor("public-key", b"credential_id", ["valid_value"]) d = PublicKeyCredentialDescriptor("wrong-type", b"credential_id") assert d.type is None with self.assertRaises(TypeError): PublicKeyCredentialDescriptor("public-key") with self.assertRaises(TypeError): PublicKeyCredentialDescriptor() def test_creation_options(self): o = PublicKeyCredentialCreationOptions( PublicKeyCredentialRpEntity(id="example.com", name="Example"), PublicKeyCredentialUserEntity(id=b"user_id", name="A. User"), b"request_challenge", [{"type": "public-key", "alg": -7}], 10000, [{"type": "public-key", "id": b"credential_id"}], { "authenticatorAttachment": "platform", "residentKey": "required", "userVerification": "required", }, "direct", ) self.assertEqual(o.rp, {"id": "example.com", "name": "Example"}) self.assertEqual(o.user, {"id": websafe_encode(b"user_id"), "name": "A. User"}) self.assertIsNone(o.extensions) js = json.dumps(dict(o)) o2 = PublicKeyCredentialCreationOptions.from_dict(json.loads(js)) self.assertEqual(o, o2) o = PublicKeyCredentialCreationOptions.from_dict( { "rp": {"id": "example.com", "name": "Example"}, "user": {"id": websafe_encode(b"user_id"), "name": "A. User"}, "challenge": websafe_encode(b"request_challenge"), "pubKeyCredParams": [{"type": "public-key", "alg": -7}], } ) self.assertEqual(o.user.id, b"user_id") self.assertEqual(o.challenge, b"request_challenge"), self.assertIsNone(o.timeout) self.assertIsNone(o.authenticator_selection) self.assertIsNone(o.attestation) self.assertIsNone( PublicKeyCredentialCreationOptions( {"id": "example.com", "name": "Example"}, {"id": b"user_id", "name": "A. User"}, b"request_challenge", [{"type": "public-key", "alg": -7}], attestation="invalid", ).attestation ) js = json.dumps(dict(o)) o2 = PublicKeyCredentialCreationOptions.from_dict(json.loads(js)) self.assertEqual(o, o2) def test_request_options(self): o = PublicKeyCredentialRequestOptions( b"request_challenge", 10000, "example.com", [PublicKeyCredentialDescriptor(type="public-key", id=b"credential_id")], "discouraged", ) self.assertEqual(o.challenge, b"request_challenge") self.assertEqual(o.rp_id, "example.com") self.assertEqual(o.timeout, 10000) self.assertIsNone(o.extensions) js = json.dumps(dict(o)) o2 = PublicKeyCredentialRequestOptions.from_dict(json.loads(js)) self.assertEqual(o, o2) o = PublicKeyCredentialRequestOptions(b"request_challenge") self.assertIsNone(o.timeout) self.assertIsNone(o.rp_id) self.assertIsNone(o.allow_credentials) self.assertIsNone(o.user_verification) self.assertIsNone( PublicKeyCredentialRequestOptions( b"request_challenge", user_verification="invalid" ).user_verification ) fido2-1.2.0/tests/test_ctap1.py0000644000175000017500000001754314721556664015676 0ustar winniewinnie# Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import unittest from unittest import mock from fido2.ctap1 import Ctap1, ApduError class TestCtap1(unittest.TestCase): def test_send_apdu_ok(self): ctap = Ctap1(mock.MagicMock()) ctap.device.call.return_value = b"response\x90\x00" self.assertEqual(b"response", ctap.send_apdu(1, 2, 3, 4, b"foobar")) ctap.device.call.assert_called_with(0x03, b"\1\2\3\4\0\0\6foobar\0\0") def test_send_apdu_err(self): ctap = Ctap1(mock.MagicMock()) ctap.device.call.return_value = b"err\x6a\x80" try: ctap.send_apdu(1, 2, 3, 4, b"foobar") self.fail("send_apdu did not raise error") except ApduError as e: self.assertEqual(e.code, 0x6A80) self.assertEqual(e.data, b"err") ctap.device.call.assert_called_with(0x03, b"\1\2\3\4\0\0\6foobar\0\0") def test_get_version(self): ctap = Ctap1(mock.MagicMock()) ctap.device.call.return_value = b"U2F_V2\x90\x00" self.assertEqual("U2F_V2", ctap.get_version()) ctap.device.call.assert_called_with(0x03, b"\0\3\0\0\0\0\0\0\0") def test_register(self): ctap = Ctap1(mock.MagicMock()) ctap.device.call.return_value = ( bytes.fromhex( "0504b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6d9402a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c253082013c3081e4a003020102020a47901280001155957352300a06082a8648ce3d0403023017311530130603550403130c476e756262792050696c6f74301e170d3132303831343138323933325a170d3133303831343138323933325a3031312f302d0603550403132650696c6f74476e756262792d302e342e312d34373930313238303030313135353935373335323059301306072a8648ce3d020106082a8648ce3d030107034200048d617e65c9508e64bcc5673ac82a6799da3c1446682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4199edd7862f23abaf0203b4b8911ba0569994e101300a06082a8648ce3d0403020347003044022060cdb6061e9c22262d1aac1d96d8c70829b2366531dda268832cb836bcd30dfa0220631b1459f09e6330055722c8d89b7f48883b9089b88d60d1d9795902b30410df304502201471899bcc3987e62e8202c9b39c33c19033f7340352dba80fcab017db9230e402210082677d673d891933ade6f617e5dbde2e247e70423fd5ad7804a6d3d3961ef871" # noqa E501 ) + b"\x90\x00" ) client_param = bytes.fromhex( "4142d21c00d94ffb9d504ada8f99b721f4b191ae4e37ca0140f696b6983cfacb" ) app_param = bytes.fromhex( "f0e6a6a97042a4f1f1c87f5f7d44315b2d852c2df5c7991cc66241bf7072d1c4" ) resp = ctap.register(client_param, app_param) ctap.device.call.assert_called_with( 0x03, b"\0\1\0\0\0\0\x40" + client_param + app_param + b"\0\0" ) self.assertEqual( resp.public_key, bytes.fromhex( "04b174bc49c7ca254b70d2e5c207cee9cf174820ebd77ea3c65508c26da51b657c1cc6b952f8621697936482da0a6d3d3826a59095daf6cd7c03e2e60385d2f6d9" # noqa E501 ), ) self.assertEqual( resp.key_handle, bytes.fromhex( "2a552dfdb7477ed65fd84133f86196010b2215b57da75d315b7b9e8fe2e3925a6019551bab61d16591659cbaf00b4950f7abfe6660e2e006f76868b772d70c25" # noqa E501 ), ) self.assertEqual( resp.certificate, bytes.fromhex( "3082013c3081e4a003020102020a47901280001155957352300a06082a8648ce3d0403023017311530130603550403130c476e756262792050696c6f74301e170d3132303831343138323933325a170d3133303831343138323933325a3031312f302d0603550403132650696c6f74476e756262792d302e342e312d34373930313238303030313135353935373335323059301306072a8648ce3d020106082a8648ce3d030107034200048d617e65c9508e64bcc5673ac82a6799da3c1446682c258c463fffdf58dfd2fa3e6c378b53d795c4a4dffb4199edd7862f23abaf0203b4b8911ba0569994e101300a06082a8648ce3d0403020347003044022060cdb6061e9c22262d1aac1d96d8c70829b2366531dda268832cb836bcd30dfa0220631b1459f09e6330055722c8d89b7f48883b9089b88d60d1d9795902b30410df" # noqa E501 ), ) self.assertEqual( resp.signature, bytes.fromhex( "304502201471899bcc3987e62e8202c9b39c33c19033f7340352dba80fcab017db9230e402210082677d673d891933ade6f617e5dbde2e247e70423fd5ad7804a6d3d3961ef871" # noqa E501 ), ) resp.verify(app_param, client_param) def test_authenticate(self): ctap = Ctap1(mock.MagicMock()) ctap.device.call.return_value = ( bytes.fromhex( "0100000001304402204b5f0cd17534cedd8c34ee09570ef542a353df4436030ce43d406de870b847780220267bb998fac9b7266eb60e7cb0b5eabdfd5ba9614f53c7b22272ec10047a923f" # noqa E501 ) + b"\x90\x00" ) client_param = bytes.fromhex( "ccd6ee2e47baef244d49a222db496bad0ef5b6f93aa7cc4d30c4821b3b9dbc57" ) app_param = bytes.fromhex( "4b0be934baebb5d12d26011b69227fa5e86df94e7d94aa2949a89f2d493992ca" ) key_handle = b"\3" * 64 resp = ctap.authenticate(client_param, app_param, key_handle) ctap.device.call.assert_called_with( 0x03, b"\0\2\3\0\0\0\x81" + client_param + app_param + b"\x40" + key_handle + b"\0\0", ) self.assertEqual(resp.user_presence, 1) self.assertEqual(resp.counter, 1) self.assertEqual( resp.signature, bytes.fromhex( "304402204b5f0cd17534cedd8c34ee09570ef542a353df4436030ce43d406de870b847780220267bb998fac9b7266eb60e7cb0b5eabdfd5ba9614f53c7b22272ec10047a923f" # noqa E501 ), ) public_key = bytes.fromhex( "04d368f1b665bade3c33a20f1e429c7750d5033660c019119d29aa4ba7abc04aa7c80a46bbe11ca8cb5674d74f31f8a903f6bad105fb6ab74aefef4db8b0025e1d" # noqa E501 ) resp.verify(app_param, client_param, public_key) key_handle = b"\4" * 8 ctap.authenticate(client_param, app_param, key_handle) ctap.device.call.assert_called_with( 0x03, b"\0\2\3\0\0\0\x49" + client_param + app_param + b"\x08" + key_handle + b"\0\0", ) ctap.authenticate(client_param, app_param, key_handle, True) ctap.device.call.assert_called_with( 0x03, b"\0\2\7\0\0\0\x49" + client_param + app_param + b"\x08" + key_handle + b"\0\0", ) fido2-1.2.0/tests/hid/0000775000175000017500000000000014741676716014014 5ustar winniewinniefido2-1.2.0/tests/hid/test_base.py0000644000175000017500000000141014721556664016326 0ustar winniewinnieimport unittest from fido2.hid.base import parse_report_descriptor class TestBase(unittest.TestCase): def test_parse_report_descriptor_1(self): max_in_size, max_out_size = parse_report_descriptor( bytes.fromhex( "06d0f10901a1010920150026ff007508954081020921150026ff00750895409102c0" ) ) self.assertEqual(max_in_size, 64) self.assertEqual(max_out_size, 64) def test_parse_report_descriptor_2(self): with self.assertRaises(ValueError): parse_report_descriptor( bytes.fromhex( "05010902a1010901a10005091901290515002501950575018102950175038101" "05010930093109381581257f750895038106c0c0" ) ) fido2-1.2.0/tests/hid/__init__.py0000644000175000017500000000000014721556664016106 0ustar winniewinniefido2-1.2.0/tests/test_attestation.py0000644000175000017500000007536214721556664017230 0ustar winniewinnie# Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from fido2.webauthn import AuthenticatorData from fido2.attestation import ( Attestation, AttestationType, UnsupportedAttestation, FidoU2FAttestation, PackedAttestation, TpmAttestation, NoneAttestation, AndroidSafetynetAttestation, AppleAttestation, InvalidData, InvalidSignature, UnsupportedType, verify_x509_chain, ) from cryptography.exceptions import UnsupportedAlgorithm, _Reasons import unittest # GS Root R2 (https://pki.goog/) _GSR2_DER = bytes.fromhex( "308203ba308202a2a003020102020b0400000000010f8626e60d300d06092a864886f70d0101050500304c3120301e060355040b1317476c6f62616c5369676e20526f6f74204341202d20523231133011060355040a130a476c6f62616c5369676e311330110603550403130a476c6f62616c5369676e301e170d3036313231353038303030305a170d3231313231353038303030305a304c3120301e060355040b1317476c6f62616c5369676e20526f6f74204341202d20523231133011060355040a130a476c6f62616c5369676e311330110603550403130a476c6f62616c5369676e30820122300d06092a864886f70d01010105000382010f003082010a0282010100a6cf240ebe2e6f28994542c4ab3e21549b0bd37f8470fa12b3cbbf875fc67f86d3b2305cd6fdadf17bdce5f86096099210f5d053defb7b7e7388ac52887b4aa6ca49a65ea8a78c5a11bc7a82ebbe8ce9b3ac962507974a992a072fb41e77bf8a0fb5027c1b96b8c5b93a2cbcd612b9eb597de2d006865f5e496ab5395e8834ecbc780c0898846ca8cd4bb4a07d0c794df0b82dcb21cad56c5b7de1a02984a1f9d39449cb24629120bcdd0bd5d9ccf9ea270a2b7391c69d1bacc8cbe8e0a0f42f908b4dfbb0361bf6197a85e06df26113885c9fe0930a51978a5aceafabd5f7aa09aa60bddcd95fdf72a960135e0001c94afa3fa4ea070321028e82ca03c29b8f0203010001a3819c308199300e0603551d0f0101ff040403020106300f0603551d130101ff040530030101ff301d0603551d0e041604149be20757671c1ec06a06de59b49a2ddfdc19862e30360603551d1f042f302d302ba029a0278625687474703a2f2f63726c2e676c6f62616c7369676e2e6e65742f726f6f742d72322e63726c301f0603551d230418301680149be20757671c1ec06a06de59b49a2ddfdc19862e300d06092a864886f70d01010505000382010100998153871c68978691ece04ab8440bab81ac274fd6c1b81c4378b30c9afcea2c3c6e611b4d4b29f59f051d26c1b8e983006245b6a90893b9a9334b189ac2f887884edbdd71341ac154da463fe0d32aab6d5422f53a62cd206fba2989d7dd91eed35ca23ea15b41f5dfe564432de9d539abd2a2dfb78bd0c080191c45c02d8ce8f82da4745649c505b54f15de6e44783987a87ebbf3791891bbf46f9dc1f08c358c5d01fbc36db9ef446d7946317e0afea982c1ffefab6e20c450c95f9d4d9b178c0ce501c9a0416a7353faa550b46e250ffb4c18f4fd52d98e69b1e8110fde88d8fb1d49f7aade95cf2078c26012db25408c6afc7e4238406412f79e81e1932e" # noqa E501 ) class TestAttestationObject(unittest.TestCase): def test_unsupported_attestation(self): attestation = Attestation.for_type("__unsupported__")() self.assertIsInstance(attestation, UnsupportedAttestation) with self.assertRaises(UnsupportedType) as ctx: attestation.verify({}, 0, b"") self.assertEqual(ctx.exception.fmt, "__unsupported__") def test_none_attestation(self): attestation = Attestation.for_type("none")() self.assertIsInstance(attestation, NoneAttestation) auth_data = AuthenticatorData( bytes.fromhex( "0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12410000002BF8A011F38C0A4D15800617111F9EDC7D0040A17370D9C1759005700C8DE77E7DFD3A0A5300E0A26E5213AA40D6DF10EE4028B58B5F34167035D840BEBAE0C5CE8FD05AD9BD33E3BE7D1C558D81AB4803570BA5010203262001215820A5FD5CE1B1C458C530A54FA61B31BF6B04BE8B97AFDE54DD8CBB69275A8A1BE1225820FA3A3231DD9DEED9D1897BE5A6228C59501E4BCD12975D3DFF730F01278EA61C" # noqa E501 ) ) res = attestation.verify({}, auth_data, b"deadbeef" * 8) self.assertEqual(res.attestation_type, AttestationType.NONE) self.assertEqual(res.trust_path, []) with self.assertRaises(InvalidData): attestation.verify({"not": "empty"}, auth_data, b"deadbeef" * 8) def test_none_windows_hello_attestation(self): attestation = Attestation.for_type("none")() self.assertIsInstance(attestation, NoneAttestation) auth_data = AuthenticatorData( bytes.fromhex( "54ce651ed715b4aaa755eecebd4ea0950815b334bd07d109893e963018cddbd945000000006028b017b1d44c02b4b3afcdafc96bb200201decfcd6d6a05c2826d52348afdc70a9800df007845047b1a23706aa6e2f315ca401030339010020590100af59f4ad4f71da800bb91045b267e240e06317f7b2b1d76f78e239a433811faeca58a1869fb00225eb2727f81b6b20cbc18c0ad8d38fa450e8df11b4ad3bc3ee5d13c77ed172fa3af0195ec6ac0c4bac8c950115dfce6d38737cbafefbe117d8401cd56c638043a0d585131bc48a153b17a8dcb96671e15a90ba1b4ff810b138b77ac0a050b039b87b6089dd8dfa45611b992109d554aad3e6b72ac82d801496e4d2d230aa466090bbbf4f5632fe4b588e4f571462378fa6f514a536a5945b223c8d98f730b7cf85de86b98c217090f9e9ebf9643cf3feceeacb837d7a18542e03271cd8c70cf81186cdb63e4cbf4efc0cbbd3c93231b06f19580d0a980264d12143010001" # noqa ) ) # noqa res = attestation.verify({}, auth_data, b"deadbeef" * 8) self.assertEqual(res.attestation_type, AttestationType.NONE) self.assertEqual(res.trust_path, []) with self.assertRaises(InvalidData): attestation.verify({"not": "empty"}, auth_data, b"deadbeef" * 8) def test_tpm_windows_hello_attestation(self): attestation = Attestation.for_type("tpm")() self.assertIsInstance(attestation, TpmAttestation) statement = { "alg": -65535, "sig": bytes.fromhex( """80e564d8cbb236577de68d2e68ecae200a8eaf6992889b5 fdc24624a4cb69caaab18df965058fbac39df9714b9c80b9a12d715cfc4dd15ed3a6e191a6d26e 7206fd402b0733c2c8b91f62ad44e4d41c940e2e914253b1d1a1c8889b1cdaf668b5449245dc33 1fab12e0b0dcdfc530cbe1f370e1f2b06c163fbd6177925a1a8998edd2e726989246a1980fa34e 6d65d3ca284944cb10254d85db0d8948294fb8174a41206c6b5e36406bae447343f8c9f97420e3 9f361815dfb268b33ccde5f29e4348a70f95abc30754c839fa7126e5bd882377d6abe3c0c95ba5 c21190a5e4fff5380b2c23cc1655e593244019e172ba8284618471d95b92c231c1ffe98ff23 """.replace( "\n", "" ) ), "x5c": [ bytes.fromhex( """308204b23082039aa0030201020210789e1a3657344c52bad2 2ed1ceb1bfaf300d06092a864886f70d01010b05003041313f303d060355040313364e43552d4e 54432d4b455949442d394642423739414130463532363237384245443135303932394137313731 45393641333542454637301e170d3139303430313038353934305a170d32393034303130383539 34305a300030820122300d06092a864886f70d01010105000382010f003082010a0282010100a6 60d1fe41564c26f943c70ff89fbd9ed6d957191d5ecaf727393d73cfff85e3ccfb9830027fe84b 171cc4b0b13811df4d9deff2bce4d8a8f9797169f7b8fc25016d9ba687c003083693716180c8f1 eccaa4410a9a7fe07b198ad7ba94ecf744e9bef0273a5e0723a4ac197994ca1ac0e5f595433970 0cf14ead419ae7cde8c3e81389771d5fa3d339f8d0856e918fd3746fa9a944d3c1f1c6a4e0ce3f 99b5ac5ba05166b3b8695405ae7d3777f9cc8e3ab8570f2191ba4f2cfc4c544337596f48d3d5f5 f9ae80575bf9eb81d5c477e99c58854645d587dd0ccdea2b0e3d482e69b326b289e65741e6b214 3fc2bca35ca7dd60e554affdcb85000762ff09b0410203010001a38201e5308201e1300e060355 1d0f0101ff040403020780300c0603551d130101ff04023000306d0603551d200101ff04633061 305f06092b060104018237151f3052305006082b0601050507020230441e420054004300500041 002000200054007200750073007400650064002000200050006c006100740066006f0072006d00 200020004900640065006e007400690074007930100603551d250409300706056781050803304a 0603551d110101ff0440303ea43c303a3138300e060567810502030c0569643a31333010060567 810502020c074e5043543678783014060567810502010c0b69643a3445353434333030301f0603 551d23041830168014c799ef2371327cb2e9e03838d0a9009fe9ed29e7301d0603551d0e041604 1429fb5f05c6187d8463b8b250b8f0ff128fd3a0713081b306082b060105050701010481a63081 a33081a006082b0601050507300286819368747470733a2f2f617a637370726f646e637561696b 7075626c6973682e626c6f622e636f72652e77696e646f77732e6e65742f6e63752d6e74632d6b 657969642d39666262373961613066353236323738626564313530393239613731373165393661 3335626566372f66383530353438392d303235612d343235322d383239302d3934646532633633 643039362e636572300d06092a864886f70d01010b0500038201010084bc4b9ac3ab6c2438bdec dd3d99e6179bfc465995481d856683602bdcf0c26327b8ab77f7b695c8c6aab5f283b079c29369 29727b839e5bf08c687a33fc59bf281ebf28e9d04e78fd626573028014028badca038e68361017 a4501b18d56a6a73e35f00e043d8febb7a4c719c837bc5cb801efe23570d6c8b40699ba411fe66 f6fe5558f7d1c56a7646ba483cd601690a9323caba9257ae561781b13c658083ad1281047d94d4 c1ab9759d90a16fbe167cec388e7b67027a20dbc1b88986dbb636107ef91ffec22c413ac5fbfec 3de9ee4aa1c6e4c173e43246193890c8b024587fcc8028eb379f515de3c678b11dfb81aef3547c 3c6e790577d52f775f9148""".replace( "\n", "" ) ), bytes.fromhex( """308205e8308203d0a003020102021333000000a5304bb34bf0 bee43e0000000000a5300d06092a864886f70d01010b050030818c310b30090603550406130255 53311330110603550408130a57617368696e67746f6e3110300e060355040713075265646d6f6e 64311e301c060355040a13154d6963726f736f667420436f72706f726174696f6e313630340603 550403132d4d6963726f736f66742054504d20526f6f7420436572746966696361746520417574 686f726974792032303134301e170d3136303831383230323032305a170d323931323331323032 3032305a3041313f303d060355040313364e43552d4e54432d4b455949442d3946424237394141 304635323632373842454431353039323941373137314539364133354245463730820122300d06 092a864886f70d01010105000382010f003082010a0282010100e0b963203494ff3b8b93855f4d 0086aabf9f5038fe2a2c04311609074565097dd16de61ae1e6086f5d16997dc7ee5342bf9988f6 bb73ca614f3f5d8ea084fd047112892ae22db792e2efbe24bcb07fd01af124666db7ad53677e45 6a95e972a659c04fe3569e882afbf019c3c5890c52d2e81d175f97234fbe341406cbf834cafa76 184c077c9bd058fbe14b4032039142128fe985ee6041819eee86a62a43491d11af9d78f08e722a 28c0e9b522fed12f172dddfd032a634a6eba2fc90c332997d3ba5f297230cd7d666b6925c0e6ea 79b2459f68fc283cd7a09e09973a610fb88eb63bb1cc29e0dc5e033ace6b966c78038c1adc049e f5360ae28696825ed10203010001a382018b30820187300b0603551d0f040403020186301b0603 551d250414301206092b06010401823715240605678105080330160603551d20040f300d300b06 092b060104018237151f30120603551d130101ff040830060101ff020100301d0603551d0e0416 0414c799ef2371327cb2e9e03838d0a9009fe9ed29e7301f0603551d230418301680147a8c0ace 2f486217e294d1ae55c152ec7174a45630700603551d1f046930673065a063a061865f68747470 3a2f2f7777772e6d6963726f736f66742e636f6d2f706b696f70732f63726c2f4d6963726f736f 667425323054504d253230526f6f742532304365727469666963617465253230417574686f7269 7479253230323031342e63726c307d06082b060105050701010471306f306d06082b0601050507 30028661687474703a2f2f7777772e6d6963726f736f66742e636f6d2f706b696f70732f636572 74732f4d6963726f736f667425323054504d253230526f6f742532304365727469666963617465 253230417574686f72697479253230323031342e637274300d06092a864886f70d01010b050003 820201003e91d074e6d6b9719bf13ffd7cd16b733938c092edaa580136ac7d8bb5295242e432f6 7c3ca5b1c8968b994665e99796a39d579a85cbe6eab02dfce1d08a4ce802b41bf6b00a01533c7c f3b96c7d0b9c0f3a5d2e04350037aea5140a5cc781ca73f370998110bd1031cfa427760920574a 5d7709a1765921d61cb36d91d2ce9d3301f0798ae4b23592b080e70bb535cdf57403f96fe6f0ff 4c0f0363f785a918a1fd3debfaaaebe6b08724a216b491e95e6e300e3d43e4e156fe3c036afba1 7ad2b442f904568af1cc3fd9ad1888cbbd9ec98d42e55af5b26fa8790b6b7da677a585fff6ae90 18e492742d4e9c5ca1a06990a3abff76c6bc4b1e22d8c226d09a96fdcc12801345b647e15850d1 0d0cdb609160b1a7a7c2c6f0eb3dbc2fcd42b765fd22a5672b26009b9a83b44388b62cb89e9169 a455ff5be5ce8f7bde0420b5d7d24ec254affdc2e7e946c961ec159b6dfc703e3934f9445b0072 8e137e11a7c66f76709ca2177b39159fc08593aaa83724b159abb93e535aef53d7d6066a317f92 d42d17888534fee9daf844260de901c3b18b49ccb2a5f81f0f4639f2e2cfa1ce1d7c791cef6f48 5d10df989aac02b1e9afd1094603f5307133f5f59ce105a5910700f98fea5a5fcf8f5cf4c797bd 79d440cc4f9161f5cc61e0e8f06592050cd1f0f0fd066bd1d6335710fdf8159b75281ee1082bff 1da2fc0b631bd346ac""".replace( "\n", "" ) ), ], "certInfo": bytes.fromhex( """ff54434780170022000b68cec627cc6411099a1f80 9fde4379f649aa170c7072d1adf230de439efc80810014f7c8b0cdeb31328648130a19733d6fff 16e76e1300000003ef605603446ed8c56aa7608d01a6ea5651ee67a8a20022000bdf681917e185 29c61e1b85a1e7952f3201eb59c609ed5d8e217e5de76b228bbd0022000b0a10d216b0c3ab82bf dc1f0a016ab9493384c7aee1937ee8800f76b30c9b71a7""".replace( "\n", "" ) ), "pubArea": bytes.fromhex( """0001000b0006047200209dffcbf36c383ae699fb986 8dc6dcb89d7153884be2803922c124158bfad22ae001000100800000000000100c706586c7f46c dffede0ee0c5ebc8b7a08b36555c8091669e9ef2cb4fd858134a01e9522d3ef924069aeeec2271 823fe9879b5079eb3123be2eb39a7e954f8b83b5ebefefda25aed01bd19eab6db1962a3713985b 7a2dd1aa7770b5c1567fb0d18521e14abebbccc16832ef10bb05dcc818bbb70c91c224475928ad a6f6181ed64f1cfb40db5e01687454cfacafa8318bdc6a677550baa6e24f8af864fa5324e9d930 a97cdeb1995b476f21a017b33ab7fe4139f2524c784fcb04cf5241c89f0c145eb23da914ad1722 d47a843692a0b2a567d94dd808c13678a51c5a0583dc042dcbba1b9ceff12b159d0539248b0994 ee18128ed50dd7a855e54d2459db005""".replace( "\n", "" ) ), } auth_data = AuthenticatorData( bytes.fromhex( "54ce651ed715b4aaa755eecebd4ea0950815b334bd07d109893e963018cddbd9450000000008987058cadc4b81b6e130de50dcbe9600206053b7b599d16fb3fb11ea17a344850ebd0d18183a5b7ca6dfbd20c63cdb462aa401030339010020590100c706586c7f46cdffede0ee0c5ebc8b7a08b36555c8091669e9ef2cb4fd858134a01e9522d3ef924069aeeec2271823fe9879b5079eb3123be2eb39a7e954f8b83b5ebefefda25aed01bd19eab6db1962a3713985b7a2dd1aa7770b5c1567fb0d18521e14abebbccc16832ef10bb05dcc818bbb70c91c224475928ada6f6181ed64f1cfb40db5e01687454cfacafa8318bdc6a677550baa6e24f8af864fa5324e9d930a97cdeb1995b476f21a017b33ab7fe4139f2524c784fcb04cf5241c89f0c145eb23da914ad1722d47a843692a0b2a567d94dd808c13678a51c5a0583dc042dcbba1b9ceff12b159d0539248b0994ee18128ed50dd7a855e54d2459db0052143010001" # noqa ) ) client_param = bytes.fromhex( "057a0ecbe7e3e99e8926941614f6af078c802b110be89eb221d69be2e17a1ba4" ) try: res = attestation.verify(statement, auth_data, client_param) except UnsupportedAlgorithm as e: if e._reason == _Reasons.UNSUPPORTED_HASH: self.skipTest( "SHA1 signature verification not supported on this machine" ) else: raise e self.assertEqual(res.attestation_type, AttestationType.ATT_CA) verify_x509_chain(res.trust_path) def test_fido_u2f_attestation(self): attestation = Attestation.for_type("fido-u2f")() self.assertIsInstance(attestation, FidoU2FAttestation) statement = { "sig": bytes.fromhex( "30450220324779C68F3380288A1197B6095F7A6EB9B1B1C127F66AE12A99FE8532EC23B9022100E39516AC4D61EE64044D50B415A6A4D4D84BA6D895CB5AB7A1AA7D081DE341FA" # noqa E501 ), "x5c": [ bytes.fromhex( "3082024A30820132A0030201020204046C8822300D06092A864886F70D01010B0500302E312C302A0603550403132359756269636F2055324620526F6F742043412053657269616C203435373230303633313020170D3134303830313030303030305A180F32303530303930343030303030305A302C312A302806035504030C2159756269636F205532462045452053657269616C203234393138323332343737303059301306072A8648CE3D020106082A8648CE3D030107034200043CCAB92CCB97287EE8E639437E21FCD6B6F165B2D5A3F3DB131D31C16B742BB476D8D1E99080EB546C9BBDF556E6210FD42785899E78CC589EBE310F6CDB9FF4A33B3039302206092B0601040182C40A020415312E332E362E312E342E312E34313438322E312E323013060B2B0601040182E51C020101040403020430300D06092A864886F70D01010B050003820101009F9B052248BC4CF42CC5991FCAABAC9B651BBE5BDCDC8EF0AD2C1C1FFB36D18715D42E78B249224F92C7E6E7A05C49F0E7E4C881BF2E94F45E4A21833D7456851D0F6C145A29540C874F3092C934B43D222B8962C0F410CEF1DB75892AF116B44A96F5D35ADEA3822FC7146F6004385BCB69B65C99E7EB6919786703C0D8CD41E8F75CCA44AA8AB725AD8E799FF3A8696A6F1B2656E631B1E40183C08FDA53FA4A8F85A05693944AE179A1339D002D15CABD810090EC722EF5DEF9965A371D415D624B68A2707CAD97BCDD1785AF97E258F33DF56A031AA0356D8E8D5EBCADC74E071636C6B110ACE5CC9B90DFEACAE640FF1BB0F1FE5DB4EFF7A95F060733F5" # noqa E501 ) ], } auth_data = AuthenticatorData( bytes.fromhex( "1194228DA8FDBDEEFD261BD7B6595CFD70A50D70C6407BCF013DE96D4EFB17DE41000000000000000000000000000000000000000000403EBD89BF77EC509755EE9C2635EFAAAC7B2B9C5CEF1736C3717DA48534C8C6B654D7FF945F50B5CC4E78055BDD396B64F78DA2C5F96200CCD415CD08FE420038A5010203262001215820E87625896EE4E46DC032766E8087962F36DF9DFE8B567F3763015B1990A60E1422582027DE612D66418BDA1950581EBC5C8C1DAD710CB14C22F8C97045F4612FB20C91" # noqa E501 ) ) client_param = bytes.fromhex( "687134968222EC17202E42505F8ED2B16AE22F16BB05B88C25DB9E602645F141" ) res = attestation.verify(statement, auth_data, client_param) self.assertEqual(res.attestation_type, AttestationType.BASIC) self.assertEqual(len(res.trust_path), 1) statement["sig"] = b"a" * len(statement["sig"]) with self.assertRaises(InvalidSignature): attestation.verify(statement, auth_data, client_param) def test_packed_attestation(self): attestation = Attestation.for_type("packed")() self.assertIsInstance(attestation, PackedAttestation) statement = { "alg": -7, "sig": bytes.fromhex( "304502200D15DAF337D727AB4719B4027114A2AC43CD565D394CED62C3D9D1D90825F0B3022100989615E7394C87F4AD91F8FDAE86F7A3326DF332B3633DB088AAC76BFFB9A46B" # noqa E501 ), "x5c": [ bytes.fromhex( "308202B73082019FA00302010202041D31330D300D06092A864886F70D01010B0500302A3128302606035504030C1F59756269636F2050726576696577204649444F204174746573746174696F6E301E170D3138303332383036333932345A170D3139303332383036333932345A306E310B300906035504061302534531123010060355040A0C0959756269636F20414231223020060355040B0C1941757468656E74696361746F72204174746573746174696F6E3127302506035504030C1E59756269636F205532462045452053657269616C203438393736333539373059301306072A8648CE3D020106082A8648CE3D030107034200047D71E8367CAFD0EA6CF0D61E4C6A416BA5BB6D8FAD52DB2389AD07969F0F463BFDDDDDC29D39D3199163EE49575A3336C04B3309D607F6160C81E023373E0197A36C306A302206092B0601040182C40A020415312E332E362E312E342E312E34313438322E312E323013060B2B0601040182E51C0201010404030204303021060B2B0601040182E51C01010404120410F8A011F38C0A4D15800617111F9EDC7D300C0603551D130101FF04023000300D06092A864886F70D01010B050003820101009B904CEADBE1F1985486FEAD02BAEAA77E5AB4E6E52B7E6A2666A4DC06E241578169193B63DADEC5B2B78605A128B2E03F7FE2A98EAEB4219F52220995F400CE15D630CF0598BA662D7162459F1AD1FC623067376D4E4091BE65AC1A33D8561B9996C0529EC1816D1710786384D5E8783AA1F7474CB99FE8F5A63A79FF454380361C299D67CB5CC7C79F0D8C09F8849B0500F6D625408C77CBBC26DDEE11CB581BEB7947137AD4F05AAF38BD98DA10042DDCAC277604A395A5B3EAA88A5C8BB27AB59C8127D59D6BBBA5F11506BF7B75FDA7561A0837C46F025FD54DCF1014FC8D17C859507AC57D4B1DEA99485DF0BA8F34D00103C3EEF2EF3BBFEC7A6613DE" # noqa E501 ) ], } auth_data = AuthenticatorData( bytes.fromhex( "0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE124100000003F8A011F38C0A4D15800617111F9EDC7D004060A386206A3AACECBDBB22D601853D955FDC5D11ADFBD1AA6A950D966B348C7663D40173714A9F987DF6461BEADFB9CD6419FFDFE4D4CF2EEC1AA605A4F59BDAA50102032620012158200EDB27580389494D74D2373B8F8C2E8B76FA135946D4F30D0E187E120B423349225820E03400D189E85A55DE9AB0F538ED60736EB750F5F0306A80060FE1B13010560D" # noqa E501 ) ) client_param = bytes.fromhex( "985B6187D042FB1258892ED637CEC88617DDF5F6632351A545617AA2B75261BF" ) res = attestation.verify(statement, auth_data, client_param) self.assertEqual(res.attestation_type, AttestationType.BASIC) self.assertEqual(len(res.trust_path), 1) statement["sig"] = b"a" * len(statement["sig"]) with self.assertRaises(InvalidSignature): attestation.verify(statement, auth_data, client_param) def test_android_safetynet_attestation(self): attestation = Attestation.for_type("android-safetynet")() self.assertIsInstance(attestation, AndroidSafetynetAttestation) statement = { "ver": "14574037", "response": b"eyJhbGciOiJSUzI1NiIsIng1YyI6WyJNSUlGa2pDQ0JIcWdBd0lCQWdJUVJYcm9OMFpPZFJrQkFBQUFBQVB1bnpBTkJna3Foa2lHOXcwQkFRc0ZBREJDTVFzd0NRWURWUVFHRXdKVlV6RWVNQndHQTFVRUNoTVZSMjl2WjJ4bElGUnlkWE4wSUZObGNuWnBZMlZ6TVJNd0VRWURWUVFERXdwSFZGTWdRMEVnTVU4eE1CNFhEVEU0TVRBeE1EQTNNVGswTlZvWERURTVNVEF3T1RBM01UazBOVm93YkRFTE1Ba0dBMVVFQmhNQ1ZWTXhFekFSQmdOVkJBZ1RDa05oYkdsbWIzSnVhV0V4RmpBVUJnTlZCQWNURFUxdmRXNTBZV2x1SUZacFpYY3hFekFSQmdOVkJBb1RDa2R2YjJkc1pTQk1URU14R3pBWkJnTlZCQU1URW1GMGRHVnpkQzVoYm1SeWIybGtMbU52YlRDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVBBRENDQVFvQ2dnRUJBTmpYa3owZUsxU0U0bSsvRzV3T28rWEdTRUNycWRuODhzQ3BSN2ZzMTRmSzBSaDNaQ1laTEZIcUJrNkFtWlZ3Mks5RkcwTzlyUlBlUURJVlJ5RTMwUXVuUzl1Z0hDNGVnOW92dk9tK1FkWjJwOTNYaHp1blFFaFVXWEN4QURJRUdKSzNTMmFBZnplOTlQTFMyOWhMY1F1WVhIRGFDN09acU5ub3NpT0dpZnM4djFqaTZIL3hobHRDWmUybEorN0d1dHpleEtweHZwRS90WlNmYlk5MDVxU2xCaDlmcGowMTVjam5RRmtVc0FVd21LVkFVdWVVejR0S2NGSzRwZXZOTGF4RUFsK09raWxNdElZRGFjRDVuZWw0eEppeXM0MTNoYWdxVzBXaGg1RlAzOWhHazlFL0J3UVRqYXpTeEdkdlgwbTZ4RlloaC8yVk15WmpUNEt6UEpFQ0F3RUFBYU9DQWxnd2dnSlVNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBVEFNQmdOVkhSTUJBZjhFQWpBQU1CMEdBMVVkRGdRV0JCUXFCUXdHV29KQmExb1RLcXVwbzRXNnhUNmoyREFmQmdOVkhTTUVHREFXZ0JTWTBmaHVFT3ZQbSt4Z254aVFHNkRyZlFuOUt6QmtCZ2dyQmdFRkJRY0JBUVJZTUZZd0p3WUlLd1lCQlFVSE1BR0dHMmgwZEhBNkx5OXZZM053TG5CcmFTNW5iMjluTDJkMGN6RnZNVEFyQmdnckJnRUZCUWN3QW9ZZmFIUjBjRG92TDNCcmFTNW5iMjluTDJkemNqSXZSMVJUTVU4eExtTnlkREFkQmdOVkhSRUVGakFVZ2hKaGRIUmxjM1F1WVc1a2NtOXBaQzVqYjIwd0lRWURWUjBnQkJvd0dEQUlCZ1puZ1F3QkFnSXdEQVlLS3dZQkJBSFdlUUlGQXpBdkJnTlZIUjhFS0RBbU1DU2dJcUFnaGg1b2RIUndPaTh2WTNKc0xuQnJhUzVuYjI5bkwwZFVVekZQTVM1amNtd3dnZ0VFQmdvckJnRUVBZFo1QWdRQ0JJSDFCSUh5QVBBQWR3Q2t1UW1RdEJoWUZJZTdFNkxNWjNBS1BEV1lCUGtiMzdqamQ4ME95QTNjRUFBQUFXWmREM1BMQUFBRUF3QklNRVlDSVFDU1pDV2VMSnZzaVZXNkNnK2dqLzl3WVRKUnp1NEhpcWU0ZVk0Yy9teXpqZ0loQUxTYmkvVGh6Y3pxdGlqM2RrM3ZiTGNJVzNMbDJCMG83NUdRZGhNaWdiQmdBSFVBVmhRR21pL1h3dXpUOWVHOVJMSSt4MFoydWJ5WkVWekE3NVNZVmRhSjBOMEFBQUZtWFE5ejVBQUFCQU1BUmpCRUFpQmNDd0E5ajdOVEdYUDI3OHo0aHIvdUNIaUFGTHlvQ3EySzAreUxSd0pVYmdJZ2Y4Z0hqdnB3Mm1CMUVTanEyT2YzQTBBRUF3Q2tuQ2FFS0ZVeVo3Zi9RdEl3RFFZSktvWklodmNOQVFFTEJRQURnZ0VCQUk5blRmUktJV2d0bFdsM3dCTDU1RVRWNmthenNwaFcxeUFjNUR1bTZYTzQxa1p6d0o2MXdKbWRSUlQvVXNDSXkxS0V0MmMwRWpnbG5KQ0YyZWF3Y0VXbExRWTJYUEx5RmprV1FOYlNoQjFpNFcyTlJHelBodDNtMWI0OWhic3R1WE02dFg1Q3lFSG5UaDhCb200L1dsRmloemhnbjgxRGxkb2d6L0syVXdNNlM2Q0IvU0V4a2lWZnYremJKMHJqdmc5NEFsZGpVZlV3a0k5Vk5NakVQNWU4eWRCM29MbDZnbHBDZUY1ZGdmU1g0VTl4MzVvai9JSWQzVUUvZFBwYi9xZ0d2c2tmZGV6dG1VdGUvS1Ntcml3Y2dVV1dlWGZUYkkzenNpa3daYmtwbVJZS21qUG1odjRybGl6R0NHdDhQbjhwcThNMktEZi9QM2tWb3QzZTE4UT0iLCJNSUlFU2pDQ0F6S2dBd0lCQWdJTkFlTzBtcUdOaXFtQkpXbFF1REFOQmdrcWhraUc5dzBCQVFzRkFEQk1NU0F3SGdZRFZRUUxFeGRIYkc5aVlXeFRhV2R1SUZKdmIzUWdRMEVnTFNCU01qRVRNQkVHQTFVRUNoTUtSMnh2WW1Gc1UybG5iakVUTUJFR0ExVUVBeE1LUjJ4dlltRnNVMmxuYmpBZUZ3MHhOekEyTVRVd01EQXdOREphRncweU1URXlNVFV3TURBd05ESmFNRUl4Q3pBSkJnTlZCQVlUQWxWVE1SNHdIQVlEVlFRS0V4VkhiMjluYkdVZ1ZISjFjM1FnVTJWeWRtbGpaWE14RXpBUkJnTlZCQU1UQ2tkVVV5QkRRU0F4VHpFd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUURRR005RjFJdk4wNXprUU85K3ROMXBJUnZKenp5T1RIVzVEekVaaEQyZVBDbnZVQTBRazI4RmdJQ2ZLcUM5RWtzQzRUMmZXQllrL2pDZkMzUjNWWk1kUy9kTjRaS0NFUFpSckF6RHNpS1VEelJybUJCSjV3dWRnem5kSU1ZY0xlL1JHR0ZsNXlPRElLZ2pFdi9TSkgvVUwrZEVhbHROMTFCbXNLK2VRbU1GKytBY3hHTmhyNTlxTS85aWw3MUkyZE44RkdmY2Rkd3VhZWo0YlhocDBMY1FCYmp4TWNJN0pQMGFNM1Q0SStEc2F4bUtGc2JqemFUTkM5dXpwRmxnT0lnN3JSMjV4b3luVXh2OHZObWtxN3pkUEdIWGt4V1k3b0c5aitKa1J5QkFCazdYckpmb3VjQlpFcUZKSlNQazdYQTBMS1cwWTN6NW96MkQwYzF0Skt3SEFnTUJBQUdqZ2dFek1JSUJMekFPQmdOVkhROEJBZjhFQkFNQ0FZWXdIUVlEVlIwbEJCWXdGQVlJS3dZQkJRVUhBd0VHQ0NzR0FRVUZCd01DTUJJR0ExVWRFd0VCL3dRSU1BWUJBZjhDQVFBd0hRWURWUjBPQkJZRUZKalIrRzRRNjgrYjdHQ2ZHSkFib090OUNmMHJNQjhHQTFVZEl3UVlNQmFBRkp2aUIxZG5IQjdBYWdiZVdiU2FMZC9jR1lZdU1EVUdDQ3NHQVFVRkJ3RUJCQ2t3SnpBbEJnZ3JCZ0VGQlFjd0FZWVphSFIwY0RvdkwyOWpjM0F1Y0d0cExtZHZiMmN2WjNOeU1qQXlCZ05WSFI4RUt6QXBNQ2VnSmFBamhpRm9kSFJ3T2k4dlkzSnNMbkJyYVM1bmIyOW5MMmR6Y2pJdlozTnlNaTVqY213d1B3WURWUjBnQkRnd05qQTBCZ1puZ1F3QkFnSXdLakFvQmdnckJnRUZCUWNDQVJZY2FIUjBjSE02THk5d2Eya3VaMjl2Wnk5eVpYQnZjMmwwYjNKNUx6QU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFHb0ErTm5uNzh5NnBSamQ5WGxRV05hN0hUZ2laL3IzUk5Ha21VbVlIUFFxNlNjdGk5UEVhanZ3UlQyaVdUSFFyMDJmZXNxT3FCWTJFVFV3Z1pRK2xsdG9ORnZoc085dHZCQ09JYXpwc3dXQzlhSjl4anU0dFdEUUg4TlZVNllaWi9YdGVEU0dVOVl6SnFQalk4cTNNRHhyem1xZXBCQ2Y1bzhtdy93SjRhMkc2eHpVcjZGYjZUOE1jRE8yMlBMUkw2dTNNNFR6czNBMk0xajZieWtKWWk4d1dJUmRBdktMV1p1L2F4QlZielltcW13a201ekxTRFc1bklBSmJFTENRQ1p3TUg1NnQyRHZxb2Z4czZCQmNDRklaVVNweHU2eDZ0ZDBWN1N2SkNDb3NpclNtSWF0ai85ZFNTVkRRaWJldDhxLzdVSzR2NFpVTjgwYXRuWnoxeWc9PSJdfQ.eyJub25jZSI6InpiNVE5NFVPaHFOWnRVUWEraWY0NnF1UDRwZWZQN2JnQWRpQ3hraDFZRGs9IiwidGltZXN0YW1wTXMiOjE1NDM0ODI1Njg4NTgsImFwa1BhY2thZ2VOYW1lIjoiY29tLmdvb2dsZS5hbmRyb2lkLmdtcyIsImFwa0RpZ2VzdFNoYTI1NiI6InIxYzZiTkJmQ0hjZHcvZWpKSE1NWjhoakIrU0xXa1BSM0lreTZjV1dhNE09IiwiY3RzUHJvZmlsZU1hdGNoIjp0cnVlLCJhcGtDZXJ0aWZpY2F0ZURpZ2VzdFNoYTI1NiI6WyI4UDFzVzBFUEpjc2x3N1V6UnNpWEw2NHcrTzUwRWQrUkJJQ3RheTFnMjRNPSJdLCJiYXNpY0ludGVncml0eSI6dHJ1ZX0.Lq9WpOJ_GilocvPCTbIN2K5FtppXW2fTQzCW2pvb1Bo5qOZnJ0oOYBUqMgxx-zghlluSkkIIfPTvYl2zZUQsY-SNlBx7JASqDbksMyRsdU9r1Jn8D2zEtipFgjmZUkozi7AngnHoA5d0Yp-NF6slmr_FLMpAOnLZY9lREw8Cxnmso3Ph7zYUu7O5SxaRGwj8eMKydYJYHa23h2C8acuQKgSWL2YlG9T-oKT0CJ8jOSrKnHr39eMo7PFX0464diUvXUsv_M9kRIIQqCP0LzilGMdJVUrvFU7kg8csnFP6KMDfY70RGZ5ey3eNqs_D5-pjPfC4XPsPsksmy_wf-3UOmw", # noqa E501 } auth_data = AuthenticatorData( bytes.fromhex( "720c20fde835785e0f5ebcad8ef6a7bd88804a91612a2e820e0059b8d5358797450000000000000000000000000000000000000000004101c8fd9b533d6adacf6710ebcfb39f6361c4d7e8787db47dc0a75ae0e7c862198c9c83b81ef2547bb5669314095fc846af4ecac6875f7b230cac7359c76b0c20f7a5010203262001215820a28851e2d411b5b2c289da50d41cc41be88498941fc256dab500b21c8dafe8d1225820d289dd467715be06a622771a7b21e1bbe2372f8713d20dd7888a6e7ae1845ca8" # noqa E501 ) ) client_param = bytes.fromhex( "8422c80f3428e4e6465f76ebc8a4a93759a0a2e1fb845ee5eea7a02027408520" ) res = attestation.verify(statement, auth_data, client_param) self.assertEqual(res.attestation_type, AttestationType.BASIC) verify_x509_chain(res.trust_path + [_GSR2_DER]) def test_apple_attestation(self): attestation = Attestation.for_type("apple")() self.assertIsInstance(attestation, AppleAttestation) statement = { "alg": -7, "x5c": [ bytes.fromhex( "30820242308201c9a00302010202060176af5359ff300a06082a8648ce3d0403023048311c301a06035504030c134170706c6520576562417574686e204341203131133011060355040a0c0a4170706c6520496e632e3113301106035504080c0a43616c69666f726e6961301e170d3230313232383136323732345a170d3230313233313136323732345a3081913149304706035504030c4038303966626331313065613835663233613862323435616563363136333530663337646665393632313232373336653431663862646365663334366138306439311a3018060355040b0c114141412043657274696669636174696f6e31133011060355040a0c0a4170706c6520496e632e3113301106035504080c0a43616c69666f726e69613059301306072a8648ce3d020106082a8648ce3d030107034200041f46a2f159fde354598cdd47e005f1b6e7c9f00ed2a941ec7a88d222f5bcf55d6b078bc5b0be9552d85a974921f5bb848e2bbc3aecd6f71a386d2c87d6eafd37a3553053300c0603551d130101ff04023000300e0603551d0f0101ff0404030204f0303306092a864886f76364080204263024a1220420e56fb6212b3aae885294464fb10184b7fea62c48a6d78e61194e07ae6dacc132300a06082a8648ce3d040302036700306402301de8f0f238eee4f5ae80c59290b51e8c3f79397bf198e444ba162d4fccaab8558b072cf00a7c662f9058ff2a98af61ae0230149403b9643066e73a98d3659563dc4da49bf84e82b2b5bbeaf57755646fa243f36344d44b80a5798203bca023e030c7" # noqa E501 ), bytes.fromhex( "30820234308201baa003020102021056255395c7a7fb40ebe228d8260853b6300a06082a8648ce3d040303304b311f301d06035504030c164170706c6520576562417574686e20526f6f7420434131133011060355040a0c0a4170706c6520496e632e3113301106035504080c0a43616c69666f726e6961301e170d3230303331383138333830315a170d3330303331333030303030305a3048311c301a06035504030c134170706c6520576562417574686e204341203131133011060355040a0c0a4170706c6520496e632e3113301106035504080c0a43616c69666f726e69613076301006072a8648ce3d020106052b8104002203620004832e872f261491810225b9f5fcd6bb6378b5f55f3fcb045bc735993475fd549044df9bfe19211765c69a1dda050b38d45083401a434fb24d112d56c3e1cfbfcb9891fec0696081bef96cbc77c88dddaf46a5aee1dd515b5afaab93be9c0b2691a366306430120603551d130101ff040830060101ff020100301f0603551d2304183016801426d764d9c578c25a67d1a7de6b12d01b63f1c6d7301d0603551d0e04160414ebae82c4ffa1ac5b51d4cf24610500be63bd7788300e0603551d0f0101ff040403020106300a06082a8648ce3d0403030368003065023100dd8b1a3481a5fad9dbb4e7657b841e144c27b75b876a4186c2b1475750337227efe554457ef648950c632e5c483e70c102302c8a6044dc201fcfe59bc34d2930c1487851d960ed6a75f1eb4acabe38cd25b897d0c805bef0c7f78b07a571c6e80e07" # noqa E501 ), ], } auth_data = AuthenticatorData( bytes.fromhex( "c46cef82ad1b546477591d008b08759ec3e6d2ecb4f39474bfea6969925d03b7450000000000000000000000000000000000000000001473d9429f4052d84debd035eb5bb7e716e3b81863a50102032620012158201f46a2f159fde354598cdd47e005f1b6e7c9f00ed2a941ec7a88d222f5bcf55d2258206b078bc5b0be9552d85a974921f5bb848e2bbc3aecd6f71a386d2c87d6eafd37" # noqa E501 ) ) client_param = bytes.fromhex( "0d3ce80fabbc3adb9dd891deabb8db84603ea1fe2da8b5d4b46d6591aab342f3" ) res = attestation.verify(statement, auth_data, client_param) self.assertEqual(res.attestation_type, AttestationType.ANON_CA) self.assertEqual(len(res.trust_path), 2) verify_x509_chain(res.trust_path) fido2-1.2.0/tests/test_cbor.py0000644000175000017500000001545614721556664015614 0ustar winniewinnie# coding=utf-8 # Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from fido2 import cbor import unittest _TEST_VECTORS = [ ("00", 0), ("01", 1), ("0a", 10), ("17", 23), ("1818", 24), ("1819", 25), ("1864", 100), ("1903e8", 1000), ("1a000f4240", 1000000), ("1b000000e8d4a51000", 1000000000000), ("1bffffffffffffffff", 18446744073709551615), # ('c249010000000000000000', 18446744073709551616), ("3bffffffffffffffff", -18446744073709551616), # ('c349010000000000000000', -18446744073709551617), ("20", -1), ("29", -10), ("3863", -100), ("3903e7", -1000), # ('f90000', 0.0), # ('f98000', -0.0), # ('f93c00', 1.0), # ('fb3ff199999999999a', 1.1), # ('f93e00', 1.5), # ('f97bff', 65504.0), # ('fa47c35000', 100000.0), # ('fa7f7fffff', 3.4028234663852886e+38), # ('fb7e37e43c8800759c', 1e+300), # ('f90001', 5.960464477539063e-08), # ('f90400', 6.103515625e-05), # ('f9c400', -4.0), # ('fbc010666666666666', -4.1), # ('f97c00', None), # ('f97e00', None), # ('f9fc00', None), # ('fa7f800000', None), # ('fa7fc00000', None), # ('faff800000', None), # ('fb7ff0000000000000', None), # ('fb7ff8000000000000', None), # ('fbfff0000000000000', None), ("f4", False), ("f5", True), # ('f6', None), # ('f7', None), # ('f0', None), # ('f818', None), # ('f8ff', None), # ('c074323031332d30332d32315432303a30343a30305a', None), # ('c11a514b67b0', None), # ('c1fb41d452d9ec200000', None), # ('d74401020304', None), # ('d818456449455446', None), # ('d82076687474703a2f2f7777772e6578616d706c652e636f6d', None), ("40", b""), ("4401020304", b"\1\2\3\4"), ("60", ""), ("6161", "a"), ("6449455446", "IETF"), ("62225c", '"\\'), ("62c3bc", "ü"), ("63e6b0b4", "水"), ("64f0908591", "𐅑"), ("80", []), ("83010203", [1, 2, 3]), ("8301820203820405", [1, [2, 3], [4, 5]]), ( "98190102030405060708090a0b0c0d0e0f101112131415161718181819", [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, ], ), ("a0", {}), ("a201020304", {1: 2, 3: 4}), ("a26161016162820203", {"a": 1, "b": [2, 3]}), ("826161a161626163", ["a", {"b": "c"}]), ( "a56161614161626142616361436164614461656145", {"c": "C", "d": "D", "a": "A", "b": "B", "e": "E"}, ), # ('5f42010243030405ff', None), # ('7f657374726561646d696e67ff', 'streaming'), # ('9fff', []), # ('9f018202039f0405ffff', [1, [2, 3], [4, 5]]), # ('9f01820203820405ff', [1, [2, 3], [4, 5]]), # ('83018202039f0405ff', [1, [2, 3], [4, 5]]), # ('83019f0203ff820405', [1, [2, 3], [4, 5]]), # ('9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]), # noqa E501 # ('bf61610161629f0203ffff', {'a': 1, 'b': [2, 3]}), # ('826161bf61626163ff', ['a', {'b': 'c'}]), # ('bf6346756ef563416d7421ff', {'Amt': -2, 'Fun': True}), ] def cbor2hex(data): return cbor.encode(data).hex() class TestCborTestVectors(unittest.TestCase): """ From https://github.com/cbor/test-vectors Unsupported values are commented out. """ def test_vectors(self): for data, value in _TEST_VECTORS: try: self.assertEqual(cbor.decode_from(bytes.fromhex(data)), (value, b"")) self.assertEqual(cbor.decode(bytes.fromhex(data)), value) self.assertEqual(cbor2hex(value), data) except Exception: print("\nERROR in test vector, %s" % data) raise class TestFidoCanonical(unittest.TestCase): """ As defined in section 6 of: https://fidoalliance.org/specs/fido-v2.0-ps-20170927/fido-client-to-authenticator-protocol-v2.0-ps-20170927.html """ def test_integers(self): self.assertEqual(cbor2hex(0), "00") self.assertEqual(cbor2hex(0), "00") self.assertEqual(cbor2hex(23), "17") self.assertEqual(cbor2hex(24), "1818") self.assertEqual(cbor2hex(255), "18ff") self.assertEqual(cbor2hex(256), "190100") self.assertEqual(cbor2hex(65535), "19ffff") self.assertEqual(cbor2hex(65536), "1a00010000") self.assertEqual(cbor2hex(4294967295), "1affffffff") self.assertEqual(cbor2hex(4294967296), "1b0000000100000000") self.assertEqual(cbor2hex(-1), "20") self.assertEqual(cbor2hex(-24), "37") self.assertEqual(cbor2hex(-25), "3818") def test_key_order(self): self.assertEqual(cbor2hex({"3": 0, b"2": 0, 1: 0}), "a30100413200613300") self.assertEqual(cbor2hex({"3": 0, b"": 0, 256: 0}), "a3190100004000613300") self.assertEqual( cbor2hex({4294967296: 0, 255: 0, 256: 0, 0: 0}), "a4000018ff00190100001b000000010000000000", ) self.assertEqual( cbor2hex({b"22": 0, b"3": 0, b"111": 0}), "a3413300423232004331313100" ) self.assertEqual( cbor2hex({b"001": 0, b"003": 0, b"002": 0}), "a3433030310043303032004330303300", ) self.assertEqual(cbor2hex({True: 0, False: 0}), "a2f400f500") fido2-1.2.0/tests/test_mds3.py0000644000175000017500000005063414721556664015532 0ustar winniewinniefrom fido2.mds3 import parse_blob, MdsAttestationVerifier from base64 import b64decode # Example data from: # https://fidoalliance.org/specs/mds/fido-metadata-service-v3.0-ps-20210518.html#examples EXAMPLE_CA = b64decode( """ MIIGGTCCBAGgAwIBAgIUdT9qLX0sVMRe8l0sLmHd3mZovQ0wDQYJKoZIhvcNAQEL BQAwgZsxHzAdBgNVBAMMFkVYQU1QTEUgTURTMyBURVNUIFJPT1QxIjAgBgkqhkiG 9w0BCQEWE2V4YW1wbGVAZXhhbXBsZS5jb20xFDASBgNVBAoMC0V4YW1wbGUgT1JH MRAwDgYDVQQLDAdFeGFtcGxlMQswCQYDVQQGEwJVUzELMAkGA1UECAwCTVkxEjAQ BgNVBAcMCVdha2VmaWVsZDAeFw0yMTA0MTkxMTM1MDdaFw00ODA5MDQxMTM1MDda MIGbMR8wHQYDVQQDDBZFWEFNUExFIE1EUzMgVEVTVCBST09UMSIwIAYJKoZIhvcN AQkBFhNleGFtcGxlQGV4YW1wbGUuY29tMRQwEgYDVQQKDAtFeGFtcGxlIE9SRzEQ MA4GA1UECwwHRXhhbXBsZTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk1ZMRIwEAYD VQQHDAlXYWtlZmllbGQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDD jF5wyEWuhwDHsZosGdGFTCcI677rW881vV+UfW38J+K2ioFFNeGVsxbcebK6AVOi CDPFj0974IpeD9SFOhwAHoDu/LCfXdQWp8ZgQ91ULYWoW8o7NNSp01nbN9zmaO6/ xKNCa0bzjmXoGqglqnP1AtRcWYvXOSKZy1rcPeDv4Dhcpdp6W72fBw0eWIqOhsrI tuY2/N8ItBPiG03EX72nACq4nZJ/nAIcUbER8STSFPPzvE97TvShsi1FD8aO6l1W kR/QkreAGjMI++GbB2Qc1nN9Y/VEDbMDhQtxXQRdpFwubTjejkN9hKOtF3B71Yrw Irng3V9RoPMFdapWMzSlI+WWHog0oTj1PqwJDDg7+z1I6vSDeVWAMKr9mq1w1OGN zgBopIjd9lRWkRtt2kQSPX9XxqS4E1gDDr8MKbpM3JuubQtNCg9D7Ljvbz6vwvUr bPHH+oREvucsp0PZ5PpizloepGIcLFxDQqCulGY2n7Ahl0JOFXJqOFCaK3TWHwBv ZsaY5DgBuUvdUrwtgZNg2eg2omWXEepiVFQn3Fvj43Wh2npPMgIe5P0rwncXvROx aczd4rtajKS1ucoB9b9iKqM2+M1y/FDIgVf1fWEHwK7YdzxMlgOeLdeV/kqRU5PE UlLU9a2EwdOErrPbPKZmIfbs/L4B3k4zejMDH3Y+ZwIDAQABo1MwUTAdBgNVHQ4E FgQU8sWwq1TrurK7xMTwO1dKfeJBbCMwHwYDVR0jBBgwFoAU8sWwq1TrurK7xMTw O1dKfeJBbCMwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAFw6M 1PiIfCPIBQ5EBUPNmRvRFuDpolOmDofnf/+mv63LqwQZAdo/W8tzZ9kOFhq24SiL w0H7fsdG/jeREXiIZMNoW/rA6Uac8sU+FYF7Q+qp6CQLlSQbDcpVMifTQjcBk2xh +aLK9SrrXBqnTAhwS+offGtAW8DpoLuH4tAcQmIjlgMlN65jnELCuqNR/wpA+zch 8LZW8saQ2cwRCwdr8mAzZoLbsDSVCHxQF3/kQjPT7Nao1q2iWcY3OYcRmKrieHDP 67yeLUbVmetfZis2d6ZlkqHLB4ZW1xX4otsEFkuTJA3HWDRsNyhTwx1YoCLsYut5 Zp0myqPNBq28w6qGMyyoJN0Z4RzMEO3R6i/MQNfhK55/8O2HciM6xb5t/aBSuHPK lBDrFWhpRnKYkaNtlUo35qV5IbKGKau3SdZdSRciaXUd/p81YmoF01UlhhMz/Rqr 1k2gyA0a9tF8+awCeanYt5izl8YO0FlrOU1SQ5UQw4szqqZqbrf4e8fRuU2TXNx4 zk+ImE7WRB44f6mSD746ZCBRogZ/SA5jUBu+OPe4/sEtERWRcQD+fXgce9ZEN0+p eyJIKAsl5Rm2Bmgyg5IoyWwSG5W+WekGyEokpslou2Yc6EjUj5ndZWz5EiHAiQ74 hNfDoCZIxVVLU3Qbp8a0S1bmsoT2JOsspIbtZUg= """ ) # NOTE: Signature changed to be properly ASN.1 formatted! EXAMPLE_BLOB = """ eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsIng1YyI6WyJNSUlDWlRDQ0FndWdBd0lCQWdJQkFUQUtC Z2dxaGtqT1BRUURBakNCb3pFbk1DVUdBMVVFQXd3ZVJWaEJUVkJNUlNCTlJGTXpJRlJGVTFRZ1NVNVVS VkpOUlVSSlFWUkZNU0l3SUFZSktvWklodmNOQVFrQkZoTmxlR0Z0Y0d4bFFHVjRZVzF3YkdVdVkyOXRN UlF3RWdZRFZRUUtEQXRGZUdGdGNHeGxJRTlTUnpFUU1BNEdBMVVFQ3d3SFJYaGhiWEJzWlRFTE1Ba0dB MVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01BazFaTVJJd0VBWURWUVFIREFsWFlXdGxabWxsYkdRd0hoY05N akV3TkRFNU1URXpOVEEzV2hjTk16RXdOREUzTVRFek5UQTNXakNCcFRFcE1DY0dBMVVFQXd3Z1JWaEJU VkJNUlNCTlJGTXpJRk5KUjA1SlRrY2dRMFZTVkVsR1NVTkJWRVV4SWpBZ0Jna3Foa2lHOXcwQkNRRVdF MlY0WVcxd2JHVkFaWGhoYlhCc1pTNWpiMjB4RkRBU0JnTlZCQW9NQzBWNFlXMXdiR1VnVDFKSE1SQXdE Z1lEVlFRTERBZEZlR0Z0Y0d4bE1Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQXdDVFZreEVqQVFC Z05WQkFjTUNWZGhhMlZtYVdWc1pEQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJOUUpz NndUcWl4YytTK1ZEQWFqRmxQTmF0MTBLRVdKRTVqY1dPdm02cXBPOVNEQUFNWnZiNEhIcnZzK1A1WVJw SHJTbFVQZHZLK3VFUWJkV2czMVA5dWpMREFxTUFrR0ExVWRFd1FDTUFBd0hRWURWUjBPQkJZRUZMcXNh cGNYVjRab1ZIQW5ScFBad1FlN1l5MjBNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUUM2N3phOEVJdXlS aUtnTkRYSVAxczFhTHIzanpIOVdWWGZIeDRiSit6Q3NnSWdHL3RWQnV0T0pVVSt2dm9ISW8vb3RBVUFj SDViTkhQM3VJemlEUytQVFVjPSIsIk1JSUVIekNDQWdlZ0F3SUJBZ0lCQWpBTkJna3Foa2lHOXcwQkFR c0ZBRENCbXpFZk1CMEdBMVVFQXd3V1JWaEJUVkJNUlNCTlJGTXpJRlJGVTFRZ1VrOVBWREVpTUNBR0NT cUdTSWIzRFFFSkFSWVRaWGhoYlhCc1pVQmxlR0Z0Y0d4bExtTnZiVEVVTUJJR0ExVUVDZ3dMUlhoaGJY QnNaU0JQVWtjeEVEQU9CZ05WQkFzTUIwVjRZVzF3YkdVeEN6QUpCZ05WQkFZVEFsVlRNUXN3Q1FZRFZR UUlEQUpOV1RFU01CQUdBMVVFQnd3SlYyRnJaV1pwWld4a01CNFhEVEl4TURReE9URXhNelV3TjFvWERU UTRNRGt3TkRFeE16VXdOMW93Z2FNeEp6QWxCZ05WQkFNTUhrVllRVTFRVEVVZ1RVUlRNeUJVUlZOVUlF bE9WRVZTVFVWRVNVRlVSVEVpTUNBR0NTcUdTSWIzRFFFSkFSWVRaWGhoYlhCc1pVQmxlR0Z0Y0d4bExt TnZiVEVVTUJJR0ExVUVDZ3dMUlhoaGJYQnNaU0JQVWtjeEVEQU9CZ05WQkFzTUIwVjRZVzF3YkdVeEN6 QUpCZ05WQkFZVEFsVlRNUXN3Q1FZRFZRUUlEQUpOV1RFU01CQUdBMVVFQnd3SlYyRnJaV1pwWld4a01G a3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRU5HdW1CYlluRlFuVGpQMVJTZmM3MGhzaGdi aUkxWnRwd1E1bjZ4UkxBL1dxMFBTQ2ZMbDVxUStyN2RsY0sxZDNyM3ZMYSt2bTZHNnZLSEdDUEVlVXpx TXZNQzB3REFZRFZSMFRCQVV3QXdFQi96QWRCZ05WSFE0RUZnUVVOazZGNFJKbkdHVkZlKzAvY2Jad2Zy WmQ3WlV3RFFZSktvWklodmNOQVFFTEJRQURnZ0lCQUNucDFmbTBGS2xXbVV0VHBsTHVZZzdtcHM0eFAv Q091OGRuYjM4dTFuTURWdU9UNCtDWmFpTTlBR3ozMTNHRDIyaGpMR3JtUHVZbjg2d0dPS0kzSE9yRXBz R2RNbWZ5N3RUbUtYL2VNL2VTM0ZFRFhabkU4MlBuNW9GSXlCVC9mOHNHdVh5T3NGWnFXQnZWZEJJSURs ZENwRDRteE1RWlpPWnRUcmx2M1d2QlFNQy9kc2ljT3hlM1FLWHZXSGk2UWIvUmh1YWlwM3JQbXdNZis0 SnBuSk8rSk1QcUFhVTFjQUg4SFZzZnJMQU1vS3MxNDhqMitjdmJwYVdtc1Q1cklvSC9lelZyUGFHL01P aUlncTc5dy9lZnV2U2k1QVg4SitrRG9MU0VmM2Q1d09na0pZQXFVcWNSeFhURUV0S0l6RE02aHphQlFG aUFXdlRuOUlsVldnbnRRYW1TWHZIK3R4YVRGOWlFbEh4VWY1SU5ZRlZjaUNwenRTcnlkZUh2L09DTlJm Ny9MVnJpY01TbG84UmgrTzN5UDlWKzJ1TmYzWDhzUUpOdHVmclFOYXFxMTh3aVhsaVRMdWZTbjAyL2cr bWtoSVVpTktmVE9KcHZDaktlQ25DRmN4UVUyL1hUM0toM0c4Z0RKd3NPNkVWUmpNVUp0NEFZS3plL2hF VUN3RjU1SUYybTNqSElvQ3U4alZmajI0Q2VFWDVkbmZ2U3IrU1Z2TjVRQjB1WjA1TTRybXlaWHlxQm0w ekszZlIraUUwL1pwSW51d0xDN1grVzgyelhsbk1rcGxJM1ErSnhkN2pmUTE1U1lORTJLNnJ2UklUMDF3 MFA5WnF5REY3a25HS3BSbHA3T3F4ZDM3YkQvVlViV3BRN2dJQWZzSk5INUtCTG93SEpGRmpXIl19.eyJ sZWdhbEhlYWRlciI6IlJldHJpZXZhbCBhbmQgdXNlIG9mIHRoaXMgQkxPQiBpbmRpY2F0ZXMgYWNjZXB 0YW5jZSBvZiB0aGUgYXBwcm9wcmlhdGUgYWdyZWVtZW50IGxvY2F0ZWQgYXQgaHR0cHM6Ly9maWRvYWx saWFuY2Uub3JnL21ldGFkYXRhL21ldGFkYXRhLWxlZ2FsLXRlcm1zLyIsIm5vIjoxNSwibmV4dFVwZGF 0ZSI6IjIwMjAtMDMtMzAiLCJlbnRyaWVzIjpbeyJhYWlkIjoiMTIzNCM1Njc4IiwibWV0YWRhdGFTdGF 0ZW1lbnQiOnsibGVnYWxIZWFkZXIiOiJodHRwczovL2ZpZG9hbGxpYW5jZS5vcmcvbWV0YWRhdGEvbWV 0YWRhdGEtc3RhdGVtZW50LWxlZ2FsLWhlYWRlci8iLCJkZXNjcmlwdGlvbiI6IkZJRE8gQWxsaWFuY2U gU2FtcGxlIFVBRiBBdXRoZW50aWNhdG9yIiwiYWFpZCI6IjEyMzQjNTY3OCIsImFsdGVybmF0aXZlRGV zY3JpcHRpb25zIjp7InJ1LVJVIjoi0J_RgNC40LzQtdGAIFVBRiDQsNGD0YLQtdC90YLQuNGE0LjQutC w0YLQvtGA0LAg0L7RgiBGSURPIEFsbGlhbmNlIiwiZnItRlIiOiJFeGVtcGxlIFVBRiBhdXRoZW50aWN hdG9yIGRlIEZJRE8gQWxsaWFuY2UifSwiYXV0aGVudGljYXRvclZlcnNpb24iOjIsInByb3RvY29sRmF taWx5IjoidWFmIiwic2NoZW1hIjozLCJ1cHYiOlt7Im1ham9yIjoxLCJtaW5vciI6MH0seyJtYWpvciI 6MSwibWlub3IiOjF9XSwiYXV0aGVudGljYXRpb25BbGdvcml0aG1zIjpbInNlY3AyNTZyMV9lY2RzYV9 zaGEyNTZfcmF3Il0sInB1YmxpY0tleUFsZ0FuZEVuY29kaW5ncyI6WyJlY2NfeDk2Ml9yYXciXSwiYXR 0ZXN0YXRpb25UeXBlcyI6WyJiYXNpY19mdWxsIl0sInVzZXJWZXJpZmljYXRpb25EZXRhaWxzIjpbW3s idXNlclZlcmlmaWNhdGlvbk1ldGhvZCI6ImZpbmdlcnByaW50X2ludGVybmFsIiwiYmFEZXNjIjp7InN lbGZBdHRlc3RlZEZBUiI6MC4wMDAwMiwibWF4UmV0cmllcyI6NSwiYmxvY2tTbG93ZG93biI6MzAsIm1 heFRlbXBsYXRlcyI6NX19XV0sImtleVByb3RlY3Rpb24iOlsiaGFyZHdhcmUiLCJ0ZWUiXSwiaXNLZXl SZXN0cmljdGVkIjp0cnVlLCJtYXRjaGVyUHJvdGVjdGlvbiI6WyJ0ZWUiXSwiY3J5cHRvU3RyZW5ndGg iOjEyOCwiYXR0YWNobWVudEhpbnQiOlsiaW50ZXJuYWwiXSwidGNEaXNwbGF5IjpbImFueSIsInRlZSJ dLCJ0Y0Rpc3BsYXlDb250ZW50VHlwZSI6ImltYWdlL3BuZyIsInRjRGlzcGxheVBOR0NoYXJhY3Rlcml zdGljcyI6W3sid2lkdGgiOjMyMCwiaGVpZ2h0Ijo0ODAsImJpdERlcHRoIjoxNiwiY29sb3JUeXBlIjo yLCJjb21wcmVzc2lvbiI6MCwiZmlsdGVyIjowLCJpbnRlcmxhY2UiOjB9XSwiYXR0ZXN0YXRpb25Sb29 0Q2VydGlmaWNhdGVzIjpbIk1JSUNQVENDQWVPZ0F3SUJBZ0lKQU91ZXh2VTNPeTJ3TUFvR0NDcUdTTTQ 5QkFNQ01Ic3hJREFlQmdOVkJBTU1GMU5oYlhCc1pTQkJkSFJsYzNSaGRHbHZiaUJTYjI5ME1SWXdGQVl EVlFRS0RBMUdTVVJQSUVGc2JHbGhibU5sTVJFd0R3WURWUVFMREFoVlFVWWdWRmRITERFU01CQUdBMVV FQnd3SlVHRnNieUJCYkhSdk1Rc3dDUVlEVlFRSURBSkRRVEVMTUFrR0ExVUVCaE1DVlZNd0hoY05NVFF 3TmpFNE1UTXpNek15V2hjTk5ERXhNVEF6TVRNek16TXlXakI3TVNBd0hnWURWUVFEREJkVFlXMXdiR1V nUVhSMFpYTjBZWFJwYjI0Z1VtOXZkREVXTUJRR0ExVUVDZ3dOUmtsRVR5QkJiR3hwWVc1alpURVJNQTh HQTFVRUN3d0lWVUZHSUZSWFJ5d3hFakFRQmdOVkJBY01DVkJoYkc4Z1FXeDBiekVMTUFrR0ExVUVDQXd DUTBFeEN6QUpCZ05WQkFZVEFsVlRNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVIOGh 2MkQwSFhhNTkvQm1wUTdSWmVoTC9GTUd6RmQxUUJnOXZBVXBPWjNham51UTk0UFI3YU16SDMzblVTQnI 4ZkhZRHJxT0JiNThweEdxSEpSeVgvNk5RTUU0d0hRWURWUjBPQkJZRUZQb0hBM0NMaHhGYkMwSXQ3ekU 0dzhoazVFSi9NQjhHQTFVZEl3UVlNQmFBRlBvSEEzQ0xoeEZiQzBJdDd6RTR3OGhrNUVKL01Bd0dBMVV kRXdRRk1BTUJBZjh3Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUloQUowNlFTWHQ5aWhJYkVLWUtJanNQa3J pVmRMSWd0ZnNiRFN1N0VySmZ6cjRBaUJxb1lDWmYwK3pJNTVhUWVBSGpJekE5WG02M3JydUF4Qlo5cHM 5ejJYTmxRPT0iXSwiaWNvbiI6ImRhdGE6aW1hZ2UvcG5nO2Jhc2U2NCxpVkJPUncwS0dnb0FBQUFOU1V oRVVnQUFBRThBQUFBdkNBWUFBQUNpd0pmY0FBQUFBWE5TUjBJQXJzNGM2UUFBQUFSblFVMUJBQUN4and 2OFlRVUFBQUFKY0VoWmN3QUFEc01BQUE3REFjZHZxR1FBQUFhaFNVUkJWR2hEN1pyNWJ4UmxHTWY5S3p UQjhBTS9ZRWhFMlc3cFFaY1dLS0JjbFNwSEFUbEVMQVJFN2tORUNDQTNGa1dLMENLS1NDRklzS0JjZ1Z DRFdHTkVTZEFZaWR3Z2dnSkJpUmlNaEZjLzR3eTg4ODR6dTlOZGxuR1RmWkpQMm4zbk8rKzg4OTMzZnZ lQkJ4K1BxQ3pKa1RVdkJiTG1wVURXdkJUSW1wY0NTWnZYTENkWDlSMDVTazE5YmI1YXRmNTk5ZkcrL2V yQTU0MXE0N2FQMUxMVmE5U0l5Vk5VaThJaThkNWtHVHNpMzBORnY3YWk5bjdRWlBNd2JkeXMyZXJVMlh NcVVkeTgrWmNhTm1HaW1FOHlYTjNSVWQzYTE4bkYwZlVsb3ZaKzBDVHpXcGQyVmorZU9tMWJFeXk2RHg 0aTVwVU1HV3ZlbzUwNnEyMjdkdHVXQkl1ZmZyNm9XcFYwRlBOTGhvdzE3NTFObTIxTHZQSDNyVnRXamZ 6NjZMZnFsOHRYN0ZSbDlZRlNYc21Tc2ViOWNlT0diWWs3TU5VY0dQZzhac2JNZTlyZlFVYWFWL0pNWDl zcWR6RENTdnAwa1pIbVRaZzl4N2JMSGNNblRoYjE2ZUorbVZmUXE4eWFVWlFORzY0aVhaKzAva3E2dU9 aRk8wUXRhdGRXS2ZYblJROTlCajkxUjVPSUZuazU0ak4wbWtVaXFsTzNYRFcrTWwrOThtS0I2dFc3cld wWmNQYyswemc0dExyWWxVYzg2RTZlR0RqSU11YlZwY3VzZWFyZmdJWUdSazZicmhaVnIvSmNIem9vTDc 1NTBqZWRMRXhvcFdjQXBpMlpVcWh1N0pMdnJWc1FVODF6a3pPUGVlbU1SWXZWdVFzWDdQYmlEUVk1SnZ ab25mdEsrMVZZOEg5dXR4NTMwaDBvYitqbVJZcWo2b3VhWXZFZW5XL1dsWWpwOGN3Yk1tNjgydFB3cVc xUjR0ai8yU0gxM0lSSllsNG1vWnZYcGlTcURyN2RYdFFIeGEvUEszLytCV3NLMWRUZ0h1NlY4dFFKM2J 3Rmt3cEZyVU9RNTBzMXIzbGV2bTh6WmNxMTcrQkJhdzdLOGxFSzVxemtZZWFyazlBOHA3UDNHekRLK25 kM0RRb3crNlVDOFNWTjgyaXV2MzhpbTdOdGFYdFYxQ1ZxNlJndzRwa3NtYmRpM2J1MkRlN1lmYUJCeGN xZnZxUHJVakZRTlRRMjJsZmRVVlZUNjhyVEpLRjVEblNtVWpnZHFnNG1TUzlwbXNmREpSM0c2VG9IMGl XOWFWN0xXTEhZWEtsbFREdDBMVEF0a1lJYWFtcDFRalZ2Kyt1eUdVeFZkSjBETlZYU20rYjFxUnhwbDg 0ZGRmWDFMcDFPL2Q2OXRzb2QwdnM1aEdyZTl4dThvK2ZwTFIxY0doTlRENlo1N0M5S01XWGVmSmRPWjk 0YmI5b3FkMVJPblM3cUlUVHpIaW1NcWl2Yk8zZzBEZFZ5azNXUUJoQnp0SzM1WUtOZE9uYzhPM2FjUzZ mRFpGZ0thWExzRUpwNXJkcmxpQnFwODljSmNzL203VHZzMHJrakdmTjRiMGtQb1puM1VKdUlPcm5aMjJ 5UDFmbXZVeCtPNWdTcWViVjFtK3pTdVlOVmhxN1RXYkRpTFZ2bGpwbExsb3A2Q0xYUCsycXR2R0xJTC8 xdmltSVNkTUJnelNvRlp5dTZUcWQranp4Z3NQYVY5QkNxZWUvTmpZazZ2NmxLOWN3aVVjL1NUdGYxSER wTTNiNTkyeTdoM1RoeDVveks2OUhMcFlXdUF3YXFTNWN2MjZxN2NlYjhlZlZZYVJlUDNpRlU4emoxa25 Td1pYSE1tbkNqWTBPZ2FsbzdVUWZTQ00zcVFRcjJIL1hGUDdzc1h4NDVZbDkxQnllQ2VwNG1vWm9IKzF mRzN4RDR0VDd4OGt3eWo4bndiOWV2MjZWMEI2ZCs3SDR6S3Z1ZEFINTM3RmpxeXpPSGRKbkhFdXptWHE vV2p4T2J2Tk1idjduaHl3c1gyYVZzV3RDOCs0OGFMZWFwRTdwNXdLWmkwQTJBUVJWNW52UjRFK3VKYyt iNjFrQXBxSW54QmdtZC80VjVRUC9tdDE4SERDN3NSSGZ0bWV1NWxtaFYwcm4vQUxYMjMyYnFkNEJGbkR 4N1ZpMWNXUzJ1ZmYwSWJCNDdxZXh4bVVqOVF1dFlqdXBkM3RZRDZhYldCQk1yaCthcE5iT0tyTkYxK3V nQ2E0cmlYR2Z3TVBQdFZpYXZoVTNZTU9BQW51VWIvUjA3TDB5T1NlT2FkRTg4QXBzWEZHZmYzMHluaGx KZ001MUNVNnZOOUV6Z25wdkhCRlV5aVZyYWVQaXdKNTNERjVaVFpub21FTmc4NWtOVWQyb0ppMldwcjR PbW1rZk40eDR6SGZpVkZjOER2OE56dWhOcU9pZGlsR3ZBNkRHdWVad083OEFBUW42Y2lFazYrcnc1VmN 2anZxTkRZUE9vSVV3YUtTaHJ4QXVYTGxrSDRhWXVHZk1ZRGMxMFdGNVRhMzFoUEpPZmNVaHJVL0psSU5 pNmM2ZWxSWWRCcG82KytZZmp4NjFsR05mUm00TUQ1ckoxajNGb0dIbmpEU0JOYXJZVWdNTHlNc3pLcGI 3dFhwb0hmUHM4aDNXcDFMek5mTms1NFh4QzF3REdVbVl6WFllZmg2ei9jS3RWbTRFQnhhOVZRR0R6WXI zTHJVTVJqSEVLa2s3emFGS1lRQTJoR1FVMXorODVORldwWERya3ozdngxMEdxeFE2QnplTmJvQms1bjh rNG5lYlJoK2sxaFdmeFRGMEQxRXlXVXM1bnYrZGdRcUtheHp1Q2RFMGlzSGwwMk5ROGFoMG1YcjEyTGE zbTBmOXdpazkrd0xOVE1ZLzg2TVBvOHlpMzFPZnhtVDZQV29xRzkrRFp1a1luYTU2bVNadDVXV1N5NXF WQTFyd1V5SnFYQWxuemtpYWkvZ0hTRDdSa1R5aWhvZ0FBQUFCSlJVNUVya0pnZ2c9PSJ9LCJzdGF0dXN SZXBvcnRzIjpbeyJzdGF0dXMiOiJGSURPX0NFUlRJRklFRCIsImVmZmVjdGl2ZURhdGUiOiIyMDE0LTA xLTA0In1dLCJ0aW1lT2ZMYXN0U3RhdHVzQ2hhbmdlIjoiMjAxNC0wMS0wNCJ9LHsiYWFndWlkIjoiMDE zMmQxMTAtYmY0ZS00MjA4LWE0MDMtYWI0ZjVmMTJlZmU1IiwibWV0YWRhdGFTdGF0ZW1lbnQiOnsibGV nYWxIZWFkZXIiOiJodHRwczovL2ZpZG9hbGxpYW5jZS5vcmcvbWV0YWRhdGEvbWV0YWRhdGEtc3RhdGV tZW50LWxlZ2FsLWhlYWRlci8iLCJkZXNjcmlwdGlvbiI6IkZJRE8gQWxsaWFuY2UgU2FtcGxlIEZJRE8 yIEF1dGhlbnRpY2F0b3IiLCJhYWd1aWQiOiIwMTMyZDExMC1iZjRlLTQyMDgtYTQwMy1hYjRmNWYxMmV mZTUiLCJhbHRlcm5hdGl2ZURlc2NyaXB0aW9ucyI6eyJydS1SVSI6ItCf0YDQuNC80LXRgCBGSURPMiD QsNGD0YLQtdC90YLQuNGE0LjQutCw0YLQvtGA0LAg0L7RgiBGSURPIEFsbGlhbmNlIiwiZnItRlIiOiJ FeGVtcGxlIEZJRE8yIGF1dGhlbnRpY2F0b3IgZGUgRklETyBBbGxpYW5jZSIsInpoLUNOIjoi5L6G6Ie qRklETyBBbGxpYW5jZeeahOekuuS-i0ZJRE8y6Lqr5Lu96amX6K2J5ZmoIn0sInByb3RvY29sRmFtaWx 5IjoiZmlkbzIiLCJzY2hlbWEiOjMsImF1dGhlbnRpY2F0b3JWZXJzaW9uIjo1LCJ1cHYiOlt7Im1ham9 yIjoxLCJtaW5vciI6MH1dLCJhdXRoZW50aWNhdGlvbkFsZ29yaXRobXMiOlsic2VjcDI1NnIxX2VjZHN hX3NoYTI1Nl9yYXciLCJyc2Fzc2FfcGtjc3YxNV9zaGEyNTZfcmF3Il0sInB1YmxpY0tleUFsZ0FuZEV uY29kaW5ncyI6WyJjb3NlIl0sImF0dGVzdGF0aW9uVHlwZXMiOlsiYmFzaWNfZnVsbCJdLCJ1c2VyVmV yaWZpY2F0aW9uRGV0YWlscyI6W1t7InVzZXJWZXJpZmljYXRpb25NZXRob2QiOiJub25lIn1dLFt7InV zZXJWZXJpZmljYXRpb25NZXRob2QiOiJwcmVzZW5jZV9pbnRlcm5hbCJ9XSxbeyJ1c2VyVmVyaWZpY2F 0aW9uTWV0aG9kIjoicGFzc2NvZGVfZXh0ZXJuYWwiLCJjYURlc2MiOnsiYmFzZSI6MTAsIm1pbkxlbmd 0aCI6NH19XSxbeyJ1c2VyVmVyaWZpY2F0aW9uTWV0aG9kIjoicGFzc2NvZGVfZXh0ZXJuYWwiLCJjYUR lc2MiOnsiYmFzZSI6MTAsIm1pbkxlbmd0aCI6NH19LHsidXNlclZlcmlmaWNhdGlvbk1ldGhvZCI6InB yZXNlbmNlX2ludGVybmFsIn1dXSwia2V5UHJvdGVjdGlvbiI6WyJoYXJkd2FyZSIsInNlY3VyZV9lbGV tZW50Il0sIm1hdGNoZXJQcm90ZWN0aW9uIjpbIm9uX2NoaXAiXSwiY3J5cHRvU3RyZW5ndGgiOjEyOCw iYXR0YWNobWVudEhpbnQiOlsiZXh0ZXJuYWwiLCJ3aXJlZCIsIndpcmVsZXNzIiwibmZjIl0sInRjRGl zcGxheSI6W10sImF0dGVzdGF0aW9uUm9vdENlcnRpZmljYXRlcyI6WyJNSUlDUFRDQ0FlT2dBd0lCQWd JSkFPdWV4dlUzT3kyd01Bb0dDQ3FHU000OUJBTUNNSHN4SURBZUJnTlZCQU1NRjFOaGJYQnNaU0JCZEh SbGMzUmhkR2x2YmlCU2IyOTBNUll3RkFZRFZRUUtEQTFHU1VSUElFRnNiR2xoYm1ObE1SRXdEd1lEVlF RTERBaFZRVVlnVkZkSExERVNNQkFHQTFVRUJ3d0pVR0ZzYnlCQmJIUnZNUXN3Q1FZRFZRUUlEQUpEUVR FTE1Ba0dBMVVFQmhNQ1ZWTXdIaGNOTVRRd05qRTRNVE16TXpNeVdoY05OREV4TVRBek1UTXpNek15V2p CN01TQXdIZ1lEVlFRRERCZFRZVzF3YkdVZ1FYUjBaWE4wWVhScGIyNGdVbTl2ZERFV01CUUdBMVVFQ2d 3TlJrbEVUeUJCYkd4cFlXNWpaVEVSTUE4R0ExVUVDd3dJVlVGR0lGUlhSeXd4RWpBUUJnTlZCQWNNQ1Z CaGJHOGdRV3gwYnpFTE1Ba0dBMVVFQ0F3Q1EwRXhDekFKQmdOVkJBWVRBbFZUTUZrd0V3WUhLb1pJemo wQ0FRWUlLb1pJemowREFRY0RRZ0FFSDhodjJEMEhYYTU5L0JtcFE3UlplaEwvRk1HekZkMVFCZzl2QVV wT1ozYWpudVE5NFBSN2FNekgzM25VU0JyOGZIWURycU9CYjU4cHhHcUhKUnlYLzZOUU1FNHdIUVlEVlI wT0JCWUVGUG9IQTNDTGh4RmJDMEl0N3pFNHc4aGs1RUovTUI4R0ExVWRJd1FZTUJhQUZQb0hBM0NMaHh GYkMwSXQ3ekU0dzhoazVFSi9NQXdHQTFVZEV3UUZNQU1CQWY4d0NnWUlLb1pJemowRUF3SURTQUF3UlF JaEFKMDZRU1h0OWloSWJFS1lLSWpzUGtyaVZkTElndGZzYkRTdTdFckpmenI0QWlCcW9ZQ1pmMCt6STU 1YVFlQUhqSXpBOVhtNjNycnVBeEJaOXBzOXoyWE5sUT09Il0sImljb24iOiJkYXRhOmltYWdlL3BuZzt iYXNlNjQsaVZCT1J3MEtHZ29BQUFBTlNVaEVVZ0FBQUU4QUFBQXZDQVlBQUFDaXdKZmNBQUFBQVhOU1I wSUFyczRjNlFBQUFBUm5RVTFCQUFDeGp3djhZUVVBQUFBSmNFaFpjd0FBRHNNQUFBN0RBY2R2cUdRQUF BYWhTVVJCVkdoRDdacjVieFJsR01mOUt6VEI4QU0vWUVoRTJXN3BRWmNXS0tCY2xTcEhBVGxFTEFSRTd rTkVDQ0EzRmtXSzBDS0tTQ0ZJc0tCY2dWQ0RXR05FU2RBWWlkd2dnZ0pCaVJpTWhGYy80d3k4ODg0enU 5TmRsbkdUZlpKUDJuM25PKys4ODkzM2Z2ZUJCeCtQcUN6SmtUVXZCYkxtcFVEV3ZCVEltcGNDU1p2WEx DZFg5UjA1U2sxOWJiNWF0ZjU5OWZHKy9lckE1NDFxNDdhUDFMTFZhOVNJeVZOVWk4SWk4ZDVrR1RzaTM wTkZ2N2FpOW43UVpQTXdiZHlzMmVyVTJYTXFVZHk4K1pjYU5tR2ltRTh5WE4zUlVkM2ExOG5GMGZVbG9 2WiswQ1R6V3BkMlZqK2VPbTFiRXl5NkR4NGk1cFVNR1d2ZW81MDZxMjI3ZHR1V0JJdWZmcjZvV3BWMEZ QTkxob3cxNzUxTm0yMUx2UEgzclZ0V2pmejY2TGZxbDh0WDdGUmw5WUZTWHNtU3NlYjljZU9HYllrN01 OVWNHUGc4WnNiTWU5cmZRVWFhVi9KTVg5c3FkekRDU3ZwMGtaSG1UWmc5eDdiTEhjTW5UaGIxNmVKK21 WZlFxOHlhVVpRTkc2NGlYWiswL2txNnVPWkZPMFF0YXRkV0tmWG5SUTk5Qmo5MVI1T0lGbms1NGpOMG1 rVWlxbE8zWERXK01sKzk4bUtCNnRXN3JXcFpjUGMrMHpnNHRMcllsVWM4NkU2ZUdEaklNdWJWcGN1c2V hcmZnSVlHUms2YnJoWlZyL0pjSHpvb0w3NTUwamVkTEV4b3BXY0FwaTJaVXFodTdKTHZyVnNRVTgxemt 6T1BlZW1NUll2VnVRc1g3UGJpRFFZNUp2Wm9uZnRLKzFWWThIOXV0eDUzMGgwb2Iram1SWXFqNm91YVl 2RWVuVy9XbFlqcDhjd2JNbTY4MnRQd3FXMVI0dGovMlNIMTNJUkpZbDRtb1p2WHBpU3FEcjdkWHRRSHh hL1BLMy8rQldzSzFkVGdIdTZWOHRRSjNid0Zrd3BGclVPUTUwczFyM2xldm04elpjcTE3K0JCYXc3Szh sRUs1cXprWWVhcms5QThwN1AzR3pESytuZDNEUW93KzZVQzhTVk44Mml1djM4aW03TnRhWHRWMUNWcTZ SZ3c0cGtzbWJkaTNidTJEZTdZZmFCQnhjcWZ2cVByVWpGUU5UUTIybGZkVVZWVDY4clRKS0Y1RG5TbVV qZ2RxZzRtU1M5cG1zZkRKUjNHNlRvSDBpVzlhVjdMV0xIWVhLbGxURHQwTFRBdGtZSWFhbXAxUWpWdis rdXlHVXhWZEowRE5WWFNtK2IxcVJ4cGw4NGRkZlgxTHAxTy9kNjl0c29kMHZzNWhHcmU5eHU4bytmcEx SMWNHaE5URDZaNTdDOUtNV1hlZkpkT1o5NGJiOW9xZDFST25TN3FJVFR6SGltTXFpdmJPM2cwRGRWeWs zV1FCaEJ6dEszNVlLTmRPbmM4TzNhY1M2ZkRaRmdLYVhMc0VKcDVyZHJsaUJxcDg5Y0pjcy9tN1R2czB ya2pHZk40YjBrUG9abjNVSnVJT3JuWjIyeVAxZm12VXgrTzVnU3FlYlYxbSt6U3VZTlZocTdUV2JEaUx WdmxqcGxMbG9wNkNMWFArMnF0dkdMSUwvMXZpbUlTZE1CZ3pTb0ZaeXU2VHFkK2p6eGdzUGFWOUJDcWV lL05qWWs2djZsSzljd2lVYy9TVHRmMUhEcE0zYjU5Mnk3aDNUaHg1b3pLNjlITHBZV3VBd2FxUzVjdjI 2cTdjZWI4ZWZWWWFSZVAzaUZVOHpqMWtuU3daWEhNbW5DalkwT2dhbG83VVFmU0NNM3FRUXIySC9YRlA 3c3NYeDQ1WWw5MUJ5ZUNlcDRtb1pvSCsxZkczeEQ0dFQ3eDhrd3lqOG53YjlldjI2VjBCNmQrN0g0ekt 2dWRBSDUzN0ZqcXl6T0hkSm5IRXV6bVhxL1dqeE9idk5NYnY3bmh5d3NYMmFWc1d0QzgrNDhhTGVhcEU 3cDV3S1ppMEEyQVFSVjVudlI0RSt1SmMrYjYxa0FwcUlueEJnbWQvNFY1UVAvbXQxOEhEQzdzUkhmdG1 ldTVsbWhWMHJuL0FMWDIzMmJxZDRCRm5EeDdWaTFjV1MydWZmMEliQjQ3cWV4eG1VajlRdXRZanVwZDN 0WUQ2YWJXQkJNcmgrYXBOYk9Lck5GMSt1Z0NhNHJpWEdmd01QUHRWaWF2aFUzWU1PQUFudVViL1IwN0w weU9TZU9hZEU4OEFwc1hGR2ZmMzB5bmhsSmdNNTFDVTZ2TjlFemducHZIQkZVeWlWcmFlUGl3SjUzREY 1WlRabm9tRU5nODVrTlVkMm9KaTJXcHI0T21ta2ZONHg0ekhmaVZGYzhEdjhOenVoTnFPaWRpbEd2QTZ ER3VlWndPNzhBQVFuNmNpRWs2K3J3NVZjdmp2cU5EWVBPb0lVd2FLU2hyeEF1WExsa0g0YVl1R2ZNWUR jMTBXRjVUYTMxaFBKT2ZjVWhyVS9KbElOaTZjNmVsUllkQnBvNisrWWZqeDYxbEdOZlJtNE1ENXJKMWo zRm9HSG5qRFNCTmFyWVVnTUx5TXN6S3BiN3RYcG9IZlBzOGgzV3AxTHpOZk5rNTRYeEMxd0RHVW1Zelh ZZWZoNnovY0t0Vm00RUJ4YTlWUUdEellyM0xyVU1SakhFS2trN3phRktZUUEyaEdRVTF6Kzg1TkZXcFh Ecmt6M3Z4MTBHcXhRNkJ6ZU5ib0JrNW44azRuZWJSaCtrMWhXZnhURjBEMUV5V1VzNW52K2RnUXFLYXh 6dUNkRTBpc0hsMDJOUThhaDBtWHIxMkxhM20wZjl3aWs5K3dMTlRNWS84Nk1Qbzh5aTMxT2Z4bVQ2UFd vcUc5K0RadWtZbmE1Nm1TWnQ1V1dTeTVxVkExcndVeUpxWEFsbnpraWFpL2dIU0Q3UmtUeWlob2dBQUF BQkpSVTVFcmtKZ2dnPT0iLCJzdXBwb3J0ZWRFeHRlbnNpb25zIjpbeyJpZCI6ImhtYWMtc2VjcmV0Iiw iZmFpbF9pZl91bmtub3duIjpmYWxzZX0seyJpZCI6ImNyZWRQcm90ZWN0IiwiZmFpbF9pZl91bmtub3d uIjpmYWxzZX1dLCJhdXRoZW50aWNhdG9yR2V0SW5mbyI6eyJ2ZXJzaW9ucyI6WyJVMkZfVjIiLCJGSUR PXzJfMCJdLCJleHRlbnNpb25zIjpbImNyZWRQcm90ZWN0IiwiaG1hYy1zZWNyZXQiXSwiYWFndWlkIjo iMDEzMmQxMTBiZjRlNDIwOGE0MDNhYjRmNWYxMmVmZTUiLCJvcHRpb25zIjp7InBsYXQiOiJmYWxzZSI sInJrIjoidHJ1ZSIsImNsaWVudFBpbiI6InRydWUiLCJ1cCI6InRydWUiLCJ1diI6InRydWUiLCJ1dlR va2VuIjoiZmFsc2UiLCJjb25maWciOiJmYWxzZSJ9LCJtYXhNc2dTaXplIjoxMjAwLCJwaW5VdkF1dGh Qcm90b2NvbHMiOlsxXSwibWF4Q3JlZGVudGlhbENvdW50SW5MaXN0IjoxNiwibWF4Q3JlZGVudGlhbEl kTGVuZ3RoIjoxMjgsInRyYW5zcG9ydHMiOlsidXNiIiwibmZjIl0sImFsZ29yaXRobXMiOlt7InR5cGU iOiJwdWJsaWMta2V5IiwiYWxnIjotN30seyJ0eXBlIjoicHVibGljLWtleSIsImFsZyI6LTI1N31dLCJ tYXhBdXRoZW50aWNhdG9yQ29uZmlnTGVuZ3RoIjoxMDI0LCJkZWZhdWx0Q3JlZFByb3RlY3QiOjIsImZ pcm13YXJlVmVyc2lvbiI6NX19LCJzdGF0dXNSZXBvcnRzIjpbeyJzdGF0dXMiOiJGSURPX0NFUlRJRkl FRCIsImVmZmVjdGl2ZURhdGUiOiIyMDE5LTAxLTA0In0seyJzdGF0dXMiOiJGSURPX0NFUlRJRklFRF9 MMSIsImVmZmVjdGl2ZURhdGUiOiIyMDIwLTExLTE5IiwiY2VydGlmaWNhdGlvbkRlc2NyaXB0b3IiOiJ GSURPIEFsbGlhbmNlIFNhbXBsZSBGSURPMiBBdXRoZW50aWNhdG9yIiwiY2VydGlmaWNhdGVOdW1iZXI iOiJGSURPMjEwMDAyMDE1MTIyMTAwMSIsImNlcnRpZmljYXRpb25Qb2xpY3lWZXJzaW9uIjoiMS4wLjE iLCJjZXJ0aWZpY2F0aW9uUmVxdWlyZW1lbnRzVmVyc2lvbiI6IjEuMC4xIn1dLCJ0aW1lT2ZMYXN0U3R hdHVzQ2hhbmdlIjoiMjAxOS0wMS0wNCJ9XX0.MEYCIQD6RzXCuiskDXpvEtdfN4OQUQ4KxsoDLZYMTOg Jj4B6PwIhAM3RtYg4CaGkcbFJrcJeCbAXCAC7LbfQSr8EdM79GyGw """.replace( "\n", "" ).encode() AAGUID = bytes.fromhex("0132d110bf4e4208a403ab4f5f12efe5") def test_parse_blob(): data = parse_blob(EXAMPLE_BLOB, EXAMPLE_CA) assert data.no == 15 assert len(data.entries) == 2 def test_find_by_aaguid(): data = parse_blob(EXAMPLE_BLOB, EXAMPLE_CA) mds = MdsAttestationVerifier(data) entry = mds.find_entry_by_aaguid(AAGUID) assert ( entry.metadata_statement.description == "FIDO Alliance Sample FIDO2 Authenticator" ) def test_find_by_aaguid_miss(): data = parse_blob(EXAMPLE_BLOB, EXAMPLE_CA) mds = MdsAttestationVerifier(data) entry = mds.find_entry_by_aaguid(bytes.fromhex("0102030405060708090a0b0c0d0e0f")) assert entry is None def test_find_by_chain_miss(): data = parse_blob(EXAMPLE_BLOB, EXAMPLE_CA) mds = MdsAttestationVerifier(data) entry = mds.find_entry_by_chain([EXAMPLE_CA]) assert entry is None def test_filter_entries(): data = parse_blob(EXAMPLE_BLOB, EXAMPLE_CA) mds = MdsAttestationVerifier(data, entry_filter=lambda e: e.aaguid != AAGUID) entry = mds.find_entry_by_aaguid(AAGUID) assert entry is None mds = MdsAttestationVerifier(data, entry_filter=lambda e: e.aaguid == AAGUID) assert mds.find_entry_by_aaguid(AAGUID) def test_lookup_filter_does_not_affect_find_entry_by_aaguid(): data = parse_blob(EXAMPLE_BLOB, EXAMPLE_CA) mds = MdsAttestationVerifier( data, attestation_filter=lambda e, _: e.aaguid != AAGUID ) assert mds.find_entry_by_aaguid(AAGUID) fido2-1.2.0/tests/test_ctap2.py0000644000175000017500000004515514721556664015677 0ustar winniewinnie# Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import unittest from unittest import mock from fido2.ctap1 import RegistrationData from fido2.ctap2 import ( Ctap2, ClientPin, PinProtocolV1, Info, AttestationResponse, AssertionResponse, ) from fido2.webauthn import AttestationObject, AuthenticatorData, AttestedCredentialData from fido2.attestation import Attestation from fido2 import cbor from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import ec _AAGUID = bytes.fromhex("F8A011F38C0A4D15800617111F9EDC7D") _INFO = bytes.fromhex( "a60182665532465f5632684649444f5f325f3002826375766d6b686d61632d7365637265740350f8a011f38c0a4d15800617111f9edc7d04a462726bf5627570f564706c6174f469636c69656e7450696ef4051904b0068101" # noqa E501 ) _INFO_EXTRA_KEY = bytes.fromhex( "A70182665532465F5632684649444F5F325F3002826375766D6B686D61632D7365637265740350F8A011F38C0A4D15800617111F9EDC7D04A462726BF5627570F564706C6174F469636C69656E7450696EF4051904B006810118631904D2" # noqa E501 ) class TestInfo(unittest.TestCase): def test_parse_bytes(self): info = Info.from_dict(cbor.decode(_INFO)) self.assertEqual(info.versions, ["U2F_V2", "FIDO_2_0"]) self.assertEqual(info.extensions, ["uvm", "hmac-secret"]) self.assertEqual(info.aaguid, _AAGUID) self.assertEqual( info.options, {"rk": True, "up": True, "plat": False, "clientPin": False} ) self.assertEqual(info.max_msg_size, 1200) self.assertEqual(info.pin_uv_protocols, [1]) assert info[0x01] == ["U2F_V2", "FIDO_2_0"] assert info[0x02] == ["uvm", "hmac-secret"] assert info[0x03] == _AAGUID assert info[0x04] == { "clientPin": False, "plat": False, "rk": True, "up": True, } assert info[0x05] == 1200 assert info[0x06] == [1] def test_info_with_extra_field(self): info = Info.from_dict(cbor.decode(_INFO_EXTRA_KEY)) self.assertEqual(info.versions, ["U2F_V2", "FIDO_2_0"]) _ATT_CRED_DATA = bytes.fromhex( "f8a011f38c0a4d15800617111f9edc7d0040fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b15783a5010203262001215820643566c206dd00227005fa5de69320616ca268043a38f08bde2e9dc45a5cafaf225820171353b2932434703726aae579fa6542432861fe591e481ea22d63997e1a5290" # noqa E501 ) _CRED_ID = bytes.fromhex( "fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b15783" # noqa E501 ) _PUB_KEY = { 1: 2, 3: -7, -1: 1, -2: bytes.fromhex( "643566c206dd00227005fa5de69320616ca268043a38f08bde2e9dc45a5cafaf" ), -3: bytes.fromhex( "171353b2932434703726aae579fa6542432861fe591e481ea22d63997e1a5290" ), } class TestAttestedCredentialData(unittest.TestCase): def test_parse_bytes(self): data = AttestedCredentialData(_ATT_CRED_DATA) self.assertEqual(data.aaguid, _AAGUID) self.assertEqual(data.credential_id, _CRED_ID) self.assertEqual(data.public_key, _PUB_KEY) def test_create_from_args(self): data = AttestedCredentialData.create(_AAGUID, _CRED_ID, _PUB_KEY) self.assertEqual(_ATT_CRED_DATA, data) _AUTH_DATA_MC = bytes.fromhex( "0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12410000001CF8A011F38C0A4D15800617111F9EDC7D0040FE3AAC036D14C1E1C65518B698DD1DA8F596BC33E11072813466C6BF3845691509B80FB76D59309B8D39E0A93452688F6CA3A39A76F3FC52744FB73948B15783A5010203262001215820643566C206DD00227005FA5DE69320616CA268043A38F08BDE2E9DC45A5CAFAF225820171353B2932434703726AAE579FA6542432861FE591E481EA22D63997E1A5290" # noqa E501 ) _AUTH_DATA_GA = bytes.fromhex( "0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12010000001D" ) _RP_ID_HASH = bytes.fromhex( "0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12" ) class TestAuthenticatorData(unittest.TestCase): def test_parse_bytes_make_credential(self): data = AuthenticatorData(_AUTH_DATA_MC) self.assertEqual(data.rp_id_hash, _RP_ID_HASH) self.assertEqual(data.flags, 0x41) self.assertEqual(data.counter, 28) self.assertEqual(data.credential_data, _ATT_CRED_DATA) self.assertIsNone(data.extensions) def test_parse_bytes_get_assertion(self): data = AuthenticatorData(_AUTH_DATA_GA) self.assertEqual(data.rp_id_hash, _RP_ID_HASH) self.assertEqual(data.flags, 0x01) self.assertEqual(data.counter, 29) self.assertIsNone(data.credential_data) self.assertIsNone(data.extensions) _MC_RESP = bytes.fromhex( "a301667061636b65640258c40021f5fc0b85cd22e60623bcd7d1ca48948909249b4776eb515154e57b66ae12410000001cf8a011f38c0a4d15800617111f9edc7d0040fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b15783a5010203262001215820643566c206dd00227005fa5de69320616ca268043a38f08bde2e9dc45a5cafaf225820171353b2932434703726aae579fa6542432861fe591e481ea22d63997e1a529003a363616c67266373696758483046022100cc1ef43edf07de8f208c21619c78a565ddcf4150766ad58781193be8e0a742ed022100f1ed7c7243e45b7d8e5bda6b1abf10af7391789d1ef21b70bd69fed48dba4cb163783563815901973082019330820138a003020102020900859b726cb24b4c29300a06082a8648ce3d0403023047310b300906035504061302555331143012060355040a0c0b59756269636f205465737431223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e301e170d3136313230343131353530305a170d3236313230323131353530305a3047310b300906035504061302555331143012060355040a0c0b59756269636f205465737431223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3059301306072a8648ce3d020106082a8648ce3d03010703420004ad11eb0e8852e53ad5dfed86b41e6134a18ec4e1af8f221a3c7d6e636c80ea13c3d504ff2e76211bb44525b196c44cb4849979cf6f896ecd2bb860de1bf4376ba30d300b30090603551d1304023000300a06082a8648ce3d0403020349003046022100e9a39f1b03197525f7373e10ce77e78021731b94d0c03f3fda1fd22db3d030e7022100c4faec3445a820cf43129cdb00aabefd9ae2d874f9c5d343cb2f113da23723f3" # noqa E501 ) _GA_RESP = bytes.fromhex( "a301a26269645840fe3aac036d14c1e1c65518b698dd1da8f596bc33e11072813466c6bf3845691509b80fb76d59309b8d39e0a93452688f6ca3a39a76f3fc52744fb73948b1578364747970656a7075626c69632d6b65790258250021f5fc0b85cd22e60623bcd7d1ca48948909249b4776eb515154e57b66ae12010000001d035846304402206765cbf6e871d3af7f01ae96f06b13c90f26f54b905c5166a2c791274fc2397102200b143893586cc799fba4da83b119eaea1bd80ac3ce88fcedb3efbd596a1f4f63" # noqa E501 ) _CRED_ID = bytes.fromhex( "FE3AAC036D14C1E1C65518B698DD1DA8F596BC33E11072813466C6BF3845691509B80FB76D59309B8D39E0A93452688F6CA3A39A76F3FC52744FB73948B15783" # noqa E501 ) _CRED = {"type": "public-key", "id": _CRED_ID} _SIGNATURE = bytes.fromhex( "304402206765CBF6E871D3AF7F01AE96F06B13C90F26F54B905C5166A2C791274FC2397102200B143893586CC799FBA4DA83B119EAEA1BD80AC3CE88FCEDB3EFBD596A1F4F63" # noqa E501 ) class TestAttestationObject(unittest.TestCase): def test_fido_u2f_attestation(self): att = AttestationObject.from_ctap1( bytes.fromhex( "1194228DA8FDBDEEFD261BD7B6595CFD70A50D70C6407BCF013DE96D4EFB17DE" ), RegistrationData( bytes.fromhex( "0504E87625896EE4E46DC032766E8087962F36DF9DFE8B567F3763015B1990A60E1427DE612D66418BDA1950581EBC5C8C1DAD710CB14C22F8C97045F4612FB20C91403EBD89BF77EC509755EE9C2635EFAAAC7B2B9C5CEF1736C3717DA48534C8C6B654D7FF945F50B5CC4E78055BDD396B64F78DA2C5F96200CCD415CD08FE4200383082024A30820132A0030201020204046C8822300D06092A864886F70D01010B0500302E312C302A0603550403132359756269636F2055324620526F6F742043412053657269616C203435373230303633313020170D3134303830313030303030305A180F32303530303930343030303030305A302C312A302806035504030C2159756269636F205532462045452053657269616C203234393138323332343737303059301306072A8648CE3D020106082A8648CE3D030107034200043CCAB92CCB97287EE8E639437E21FCD6B6F165B2D5A3F3DB131D31C16B742BB476D8D1E99080EB546C9BBDF556E6210FD42785899E78CC589EBE310F6CDB9FF4A33B3039302206092B0601040182C40A020415312E332E362E312E342E312E34313438322E312E323013060B2B0601040182E51C020101040403020430300D06092A864886F70D01010B050003820101009F9B052248BC4CF42CC5991FCAABAC9B651BBE5BDCDC8EF0AD2C1C1FFB36D18715D42E78B249224F92C7E6E7A05C49F0E7E4C881BF2E94F45E4A21833D7456851D0F6C145A29540C874F3092C934B43D222B8962C0F410CEF1DB75892AF116B44A96F5D35ADEA3822FC7146F6004385BCB69B65C99E7EB6919786703C0D8CD41E8F75CCA44AA8AB725AD8E799FF3A8696A6F1B2656E631B1E40183C08FDA53FA4A8F85A05693944AE179A1339D002D15CABD810090EC722EF5DEF9965A371D415D624B68A2707CAD97BCDD1785AF97E258F33DF56A031AA0356D8E8D5EBCADC74E071636C6B110ACE5CC9B90DFEACAE640FF1BB0F1FE5DB4EFF7A95F060733F530450220324779C68F3380288A1197B6095F7A6EB9B1B1C127F66AE12A99FE8532EC23B9022100E39516AC4D61EE64044D50B415A6A4D4D84BA6D895CB5AB7A1AA7D081DE341FA" # noqa E501 ) ), ) Attestation.for_type(att.fmt)().verify( att.att_stmt, att.auth_data, bytes.fromhex( "687134968222EC17202E42505F8ED2B16AE22F16BB05B88C25DB9E602645F141" ), ) def test_packed_attestation(self): att = AttestationResponse.from_dict( cbor.decode( bytes.fromhex( "a301667061636b65640258c40021f5fc0b85cd22e60623bcd7d1ca48948909249b4776eb515154e57b66ae124100000003f8a011f38c0a4d15800617111f9edc7d004060a386206a3aacecbdbb22d601853d955fdc5d11adfbd1aa6a950d966b348c7663d40173714a9f987df6461beadfb9cd6419ffdfe4d4cf2eec1aa605a4f59bdaa50102032620012158200edb27580389494d74d2373b8f8c2e8b76fa135946d4f30d0e187e120b423349225820e03400d189e85a55de9ab0f538ed60736eb750f5f0306a80060fe1b13010560d03a363616c6726637369675847304502200d15daf337d727ab4719b4027114a2ac43cd565d394ced62c3d9d1d90825f0b3022100989615e7394c87f4ad91f8fdae86f7a3326df332b3633db088aac76bffb9a46b63783563815902bb308202b73082019fa00302010202041d31330d300d06092a864886f70d01010b0500302a3128302606035504030c1f59756269636f2050726576696577204649444f204174746573746174696f6e301e170d3138303332383036333932345a170d3139303332383036333932345a306e310b300906035504061302534531123010060355040a0c0959756269636f20414231223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3127302506035504030c1e59756269636f205532462045452053657269616c203438393736333539373059301306072a8648ce3d020106082a8648ce3d030107034200047d71e8367cafd0ea6cf0d61e4c6a416ba5bb6d8fad52db2389ad07969f0f463bfdddddc29d39d3199163ee49575a3336c04b3309d607f6160c81e023373e0197a36c306a302206092b0601040182c40a020415312e332e362e312e342e312e34313438322e312e323013060b2b0601040182e51c0201010404030204303021060b2b0601040182e51c01010404120410f8a011f38c0a4d15800617111f9edc7d300c0603551d130101ff04023000300d06092a864886f70d01010b050003820101009b904ceadbe1f1985486fead02baeaa77e5ab4e6e52b7e6a2666a4dc06e241578169193b63dadec5b2b78605a128b2e03f7fe2a98eaeb4219f52220995f400ce15d630cf0598ba662d7162459f1ad1fc623067376d4e4091be65ac1a33d8561b9996c0529ec1816d1710786384d5e8783aa1f7474cb99fe8f5a63a79ff454380361c299d67cb5cc7c79f0d8c09f8849b0500f6d625408c77cbbc26ddee11cb581beb7947137ad4f05aaf38bd98da10042ddcac277604a395a5b3eaa88a5c8bb27ab59c8127d59d6bbba5f11506bf7b75fda7561a0837c46f025fd54dcf1014fc8d17c859507ac57d4b1dea99485df0ba8f34d00103c3eef2ef3bbfec7a6613de" # noqa E501 ) ) ) Attestation.for_type(att.fmt)().verify( att.att_stmt, att.auth_data, bytes.fromhex( "985B6187D042FB1258892ED637CEC88617DDF5F6632351A545617AA2B75261BF" ), ) class TestCtap2(unittest.TestCase): def mock_ctap(self): device = mock.MagicMock() device.call.return_value = b"\0" + _INFO return Ctap2(device) def test_send_cbor_ok(self): ctap = self.mock_ctap() ctap.device.call.return_value = b"\0" + cbor.encode({1: b"response"}) self.assertEqual({1: b"response"}, ctap.send_cbor(2, b"foobar")) ctap.device.call.assert_called_with( 0x10, b"\2" + cbor.encode(b"foobar"), mock.ANY, None ) def test_get_info(self): ctap = self.mock_ctap() info = ctap.get_info() ctap.device.call.assert_called_with(0x10, b"\4", mock.ANY, None) self.assertIsInstance(info, Info) def test_make_credential(self): ctap = self.mock_ctap() ctap.device.call.return_value = b"\0" + _MC_RESP resp = ctap.make_credential(1, 2, 3, 4) ctap.device.call.assert_called_with( 0x10, b"\1" + cbor.encode({1: 1, 2: 2, 3: 3, 4: 4}), mock.ANY, None ) self.assertIsInstance(resp, AttestationResponse) self.assertEqual(resp, AttestationResponse.from_dict(cbor.decode(_MC_RESP))) self.assertEqual(resp.fmt, "packed") self.assertEqual(resp.auth_data, _AUTH_DATA_MC) self.assertSetEqual(set(resp.att_stmt.keys()), {"alg", "sig", "x5c"}) def test_get_assertion(self): ctap = self.mock_ctap() ctap.device.call.return_value = b"\0" + _GA_RESP resp = ctap.get_assertion(1, 2) ctap.device.call.assert_called_with( 0x10, b"\2" + cbor.encode({1: 1, 2: 2}), mock.ANY, None ) self.assertIsInstance(resp, AssertionResponse) self.assertEqual(resp, AssertionResponse.from_dict(cbor.decode(_GA_RESP))) self.assertEqual(resp.credential, _CRED) self.assertEqual(resp.auth_data, _AUTH_DATA_GA) self.assertEqual(resp.signature, _SIGNATURE) self.assertIsNone(resp.user) self.assertIsNone(resp.number_of_credentials) EC_PRIV = 0x7452E599FEE739D8A653F6A507343D12D382249108A651402520B72F24FE7684 EC_PUB_X = bytes.fromhex( "44D78D7989B97E62EA993496C9EF6E8FD58B8B00715F9A89153DDD9C4657E47F" ) EC_PUB_Y = bytes.fromhex( "EC802EE7D22BD4E100F12E48537EB4E7E96ED3A47A0A3BD5F5EEAB65001664F9" ) DEV_PUB_X = bytes.fromhex( "0501D5BC78DA9252560A26CB08FCC60CBE0B6D3B8E1D1FCEE514FAC0AF675168" ) DEV_PUB_Y = bytes.fromhex( "D551B3ED46F665731F95B4532939C25D91DB7EB844BD96D4ABD4083785F8DF47" ) SHARED = bytes.fromhex( "c42a039d548100dfba521e487debcbbb8b66bb7496f8b1862a7a395ed83e1a1c" ) TOKEN_ENC = bytes.fromhex("7A9F98E31B77BE90F9C64D12E9635040") TOKEN = bytes.fromhex("aff12c6dcfbf9df52f7a09211e8865cd") PIN_HASH_ENC = bytes.fromhex("afe8327ce416da8ee3d057589c2ce1a9") class TestClientPin(unittest.TestCase): @mock.patch("cryptography.hazmat.primitives.asymmetric.ec.generate_private_key") def test_establish_shared_secret(self, patched_generate): ctap = mock.MagicMock() ctap.info.options = {"clientPin": True} prot = ClientPin(ctap, PinProtocolV1()) patched_generate.return_value = ec.derive_private_key( EC_PRIV, ec.SECP256R1(), default_backend() ) ctap.client_pin.return_value = { 1: {1: 2, 3: -25, -1: 1, -2: DEV_PUB_X, -3: DEV_PUB_Y} } key_agreement, shared = prot._get_shared_secret() self.assertEqual(shared, SHARED) self.assertEqual(key_agreement[-2], EC_PUB_X) self.assertEqual(key_agreement[-3], EC_PUB_Y) def test_get_pin_token(self): ctap = mock.MagicMock() ctap.info.options = {"clientPin": True} prot = ClientPin(ctap, PinProtocolV1()) prot._get_shared_secret = mock.Mock(return_value=({}, SHARED)) prot.ctap.client_pin.return_value = {2: TOKEN_ENC} self.assertEqual(prot.get_pin_token("1234"), TOKEN) prot.ctap.client_pin.assert_called_once() self.assertEqual( prot.ctap.client_pin.call_args[1]["pin_hash_enc"], PIN_HASH_ENC ) def test_set_pin(self): ctap = mock.MagicMock() ctap.info.options = {"clientPin": True} prot = ClientPin(ctap, PinProtocolV1()) prot._get_shared_secret = mock.Mock(return_value=({}, SHARED)) prot.set_pin("1234") prot.ctap.client_pin.assert_called_with( 1, 3, key_agreement={}, new_pin_enc=bytes.fromhex( "0222fc42c6dd76a274a7057858b9b29d98e8a722ec2dc6668476168c5320473cec9907b4cd76ce7943c96ba5683943211d84471e64d9c51e54763488cd66526a" # noqa E501 ), pin_uv_param=bytes.fromhex("7b40c084ccc5794194189ab57836475f"), ) def test_change_pin(self): ctap = mock.MagicMock() ctap.info.options = {"clientPin": True} prot = ClientPin(ctap, PinProtocolV1()) prot._get_shared_secret = mock.Mock(return_value=({}, SHARED)) prot.change_pin("1234", "4321") prot.ctap.client_pin.assert_called_with( 1, 4, key_agreement={}, new_pin_enc=bytes.fromhex( "4280e14aac4fcbf02dd079985f0c0ffc9ea7d5f9c173fd1a4c843826f7590cb3c2d080c6923e2fe6d7a52c31ea1309d3fcca3dedae8a2ef14b6330cafc79339e" # noqa E501 ), pin_uv_param=bytes.fromhex("fb97e92f3724d7c85e001d7f93e6490a"), pin_hash_enc=bytes.fromhex("afe8327ce416da8ee3d057589c2ce1a9"), ) def test_short_pin(self): ctap = mock.MagicMock() ctap.info.options = {"clientPin": True} prot = ClientPin(ctap, PinProtocolV1()) with self.assertRaises(ValueError): prot.set_pin("123") def test_long_pin(self): ctap = mock.MagicMock() ctap.info.options = {"clientPin": True} prot = ClientPin(ctap, PinProtocolV1()) with self.assertRaises(ValueError): prot.set_pin("1" * 256) fido2-1.2.0/tests/__init__.py0000644000175000017500000000011314721556664015347 0ustar winniewinnieimport fido2.features fido2.features.webauthn_json_mapping.enabled = True fido2-1.2.0/tests/test_utils.py0000644000175000017500000000713014721556664016015 0ustar winniewinnie# coding=utf-8 # Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import unittest from fido2.utils import hmac_sha256, sha256, websafe_encode, websafe_decode class TestSha256(unittest.TestCase): def test_sha256_vectors(self): self.assertEqual( sha256(b"abc"), bytes.fromhex( "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" ), ) self.assertEqual( sha256(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"), bytes.fromhex( "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1" ), ) class TestHmacSha256(unittest.TestCase): def test_hmac_sha256_vectors(self): self.assertEqual( hmac_sha256(b"\x0b" * 20, b"Hi There"), bytes.fromhex( "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7" ), ) self.assertEqual( hmac_sha256(b"Jefe", b"what do ya want for nothing?"), bytes.fromhex( "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843" ), ) class TestWebSafe(unittest.TestCase): # Base64 vectors adapted from https://tools.ietf.org/html/rfc4648#section-10 def test_websafe_decode(self): self.assertEqual(websafe_decode(b""), b"") self.assertEqual(websafe_decode(b"Zg"), b"f") self.assertEqual(websafe_decode(b"Zm8"), b"fo") self.assertEqual(websafe_decode(b"Zm9v"), b"foo") self.assertEqual(websafe_decode(b"Zm9vYg"), b"foob") self.assertEqual(websafe_decode(b"Zm9vYmE"), b"fooba") self.assertEqual(websafe_decode(b"Zm9vYmFy"), b"foobar") def test_websafe_decode_unicode(self): self.assertEqual(websafe_decode(""), b"") self.assertEqual(websafe_decode("Zm9vYmFy"), b"foobar") def test_websafe_encode(self): self.assertEqual(websafe_encode(b""), "") self.assertEqual(websafe_encode(b"f"), "Zg") self.assertEqual(websafe_encode(b"fo"), "Zm8") self.assertEqual(websafe_encode(b"foo"), "Zm9v") self.assertEqual(websafe_encode(b"foob"), "Zm9vYg") self.assertEqual(websafe_encode(b"fooba"), "Zm9vYmE") self.assertEqual(websafe_encode(b"foobar"), "Zm9vYmFy") fido2-1.2.0/tests/test_webauthn_legacy_mapping.py0000644000175000017500000001102614721556664021530 0ustar winniewinnie# Copyright (c) 2019 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from fido2.webauthn import ( PublicKeyCredentialUserEntity, PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions, ) from fido2.features import webauthn_json_mapping import unittest class TestLegacyMapping(unittest.TestCase): @classmethod def setUpClass(cls): webauthn_json_mapping._enabled = False @classmethod def tearDownClass(cls): webauthn_json_mapping._enabled = True def test_user_entity(self): o = PublicKeyCredentialUserEntity("Example", b"user", display_name="Display") self.assertEqual( o, {"id": b"user", "name": "Example", "displayName": "Display"} ) self.assertEqual(o.id, b"user") self.assertEqual(o.name, "Example") self.assertEqual(o.display_name, "Display") def test_creation_options(self): o = PublicKeyCredentialCreationOptions( {"id": "example.com", "name": "Example"}, {"id": b"user_id", "name": "A. User"}, b"request_challenge", [{"type": "public-key", "alg": -7}], 10000, [{"type": "public-key", "id": b"credential_id"}], { "authenticatorAttachment": "platform", "residentKey": "required", "userVerification": "required", }, "direct", ) self.assertEqual(o.rp, {"id": "example.com", "name": "Example"}) self.assertEqual(o.user, {"id": b"user_id", "name": "A. User"}) self.assertIsNone(o.extensions) o2 = PublicKeyCredentialCreationOptions.from_dict(dict(o)) self.assertEqual(o, o2) o = PublicKeyCredentialCreationOptions( {"id": "example.com", "name": "Example"}, {"id": b"user_id", "name": "A. User"}, b"request_challenge", [{"type": "public-key", "alg": -7}], ) self.assertIsNone(o.timeout) self.assertIsNone(o.authenticator_selection) self.assertIsNone(o.attestation) self.assertIsNone( PublicKeyCredentialCreationOptions( {"id": "example.com", "name": "Example"}, {"id": b"user_id", "name": "A. User"}, b"request_challenge", [{"type": "public-key", "alg": -7}], attestation="invalid", ).attestation ) def test_request_options(self): o = PublicKeyCredentialRequestOptions( b"request_challenge", 10000, "example.com", [{"type": "public-key", "id": b"credential_id"}], "discouraged", ) self.assertEqual(o.challenge, b"request_challenge") self.assertEqual(o.rp_id, "example.com") self.assertEqual(o.timeout, 10000) self.assertIsNone(o.extensions) o = PublicKeyCredentialRequestOptions(b"request_challenge") self.assertIsNone(o.timeout) self.assertIsNone(o.rp_id) self.assertIsNone(o.allow_credentials) self.assertIsNone(o.user_verification) self.assertIsNone( PublicKeyCredentialRequestOptions( b"request_challenge", user_verification="invalid" ).user_verification ) fido2-1.2.0/tests/test_rpid.py0000644000175000017500000000620314721556664015613 0ustar winniewinnie# coding=utf-8 # Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from fido2.rpid import verify_rp_id import unittest class TestRpId(unittest.TestCase): def test_valid_ids(self): self.assertTrue(verify_rp_id("example.com", "https://register.example.com")) self.assertTrue(verify_rp_id("example.com", "https://fido.example.com")) self.assertTrue(verify_rp_id("example.com", "https://www.example.com:444")) def test_invalid_ids(self): self.assertFalse(verify_rp_id("example.com", "http://example.com")) self.assertFalse(verify_rp_id("example.com", "http://www.example.com")) self.assertFalse(verify_rp_id("example.com", "https://example-test.com")) self.assertFalse( verify_rp_id("companyA.hosting.example.com", "https://register.example.com") ) self.assertFalse( verify_rp_id( "companyA.hosting.example.com", "https://companyB.hosting.example.com" ) ) def test_suffix_list(self): self.assertFalse(verify_rp_id("co.uk", "https://foobar.co.uk")) self.assertTrue(verify_rp_id("foobar.co.uk", "https://site.foobar.co.uk")) self.assertFalse(verify_rp_id("appspot.com", "https://example.appspot.com")) self.assertTrue( verify_rp_id("example.appspot.com", "https://example.appspot.com") ) def test_localhost_http_secure_context(self): # Localhost and subdomains are secure contexts in most browsers self.assertTrue(verify_rp_id("localhost", "http://localhost")) self.assertTrue(verify_rp_id("localhost", "http://example.localhost")) self.assertTrue(verify_rp_id("example.localhost", "http://example.localhost")) self.assertTrue(verify_rp_id("localhost", "http://localhost:8000")) self.assertFalse(verify_rp_id("localhost", "http://")) fido2-1.2.0/tests/test_tpm.py0000644000175000017500000000556714721556664015471 0ustar winniewinnie# Copyright (c) 2019 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from fido2.attestation.tpm import TpmAttestationFormat, TpmPublicFormat import unittest class TestTpmObject(unittest.TestCase): def test_parse_tpm(self): data = bytes.fromhex( "ff54434780170022000b68cec627cc6411099a1f809fde4379f649aa170c7072d1adf230de439efc80810014f7c8b0cdeb31328648130a19733d6fff16e76e1300000003ef605603446ed8c56aa7608d01a6ea5651ee67a8a20022000bdf681917e18529c61e1b85a1e7952f3201eb59c609ed5d8e217e5de76b228bbd0022000b0a10d216b0c3ab82bfdc1f0a016ab9493384c7aee1937ee8800f76b30c9b71a7" # noqa ) tpm = TpmAttestationFormat.parse(data) self.assertEqual( tpm.data, bytes.fromhex("f7c8b0cdeb31328648130a19733d6fff16e76e13") ) def test_parse_too_short_of_a_tpm(self): with self.assertRaises(ValueError): TpmAttestationFormat.parse(bytes.fromhex("ff5443")) with self.assertRaises(ValueError) as e: data = bytes.fromhex( "ff54434780170022000b68cec627cc6411099a1f809fde4379f649aa170c7072d1adf230de439efc80810014f7c8b0cdeb31328648" # noqa ) TpmAttestationFormat.parse(data) self.assertEqual( e.exception.args[0], "Not enough data to read (need: 20, had: 9)." ) def test_parse_public_ecc(self): data = bytes.fromhex( "0023000b00060472000000100010000300100020b9174cd199f77552afcffe6b1f069c032ffdc4f56068dec4e189e7967b3bf6b0002037bf8aa7d93fddb9507319141c6fa31c8e48a1c6da013603a9f6e3913d157c66" # noqa ) TpmPublicFormat.parse(data) fido2-1.2.0/tests/utils.py0000644000175000017500000000372114721556664014760 0ustar winniewinniefrom cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import serialization from fido2.utils import sha256, bytes2int from fido2.webauthn import AuthenticatorData class U2FDevice(object): _priv_key_bytes = bytes.fromhex( "308184020100301006072a8648ce3d020106052b8104000a046d306b02010104201672f5ceb963e07d475f5db9a975b7ad42ac3bf71ccddfb6539cc651a1156a6ba144034200045a4be44eb94eebff3ed665dde31deb74a060fabd214c5f5713aa043efa805dac8f45e0e17afe2eafbd1802c413c1e485fd854af9f06ef20938398f6795f12e0e" # noqa E501 ) def __init__(self, credential_id, app_id): assert isinstance(credential_id, bytes) assert isinstance(app_id, bytes) # Note: do not use in production, no garantees is provided this is # cryptographically safe to use. priv_key_params = ConcatKDFHash( algorithm=hashes.SHA256(), length=32, otherinfo=credential_id + app_id, backend=default_backend(), ).derive(self._priv_key_bytes) self.app_id = app_id self.priv_key = ec.derive_private_key( bytes2int(priv_key_params), ec.SECP256R1(), default_backend() ) self.pub_key = self.priv_key.public_key() self.public_key_bytes = self.pub_key.public_bytes( serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint ) self.credential_id = self.key_handle = credential_id def sign(self, client_data): authenticator_data = AuthenticatorData.create( sha256(self.app_id), flags=AuthenticatorData.FLAG.UP, counter=0 ) signature = self.priv_key.sign( authenticator_data + client_data.hash, ec.ECDSA(hashes.SHA256()) ) return authenticator_data, signature fido2-1.2.0/tests/test_server.py0000644000175000017500000001611214721556664016163 0ustar winniewinnieimport unittest from fido2.server import Fido2Server, U2FFido2Server, verify_app_id from fido2.webauthn import ( CollectedClientData, PublicKeyCredentialRpEntity, UserVerificationRequirement, AttestedCredentialData, AuthenticatorData, ) from fido2.utils import websafe_encode from .test_ctap2 import _ATT_CRED_DATA, _CRED_ID from .utils import U2FDevice class TestAppId(unittest.TestCase): def test_valid_ids(self): self.assertTrue( verify_app_id("https://example.com", "https://register.example.com") ) self.assertTrue( verify_app_id("https://example.com", "https://fido.example.com") ) self.assertTrue( verify_app_id("https://example.com", "https://www.example.com:444") ) self.assertTrue( verify_app_id( "https://companyA.hosting.example.com", "https://fido.companyA.hosting.example.com", ) ) self.assertTrue( verify_app_id( "https://companyA.hosting.example.com", "https://xyz.companyA.hosting.example.com", ) ) def test_invalid_ids(self): self.assertFalse(verify_app_id("https://example.com", "http://example.com")) self.assertFalse(verify_app_id("https://example.com", "http://www.example.com")) self.assertFalse( verify_app_id("https://example.com", "https://example-test.com") ) self.assertFalse( verify_app_id( "https://companyA.hosting.example.com", "https://register.example.com" ) ) self.assertFalse( verify_app_id( "https://companyA.hosting.example.com", "https://companyB.hosting.example.com", ) ) def test_effective_tld_names(self): self.assertFalse( verify_app_id("https://appspot.com", "https://foo.appspot.com") ) self.assertFalse(verify_app_id("https://co.uk", "https://example.co.uk")) class TestPublicKeyCredentialRpEntity(unittest.TestCase): def test_id_hash(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") rp_id_hash = ( b"\xa3y\xa6\xf6\xee\xaf\xb9\xa5^7\x8c\x11\x804\xe2u\x1eh/" b"\xab\x9f-0\xab\x13\xd2\x12U\x86\xce\x19G" ) self.assertEqual(rp.id_hash, rp_id_hash) USER = {"id": b"user_id", "name": "A. User"} class TestFido2Server(unittest.TestCase): def test_register_begin_rp(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") server = Fido2Server(rp) request, state = server.register_begin(USER) self.assertEqual( request["publicKey"]["rp"], {"id": "example.com", "name": "Example"} ) def test_register_begin_custom_challenge(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") server = Fido2Server(rp) challenge = b"1234567890123456" request, state = server.register_begin(USER, challenge=challenge) self.assertEqual(request["publicKey"]["challenge"], websafe_encode(challenge)) def test_register_begin_custom_challenge_too_short(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") server = Fido2Server(rp) challenge = b"123456789012345" with self.assertRaises(ValueError): request, state = server.register_begin(USER, challenge=challenge) def test_authenticate_complete_invalid_signature(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") server = Fido2Server(rp) state = { "challenge": "GAZPACHO!", "user_verification": UserVerificationRequirement.PREFERRED, } client_data = CollectedClientData.create( CollectedClientData.TYPE.GET, "GAZPACHO!", "https://example.com", ) _AUTH_DATA = bytes.fromhex( "A379A6F6EEAFB9A55E378C118034E2751E682FAB9F2D30AB13D2125586CE1947010000001D" ) with self.assertRaisesRegex(ValueError, "Invalid signature."): server.authenticate_complete( state, [AttestedCredentialData(_ATT_CRED_DATA)], _CRED_ID, client_data, AuthenticatorData(_AUTH_DATA), b"INVALID", ) class TestU2FFido2Server(unittest.TestCase): def test_u2f(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") app_id = b"https://example.com" server = U2FFido2Server(app_id=app_id.decode("ascii"), rp=rp) state = { "challenge": "GAZPACHO!", "user_verification": UserVerificationRequirement.PREFERRED, } client_data = CollectedClientData.create( CollectedClientData.TYPE.GET, "GAZPACHO!", "https://example.com", ) param = b"TOMATO GIVES " device = U2FDevice(param, app_id) auth_data = AttestedCredentialData.from_ctap1(param, device.public_key_bytes) authenticator_data, signature = device.sign(client_data) server.authenticate_complete( state, [auth_data], device.credential_id, client_data, authenticator_data, signature, ) def test_u2f_facets(self): rp = PublicKeyCredentialRpEntity("Example", "example.com") app_id = b"https://www.example.com/facets.json" def verify_u2f_origin(origin): return origin in ("https://oauth.example.com", "https://admin.example.com") server = U2FFido2Server( app_id=app_id.decode("ascii"), rp=rp, verify_u2f_origin=verify_u2f_origin ) state = { "challenge": "GAZPACHO!", "user_verification": UserVerificationRequirement.PREFERRED, } client_data = CollectedClientData.create( CollectedClientData.TYPE.GET, "GAZPACHO!", "https://oauth.example.com", ) param = b"TOMATO GIVES " device = U2FDevice(param, app_id) auth_data = AttestedCredentialData.from_ctap1(param, device.public_key_bytes) authenticator_data, signature = device.sign(client_data) server.authenticate_complete( state, [auth_data], device.credential_id, client_data, authenticator_data, signature, ) # Now with something not whitelisted client_data = CollectedClientData.create( CollectedClientData.TYPE.GET, "GAZPACHO!", "https://publicthingy.example.com", ) authenticator_data, signature = device.sign(client_data) with self.assertRaisesRegex( ValueError, "Invalid origin in CollectedClientData." ): server.authenticate_complete( state, [auth_data], device.credential_id, client_data, authenticator_data, signature, ) fido2-1.2.0/tests/test_pcsc.py0000644000175000017500000000631414721556664015610 0ustar winniewinnie# Copyright (c) 2019 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. import unittest import sys from unittest import mock from fido2.hid import CTAPHID sys.modules["smartcard"] = mock.Mock() sys.modules["smartcard.Exceptions"] = mock.Mock() sys.modules["smartcard.System"] = mock.Mock() sys.modules["smartcard.CardConnection"] = mock.Mock() sys.modules["smartcard.pcsc"] = mock.Mock() sys.modules["smartcard.pcsc.PCSCExceptions"] = mock.Mock() sys.modules["smartcard.pcsc.PCSCContext"] = mock.Mock() from fido2.pcsc import CtapPcscDevice # noqa E402 class PcscTest(unittest.TestCase): def test_pcsc_call_cbor(self): connection = mock.Mock() connection.transmit.side_effect = [(b"U2F_V2", 0x90, 0x00), (b"", 0x90, 0x00)] CtapPcscDevice(connection, "Mock") connection.transmit.assert_called_with( [0x80, 0x10, 0x80, 0x00, 0x01, 0x04, 0x00], None ) def test_pcsc_call_u2f(self): connection = mock.Mock() connection.transmit.side_effect = [ (b"U2F_V2", 0x90, 0x00), (b"", 0x90, 0x00), (b"u2f_resp", 0x90, 0x00), ] dev = CtapPcscDevice(connection, "Mock") res = dev.call(CTAPHID.MSG, b"\x00\x01\x00\x00\x05" + b"\x01" * 5 + b"\x00") connection.transmit.assert_called_with( [0x00, 0x01, 0x00, 0x00, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00], None ) self.assertEqual(res, b"u2f_resp\x90\x00") def test_pcsc_call_version_2(self): connection = mock.Mock() connection.transmit.side_effect = [(b"U2F_V2", 0x90, 0x00), (b"", 0x90, 0x00)] dev = CtapPcscDevice(connection, "Mock") self.assertEqual(dev.version, 2) def test_pcsc_call_version_1(self): connection = mock.Mock() connection.transmit.side_effect = [(b"U2F_V2", 0x90, 0x00), (b"", 0x63, 0x85)] dev = CtapPcscDevice(connection, "Mock") self.assertEqual(dev.version, 1) fido2-1.2.0/tests/test_cose.py0000644000175000017500000001435414721556664015614 0ustar winniewinnie# Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import absolute_import, unicode_literals from fido2 import cbor from fido2.cose import CoseKey, ES256, RS256, EdDSA, UnsupportedKey from binascii import a2b_hex import unittest _ES256_KEY = a2b_hex( b"A5010203262001215820A5FD5CE1B1C458C530A54FA61B31BF6B04BE8B97AFDE54DD8CBB69275A8A1BE1225820FA3A3231DD9DEED9D1897BE5A6228C59501E4BCD12975D3DFF730F01278EA61C" # noqa E501 ) _RS256_KEY = a2b_hex( b"A401030339010020590100B610DCE84B65029FAE24F7BF8A1730D37BC91435642A628E691E9B030BF3F7CEC59FF91CBE82C54DE16C136FA4FA8A58939B5A950B32E03073592FEC8D8B33601C04F70E5E2D5CF7B4E805E1990EA5A86928A1B390EB9026527933ACC03E6E41DC0BE40AA5EB7B9B460743E4DD80895A758FB3F3F794E5E9B8310D3A60C28F2410D95CF6E732749A243A30475267628B456DE770BC2185BBED1D451ECB0062A3D132C0E4D842E0DDF93A444A3EE33A85C2E913156361713155F1F1DC64E8E68ED176466553BBDE669EB82810B104CB4407D32AE6316C3BD6F382EC3AE2C5FD49304986D64D92ED11C25B6C5CF1287233545A987E9A3E169F99790603DBA5C8AD2143010001" # noqa E501 ) _EdDSA_KEY = a2b_hex( b"a4010103272006215820ee9b21803405d3cf45601e58b6f4c06ea93862de87d3af903c5870a5016e86f5" # noqa E501 ) class TestCoseKey(unittest.TestCase): def test_ES256_parse_verify(self): key = CoseKey.parse(cbor.decode(_ES256_KEY)) self.assertIsInstance(key, ES256) self.assertEqual( key, { 1: 2, 3: -7, -1: 1, -2: a2b_hex( b"A5FD5CE1B1C458C530A54FA61B31BF6B04BE8B97AFDE54DD8CBB69275A8A1BE1" ), -3: a2b_hex( b"FA3A3231DD9DEED9D1897BE5A6228C59501E4BCD12975D3DFF730F01278EA61C" ), }, ) key.verify( a2b_hex( b"0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12010000002C" # noqa E501 + b"7B89F12A9088B0F5EE0EF8F6718BCCC374249C31AEEBAEB79BD0450132CD536C" ), a2b_hex( b"304402202B3933FE954A2D29DE691901EB732535393D4859AAA80D58B08741598109516D0220236FBE6B52326C0A6B1CFDC6BF0A35BDA92A6C2E41E40C3A1643428D820941E0" # noqa E501 ), ) def test_RS256_parse_verify(self): key = CoseKey.parse(cbor.decode(_RS256_KEY)) self.assertIsInstance(key, RS256) self.assertEqual( key, { 1: 3, 3: -257, -1: a2b_hex( b"B610DCE84B65029FAE24F7BF8A1730D37BC91435642A628E691E9B030BF3F7CEC59FF91CBE82C54DE16C136FA4FA8A58939B5A950B32E03073592FEC8D8B33601C04F70E5E2D5CF7B4E805E1990EA5A86928A1B390EB9026527933ACC03E6E41DC0BE40AA5EB7B9B460743E4DD80895A758FB3F3F794E5E9B8310D3A60C28F2410D95CF6E732749A243A30475267628B456DE770BC2185BBED1D451ECB0062A3D132C0E4D842E0DDF93A444A3EE33A85C2E913156361713155F1F1DC64E8E68ED176466553BBDE669EB82810B104CB4407D32AE6316C3BD6F382EC3AE2C5FD49304986D64D92ED11C25B6C5CF1287233545A987E9A3E169F99790603DBA5C8AD" # noqa E501 ), -2: a2b_hex(b"010001"), }, ) key.verify( a2b_hex( b"0021F5FC0B85CD22E60623BCD7D1CA48948909249B4776EB515154E57B66AE12010000002E" # noqa E501 + b"CC9340FD84950987BA667DBE9B2C97C7241E15E2B54869A0DD1CE2013C4064B8" ), a2b_hex( b"071B707D11F0E7F62861DFACA89C4E674321AD8A6E329FDD40C7D6971348FBB0514E7B2B0EFE215BAAC0365C4124A808F8180D6575B710E7C01DAE8F052D0C5A2CE82F487C656E7AD824F3D699BE389ADDDE2CBF39E87A8955E93202BAE8830AB4139A7688DFDAD849F1BB689F3852BA05BED70897553CC44704F6941FD1467AD6A46B4DAB503716D386FE7B398E78E0A5A8C4040539D2C9BFA37E4D94F96091FFD1D194DE2CA58E9124A39757F013801421E09BD261ADA31992A8B0386A80AF51A87BD0CEE8FDAB0D4651477670D4C7B245489BED30A57B83964DB79418D5A4F5F2E5ABCA274426C9F90B007A962AE15DFF7343AF9E110746E2DB9226D785C6" # noqa E501 ), ) def test_EdDSA_parse_verify(self): key = CoseKey.parse(cbor.decode(_EdDSA_KEY)) self.assertIsInstance(key, EdDSA) self.assertEqual( key, { 1: 1, 3: -8, -1: 6, -2: a2b_hex( "EE9B21803405D3CF45601E58B6F4C06EA93862DE87D3AF903C5870A5016E86F5" ), }, ) key.verify( a2b_hex( b"a379a6f6eeafb9a55e378c118034e2751e682fab9f2d30ab13d2125586ce1947010000000500a11a323057d1103784ddff99a354ddd42348c2f00e88d8977b916cabf92268" # noqa E501 ), a2b_hex( b"e8c927ef1a57c738ff4ba8d6f90e06d837a5219eee47991f96b126b0685d512520c9c2eedebe4b88ff2de2b19cb5f8686efc7c4261e9ed1cb3ac5de50869be0a" # noqa E501 ), ) def test_unsupported_key(self): key = CoseKey.parse({1: 4711, 3: 4712, -1: b"123", -2: b"456"}) self.assertIsInstance(key, UnsupportedKey) self.assertEqual(key, {1: 4711, 3: 4712, -1: b"123", -2: b"456"}) fido2-1.2.0/tests/test_hid.py0000644000175000017500000000370414721556664015424 0ustar winniewinnie# Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from fido2.hid import CtapHidDevice import unittest class HidTest(unittest.TestCase): def get_device(self): try: devs = list(CtapHidDevice.list_devices()) assert len(devs) == 1 return devs[0] except Exception: self.skipTest("Tests require a single FIDO HID device") def test_ping(self): msg1 = b"hello world!" msg2 = b" " msg3 = b"" dev = self.get_device() self.assertEqual(dev.ping(msg1), msg1) self.assertEqual(dev.ping(msg2), msg2) self.assertEqual(dev.ping(msg3), msg3) fido2-1.2.0/examples/0000775000175000017500000000000014741676716013724 5ustar winniewinniefido2-1.2.0/examples/acr1252u.py0000644000175000017500000001304514721556664015540 0ustar winniewinniefrom 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-1.2.0/examples/large_blobs.py0000644000175000017500000001003214721556664016540 0ustar winniewinnie# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to the first FIDO device found (starts from USB, then looks into NFC), creates a new credential for it, and authenticates the credential. This works with both FIDO 2.0 devices as well as with U2F devices. On Windows, the native WebAuthn API will be used. """ from fido2.server import Fido2Server from exampleutils import get_client from fido2.utils import websafe_encode, websafe_decode import sys # Locate a suitable FIDO authenticator client = get_client(lambda client: "largeBlobKey" in client.info.extensions) # LargeBlob requires UV if it is configured uv = "discouraged" if client.info.options.get("clientPin"): uv = "required" server = Fido2Server({"id": "example.com", "name": "Example RP"}) user = {"id": b"user_id", "name": "A. User"} # Prepare parameters for makeCredential create_options, state = server.register_begin( user, resident_key_requirement="required", user_verification=uv, authenticator_attachment="cross-platform", ) print("Creating a credential with LargeBlob support...") # Create a credential result = client.make_credential( { **create_options["publicKey"], # Enable largeBlob "extensions": {"largeBlob": {"support": "required"}}, } ) # Complete registration auth_data = server.register_complete( state, result.client_data, result.attestation_object ) credentials = [auth_data.credential_data] if auth_data.is_user_verified(): # The WindowsClient doesn't know about authenticator config until now uv = "required" if not result.extension_results.get("largeBlob", {}).get("supported"): print("Credential does not support largeBlob, failure!") sys.exit(1) print("Credential created! Writing a blob...") # Prepare parameters for getAssertion request_options, state = server.authenticate_begin(credentials, user_verification=uv) # Authenticate the credential selection = client.get_assertion( { **request_options["publicKey"], # Write a large blob "extensions": { "largeBlob": {"write": websafe_encode(b"Here is some data to store!")} }, } ) # Only one cred in allowCredentials, only one response. result = selection.get_response(0) if not result.extension_results.get("largeBlob", {}).get("written"): print("Failed to write blob!") sys.exit(1) print("Blob written! Reading back the blob...") # Authenticate the credential selection = client.get_assertion( { **request_options["publicKey"], # Read the blob "extensions": {"largeBlob": {"read": True}}, } ) # Only one cred in allowCredentials, only one response. result = selection.get_response(0) print("Read blob:", websafe_decode(result.extension_results["largeBlob"]["blob"])) fido2-1.2.0/examples/get_info.py0000644000175000017500000000470414721556664016070 0ustar winniewinnie# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to each attached FIDO device, and: 1. If the device supports CBOR commands, perform a getInfo command. 2. If the device supports WINK, perform the wink command. """ from fido2.hid import CtapHidDevice, CAPABILITY from fido2.ctap2 import Ctap2 try: from fido2.pcsc import CtapPcscDevice except ImportError: CtapPcscDevice = None def enumerate_devices(): for dev in CtapHidDevice.list_devices(): yield dev if CtapPcscDevice: for dev in CtapPcscDevice.list_devices(): yield dev for dev in enumerate_devices(): print("CONNECT: %s" % dev) print("Product name: %s" % dev.product_name) print("Serial number: %s" % dev.serial_number) print("CTAPHID protocol version: %d" % dev.version) if dev.capabilities & CAPABILITY.CBOR: ctap2 = Ctap2(dev) info = ctap2.get_info() print("DEVICE INFO: %s" % info) else: print("Device does not support CBOR") if dev.capabilities & CAPABILITY.WINK: dev.wink() print("WINK sent!") else: print("Device does not support WINK") dev.close() fido2-1.2.0/examples/exampleutils.py0000644000175000017500000000656014721556664017014 0ustar winniewinnie# Copyright (c) 2024 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. """ Utilities for common functionality used by several examples in this directory. """ from fido2.hid import CtapHidDevice from fido2.client import Fido2Client, WindowsClient, UserInteraction from getpass import getpass import ctypes try: from fido2.pcsc import CtapPcscDevice except ImportError: CtapPcscDevice = None # Handle user interaction via CLI prompts class CliInteraction(UserInteraction): def __init__(self): self._pin = None def prompt_up(self): print("\nTouch your authenticator device now...\n") def request_pin(self, permissions, rd_id): if not self._pin: self._pin = getpass("Enter PIN: ") return self._pin def request_uv(self, permissions, rd_id): print("User Verification required.") return True def enumerate_devices(): for dev in CtapHidDevice.list_devices(): yield dev if CtapPcscDevice: for dev in CtapPcscDevice.list_devices(): yield dev def get_client(predicate=None, **kwargs): """Locate a CTAP device suitable for use. If running on Windows as non-admin, the predicate check will be skipped and a webauthn.dll based client will be returned. Extra kwargs will be passed to the constructor of Fido2Client. """ if WindowsClient.is_available() and not ctypes.windll.shell32.IsUserAnAdmin(): # Use the Windows WebAuthn API if available, and we're not running as admin return WindowsClient("https://example.com") user_interaction = kwargs.pop("user_interaction", None) or CliInteraction() # Locate a device for dev in enumerate_devices(): # Set up a FIDO 2 client using the origin https://example.com client = Fido2Client( dev, "https://example.com", user_interaction=user_interaction, **kwargs, ) # Check if it is suitable for use if predicate is None or predicate(client): return client else: raise ValueError("No suitable Authenticator found!") fido2-1.2.0/examples/resident_key.py0000644000175000017500000000743614721556664016770 0ustar winniewinnie# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to the first FIDO device found (starts from USB, then looks into NFC), creates a new credential for it, and authenticates the credential. This works with both FIDO 2.0 devices as well as with U2F devices. On Windows, the native WebAuthn API will be used. """ from fido2.server import Fido2Server from exampleutils import get_client # Locate a suitable FIDO authenticator client = get_client(lambda client: client.info.options.get("rk")) # Prefer UV if supported and configured uv = "discouraged" if client.info.options.get("uv") or client.info.options.get("bioEnroll"): uv = "preferred" print("Authenticator is configured for User Verification") server = Fido2Server({"id": "example.com", "name": "Example RP"}, attestation="direct") user = {"id": b"user_id", "name": "A. User"} # Prepare parameters for makeCredential create_options, state = server.register_begin( user, resident_key_requirement="required", user_verification=uv, authenticator_attachment="cross-platform", ) # Create a credential result = client.make_credential( { **create_options["publicKey"], # This extension isn't needed, but can be used to verify that the created # credential uses resident key "extensions": {"credProps": True}, } ) # Complete registration auth_data = server.register_complete( state, result.client_data, result.attestation_object ) credentials = [auth_data.credential_data] print("New credential created!") print("CLIENT DATA:", result.client_data) print("ATTESTATION OBJECT:", result.attestation_object) print() print("CREDENTIAL DATA:", auth_data.credential_data) print() print("Credential Properties:", result.extension_results.get("credProps")) # credProps: cred_props = result.extension_results.get("credProps") print("CredProps", cred_props) # Prepare parameters for getAssertion request_options, state = server.authenticate_begin(user_verification=uv) # Authenticate the credential selection = client.get_assertion(request_options["publicKey"]) result = selection.get_response(0) # There may be multiple responses, get the first. print("USER ID:", result.user_handle) # Complete authenticator server.authenticate_complete( state, credentials, result.credential_id, result.client_data, result.authenticator_data, result.signature, ) print("Credential authenticated!") print("CLIENT DATA:", result.client_data) print() print("AUTHENTICATOR DATA:", result.authenticator_data) fido2-1.2.0/examples/acr122u.py0000644000175000017500000000446414721556664015460 0ustar winniewinniefrom fido2.pcsc import CtapPcscDevice import time class Acr122uPcscDevice(object): def __init__(self, pcsc_device): self.pcsc = pcsc_device def reader_version(self): """ Get reader's version from reader :return: string. Reader's version """ try: result, sw1, sw2 = self.pcsc.apdu_exchange(b"\xff\x00\x48\x00\x00") if len(result) > 0: str_result = result + bytes([sw1]) + bytes([sw2]) str_result = str_result.decode("utf-8") return str_result except Exception as e: print("Get version error:", e) return "n/a" def led_control( self, red=False, green=False, blink_count=0, red_end_blink=False, green_end_blink=False, ): """ Reader's led control :param red: boolean. red led on :param green: boolean. green let on :param blink_count: int. if needs to blink value > 0. blinks count :param red_end_blink: boolean. state of red led at the end of blinking :param green_end_blink: boolean. state of green led at the end of blinking :return: """ try: if blink_count > 0: cbyte = ( 0b00001100 + (0b01 if red_end_blink else 0b00) + (0b10 if green_end_blink else 0b00) ) cbyte |= (0b01000000 if red else 0b00000000) + ( 0b10000000 if green else 0b00000000 ) else: cbyte = 0b00001100 + (0b01 if red else 0b00) + (0b10 if green else 0b00) apdu = ( b"\xff\x00\x40" + bytes([cbyte & 0xFF]) + b"\4" + b"\5\3" + bytes([blink_count]) + b"\0" ) self.pcsc.apdu_exchange(apdu) except Exception as e: print("LED control error:", e) dev = next(CtapPcscDevice.list_devices()) print("CONNECT: %s" % dev) pcsc_device = Acr122uPcscDevice(dev) pcsc_device.led_control(False, True, 0) print("version: %s" % pcsc_device.reader_version()) pcsc_device.led_control(True, False, 0) time.sleep(1) pcsc_device.led_control(False, True, 3) fido2-1.2.0/examples/u2f_nfc.py0000644000175000017500000000174214721556664015617 0ustar winniewinniefrom 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-1.2.0/examples/cred_blob.py0000644000175000017500000000666514721556664016221 0ustar winniewinnie# Copyright (c) 2020 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to the first FIDO device found which supports the CredBlob extension, creates a new credential for it with the extension enabled, and stores some data. """ from fido2.server import Fido2Server from exampleutils import get_client import sys import os # Locate a suitable FIDO authenticator client = get_client(lambda client: "credBlob" in client.info.extensions) # Prefer UV token if supported uv = "discouraged" if client.info.options.get("uv") or client.info.options.get("bioEnroll"): uv = "preferred" print("Authenticator is configured for User Verification") server = Fido2Server({"id": "example.com", "name": "Example RP"}) user = {"id": b"user_id", "name": "A. User"} # Prepare parameters for makeCredential create_options, state = server.register_begin( user, resident_key_requirement="required", user_verification=uv, authenticator_attachment="cross-platform", ) # Add CredBlob extension, attach data blob = os.urandom(32) # 32 random bytes # Create a credential result = client.make_credential( { **create_options["publicKey"], "extensions": {"credBlob": blob}, } ) # Complete registration auth_data = server.register_complete( state, result.client_data, result.attestation_object ) credentials = [auth_data.credential_data] # CredBlob result: if not auth_data.extensions.get("credBlob"): print("Credential was registered, but credBlob was NOT saved.") sys.exit(1) print("New credential created, with the CredBlob extension.") # Prepare parameters for getAssertion request_options, state = server.authenticate_begin() # Authenticate the credential # Only one cred in allowCredentials, only one response. result = client.get_assertion( { **request_options["publicKey"], "extensions": {"getCredBlob": True}, } ).get_response(0) blob_res = result.authenticator_data.extensions.get("credBlob") if blob == blob_res: print("Authenticated, got correct blob:", blob.hex()) else: print( "Authenticated, got incorrect blob! (was %s, expected %s)" % (blob_res.hex(), blob.hex()) ) sys.exit(1) fido2-1.2.0/examples/multi_device.py0000644000175000017500000000676514721556664016760 0ustar winniewinnie# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to each FIDO device found, and causes them all to blink until the user triggers one to select it. A new credential is created for that authenticator, and the operation is cancelled for the others. """ from fido2.hid import CtapHidDevice from fido2.client import Fido2Client, ClientError, UserInteraction from threading import Event, Thread from getpass import getpass import sys # Locate a device devs = list(CtapHidDevice.list_devices()) if not devs: print("No FIDO device found") sys.exit(1) # Handle user interaction class CliInteraction(UserInteraction): def prompt_up(self): print("\nTouch your authenticator device now...\n") def request_pin(self, permissions, rd_id): return getpass("Enter PIN: ") def request_uv(self, permissions, rd_id): print("User Verification required.") return True cli_interaction = CliInteraction() clients = [ Fido2Client(d, "https://example.com", user_interaction=cli_interaction) for d in devs ] # Prepare parameters for makeCredential rp = {"id": "example.com", "name": "Example RP"} user = {"id": b"user_id", "name": "A. User"} challenge = b"Y2hhbGxlbmdl" cancel = Event() selected = None def select(client): global selected try: client.selection(cancel) selected = client except ClientError as e: if e.code != ClientError.ERR.TIMEOUT: raise else: return cancel.set() print("\nTouch the authenticator you wish to use...\n") threads = [] for client in clients: t = Thread(target=select, args=(client,)) threads.append(t) t.start() for t in threads: t.join() if cancel.is_set(): print("Authenticator selected, making credential...") result = selected.make_credential( { "rp": rp, "user": user, "challenge": challenge, "pubKeyCredParams": [{"type": "public-key", "alg": -7}], }, ) print("New credential created!") print("ATTESTATION OBJECT:", result.attestation_object) print() print("CREDENTIAL DATA:", result.attestation_object.auth_data.credential_data) else: print("Operation timed out!") fido2-1.2.0/examples/hmac_secret.py0000644000175000017500000001267414721556664016560 0ustar winniewinnie# 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. NOTE: This extension is not enabled by default as direct access to the extension is now allowed in a browser setting. See also prf.py for an example which uses the PRF extension which is enabled by default. """ from fido2.hid import CtapHidDevice from fido2.server import Fido2Server from fido2.client import Fido2Client, WindowsClient from fido2.ctap2.extensions import HmacSecretExtension from exampleutils import CliInteraction import ctypes 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 uv = "discouraged" if WindowsClient.is_available() and not ctypes.windll.shell32.IsUserAnAdmin(): # Use the Windows WebAuthn API if available, and we're not running as admin # By default only the PRF extension is allowed, we need to explicitly # configure the client to allow hmac-secret client = WindowsClient("https://example.com", allow_hmac_secret=True) else: # Locate a device for dev in enumerate_devices(): client = Fido2Client( dev, "https://example.com", user_interaction=CliInteraction(), # By default only the PRF extension is allowed, we need to explicitly # configure the client to allow hmac-secret extensions=[HmacSecretExtension(allow_hmac_secret=True)], ) if "hmac-secret" in client.info.extensions: break else: print("No Authenticator with the HmacSecret extension found!") sys.exit(1) server = Fido2Server({"id": "example.com", "name": "Example RP"}, attestation="none") user = {"id": b"user_id", "name": "A. User"} # Prepare parameters for makeCredential create_options, state = server.register_begin( user, resident_key_requirement="discouraged", user_verification=uv, authenticator_attachment="cross-platform", ) # Create a credential result = client.make_credential( { **create_options["publicKey"], "extensions": {"hmacCreateSecret": True}, } ) # Complete registration auth_data = server.register_complete( state, result.client_data, result.attestation_object ) credentials = [auth_data.credential_data] # HmacSecret result: if not result.extension_results.get("hmacCreateSecret"): print("Failed to create credential with HmacSecret") sys.exit(1) credential = result.attestation_object.auth_data.credential_data print("New credential created, with the HmacSecret extension.") # Prepare parameters for getAssertion allow_list = [{"type": "public-key", "id": credential.credential_id}] # Generate a salt for HmacSecret: salt = os.urandom(32) print("Authenticate with salt:", salt.hex()) # Prepare parameters for getAssertion request_options, state = server.authenticate_begin(credentials, user_verification=uv) # Authenticate the credential result = client.get_assertion( { **request_options["publicKey"], "extensions": {"hmacGetSecret": {"salt1": salt}}, } ) # Only one cred in allowCredentials, only one response. result = result.get_response(0) output1 = result.extension_results.hmac_get_secret.output1 print("Authenticated, secret:", output1.hex()) # Authenticate again, using two salts to generate two secrets: # Generate a second salt for HmacSecret: salt2 = os.urandom(32) print("Authenticate with second salt:", salt2.hex()) # The first salt is reused, which should result in the same secret. result = client.get_assertion( { **request_options["publicKey"], "extensions": {"hmacGetSecret": {"salt1": salt, "salt2": salt2}}, } ) # Only one cred in allowCredentials, only one response. result = result.get_response(0) output = result.extension_results.hmac_get_secret print("Old secret:", output.output1.hex()) print("New secret:", output.output2.hex()) fido2-1.2.0/examples/credential.py0000644000175000017500000000645314721556664016413 0ustar winniewinnie# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to the first FIDO device found (starts from USB, then looks into NFC), creates a new credential for it, and authenticates the credential. This works with both FIDO 2.0 devices as well as with U2F devices. On Windows, the native WebAuthn API will be used. """ from fido2.server import Fido2Server from exampleutils import get_client # Locate a suitable FIDO authenticator client = get_client() # Prefer UV if supported and configured if client.info.options.get("uv") or client.info.options.get("bioEnroll"): uv = "preferred" print("Authenticator supports User Verification") else: uv = "discouraged" server = Fido2Server({"id": "example.com", "name": "Example RP"}, attestation="direct") user = {"id": b"user_id", "name": "A. User"} # Prepare parameters for makeCredential create_options, state = server.register_begin( user, user_verification=uv, authenticator_attachment="cross-platform" ) # Create a credential result = client.make_credential(create_options["publicKey"]) # Complete registration auth_data = server.register_complete( state, result.client_data, result.attestation_object ) credentials = [auth_data.credential_data] print("New credential created!") print("CLIENT DATA:", result.client_data) print("ATTESTATION OBJECT:", result.attestation_object) print() print("CREDENTIAL DATA:", auth_data.credential_data) # Prepare parameters for getAssertion request_options, state = server.authenticate_begin(credentials, user_verification=uv) # Authenticate the credential result = client.get_assertion(request_options["publicKey"]) # Only one cred in allowCredentials, only one response. result = result.get_response(0) # Complete authenticator server.authenticate_complete( state, credentials, result.credential_id, result.client_data, result.authenticator_data, result.signature, ) print("Credential authenticated!") print("CLIENT DATA:", result.client_data) print() print("AUTH DATA:", result.authenticator_data) fido2-1.2.0/examples/bio_enrollment.py0000644000175000017500000000562514721556664017311 0ustar winniewinnie# Copyright (c) 2020 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ Connects to the first FIDO device found over USB, and attempts to enroll a new fingerprint. This requires that a PIN is already set. NOTE: This uses a draft bio enrollment specification which is not yet final. Consider this highly experimental. """ from fido2.hid import CtapHidDevice from fido2.ctap2 import Ctap2, FPBioEnrollment, CaptureError from fido2.ctap2.pin import ClientPin from fido2.ctap2.bio import BioEnrollment from getpass import getpass import sys pin = None uv = "discouraged" for dev in CtapHidDevice.list_devices(): try: ctap = Ctap2(dev) if BioEnrollment.is_supported(ctap.info): break except Exception: # nosec continue else: print("No Authenticator supporting bioEnroll found") sys.exit(1) if not ctap.info.options.get("clientPin"): print("PIN not set for the device!") sys.exit(1) # Authenticate with PIN print("Preparing to enroll a new fingerprint.") pin = getpass("Please enter PIN: ") client_pin = ClientPin(ctap) pin_token = client_pin.get_pin_token(pin, ClientPin.PERMISSION.BIO_ENROLL) bio = FPBioEnrollment(ctap, client_pin.protocol, pin_token) print(bio.enumerate_enrollments()) # Start enrollment enroller = bio.enroll() template_id = None while template_id is None: print("Press your fingerprint against the sensor now...") try: template_id = enroller.capture() print(enroller.remaining, "more scans needed.") except CaptureError as e: print(e) bio.set_name(template_id, "Example") print("Fingerprint registered successfully with ID:", template_id) fido2-1.2.0/examples/prf.py0000644000175000017500000001062714721556664015066 0ustar winniewinnie# Copyright (c) 2024 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 PRF extension, creates a new credential for it with the extension enabled, and uses it to derive two separate secrets. """ from fido2.server import Fido2Server from fido2.utils import websafe_encode from exampleutils import get_client import sys import os # Locate a suitable FIDO authenticator client = get_client(lambda client: "hmac-secret" in client.info.extensions) uv = "discouraged" server = Fido2Server({"id": "example.com", "name": "Example RP"}, attestation="none") user = {"id": b"user_id", "name": "A. User"} # Prepare parameters for makeCredential create_options, state = server.register_begin( user, resident_key_requirement="discouraged", user_verification=uv, authenticator_attachment="cross-platform", ) # Create a credential result = client.make_credential( { **create_options["publicKey"], "extensions": {"prf": {}}, } ) # Complete registration auth_data = server.register_complete( state, result.client_data, result.attestation_object ) credential = auth_data.credential_data # PRF result: if not result.extension_results.get("prf", {}).get("enabled"): print("Failed to create credential with PRF", result.extension_results) sys.exit(1) print("New credential created, with the PRF extension.") # If created with UV, keep using UV if result.attestation_object.auth_data.is_user_verified(): uv = "required" # Generate a salt for PRF: salt = websafe_encode(os.urandom(32)) print("Authenticate with salt:", salt) # Prepare parameters for getAssertion credentials = [credential] request_options, state = server.authenticate_begin(credentials, user_verification=uv) # Authenticate the credential result = client.get_assertion( { **request_options["publicKey"], "extensions": {"prf": {"eval": {"first": salt}}}, } ) # Only one cred in allowCredentials, only one response. response = result.get_response(0) output1 = response.extension_results["prf"]["results"]["first"] print("Authenticated, secret:", output1) # Authenticate again, using two salts to generate two secrets. # This time we will use evalByCredential, which can be used if there are multiple # credentials which use different salts. Here it is not needed, but provided for # completeness of the example. # Generate a second salt for PRF: salt2 = websafe_encode(os.urandom(32)) print("Authenticate with second salt:", salt2) # The first salt is reused, which should result in the same secret. result = client.get_assertion( { **request_options["publicKey"], "extensions": { "prf": { "evalByCredential": { websafe_encode(credential.credential_id): { "first": salt, "second": salt2, } } } }, } ) # Only one cred in allowCredentials, only one response. response = result.get_response(0) output = response.extension_results["prf"]["results"] print("Old secret:", output["first"]) print("New secret:", output["second"]) fido2-1.2.0/examples/server/0000775000175000017500000000000014741676716015232 5ustar winniewinniefido2-1.2.0/examples/server/poetry.lock0000755000175000017500000007560514721556664017441 0ustar winniewinnie# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "blinker" version = "1.8.2" description = "Fast, simple object-to-object and broadcast signaling" optional = false python-versions = ">=3.8" files = [ {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"}, {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"}, ] [[package]] name = "cffi" version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] [package.dependencies] pycparser = "*" [[package]] name = "click" version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "cryptography" version = "43.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"}, {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"}, {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"}, {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"}, {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"}, {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"}, {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"}, {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"}, {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"}, {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"}, {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"}, {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"}, {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"}, {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, ] [package.dependencies] cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] name = "fido2" version = "1.2.0" description = "FIDO2/WebAuthn library for implementing clients and servers." optional = false python-versions = "^3.8" files = [] develop = false [package.dependencies] cryptography = ">=2.6, !=35, <45" [package.extras] pcsc = ["pyscard (>=1.9,<3)"] [package.source] type = "directory" url = "../.." [[package]] name = "flask" version = "2.3.3" description = "A simple framework for building complex web applications." optional = false python-versions = ">=3.8" files = [ {file = "flask-2.3.3-py3-none-any.whl", hash = "sha256:f69fcd559dc907ed196ab9df0e48471709175e696d6e698dd4dbe940f96ce66b"}, {file = "flask-2.3.3.tar.gz", hash = "sha256:09c347a92aa7ff4a8e7f3206795f30d826654baf38b873d0744cd571ca609efc"}, ] [package.dependencies] blinker = ">=1.6.2" click = ">=8.1.3" importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} itsdangerous = ">=2.1.2" Jinja2 = ">=3.1.2" Werkzeug = ">=2.3.7" [package.extras] async = ["asgiref (>=3.2)"] dotenv = ["python-dotenv"] [[package]] name = "importlib-metadata" version = "8.5.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, ] [package.dependencies] zipp = ">=3.20" [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] name = "itsdangerous" version = "2.2.0" description = "Safely pass data to untrusted environments and back." optional = false python-versions = ">=3.8" files = [ {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, ] [[package]] name = "jinja2" version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] [[package]] name = "markupsafe" version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" files = [ {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] [[package]] name = "pycparser" version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] [[package]] name = "werkzeug" version = "3.0.6" description = "The comprehensive WSGI web application library." optional = false python-versions = ">=3.8" files = [ {file = "werkzeug-3.0.6-py3-none-any.whl", hash = "sha256:1bc0c2310d2fbb07b1dd1105eba2f7af72f322e1e455f2f93c993bee8c8a5f17"}, {file = "werkzeug-3.0.6.tar.gz", hash = "sha256:a8dd59d4de28ca70471a34cba79bed5f7ef2e036a76b3ab0835474246eb41f8d"}, ] [package.dependencies] MarkupSafe = ">=2.1.1" [package.extras] watchdog = ["watchdog (>=2.3)"] [[package]] name = "zipp" version = "3.20.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, ] [package.extras] check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.8" content-hash = "2fce33bd11a195af8dd3d95f62169819676ed4bba09e10863a6e61caa33a74a8" fido2-1.2.0/examples/server/README.adoc0000644000175000017500000000476314721556664017024 0ustar winniewinnie== WebAuthn Server Example This example shows a minimal website that uses python-fido2 to implement WebAuthn credential registration, and use. === Running To run this sample, you will need `poetry`. For instructions on installing `poetry`, see https://python-poetry.org/. Run the following command in the `examples/server` directory to set up the example: $ poetry install Once the environment has been created, you can run the server by running: $ poetry run server When the server is running, use a browser supporting WebAuthn and open http://localhost:5000 to access the website. NOTE: Webauthn requires a secure context (HTTPS), which involves obtaining a valid TLS certificate. However, most browsers also treat http://localhost as a secure context. This example runs without TLS as a demo, but otherwise you should always use HTTPS with a valid certificate when using Webauthn. === Using the website The site allows you to register a WebAuthn credential, and to authenticate it. Credentials are only stored in memory, and stopping the server will cause it to "forget" any registered credentials. ==== Registration 1. Click on the `Register` link to begin credential registration. 2. If not already inserted, insert your U2F/FIDO2 Authenticator now. 3. Touch the button to activate the Authenticator. 4. A popup will indicate whether the registration was successful. Click `OK`. ==== Authentication NOTE: You must register a credential prior to authentication. 1. Click on the `Authenticate` link to begin authentication. 2. If not already inserted, insert your U2F/FIDO2 Authenticator now. 3. Touch the button to activate the Authenticator. 4. A popup will indicate whether the authentication was successful. Click `OK`. === Supporting existing U2F credentials If you have existing U2F credentials that you wish to support, this library offers a U2FFido2Server class which can help with this. This directory includes a slightly altered version of the example server which uses this class to authenticate U2F credentials as well as WebAuthn credentials. To run this version of the server, run: $ poetry run server-u2f This version allows registration both using the newer WebAuthn APIs and by using the legacy U2F APIs, so that you can test authentication using both credential types. The source code for this version of the server is in `server/server_u2f.py`. NOTE: There should be no need to support registration of new U2F credentials as new registrations should be using the WebAuthn APIs, even for existing users. fido2-1.2.0/examples/server/pyproject.toml0000644000175000017500000000073614721556664020147 0ustar winniewinnie[tool.poetry] name = "fido2-example-server" version = "0.1.0" description = "Example server for python-fido2" authors = ["Dain Nilsson "] license = "Apache-2" packages = [ { include = "server" }, ] [tool.poetry.dependencies] python = "^3.8" Flask = "^2.0" fido2 = {path = "../.."} [tool.poetry.dev-dependencies] [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" [tool.poetry.scripts] server = "server.server:main" fido2-1.2.0/examples/server/server/0000775000175000017500000000000014741676716016540 5ustar winniewinniefido2-1.2.0/examples/server/server/static/0000775000175000017500000000000014741676716020027 5ustar winniewinniefido2-1.2.0/examples/server/server/static/webauthn-json.browser-ponyfill.js0000644000175000017500000001337214721556664026466 0ustar winniewinnie// src/webauthn-json/base64url.ts function base64urlToBuffer(baseurl64String) { const padding = "==".slice(0, (4 - baseurl64String.length % 4) % 4); const base64String = baseurl64String.replace(/-/g, "+").replace(/_/g, "/") + padding; const str = atob(base64String); const buffer = new ArrayBuffer(str.length); const byteView = new Uint8Array(buffer); for (let i = 0; i < str.length; i++) { byteView[i] = str.charCodeAt(i); } return buffer; } function bufferToBase64url(buffer) { const byteView = new Uint8Array(buffer); let str = ""; for (const charCode of byteView) { str += String.fromCharCode(charCode); } const base64String = btoa(str); const base64urlString = base64String.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); return base64urlString; } // src/webauthn-json/convert.ts var copyValue = "copy"; var convertValue = "convert"; function convert(conversionFn, schema, input) { if (schema === copyValue) { return input; } if (schema === convertValue) { return conversionFn(input); } if (schema instanceof Array) { return input.map((v) => convert(conversionFn, schema[0], v)); } if (schema instanceof Object) { const output = {}; for (const [key, schemaField] of Object.entries(schema)) { if (schemaField.derive) { const v = schemaField.derive(input); if (v !== void 0) { input[key] = v; } } if (!(key in input)) { if (schemaField.required) { throw new Error(`Missing key: ${key}`); } continue; } if (input[key] == null) { output[key] = null; continue; } output[key] = convert(conversionFn, schemaField.schema, input[key]); } return output; } } function derived(schema, derive) { return { required: true, schema, derive }; } function required(schema) { return { required: true, schema }; } function optional(schema) { return { required: false, schema }; } // src/webauthn-json/basic/schema.ts var publicKeyCredentialDescriptorSchema = { type: required(copyValue), id: required(convertValue), transports: optional(copyValue) }; var simplifiedExtensionsSchema = { appid: optional(copyValue), appidExclude: optional(copyValue), credProps: optional(copyValue) }; var simplifiedClientExtensionResultsSchema = { appid: optional(copyValue), appidExclude: optional(copyValue), credProps: optional(copyValue) }; var credentialCreationOptions = { publicKey: required({ rp: required(copyValue), user: required({ id: required(convertValue), name: required(copyValue), displayName: required(copyValue) }), challenge: required(convertValue), pubKeyCredParams: required(copyValue), timeout: optional(copyValue), excludeCredentials: optional([publicKeyCredentialDescriptorSchema]), authenticatorSelection: optional(copyValue), attestation: optional(copyValue), extensions: optional(simplifiedExtensionsSchema) }), signal: optional(copyValue) }; var publicKeyCredentialWithAttestation = { type: required(copyValue), id: required(copyValue), rawId: required(convertValue), authenticatorAttachment: optional(copyValue), response: required({ clientDataJSON: required(convertValue), attestationObject: required(convertValue), transports: derived(copyValue, (response) => { var _a; return ((_a = response.getTransports) == null ? void 0 : _a.call(response)) || []; }) }), clientExtensionResults: derived(simplifiedClientExtensionResultsSchema, (pkc) => pkc.getClientExtensionResults()) }; var credentialRequestOptions = { mediation: optional(copyValue), publicKey: required({ challenge: required(convertValue), timeout: optional(copyValue), rpId: optional(copyValue), allowCredentials: optional([publicKeyCredentialDescriptorSchema]), userVerification: optional(copyValue), extensions: optional(simplifiedExtensionsSchema) }), signal: optional(copyValue) }; var publicKeyCredentialWithAssertion = { type: required(copyValue), id: required(copyValue), rawId: required(convertValue), authenticatorAttachment: optional(copyValue), response: required({ clientDataJSON: required(convertValue), authenticatorData: required(convertValue), signature: required(convertValue), userHandle: required(convertValue) }), clientExtensionResults: derived(simplifiedClientExtensionResultsSchema, (pkc) => pkc.getClientExtensionResults()) }; // src/webauthn-json/basic/api.ts function createRequestFromJSON(requestJSON) { return convert(base64urlToBuffer, credentialCreationOptions, requestJSON); } function createResponseToJSON(credential) { return convert(bufferToBase64url, publicKeyCredentialWithAttestation, credential); } function getRequestFromJSON(requestJSON) { return convert(base64urlToBuffer, credentialRequestOptions, requestJSON); } function getResponseToJSON(credential) { return convert(bufferToBase64url, publicKeyCredentialWithAssertion, credential); } // src/webauthn-json/basic/supported.ts function supported() { return !!(navigator.credentials && navigator.credentials.create && navigator.credentials.get && window.PublicKeyCredential); } // src/webauthn-json/browser-ponyfill.ts async function create(options) { const response = await navigator.credentials.create(options); response.toJSON = () => createResponseToJSON(response); return response; } async function get(options) { const response = await navigator.credentials.get(options); response.toJSON = () => getResponseToJSON(response); return response; } export { create, get, createRequestFromJSON as parseCreationOptionsFromJSON, getRequestFromJSON as parseRequestOptionsFromJSON, supported }; //# sourceMappingURL=webauthn-json.browser-ponyfill.js.map fido2-1.2.0/examples/server/server/static/authenticate.html0000644000175000017500000000310614721556664023366 0ustar winniewinnie Fido 2.0 webauthn demo

WebAuthn demo using python-fido2

This demo requires a browser supporting the WebAuthn API!


Authenticate using a credential

fido2-1.2.0/examples/server/server/static/index.html0000644000175000017500000000107114721556664022016 0ustar winniewinnie Fido 2.0 webauthn demo

WebAuthn demo using python-fido2

This demo requires a browser supporting the WebAuthn API!


Available actions

Register
Authenticate
fido2-1.2.0/examples/server/server/static/register.html0000644000175000017500000000312614721556664022536 0ustar winniewinnie Fido 2.0 webauthn demo

WebAuthn demo using python-fido2

This demo requires a browser supporting the WebAuthn API!


Register a credential

fido2-1.2.0/examples/server/server/static/webauthn-json.browser-ponyfill.js.map0000644000175000017500000003304414721556664027240 0ustar winniewinnie{ "version": 3, "sources": ["../../src/webauthn-json/base64url.ts", "../../src/webauthn-json/convert.ts", "../../src/webauthn-json/basic/schema.ts", "../../src/webauthn-json/basic/api.ts", "../../src/webauthn-json/basic/supported.ts", "../../src/webauthn-json/browser-ponyfill.ts"], "sourcesContent": ["export type Base64urlString = string;\n\nexport function base64urlToBuffer(\n baseurl64String: Base64urlString,\n): ArrayBuffer {\n // Base64url to Base64\n const padding = \"==\".slice(0, (4 - (baseurl64String.length % 4)) % 4);\n const base64String =\n baseurl64String.replace(/-/g, \"+\").replace(/_/g, \"/\") + padding;\n\n // Base64 to binary string\n const str = atob(base64String);\n\n // Binary string to buffer\n const buffer = new ArrayBuffer(str.length);\n const byteView = new Uint8Array(buffer);\n for (let i = 0; i < str.length; i++) {\n byteView[i] = str.charCodeAt(i);\n }\n return buffer;\n}\n\nexport function bufferToBase64url(buffer: ArrayBuffer): Base64urlString {\n // Buffer to binary string\n const byteView = new Uint8Array(buffer);\n let str = \"\";\n for (const charCode of byteView) {\n str += String.fromCharCode(charCode);\n }\n\n // Binary string to base64\n const base64String = btoa(str);\n\n // Base64 to base64url\n // We assume that the base64url string is well-formed.\n const base64urlString = base64String\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=/g, \"\");\n return base64urlString;\n}\n", "// We export these values in order so that they can be used to deduplicate\n// schema definitions in minified JS code.\n\nimport { Schema, SchemaProperty } from \"./schema-format\";\n\n// TODO: Parcel isn't deduplicating these values.\nexport const copyValue = \"copy\";\nexport const convertValue = \"convert\";\n\nexport function convert(\n conversionFn: (v: From) => To,\n schema: Schema,\n input: any,\n): any {\n if (schema === copyValue) {\n return input;\n }\n if (schema === convertValue) {\n return conversionFn(input);\n }\n if (schema instanceof Array) {\n return input.map((v: any) => convert(conversionFn, schema[0], v));\n }\n if (schema instanceof Object) {\n const output: any = {};\n for (const [key, schemaField] of Object.entries(schema)) {\n if (schemaField.derive) {\n const v = schemaField.derive(input);\n if (v !== undefined) {\n input[key] = v;\n }\n }\n\n if (!(key in input)) {\n if (schemaField.required) {\n throw new Error(`Missing key: ${key}`);\n }\n continue;\n }\n // Fields can be null (rather than missing or `undefined`), e.g. the\n // `userHandle` field of the `AuthenticatorAssertionResponse`:\n // https://www.w3.org/TR/webauthn/#iface-authenticatorassertionresponse\n if (input[key] == null) {\n output[key] = null;\n continue;\n }\n output[key] = convert(\n conversionFn,\n schemaField.schema,\n input[key],\n );\n }\n return output;\n }\n}\n\nexport function derived(\n schema: Schema,\n derive: (v: any) => any,\n): SchemaProperty {\n return {\n required: true,\n schema,\n derive,\n };\n}\n\nexport function required(schema: Schema): SchemaProperty {\n return {\n required: true,\n schema,\n };\n}\n\nexport function optional(schema: Schema): SchemaProperty {\n return {\n required: false,\n schema,\n };\n}\n", "import { Schema } from \"../schema-format\";\nimport {\n convertValue as convert,\n copyValue as copy,\n derived,\n optional,\n required,\n} from \"../convert\";\n\n// Shared by `create()` and `get()`.\n\nconst publicKeyCredentialDescriptorSchema: Schema = {\n type: required(copy),\n id: required(convert),\n transports: optional(copy),\n};\n\nconst simplifiedExtensionsSchema: Schema = {\n appid: optional(copy),\n appidExclude: optional(copy),\n credProps: optional(copy),\n};\n\nconst simplifiedClientExtensionResultsSchema = {\n appid: optional(copy),\n appidExclude: optional(copy),\n credProps: optional(copy),\n};\n\n// `navigator.create()` request\n\nexport const credentialCreationOptions: Schema = {\n publicKey: required({\n rp: required(copy),\n user: required({\n id: required(convert),\n name: required(copy),\n displayName: required(copy),\n }),\n\n challenge: required(convert),\n pubKeyCredParams: required(copy),\n\n timeout: optional(copy),\n excludeCredentials: optional([publicKeyCredentialDescriptorSchema]),\n authenticatorSelection: optional(copy),\n attestation: optional(copy),\n extensions: optional(simplifiedExtensionsSchema),\n }),\n signal: optional(copy),\n};\n\n// `navigator.create()` response\n\nexport const publicKeyCredentialWithAttestation: Schema = {\n type: required(copy),\n id: required(copy),\n rawId: required(convert),\n authenticatorAttachment: optional(copy),\n response: required({\n clientDataJSON: required(convert),\n attestationObject: required(convert),\n transports: derived(\n copy,\n (response: any) => response.getTransports?.() || [],\n ),\n }),\n clientExtensionResults: derived(\n simplifiedClientExtensionResultsSchema,\n (pkc: PublicKeyCredential) => pkc.getClientExtensionResults(),\n ),\n};\n\n// `navigator.get()` request\n\nexport const credentialRequestOptions: Schema = {\n mediation: optional(copy),\n publicKey: required({\n challenge: required(convert),\n timeout: optional(copy),\n rpId: optional(copy),\n allowCredentials: optional([publicKeyCredentialDescriptorSchema]),\n userVerification: optional(copy),\n extensions: optional(simplifiedExtensionsSchema),\n }),\n signal: optional(copy),\n};\n\n// `navigator.get()` response\n\nexport const publicKeyCredentialWithAssertion: Schema = {\n type: required(copy),\n id: required(copy),\n rawId: required(convert),\n authenticatorAttachment: optional(copy),\n response: required({\n clientDataJSON: required(convert),\n authenticatorData: required(convert),\n signature: required(convert),\n userHandle: required(convert),\n }),\n clientExtensionResults: derived(\n simplifiedClientExtensionResultsSchema,\n (pkc: PublicKeyCredential) => pkc.getClientExtensionResults(),\n ),\n};\n\nexport const schema: { [s: string]: Schema } = {\n credentialCreationOptions,\n publicKeyCredentialWithAttestation,\n credentialRequestOptions,\n publicKeyCredentialWithAssertion,\n};\n", "import { base64urlToBuffer, bufferToBase64url } from \"../base64url\";\nimport { convert } from \"../convert\";\nimport {\n CredentialCreationOptionsJSON,\n CredentialRequestOptionsJSON,\n PublicKeyCredentialWithAssertionJSON,\n PublicKeyCredentialWithAttestationJSON,\n} from \"./json\";\nimport {\n credentialCreationOptions,\n credentialRequestOptions,\n publicKeyCredentialWithAssertion,\n publicKeyCredentialWithAttestation,\n} from \"./schema\";\n\nexport function createRequestFromJSON(\n requestJSON: CredentialCreationOptionsJSON,\n): CredentialCreationOptions {\n return convert(base64urlToBuffer, credentialCreationOptions, requestJSON);\n}\n\nexport function createResponseToJSON(\n credential: PublicKeyCredential,\n): PublicKeyCredentialWithAttestationJSON {\n return convert(\n bufferToBase64url,\n publicKeyCredentialWithAttestation,\n credential,\n );\n}\n\nexport async function create(\n requestJSON: CredentialCreationOptionsJSON,\n): Promise {\n const credential = (await navigator.credentials.create(\n createRequestFromJSON(requestJSON),\n )) as PublicKeyCredential;\n return createResponseToJSON(credential);\n}\n\nexport function getRequestFromJSON(\n requestJSON: CredentialRequestOptionsJSON,\n): CredentialRequestOptions {\n return convert(base64urlToBuffer, credentialRequestOptions, requestJSON);\n}\n\nexport function getResponseToJSON(\n credential: PublicKeyCredential,\n): PublicKeyCredentialWithAssertionJSON {\n return convert(\n bufferToBase64url,\n publicKeyCredentialWithAssertion,\n credential,\n );\n}\n\nexport async function get(\n requestJSON: CredentialRequestOptionsJSON,\n): Promise {\n const credential = (await navigator.credentials.get(\n getRequestFromJSON(requestJSON),\n )) as PublicKeyCredential;\n return getResponseToJSON(credential);\n}\n\ndeclare global {\n interface Window {\n PublicKeyCredential: PublicKeyCredential | undefined;\n }\n}\n", "// This function does a simple check to test for the credential management API\n// functions we need, and an indication of public key credential authentication\n// support.\n// https://developers.google.com/web/updates/2018/03/webauthn-credential-management\n\nexport function supported(): boolean {\n return !!(\n navigator.credentials &&\n navigator.credentials.create &&\n navigator.credentials.get &&\n window.PublicKeyCredential\n );\n}\n", "import {\n createRequestFromJSON as parseCreationOptionsFromJSON,\n createResponseToJSON,\n getRequestFromJSON as parseRequestOptionsFromJSON,\n getResponseToJSON,\n} from \"./basic/api\";\nimport { supported } from \"./basic/supported\";\n\nimport {\n CredentialCreationOptionsJSON,\n CredentialRequestOptionsJSON,\n PublicKeyCredentialWithAssertionJSON as AuthenticationResponseJSON,\n PublicKeyCredentialWithAttestationJSON as RegistrationResponseJSON,\n} from \"./basic/json\";\n\nexport { parseCreationOptionsFromJSON, parseRequestOptionsFromJSON, supported };\nexport type {\n CredentialCreationOptionsJSON,\n CredentialRequestOptionsJSON,\n AuthenticationResponseJSON,\n RegistrationResponseJSON,\n};\n\nexport interface RegistrationPublicKeyCredential extends PublicKeyCredential {\n toJSON(): RegistrationResponseJSON;\n}\n\nexport async function create(\n options: CredentialCreationOptions,\n): Promise {\n const response = (await navigator.credentials.create(\n options,\n )) as RegistrationPublicKeyCredential;\n response.toJSON = () => createResponseToJSON(response);\n return response;\n}\n\nexport interface AuthenticationPublicKeyCredential extends PublicKeyCredential {\n toJSON(): AuthenticationResponseJSON;\n}\n\nexport async function get(\n options: CredentialRequestOptions,\n): Promise {\n const response = (await navigator.credentials.get(\n options,\n )) as AuthenticationPublicKeyCredential;\n response.toJSON = () => getResponseToJSON(response);\n return response;\n}\n"], "mappings": ";AAEO,2BACL,iBACa;AAEb,QAAM,UAAU,KAAK,MAAM,GAAI,KAAK,gBAAgB,SAAS,KAAM;AACnE,QAAM,eACJ,gBAAgB,QAAQ,MAAM,KAAK,QAAQ,MAAM,OAAO;AAG1D,QAAM,MAAM,KAAK;AAGjB,QAAM,SAAS,IAAI,YAAY,IAAI;AACnC,QAAM,WAAW,IAAI,WAAW;AAChC,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,aAAS,KAAK,IAAI,WAAW;AAAA;AAE/B,SAAO;AAAA;AAGF,2BAA2B,QAAsC;AAEtE,QAAM,WAAW,IAAI,WAAW;AAChC,MAAI,MAAM;AACV,aAAW,YAAY,UAAU;AAC/B,WAAO,OAAO,aAAa;AAAA;AAI7B,QAAM,eAAe,KAAK;AAI1B,QAAM,kBAAkB,aACrB,QAAQ,OAAO,KACf,QAAQ,OAAO,KACf,QAAQ,MAAM;AACjB,SAAO;AAAA;;;ACjCF,IAAM,YAAY;AAClB,IAAM,eAAe;AAErB,iBACL,cACA,QACA,OACK;AACL,MAAI,WAAW,WAAW;AACxB,WAAO;AAAA;AAET,MAAI,WAAW,cAAc;AAC3B,WAAO,aAAa;AAAA;AAEtB,MAAI,kBAAkB,OAAO;AAC3B,WAAO,MAAM,IAAI,CAAC,MAAW,QAAkB,cAAc,OAAO,IAAI;AAAA;AAE1E,MAAI,kBAAkB,QAAQ;AAC5B,UAAM,SAAc;AACpB,eAAW,CAAC,KAAK,gBAAgB,OAAO,QAAQ,SAAS;AACvD,UAAI,YAAY,QAAQ;AACtB,cAAM,IAAI,YAAY,OAAO;AAC7B,YAAI,MAAM,QAAW;AACnB,gBAAM,OAAO;AAAA;AAAA;AAIjB,UAAI,CAAE,QAAO,QAAQ;AACnB,YAAI,YAAY,UAAU;AACxB,gBAAM,IAAI,MAAM,gBAAgB;AAAA;AAElC;AAAA;AAKF,UAAI,MAAM,QAAQ,MAAM;AACtB,eAAO,OAAO;AACd;AAAA;AAEF,aAAO,OAAO,QACZ,cACA,YAAY,QACZ,MAAM;AAAA;AAGV,WAAO;AAAA;AAAA;AAIJ,iBACL,QACA,QACgB;AAChB,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,IACA;AAAA;AAAA;AAIG,kBAAkB,QAAgC;AACvD,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA;AAAA;AAIG,kBAAkB,QAAgC;AACvD,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA;AAAA;;;AClEJ,IAAM,sCAA8C;AAAA,EAClD,MAAM,SAAS;AAAA,EACf,IAAI,SAAS;AAAA,EACb,YAAY,SAAS;AAAA;AAGvB,IAAM,6BAAqC;AAAA,EACzC,OAAO,SAAS;AAAA,EAChB,cAAc,SAAS;AAAA,EACvB,WAAW,SAAS;AAAA;AAGtB,IAAM,yCAAyC;AAAA,EAC7C,OAAO,SAAS;AAAA,EAChB,cAAc,SAAS;AAAA,EACvB,WAAW,SAAS;AAAA;AAKf,IAAM,4BAAoC;AAAA,EAC/C,WAAW,SAAS;AAAA,IAClB,IAAI,SAAS;AAAA,IACb,MAAM,SAAS;AAAA,MACb,IAAI,SAAS;AAAA,MACb,MAAM,SAAS;AAAA,MACf,aAAa,SAAS;AAAA;AAAA,IAGxB,WAAW,SAAS;AAAA,IACpB,kBAAkB,SAAS;AAAA,IAE3B,SAAS,SAAS;AAAA,IAClB,oBAAoB,SAAS,CAAC;AAAA,IAC9B,wBAAwB,SAAS;AAAA,IACjC,aAAa,SAAS;AAAA,IACtB,YAAY,SAAS;AAAA;AAAA,EAEvB,QAAQ,SAAS;AAAA;AAKZ,IAAM,qCAA6C;AAAA,EACxD,MAAM,SAAS;AAAA,EACf,IAAI,SAAS;AAAA,EACb,OAAO,SAAS;AAAA,EAChB,yBAAyB,SAAS;AAAA,EAClC,UAAU,SAAS;AAAA,IACjB,gBAAgB,SAAS;AAAA,IACzB,mBAAmB,SAAS;AAAA,IAC5B,YAAY,QACV,WACA,CAAC,aAAe;AAhEtB;AAgEyB,6BAAS,kBAAT,sCAA8B;AAAA;AAAA;AAAA,EAGrD,wBAAwB,QACtB,wCACA,CAAC,QAA6B,IAAI;AAAA;AAM/B,IAAM,2BAAmC;AAAA,EAC9C,WAAW,SAAS;AAAA,EACpB,WAAW,SAAS;AAAA,IAClB,WAAW,SAAS;AAAA,IACpB,SAAS,SAAS;AAAA,IAClB,MAAM,SAAS;AAAA,IACf,kBAAkB,SAAS,CAAC;AAAA,IAC5B,kBAAkB,SAAS;AAAA,IAC3B,YAAY,SAAS;AAAA;AAAA,EAEvB,QAAQ,SAAS;AAAA;AAKZ,IAAM,mCAA2C;AAAA,EACtD,MAAM,SAAS;AAAA,EACf,IAAI,SAAS;AAAA,EACb,OAAO,SAAS;AAAA,EAChB,yBAAyB,SAAS;AAAA,EAClC,UAAU,SAAS;AAAA,IACjB,gBAAgB,SAAS;AAAA,IACzB,mBAAmB,SAAS;AAAA,IAC5B,WAAW,SAAS;AAAA,IACpB,YAAY,SAAS;AAAA;AAAA,EAEvB,wBAAwB,QACtB,wCACA,CAAC,QAA6B,IAAI;AAAA;;;ACxF/B,+BACL,aAC2B;AAC3B,SAAO,QAAQ,mBAAmB,2BAA2B;AAAA;AAGxD,8BACL,YACwC;AACxC,SAAO,QACL,mBACA,oCACA;AAAA;AAaG,4BACL,aAC0B;AAC1B,SAAO,QAAQ,mBAAmB,0BAA0B;AAAA;AAGvD,2BACL,YACsC;AACtC,SAAO,QACL,mBACA,kCACA;AAAA;;;AC/CG,qBAA8B;AACnC,SAAO,CAAC,CACN,WAAU,eACV,UAAU,YAAY,UACtB,UAAU,YAAY,OACtB,OAAO;AAAA;;;ACiBX,sBACE,SAC0C;AAC1C,QAAM,WAAY,MAAM,UAAU,YAAY,OAC5C;AAEF,WAAS,SAAS,MAAM,qBAAqB;AAC7C,SAAO;AAAA;AAOT,mBACE,SAC4C;AAC5C,QAAM,WAAY,MAAM,UAAU,YAAY,IAC5C;AAEF,WAAS,SAAS,MAAM,kBAAkB;AAC1C,SAAO;AAAA;", "names": [] } fido2-1.2.0/examples/server/server/__init__.py0000644000175000017500000000000014721556664020632 0ustar winniewinniefido2-1.2.0/examples/server/server/server.py0000644000175000017500000001003514721556664020412 0ustar winniewinnie# 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 http://localhost:5000 in a supported web browser. """ from fido2.webauthn import PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity from fido2.server import Fido2Server from flask import Flask, session, request, redirect, abort, jsonify import os import fido2.features fido2.features.webauthn_json_mapping.enabled = True app = Flask(__name__, static_url_path="") app.secret_key = os.urandom(32) # Used for session. rp = PublicKeyCredentialRpEntity(name="Demo server", id="localhost") server = Fido2Server(rp) # Registered credentials are stored globally, in memory only. Single user # support, state is lost when the server terminates. credentials = [] @app.route("/") def index(): return redirect("/index.html") @app.route("/api/register/begin", methods=["POST"]) def register_begin(): options, state = server.register_begin( PublicKeyCredentialUserEntity( id=b"user_id", name="a_user", display_name="A. User", ), credentials, user_verification="discouraged", authenticator_attachment="cross-platform", ) session["state"] = state print("\n\n\n\n") print(options) print("\n\n\n\n") return jsonify(dict(options)) @app.route("/api/register/complete", methods=["POST"]) def register_complete(): response = request.json print("RegistrationResponse:", response) auth_data = server.register_complete(session["state"], response) credentials.append(auth_data.credential_data) print("REGISTERED CREDENTIAL:", auth_data.credential_data) return jsonify({"status": "OK"}) @app.route("/api/authenticate/begin", methods=["POST"]) def authenticate_begin(): if not credentials: abort(404) options, state = server.authenticate_begin(credentials) session["state"] = state return jsonify(dict(options)) @app.route("/api/authenticate/complete", methods=["POST"]) def authenticate_complete(): if not credentials: abort(404) response = request.json print("AuthenticationResponse:", response) server.authenticate_complete( session.pop("state"), credentials, response, ) print("ASSERTION OK") return jsonify({"status": "OK"}) def main(): print(__doc__) # Note: using localhost without TLS, as some browsers do # not allow Webauthn in case of TLS certificate errors. # See https://lists.w3.org/Archives/Public/public-webauthn/2022Nov/0135.html app.run(host="localhost", debug=False) if __name__ == "__main__": main() fido2-1.2.0/examples/verify_attestation.py0000644000175000017500000001036614721556664020222 0ustar winniewinnie# Copyright (c) 2021 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ This example shows how to use an AttestationVerifier to only allow credentials signed by a specific CA. It connects to the first FIDO device found (starts from USB, then looks into NFC), creates a new credential for it, and verifies that attestation is signed by the Yubico FIDO root CA (this will only work for Yubico devices). On Windows, the native WebAuthn API will be used. """ from fido2.server import Fido2Server from fido2.attestation import AttestationVerifier from exampleutils import get_client from base64 import b64decode # Official Yubico root CA for FIDO Authenticators YUBICO_CA = b64decode( """ MIIDHjCCAgagAwIBAgIEG0BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ dWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw MDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290 IENBIFNlcmlhbCA0NTcyMDA2MzEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK AoIBAQC/jwYuhBVlqaiYWEMsrWFisgJ+PtM91eSrpI4TK7U53mwCIawSDHy8vUmk 5N2KAj9abvT9NP5SMS1hQi3usxoYGonXQgfO6ZXyUA9a+KAkqdFnBnlyugSeCOep 8EdZFfsaRFtMjkwz5Gcz2Py4vIYvCdMHPtwaz0bVuzneueIEz6TnQjE63Rdt2zbw nebwTG5ZybeWSwbzy+BJ34ZHcUhPAY89yJQXuE0IzMZFcEBbPNRbWECRKgjq//qT 9nmDOFVlSRCt2wiqPSzluwn+v+suQEBsUjTGMEd25tKXXTkNW21wIWbxeSyUoTXw LvGS6xlwQSgNpk2qXYwf8iXg7VWZAgMBAAGjQjBAMB0GA1UdDgQWBBQgIvz0bNGJ hjgpToksyKpP9xv9oDAPBgNVHRMECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAN BgkqhkiG9w0BAQsFAAOCAQEAjvjuOMDSa+JXFCLyBKsycXtBVZsJ4Ue3LbaEsPY4 MYN/hIQ5ZM5p7EjfcnMG4CtYkNsfNHc0AhBLdq45rnT87q/6O3vUEtNMafbhU6kt hX7Y+9XFN9NpmYxr+ekVY5xOxi8h9JDIgoMP4VB1uS0aunL1IGqrNooL9mmFnL2k LVVee6/VR6C5+KSTCMCWppMuJIZII2v9o4dkoZ8Y7QRjQlLfYzd3qGtKbw7xaF1U sG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqc U9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw== """ ) class YubicoAttestationVerifier(AttestationVerifier): """Example implementation of an AttestationVerifier. This simple example will attempt to verify all trust paths using the Yubico CA. A real implementation can use the information in the attestation result, or the authenticator data, to determine which CA should be used to verify the path. """ def ca_lookup(self, result, auth_data): return YUBICO_CA # Locate a suitable FIDO authenticator client = get_client() server = Fido2Server( {"id": "example.com", "name": "Example RP"}, attestation="direct", verify_attestation=YubicoAttestationVerifier(), ) user = {"id": b"user_id", "name": "A. User"} # Prepare parameters for makeCredential create_options, state = server.register_begin( user, user_verification="discouraged", authenticator_attachment="cross-platform" ) # Create a credential result = client.make_credential(create_options["publicKey"]) # Complete registration auth_data = server.register_complete( state, result.client_data, result.attestation_object ) credentials = [auth_data.credential_data] print("New credential created, attestation verified!") print("Yubico device AAGUID:", auth_data.credential_data.aaguid.hex()) fido2-1.2.0/examples/verify_attestation_mds3.py0000644000175000017500000001174014721556664021145 0ustar winniewinnie# Copyright (c) 2021 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ This example shows how to use the FIDO MDS to only allow authenticators for which metadata is available. It connects to the first FIDO device found (starts from USB, then looks into NFC), creates a new credential for it, and verifies that attestation is correctly signed and valid according to its metadata statement. On Windows, the native WebAuthn API will be used. NOTE: You need to retrieve a MDS3 blob to run this example. See https://fidoalliance.org/metadata/ for more info. """ from fido2.server import Fido2Server from fido2.attestation import UntrustedAttestation from fido2.mds3 import parse_blob, MdsAttestationVerifier from exampleutils import get_client from base64 import b64decode import sys # Load the root CA used to sign the Metadata Statement blob ca = b64decode( """ MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK 6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH WD9f""" ) # Parse the MDS3 blob if len(sys.argv) != 2: print("This example requires a FIDO MDS3 metadata blob, which you can get here:") print("https://fidoalliance.org/metadata/") print() print("USAGE: python verify_attestation_mds3.py blob.jwt") sys.exit(1) with open(sys.argv[1], "rb") as f: metadata = parse_blob(f.read(), ca) # The verifier is used to query for data in the blob and to verify attestation. # We could optionally pass a filter function to only allow specific authenticators. mds = MdsAttestationVerifier(metadata) # Locate a suitable FIDO authenticator client = get_client() # The MDS verifier is passed to the server to verify that new credentials registered # exist in the MDS blob, else the registration will fail. server = Fido2Server( {"id": "example.com", "name": "Example RP"}, attestation="direct", verify_attestation=mds, ) user = {"id": b"user_id", "name": "A. User"} # Prepare parameters for makeCredential create_options, state = server.register_begin( user, user_verification="discouraged", authenticator_attachment="cross-platform" ) # Create a credential result = client.make_credential(create_options["publicKey"]) # Complete registration try: auth_data = server.register_complete( state, result.client_data, result.attestation_object ) print("Registration completed") # mds can also be used to get the metadata for the Authenticator, # regardless of if it was used to verify the attestation or not: entry = mds.find_entry(result.attestation_object, result.client_data.hash) print("Authenticator description:", entry.metadata_statement.description) except UntrustedAttestation: print("Authenticator metadata not found") fido2-1.2.0/examples/acr122usam.py0000644000175000017500000002461114721556664016155 0ustar winniewinnie# 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 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(iter(apdu)), protocol) response = bytes(bytearray(resp)) # print('<< [0x%04x] %s' % (sw1 * 0x100 + sw2, b2a_hex(response))) return response, sw1, sw2 def pseudo_apdu_ex(self, apdu, protocol=None): req = b"\xff\x00\x00\x00" + bytes([len(apdu) & 0xFF]) + apdu resp, sw1, sw2 = self.apdu_plain(req, protocol) if sw1 != 0x61: return resp, sw1, sw2 return self.apdu_plain(b"\xff\xc0\x00\x00" + bytes([sw2]), protocol) # override base method # commands in PN 532 User manual (UM0701-02) # page 178. 7.4.5 DEP chaining mechanism # page 136. 7.3.9 InCommunicateThru # chaining ISO 14443-4:2001 # page 20. 7.5.2 Chaining def apdu_exchange(self, apdu, protocol=None): all_response = b"" alen = 0 while True: vapdu = apdu[alen : alen + self.max_block_len] # input chaining chaining = alen + len(vapdu) < len(apdu) vb = 0x02 | (0x01 if self.vparity else 0x00) | (0x10 if chaining else 0x00) # 7.3.9 InCommunicateThru resp, sw1, sw2 = self.pseudo_apdu_ex( b"\xd4\x42" + bytes([vb]) + vapdu, protocol ) self.vparity = not self.vparity if len(resp) > 2 and resp[2] > 0: print("Error: 0x%02x" % resp[2]) return b"", 0x6F, resp[2] if sw1 != 0x90 or len(resp) < 3 or resp[0] != 0xD5 or resp[1] != 0x43: return b"", 0x67, 0x00 alen += len(vapdu) if not chaining: break if len(resp) > 3: if resp[3] & 0x10 == 0: return resp[4:-2], resp[-2], resp[-1] else: if resp[3] != 0xF2: all_response = resp[4:] else: return b"", 0x90, 0x00 while True: if len(resp) > 3 and resp[3] == 0xF2: # WTX answer = resp[3:5] else: # ACK answer = bytes([0xA2 | (0x01 if self.vparity else 0x00)]) self.vparity = not self.vparity # 7.3.9 InCommunicateThru resp, sw1, sw2 = self.pseudo_apdu_ex(b"\xd4\x42" + answer, protocol) if len(resp) > 2 and resp[2] > 0: print("Error: 0x%02x" % resp[2]) return b"", 0x6F, resp[2] if sw1 != 0x90 or len(resp) < 3 or resp[0] != 0xD5 or resp[1] != 0x43: return b"", 0x67, 0x00 response_chaining = len(resp) > 3 and resp[3] & 0x10 != 0 # if I block if len(resp) > 3 and resp[3] & 0xE0 == 0x00: all_response += resp[4:] if not response_chaining: break return all_response[:-2], resp[-2], resp[-1] def get_ats(self, verbose=False): self.field_reset() self.ats = b"" resp, sw1, sw2 = self.pseudo_apdu_ex(b"\xd4\x4a\x01\x00") if sw1 == 0x90 and len(resp) > 8 and resp[2] > 0x00: if verbose: print("ATQA 0x%02x%02x" % (resp[4], resp[5])) print("SAK 0x%02x" % resp[6]) uid_len = resp[7] if verbose: print("UID [%d] %s" % (uid_len, resp[8 : 8 + uid_len].hex())) self.ats = resp[8 + uid_len :] if verbose: print("ATS [%d] %s" % (len(self.ats), self.ats.hex())) self.vparity = False return self.ats return b"" def set_default_retry_timeout(self): result, sw1, sw2 = self.pseudo_apdu_ex(b"\xd4\x32\x05\x00\x00\x00") if sw1 != 0x90 or sw2 != 0x00 or result != b"\xd5\x33": print("set default retry time error") return False # 14443 timeout. UM0701-02 PN432 user manual. page 101. # RFU, fATR_RES_Timeout, fRetryTimeout # 0b 102ms, 0c - 204ms, 0d - 409ms, 0f - 1.6s result, sw1, sw2 = self.pseudo_apdu_ex(b"\xd4\x32\x02\x00\x0c\x0f") if sw1 != 0x90 or sw2 != 0x00 or result != b"\xd5\x33": print("set fRetryTimeout error") return False return True def set_auto_iso14443_4_activation(self, activate=True): result, sw1, sw2 = self.pseudo_apdu_ex( b"\xd4\x12" + bytes([0x34 if activate else 0x24]) ) if sw1 != 0x90 or sw2 != 0x00 or result != b"\xd5\x13": print("set automatic iso-14443-4 activation error") return False return True def field_control(self, field_on=True): result, sw1, sw2 = self.pseudo_apdu_ex( b"\xd4\x32\x01" + bytes([0x01 if field_on else 0x00]) ) if sw1 != 0x90 or sw2 != 0x00 or result != b"\xd5\x33": print("set field state error") return False return True def field_reset(self): self.led_control(True, False) result = self.field_control(False) time.sleep(0.2) result |= self.field_control(True) self.led_control() return result def reader_version(self): """ Get reader's version from reader :return: string. Reader's version """ try: result, sw1, sw2 = self.apdu_plain(b"\xff\x00\x48\x00\x00") if len(result) > 0: str_result = result + bytes([sw1]) + bytes([sw2]) str_result = str_result.decode("utf-8") return str_result except Exception as e: print("Get version error:", e) return "n/a" def led_control( self, red=False, green=False, blink_count=0, red_end_blink=False, green_end_blink=False, ): """ Reader's led control :param red: boolean. red led on :param green: boolean. green let on :param blink_count: int. if needs to blink value > 0. blinks count :param red_end_blink: boolean. state of red led at the end of blinking :param green_end_blink: boolean. state of green led at the end of blinking :return: """ try: if blink_count > 0: cbyte = ( 0b00001100 + (0b01 if red_end_blink else 0b00) + (0b10 if green_end_blink else 0b00) ) cbyte |= (0b01000000 if red else 0b00000000) + ( 0b10000000 if green else 0b00000000 ) else: cbyte = 0b00001100 + (0b01 if red else 0b00) + (0b10 if green else 0b00) apdu = ( b"\xff\x00\x40" + bytes([cbyte & 0xFF]) + b"\4" + b"\5\3" + bytes([blink_count]) + b"\0" ) self.apdu_plain(apdu) except Exception as e: print("LED control error:", e) dev = next(Acr122uSamPcscDevice.list_devices()) print("CONNECT: %s" % dev) print("version: %s" % dev.reader_version()) print("atr: %s" % dev.get_atr().hex()) print("ats: %s" % dev.ats.hex()) # uncomment if you want to see parameters from card's selection # dev.get_ats(True) # dev._select() dev.led_control(False, True, 0) chal = sha256(b"AAA") appid = sha256(b"BBB") ctap1 = CTAP1(dev) print("ctap1 version:", ctap1.get_version()) reg = ctap1.register(chal, appid) print("u2f register:", reg) reg.verify(appid, chal) print("Register message verify OK") auth = ctap1.authenticate(chal, appid, reg.key_handle) print("u2f authenticate: ", auth) res = auth.verify(appid, chal, reg.public_key) print("Authenticate message verify OK") dev.led_control() fido2-1.2.0/fido2/0000775000175000017500000000000014741676716013111 5ustar winniewinniefido2-1.2.0/fido2/cbor.py0000644000175000017500000001314414721556664014406 0ustar winniewinnie# 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. Use the :func:`encode`, :func:`decode` and :func:`decode_from` functions to encode and decode objects to/from CBOR. DO NOT use the dump_x/load_x functions directly, these will be made private in python-fido2 2.0. """ from __future__ import annotations import struct from typing import Any, Tuple, Union, Sequence, Mapping, Type, Callable CborType = Union[int, bool, str, bytes, Sequence[Any], Mapping[Any, Any]] # TODO 2.0: Make dump_x/load_x functions private def dump_int(data: int, mt: int = 0) -> bytes: if data < 0: mt = 1 data = -1 - data mt = mt << 5 if data <= 23: args: Any = (">B", mt | data) elif data <= 0xFF: args = (">BB", mt | 24, data) elif data <= 0xFFFF: args = (">BH", mt | 25, data) elif data <= 0xFFFFFFFF: args = (">BI", mt | 26, data) else: args = (">BQ", mt | 27, data) return struct.pack(*args) def dump_bool(data: bool) -> bytes: return b"\xf5" if data else b"\xf4" def dump_list(data: Sequence[CborType]) -> bytes: return dump_int(len(data), mt=4) + b"".join([encode(x) for x in data]) def _sort_keys(entry): key = entry[0] return key[0], len(key), key def dump_dict(data: Mapping[CborType, CborType]) -> bytes: items = [(encode(k), encode(v)) for k, v in data.items()] items.sort(key=_sort_keys) return dump_int(len(items), mt=5) + b"".join([k + v for (k, v) in items]) def dump_bytes(data: bytes) -> bytes: return dump_int(len(data), mt=2) + data def dump_text(data: str) -> bytes: data_bytes = data.encode("utf8") return dump_int(len(data_bytes), mt=3) + data_bytes _SERIALIZERS: Sequence[Tuple[Type, Callable[[Any], bytes]]] = [ (bool, dump_bool), (int, dump_int), (str, dump_text), (bytes, dump_bytes), (Mapping, dump_dict), (Sequence, dump_list), ] def load_int(ai: int, data: bytes) -> Tuple[int, bytes]: if ai < 24: return ai, data elif ai == 24: return data[0], data[1:] elif ai == 25: return struct.unpack_from(">H", data)[0], data[2:] elif ai == 26: return struct.unpack_from(">I", data)[0], data[4:] elif ai == 27: return struct.unpack_from(">Q", data)[0], data[8:] raise ValueError("Invalid additional information") def load_nint(ai: int, data: bytes) -> Tuple[int, bytes]: val, rest = load_int(ai, data) return -1 - val, rest def load_bool(ai: int, data: bytes) -> Tuple[bool, bytes]: return ai == 21, data def load_bytes(ai: int, data: bytes) -> Tuple[bytes, bytes]: l, data = load_int(ai, data) return data[:l], data[l:] def load_text(ai: int, data: bytes) -> Tuple[str, bytes]: enc, rest = load_bytes(ai, data) return enc.decode("utf8"), rest def load_array(ai: int, data: bytes) -> Tuple[Sequence[CborType], bytes]: l, data = load_int(ai, data) values = [] for i in range(l): val, data = decode_from(data) values.append(val) return values, data def load_map(ai: int, data: bytes) -> Tuple[Mapping[CborType, CborType], bytes]: l, data = load_int(ai, data) values = {} for i in range(l): k, data = decode_from(data) v, data = decode_from(data) values[k] = v return values, data _DESERIALIZERS = { 0: load_int, 1: load_nint, 2: load_bytes, 3: load_text, 4: load_array, 5: load_map, 7: load_bool, } def encode(data: CborType) -> bytes: """Encodes data to a CBOR byte string.""" for k, v in _SERIALIZERS: if isinstance(data, k): return v(data) raise ValueError(f"Unsupported value: {data!r}") def decode_from(data: bytes) -> Tuple[Any, bytes]: """Decodes a CBOR-encoded value from the start of a byte string. Additional data after a valid CBOR object is returned as well. :return: The decoded object, and any remaining data.""" fb = data[0] return _DESERIALIZERS[fb >> 5](fb & 0b11111, data[1:]) def decode(data) -> CborType: """Decodes data from a CBOR-encoded byte string. Also validates that no extra data follows the encoded object. """ value, rest = decode_from(data) if rest != b"": raise ValueError("Extraneous data") return value fido2-1.2.0/fido2/win_api.py0000644000175000017500000012045614721556664015114 0ustar winniewinnie# Copyright (c) 2019 Onica Group LLC. # Modified work Copyright 2019 Yubico. # All rights reserved. # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ Structs based on Microsoft's WebAuthN API. https://github.com/microsoft/webauthn """ # With the ctypes.Structure a lot of the property names # will be invalid, and when creating the __init__ methods # we do not need to call super() for the Structure class # # pylint: disable=invalid-name, super-init-not-called, too-few-public-methods from __future__ import annotations from .utils import websafe_decode from .webauthn import AttestationObject, AuthenticatorData, ResidentKeyRequirement from enum import IntEnum, unique from ctypes.wintypes import BOOL, DWORD, LONG, LPCWSTR, HWND, WORD from threading import Thread from typing import Mapping, Dict, Any, Tuple import ctypes from ctypes import WinDLL # type: ignore from ctypes import LibraryLoader import warnings windll = LibraryLoader(WinDLL) PBYTE = ctypes.POINTER(ctypes.c_ubyte) # Different from wintypes.PBYTE, which is signed PCWSTR = ctypes.c_wchar_p class BytesProperty: """Property for structs storing byte arrays as DWORD + PBYTE. Allows for easy reading/writing to struct fields using Python bytes objects. """ def __init__(self, name): self.cbName = "cb" + name self.pbName = "pb" + name def __get__(self, instance, owner): return bytes( bytearray(getattr(instance, self.pbName)[: getattr(instance, self.cbName)]) ) def __set__(self, instance, value): setattr(instance, self.cbName, len(value) if value is not None else 0) setattr(instance, self.pbName, ctypes.cast(value or 0, 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 WebAuthNHmacSecretSalt(ctypes.Structure): _fields_ = [ ("cbFirst", DWORD), ("pbFirst", PBYTE), ("cbSecond", DWORD), ("pbSecond", PBYTE), ] first = BytesProperty("First") second = BytesProperty("Second") def __init__(self, first, second=None): self.first = first self.second = second class WebAuthNCredWithHmacSecretSalt(ctypes.Structure): _fields_ = [ ("cbCredID", DWORD), ("pbCredID", PBYTE), ("pHmacSecretSalt", ctypes.POINTER(WebAuthNHmacSecretSalt)), ] cred_id = BytesProperty("CredID") def __init__(self, cred_id, salt): self.cred_id = cred_id self.salt = ctypes.pointer(salt) class WebAuthNHmacSecretSaltValues(ctypes.Structure): _fields_ = [ ("pGlobalHmacSalt", ctypes.POINTER(WebAuthNHmacSecretSalt)), ("cCredWithHmacSecretSaltList", DWORD), ("pCredWithHmacSecretSaltList", ctypes.POINTER(WebAuthNCredWithHmacSecretSalt)), ] def __init__(self, global_salt, credential_salts=[]): if global_salt: self.pGlobalHmacSalt = ctypes.pointer(global_salt) self.cCredWithHmacSecretSaltList = len(credential_salts) self.pCredWithHmacSecretSaltList = ( WebAuthNCredWithHmacSecretSalt * len(credential_salts) )(*credential_salts) class WebAuthNCredProtectExtensionIn(ctypes.Structure): """Maps to WEBAUTHN_CRED_PROTECT_EXTENSION_IN Struct. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L493 """ _fields_ = [ ("dwCredProtect", DWORD), ("bRequireCredProtect", BOOL), ] def __init__(self, cred_protect, require_cred_protect): self.dwCredProtect = cred_protect self.bRequireCredProtect = require_cred_protect class WebAuthNCredBlobExtension(ctypes.Structure): _fields_ = [ ("cbCredBlob", DWORD), ("pbCredBlob", PBYTE), ] cred_blob = BytesProperty("CredBlob") def __init__(self, blob): self.cred_blob = blob 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), ] def __init__(self, identifier, value): self.pwszExtensionIdentifier = identifier self.cbExtension = ctypes.sizeof(value) self.pvExtension = ctypes.cast(ctypes.pointer(value), 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)), ] def __init__(self, extensions): self.cExtensions = len(extensions) self.pExtensions = (WebAuthNExtension * len(extensions))(*extensions) 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 CtapCborHybridStorageLinkedData(ctypes.Structure): """Maps to CTAPCBOR_HYBRID_STORAGE_LINKED_DATA Struct. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L356 """ _fields_ = [ ("dwVersion", DWORD), ("cbContactId", DWORD), ("pbContactId", PBYTE), ("cbLinkId", DWORD), ("pbLinkId", PBYTE), ("cbLinkSecret", DWORD), ("pbLinkSecret", PBYTE), ("cbPublicKey", DWORD), ("pbPublicKey", PBYTE), ("pwszAuthenticatorName", PCWSTR), ("wEncodedTunnelServerDomain", WORD), ] # TODO contact_id = BytesProperty("ContactId") link_id = BytesProperty("LinkId") link_secret = BytesProperty("LinkSecret") public_key = BytesProperty("PublicKey") 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", ctypes.POINTER(BOOL)), ("pCancellationId", ctypes.POINTER(GUID)), ("pAllowCredentialList", ctypes.POINTER(WebAuthNCredentialList)), ("dwCredLargeBlobOperation", DWORD), ("cbCredLargeBlob", DWORD), ("pbCredLargeBlob", PBYTE), ("pHmacSecretSaltValues", ctypes.POINTER(WebAuthNHmacSecretSaltValues)), ("bBrowserInPrivateMode", BOOL), ("pLinkedDevice", ctypes.POINTER(CtapCborHybridStorageLinkedData)), ("bAutoFill", BOOL), ("cbJsonExt", DWORD), ("pbJsonExt", PBYTE), ] cred_large_blob = BytesProperty("CredLargeBlob") json_ext = BytesProperty("JsonExt") def __init__( self, timeout, attachment, user_verification_requirement, credentials, cancellationId, cred_large_blob_operation, cred_large_blob, hmac_secret_salts=None, extensions=None, flags=0, u2f_appid=None, u2f_appid_used=None, ): self.dwVersion = get_version(self.__class__.__name__) self.dwTimeoutMilliseconds = timeout self.dwAuthenticatorAttachment = attachment self.dwUserVerificationRequirement = user_verification_requirement self.dwFlags = flags if extensions: self.Extensions = WebAuthNExtensions(extensions) if self.dwVersion >= 2: self.pwszU2fAppId = u2f_appid if u2f_appid_used is not None: self.pbU2fAppId = ctypes.pointer(u2f_appid_used) if self.dwVersion >= 3: self.pCancellationId = cancellationId if self.dwVersion >= 4: clist = WebAuthNCredentialList(credentials) self.pAllowCredentialList = ctypes.pointer(clist) else: self.CredentialList = WebAuthNCredentials(credentials) if self.dwVersion >= 5: self.dwCredLargeBlobOperation = cred_large_blob_operation self.cred_large_blob = cred_large_blob if self.dwVersion >= 6 and hmac_secret_salts: self.pHmacSecretSaltValues = ctypes.pointer(hmac_secret_salts) class WebAuthNAssertion(ctypes.Structure): """Maps to WEBAUTHN_ASSERTION Struct. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L616 """ _fields_ = [ ("dwVersion", DWORD), ("cbAuthenticatorData", DWORD), ("pbAuthenticatorData", PBYTE), ("cbSignature", DWORD), ("pbSignature", PBYTE), ("Credential", WebAuthNCredential), ("cbUserId", DWORD), ("pbUserId", PBYTE), ("Extensions", WebAuthNExtensions), ("cbCredLargeBlob", DWORD), ("pbCredLargeBlob", PBYTE), ("dwCredLargeBlobStatus", DWORD), ("pHmacSecret", ctypes.POINTER(WebAuthNHmacSecretSalt)), ("dwUsedTransports", DWORD), ("cbUnsignedExtensionOutputs", DWORD), ("pbUnsignedExtensionOutputs", PBYTE), ] auth_data = BytesProperty("AuthenticatorData") signature = BytesProperty("Signature") user_id = BytesProperty("UserId") cred_large_blob = BytesProperty("CredLargeBlob") unsigned_extension_outputs = BytesProperty("UnsignedExtensionOutputs") 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 WebAuthNAttestationConveyancePreference attestation_convoyence: Attestation Conveyance Preference. :param List[Dict[str,Any]] credentials: Credentials used for exclusion. """ _fields_ = [ ("dwVersion", DWORD), ("dwTimeoutMilliseconds", DWORD), ("CredentialList", WebAuthNCredentials), ("Extensions", WebAuthNExtensions), ("dwAuthenticatorAttachment", DWORD), ("bRequireResidentKey", BOOL), ("dwUserVerificationRequirement", DWORD), ("dwAttestationConveyancePreference", DWORD), ("dwFlags", DWORD), ("pCancellationId", ctypes.POINTER(GUID)), ("pExcludeCredentialList", ctypes.POINTER(WebAuthNCredentialList)), ("dwEnterpriseAttestation", DWORD), ("dwLargeBlobSupport", DWORD), ("bPreferResidentKey", BOOL), ("bBrowserInPrivateMode", BOOL), ("bEnablePrf", BOOL), ("pLinkedDevice", ctypes.POINTER(CtapCborHybridStorageLinkedData)), ("cbJsonExt", DWORD), ("pbJsonExt", PBYTE), ] json_ext = BytesProperty("JsonExt") def __init__( self, timeout, require_resident_key, attachment, user_verification_requirement, attestation_convoyence, credentials, cancellationId, enterprise_attestation, large_blob_support, prefer_resident_key, enable_prf=False, extensions=None, ): 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 extensions: self.Extensions = WebAuthNExtensions(extensions) if self.dwVersion >= 2: self.pCancellationId = cancellationId if self.dwVersion >= 3: self.pExcludeCredentialList = ctypes.pointer( WebAuthNCredentialList(credentials) ) else: self.CredentialList = WebAuthNCredentials(credentials) if self.dwVersion >= 4: self.dwEnterpriseAttestation = enterprise_attestation self.dwLargeBlobSupport = large_blob_support self.bPreferResidentKey = prefer_resident_key if self.dwVersion >= 6: self.bEnablePrf = enable_prf class WebAuthNCredentialAttestation(ctypes.Structure): """Maps to WEBAUTHN_CREDENTIAL_ATTESTATION Struct. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L582 """ _fields_ = [ ("dwVersion", DWORD), ("pwszFormatType", LPCWSTR), ("cbAuthenticatorData", DWORD), ("pbAuthenticatorData", PBYTE), ("cbAttestation", DWORD), ("pbAttestation", PBYTE), ("dwAttestationDecodeType", DWORD), ("pvAttestationDecode", PBYTE), ("cbAttestationObject", DWORD), ("pbAttestationObject", PBYTE), ("cbCredentialId", DWORD), ("pbCredentialId", PBYTE), ("Extensions", WebAuthNExtensions), ("dwUsedTransport", DWORD), ("bEpAtt", BOOL), ("bLargeBlobSupported", BOOL), ("bResidentKey", BOOL), ("bPrfEnabled", BOOL), ("cbUnsignedExtensionOutputs", DWORD), ("pbUnsignedExtensionOutputs", PBYTE), ] auth_data = BytesProperty("AuthenticatorData") attestation = BytesProperty("Attestation") attestation_object = BytesProperty("AttestationObject") credential_id = BytesProperty("CredentialId") unsigned_extension_outputs = BytesProperty("UnsignedExtensionOutputs") def __del__(self): WEBAUTHN.WebAuthNFreeCredentialAttestation(ctypes.byref(self)) class _FromString(object): @classmethod def from_string(cls, value): return getattr(cls, value.upper().replace("-", "_")) @unique class WebAuthNUserVerificationRequirement(_FromString, IntEnum): """Maps to WEBAUTHN_USER_VERIFICATION_REQUIREMENT_*. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L335 """ ANY = 0 REQUIRED = 1 PREFERRED = 2 DISCOURAGED = 3 @unique class WebAuthNAttestationConveyancePreference(_FromString, IntEnum): """Maps to WEBAUTHN_ATTESTATION_CONVEYANCE_PREFERENCE_*. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L340 """ ANY = 0 NONE = 1 INDIRECT = 2 DIRECT = 3 @unique class WebAuthNAuthenticatorAttachment(_FromString, IntEnum): """Maps to WEBAUTHN_AUTHENTICATOR_ATTACHMENT_*. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L330 """ ANY = 0 PLATFORM = 1 CROSS_PLATFORM = 2 CROSS_PLATFORM_U2F_V2 = 3 @unique class WebAuthNCTAPTransport(_FromString, IntEnum): """Maps to WEBAUTHN_CTAP_TRANSPORT_*. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L225 """ ANY = 0x00000000 USB = 0x00000001 NFC = 0x00000002 BLE = 0x00000004 TEST = 0x00000008 INTERNAL = 0x00000010 FLAGS_MASK = 0x0000001F @unique class WebAuthNEnterpriseAttestation(_FromString, IntEnum): """Maps to WEBAUTHN_ENTERPRISE_ATTESTATION_*. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L401 """ NONE = 0 VENDOR_FACILITATED = 1 PLATFORM_MANAGED = 2 @unique class WebAuthNLargeBlobSupport(_FromString, IntEnum): """Maps to WEBAUTHN_LARGE_BLOB_SUPPORT_*. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L405 """ NONE = 0 REQUIRED = 1 PREFERRED = 2 @unique class WebAuthNLargeBlobOperation(_FromString, IntEnum): """Maps to WEBAUTHN_LARGE_BLOB_OPERATION_*. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L478 """ NONE = 0 GET = 1 SET = 2 DELETE = 3 @unique class WebAuthNUserVerification(_FromString, IntEnum): """Maps to WEBAUTHN_USER_VERIFICATION_*. https://github.com/microsoft/webauthn/blob/master/webauthn.h#L482 """ ANY = 0 OPTIONAL = 1 OPTIONAL_WITH_CREDENTIAL_ID_LIST = 2 REQUIRED = 3 HRESULT = ctypes.HRESULT # type: ignore WEBAUTHN = windll.webauthn # type: ignore WEBAUTHN_API_VERSION = WEBAUTHN.WebAuthNGetApiVersionNumber() # The following is derived from # https://github.com/microsoft/webauthn/blob/master/webauthn.h#L37 WEBAUTHN.WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable.argtypes = [ ctypes.POINTER(ctypes.c_bool) ] WEBAUTHN.WebAuthNIsUserVerifyingPlatformAuthenticatorAvailable.restype = HRESULT WEBAUTHN.WebAuthNAuthenticatorMakeCredential.argtypes = [ HWND, ctypes.POINTER(WebAuthNRpEntityInformation), ctypes.POINTER(WebAuthNUserEntityInformation), ctypes.POINTER(WebAuthNCoseCredentialParameters), ctypes.POINTER(WebAuthNClientData), ctypes.POINTER(WebAuthNMakeCredentialOptions), ctypes.POINTER(ctypes.POINTER(WebAuthNCredentialAttestation)), ] WEBAUTHN.WebAuthNAuthenticatorMakeCredential.restype = HRESULT WEBAUTHN.WebAuthNAuthenticatorGetAssertion.argtypes = [ HWND, LPCWSTR, ctypes.POINTER(WebAuthNClientData), ctypes.POINTER(WebAuthNGetAssertionOptions), ctypes.POINTER(ctypes.POINTER(WebAuthNAssertion)), ] WEBAUTHN.WebAuthNAuthenticatorGetAssertion.restype = HRESULT WEBAUTHN.WebAuthNFreeCredentialAttestation.argtypes = [ ctypes.POINTER(WebAuthNCredentialAttestation) ] WEBAUTHN.WebAuthNFreeAssertion.argtypes = [ctypes.POINTER(WebAuthNAssertion)] WEBAUTHN.WebAuthNGetCancellationId.argtypes = [ctypes.POINTER(GUID)] WEBAUTHN.WebAuthNGetCancellationId.restype = HRESULT WEBAUTHN.WebAuthNCancelCurrentOperation.argtypes = [ctypes.POINTER(GUID)] WEBAUTHN.WebAuthNCancelCurrentOperation.restype = HRESULT WEBAUTHN.WebAuthNGetErrorName.argtypes = [HRESULT] WEBAUTHN.WebAuthNGetErrorName.restype = PCWSTR WEBAUTHN_STRUCT_VERSIONS: Mapping[int, Mapping[str, int]] = { 1: { "WebAuthNRpEntityInformation": 1, "WebAuthNUserEntityInformation": 1, "WebAuthNClientData": 1, "WebAuthNCoseCredentialParameter": 1, "WebAuthNCredential": 1, "WebAuthNCredentialEx": 1, "WebAuthNMakeCredentialOptions": 3, "WebAuthNGetAssertionOptions": 4, "WebAuthNCommonAttestation": 1, "WebAuthNCredentialAttestation": 3, "WebAuthNAssertion": 1, }, 2: {}, 3: { "WebAuthNMakeCredentialOptions": 4, "WebAuthNGetAssertionOptions": 5, "WebAuthNCredentialAttestation": 4, "WebAuthNAssertion": 2, }, 4: { "WebAuthNMakeCredentialOptions": 5, "WebAuthNGetAssertionOptions": 6, "WebAuthNAssertion": 3, "WebAuthNCredentialDetails": 1, # Not implemented }, 5: { "WebAuthNCredentialDetails": 2, }, 6: { "WebAuthNMakeCredentialOptions": 6, "WebAuthNCredentialAttestation": 5, "WebAuthNAssertion": 4, }, 7: { "WebAuthNMakeCredentialOptions": 7, "WebAuthNGetAssertionOptions": 7, "WebAuthNCredentialAttestation": 6, "WebAuthNAssertion": 5, }, } def get_version(class_name: str) -> int: """Get version of struct. :param str class_name: Struct class name. :returns: Version of Struct to use. :rtype: int """ for api_version in range(WEBAUTHN_API_VERSION, 0, -1): if ( api_version in WEBAUTHN_STRUCT_VERSIONS and class_name in WEBAUTHN_STRUCT_VERSIONS[api_version] ): return WEBAUTHN_STRUCT_VERSIONS[api_version][class_name] raise ValueError("Unknown class name") class CancelThread(Thread): def __init__(self, event): super().__init__() self.daemon = True self._completed = False self.event = event self.guid = GUID() WEBAUTHN.WebAuthNGetCancellationId(ctypes.byref(self.guid)) def run(self): self.event.wait() if not self._completed: WEBAUTHN.WebAuthNCancelCurrentOperation(ctypes.byref(self.guid)) def complete(self): self._completed = True self.event.set() self.join() # Not implemented: Platform credentials support class WinAPI: """Implementation of Microsoft's WebAuthN APIs. :param ctypes.HWND handle: Window handle to use for API calls. """ version = WEBAUTHN_API_VERSION def __init__(self, handle=None, return_extensions=False, allow_hmac_secret=False): self.handle = handle or windll.user32.GetForegroundWindow() # TODO 2.0: Remove return_extensions and always return them if not return_extensions: warnings.warn( "WinAPI will start returning extension outputs in the next major " "version, to opt in to this behaivor now, set return_extensions=True.", DeprecationWarning, ) self._return_extensions = return_extensions self._allow_hmac_secret = allow_hmac_secret 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=WebAuthNAttestationConveyancePreference.DIRECT, exclude_credentials=None, extensions=None, event=None, enterprise_attestation=WebAuthNEnterpriseAttestation.NONE, ) -> Tuple[AttestationObject, Dict[str, Any]]: """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 WebAuthNAttestationConveyancePreference 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. """ # TODO 2.0: Require resident_key be ResidentKeyRequirement if isinstance(resident_key, bool): warnings.warn( "Passing resident_key as a bool is deprecated. " "Use ResidentKeyRequirement instead.", DeprecationWarning, ) win_extensions = [] large_blob_support = WebAuthNLargeBlobSupport.NONE enable_prf = False if extensions: if "credentialProtectionPolicy" in extensions: win_extensions.append( WebAuthNExtension( "credProtect", WebAuthNCredProtectExtensionIn( WebAuthNUserVerification.from_string( extensions["credentialProtectionPolicy"] ), extensions.get("enforceCredentialProtectionPolicy", False), ), ) ) if "credBlob" in extensions: win_extensions.append( WebAuthNExtension( "credBlob", WebAuthNCredBlobExtension(extensions["credBlob"]), ) ) if "largeBlob" in extensions: large_blob_support = WebAuthNLargeBlobSupport.from_string( extensions["largeBlob"].get("support", "none") ) if extensions.get("minPinLength", True): win_extensions.append(WebAuthNExtension("minPinLength", BOOL(True))) if "prf" in extensions: resident_key = True # Windows requires resident key for hmac-secret enable_prf = True win_extensions.append(WebAuthNExtension("hmac-secret", BOOL(True))) elif "hmacCreateSecret" in extensions and self._allow_hmac_secret: resident_key = True # Windows requires resident key for hmac-secret win_extensions.append(WebAuthNExtension("hmac-secret", BOOL(True))) else: extensions = {} if event: t = CancelThread(event) t.start() 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 in (True, ResidentKeyRequirement.REQUIRED), platform_attachment, user_verification, attestation, exclude_credentials or [], ctypes.pointer(t.guid) if event else None, enterprise_attestation, large_blob_support, resident_key == ResidentKeyRequirement.PREFERRED, enable_prf, win_extensions, ) ), ctypes.byref(attestation_pointer), ) if event: t.complete() obj = attestation_pointer.contents att_obj = AttestationObject(obj.attestation_object) extensions_out = att_obj.auth_data.extensions or {} extension_outputs = {} if extensions.get("credProps"): extension_outputs["credProps"] = {"rk": bool(obj.bResidentKey)} if "hmac-secret" in extensions_out: if enable_prf: extension_outputs["prf"] = {"enabled": extensions_out["hmac-secret"]} else: extension_outputs["hmacCreateSecret"] = extensions_out["hmac-secret"] if "largeBlob" in extensions: extension_outputs["largeBlob"] = { "supported": bool(obj.bLargeBlobSupported) } if self._return_extensions: return att_obj, extension_outputs else: return att_obj # type: ignore 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, ) -> Tuple[Dict[str, Any], AuthenticatorData, bytes, bytes, Dict[str, Any]]: """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. """ flags = 0 large_blob = None large_blob_operation = WebAuthNLargeBlobOperation.NONE hmac_secret_salts = None win_extensions = [] u2f_appid = None u2f_appid_used = BOOL(False) if extensions: if extensions.get("appid"): u2f_appid = extensions["appid"] if extensions.get("getCredBlob"): win_extensions.append(WebAuthNExtension("credBlob", BOOL(True))) if "largeBlob" in extensions: if extensions["largeBlob"].get("read", False): large_blob_operation = WebAuthNLargeBlobOperation.GET else: large_blob = extensions["largeBlob"]["write"] large_blob_operation = WebAuthNLargeBlobOperation.SET if "prf" in extensions: global_salts = extensions["prf"].get("eval") cred_salts = extensions["prf"].get("evalByCredential", {}) hmac_secret_salts = WebAuthNHmacSecretSaltValues( WebAuthNHmacSecretSalt(**global_salts) if global_salts else None, [ WebAuthNCredWithHmacSecretSalt( websafe_decode(cred_id), WebAuthNHmacSecretSalt(**salts), ) for cred_id, salts in cred_salts.items() ], ) elif "hmacGetSecret" in extensions and self._allow_hmac_secret: flags |= 0x00100000 salts = extensions["hmacGetSecret"] hmac_secret_salts = WebAuthNHmacSecretSaltValues( WebAuthNHmacSecretSalt(salts["salt1"], salts.get("salt2")) ) if event: t = CancelThread(event) t.start() 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, large_blob_operation, large_blob, hmac_secret_salts, win_extensions, flags, u2f_appid, u2f_appid_used, ) ), ctypes.byref(assertion_pointer), ) if event: t.complete() obj = assertion_pointer.contents auth_data = AuthenticatorData(obj.auth_data) extension_outputs: Dict[str, Any] = {} if u2f_appid and obj.dwVersion >= 2: extension_outputs["appid"] = bool(u2f_appid_used.value) if extensions: if hmac_secret_salts and obj.dwVersion >= 3: secret = obj.pHmacSecret.contents if "prf" in extensions: result = {"first": secret.first} if secret.second: result["second"] = secret.second extension_outputs["prf"] = {"results": result} else: result = {"output1": secret.first} if secret.second: result["output2"] = secret.second extension_outputs["hmacGetSecret"] = result if obj.dwCredLargeBlobStatus != 0: if extensions["largeBlob"].get("read", False): extension_outputs["largeBlob"] = {"blob": obj.cred_large_blob} else: extension_outputs["largeBlob"] = { "written": obj.dwCredLargeBlobStatus == 1 } if self._return_extensions: return ( obj.Credential.descriptor, auth_data, obj.signature, obj.user_id, extension_outputs, ) else: return ( obj.Credential.descriptor, auth_data, obj.signature, obj.user_id, ) # type: ignore fido2-1.2.0/fido2/ctap.py0000644000175000017500000001336514721556664014415 0ustar winniewinnie# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations import abc from enum import IntEnum, unique from threading import Event from typing import Optional, Callable, Iterator @unique class STATUS(IntEnum): """Status code for CTAP keep-alive message.""" PROCESSING = 1 UPNEEDED = 2 class CtapDevice(abc.ABC): """ CTAP-capable device. Subclasses of this should implement :func:`call`, as well as :func:`list_devices`, which should return a generator over discoverable devices. """ @property @abc.abstractmethod def capabilities(self) -> int: """Get device capabilities""" @abc.abstractmethod def call( self, cmd: int, data: bytes = b"", event: Optional[Event] = None, on_keepalive: Optional[Callable[[STATUS], None]] = None, ) -> bytes: """Sends a command to the authenticator, and reads the response. :param cmd: The integer value of the command. :param data: The payload of the command. :param event: An optional threading.Event which can be used to cancel the invocation. :param on_keepalive: An optional callback to handle keep-alive messages from the authenticator. The function is only called once for consecutive keep-alive messages with the same status. :return: The response from the authenticator. """ def close(self) -> None: """Close the device, releasing any held resources.""" def __enter__(self): return self def __exit__(self, typ, value, traceback): self.close() @classmethod @abc.abstractmethod def list_devices(cls) -> Iterator[CtapDevice]: """Generates instances of cls for discoverable devices.""" class CtapError(Exception): """Error returned from the Authenticator when a command fails.""" class UNKNOWN_ERR(int): """CTAP error status code that is not recognized.""" name = "UNKNOWN_ERR" @property def value(self) -> int: return int(self) def __repr__(self): return "" % self def __str__(self): return f"0x{self:02X} - UNKNOWN" @unique class ERR(IntEnum): """CTAP status codes. https://fidoalliance.org/specs/fido-v2.1-rd-20201208/fido-client-to-authenticator-protocol-v2.1-rd-20201208.html#error-responses """ SUCCESS = 0x00 INVALID_COMMAND = 0x01 INVALID_PARAMETER = 0x02 INVALID_LENGTH = 0x03 INVALID_SEQ = 0x04 TIMEOUT = 0x05 CHANNEL_BUSY = 0x06 LOCK_REQUIRED = 0x0A INVALID_CHANNEL = 0x0B CBOR_UNEXPECTED_TYPE = 0x11 INVALID_CBOR = 0x12 MISSING_PARAMETER = 0x14 LIMIT_EXCEEDED = 0x15 # UNSUPPORTED_EXTENSION = 0x16 # No longer in spec FP_DATABASE_FULL = 0x17 LARGE_BLOB_STORAGE_FULL = 0x18 CREDENTIAL_EXCLUDED = 0x19 PROCESSING = 0x21 INVALID_CREDENTIAL = 0x22 USER_ACTION_PENDING = 0x23 OPERATION_PENDING = 0x24 NO_OPERATIONS = 0x25 UNSUPPORTED_ALGORITHM = 0x26 OPERATION_DENIED = 0x27 KEY_STORE_FULL = 0x28 # NOT_BUSY = 0x29 # No longer in spec # NO_OPERATION_PENDING = 0x2A # No longer in spec UNSUPPORTED_OPTION = 0x2B INVALID_OPTION = 0x2C KEEPALIVE_CANCEL = 0x2D NO_CREDENTIALS = 0x2E USER_ACTION_TIMEOUT = 0x2F NOT_ALLOWED = 0x30 PIN_INVALID = 0x31 PIN_BLOCKED = 0x32 PIN_AUTH_INVALID = 0x33 PIN_AUTH_BLOCKED = 0x34 PIN_NOT_SET = 0x35 PUAT_REQUIRED = 0x36 PIN_POLICY_VIOLATION = 0x37 PIN_TOKEN_EXPIRED = 0x38 REQUEST_TOO_LARGE = 0x39 ACTION_TIMEOUT = 0x3A UP_REQUIRED = 0x3B UV_BLOCKED = 0x3C INTEGRITY_FAILURE = 0x3D INVALID_SUBCOMMAND = 0x3E UV_INVALID = 0x3F UNAUTHORIZED_PERMISSION = 0x40 OTHER = 0x7F SPEC_LAST = 0xDF EXTENSION_FIRST = 0xE0 EXTENSION_LAST = 0xEF VENDOR_FIRST = 0xF0 VENDOR_LAST = 0xFF def __str__(self): return f"0x{self.value:02X} - {self.name}" def __init__(self, code: int): try: self.code = CtapError.ERR(code) except ValueError: self.code = CtapError.UNKNOWN_ERR(code) # type: ignore super().__init__(f"CTAP error: {self.code}") fido2-1.2.0/fido2/webauthn.py0000644000175000017500000005353414721556664015305 0ustar winniewinnie# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations from . import cbor from .cose import CoseKey, ES256 from .utils import ( sha256, websafe_decode, websafe_encode, ByteBuffer, _JsonDataObject, ) from .features import webauthn_json_mapping from enum import Enum, EnumMeta, unique, IntFlag from dataclasses import dataclass, field from typing import Any, Mapping, Optional, Sequence, Tuple, Union, cast import struct import json """ Data classes based on the W3C WebAuthn specification (https://www.w3.org/TR/webauthn/). See the specification for a description and details on their usage. """ # Binary types class Aaguid(bytes): def __init__(self, data: bytes): if len(self) != 16: raise ValueError("AAGUID must be 16 bytes") def __bool__(self): return self != Aaguid.NONE def __str__(self): h = self.hex() return f"{h[:8]}-{h[8:12]}-{h[12:16]}-{h[16:20]}-{h[20:]}" def __repr__(self): return f"AAGUID({str(self)})" @classmethod def parse(cls, value: str) -> Aaguid: return cls.fromhex(value.replace("-", "")) NONE: Aaguid # Special instance of AAGUID used when there is no AAGUID Aaguid.NONE = Aaguid(b"\0" * 16) @dataclass(init=False, frozen=True) class AttestedCredentialData(bytes): aaguid: Aaguid credential_id: bytes public_key: CoseKey def __init__(self, _: bytes): super().__init__() parsed = AttestedCredentialData._parse(self) object.__setattr__(self, "aaguid", parsed[0]) object.__setattr__(self, "credential_id", parsed[1]) object.__setattr__(self, "public_key", parsed[2]) if parsed[3]: raise ValueError("Wrong length") def __str__(self): # Override default implementation from bytes. return repr(self) @staticmethod def _parse(data: bytes) -> Tuple[bytes, bytes, CoseKey, bytes]: """Parse the components of an AttestedCredentialData from a binary string, and return them. :param data: A binary string containing an attested credential data. :return: AAGUID, credential ID, public key, and remaining data. """ reader = ByteBuffer(data) aaguid = Aaguid(reader.read(16)) cred_id = reader.read(reader.unpack(">H")) pub_key, rest = cbor.decode_from(reader.read()) return aaguid, cred_id, CoseKey.parse(pub_key), rest @classmethod def create( cls, aaguid: bytes, credential_id: bytes, public_key: CoseKey ) -> AttestedCredentialData: """Create an AttestedCredentialData by providing its components. :param aaguid: The AAGUID of the authenticator. :param credential_id: The binary ID of the credential. :param public_key: A COSE formatted public key. :return: The attested credential data. """ return cls( aaguid + struct.pack(">H", len(credential_id)) + credential_id + cbor.encode(public_key) ) @classmethod def unpack_from(cls, data: bytes) -> Tuple[AttestedCredentialData, bytes]: """Unpack an AttestedCredentialData from a byte string, returning it and any remaining data. :param data: A binary string containing an attested credential data. :return: The parsed AttestedCredentialData, and any remaining data from the input. """ aaguid, cred_id, pub_key, rest = cls._parse(data) return cls.create(aaguid, cred_id, pub_key), rest @classmethod def from_ctap1(cls, key_handle: bytes, public_key: bytes) -> AttestedCredentialData: """Create an AttestatedCredentialData from a CTAP1 RegistrationData instance. :param key_handle: The CTAP1 credential key_handle. :type key_handle: bytes :param public_key: The CTAP1 65 byte public key. :type public_key: bytes :return: The credential data, using an all-zero AAGUID. :rtype: AttestedCredentialData """ return cls.create(Aaguid.NONE, key_handle, ES256.from_ctap1(public_key)) @dataclass(init=False, frozen=True) class AuthenticatorData(bytes): """Binary encoding of the authenticator data. :param _: The binary representation of the authenticator data. :ivar rp_id_hash: SHA256 hash of the RP ID. :ivar flags: The flags of the authenticator data, see AuthenticatorData.FLAG. :ivar counter: The signature counter of the authenticator. :ivar credential_data: Attested credential data, if available. :ivar extensions: Authenticator extensions, if available. """ class FLAG(IntFlag): """Authenticator data flags See https://www.w3.org/TR/webauthn/#sec-authenticator-data for details """ # Names used in WebAuthn UP = 0x01 UV = 0x04 BE = 0x08 BS = 0x10 AT = 0x40 ED = 0x80 # Aliases (for historical purposes) USER_PRESENT = 0x01 USER_VERIFIED = 0x04 BACKUP_ELIGIBILITY = 0x08 BACKUP_STATE = 0x10 ATTESTED = 0x40 EXTENSION_DATA = 0x80 rp_id_hash: bytes flags: AuthenticatorData.FLAG counter: int credential_data: Optional[AttestedCredentialData] extensions: Optional[Mapping] def __init__(self, _: bytes): super().__init__() reader = ByteBuffer(self) object.__setattr__(self, "rp_id_hash", reader.read(32)) object.__setattr__(self, "flags", reader.unpack("B")) object.__setattr__(self, "counter", reader.unpack(">I")) rest = reader.read() if self.flags & AuthenticatorData.FLAG.AT: credential_data, rest = AttestedCredentialData.unpack_from(rest) else: credential_data = None object.__setattr__(self, "credential_data", credential_data) if self.flags & AuthenticatorData.FLAG.ED: extensions, rest = cbor.decode_from(rest) else: extensions = None object.__setattr__(self, "extensions", extensions) if rest: raise ValueError("Wrong length") def __str__(self): # Override default implementation from bytes. return repr(self) @classmethod def create( cls, rp_id_hash: bytes, flags: AuthenticatorData.FLAG, counter: int, credential_data: bytes = b"", extensions: Optional[Mapping] = None, ): """Create an AuthenticatorData instance. :param rp_id_hash: SHA256 hash of the RP ID. :param flags: Flags of the AuthenticatorData. :param counter: Signature counter of the authenticator data. :param credential_data: Authenticated credential data (only if attested credential data flag is set). :param extensions: Authenticator extensions (only if ED flag is set). :return: The authenticator data. """ return cls( rp_id_hash + struct.pack(">BI", flags, counter) + credential_data + (cbor.encode(extensions) if extensions is not None else b"") ) def is_user_present(self) -> bool: """Return true if the User Present flag is set.""" return bool(self.flags & AuthenticatorData.FLAG.UP) def is_user_verified(self) -> bool: """Return true if the User Verified flag is set.""" return bool(self.flags & AuthenticatorData.FLAG.UV) def is_backup_eligible(self) -> bool: """Return true if the Backup Eligibility flag is set.""" return bool(self.flags & AuthenticatorData.FLAG.BE) def is_backed_up(self) -> bool: """Return true if the Backup State flag is set.""" return bool(self.flags & AuthenticatorData.FLAG.BS) def is_attested(self) -> bool: """Return true if the Attested credential data flag is set.""" return bool(self.flags & AuthenticatorData.FLAG.AT) def has_extension_data(self) -> bool: """Return true if the Extenstion data flag is set.""" return bool(self.flags & AuthenticatorData.FLAG.ED) @dataclass(init=False, frozen=True) class AttestationObject(bytes): # , Mapping[str, Any]): """Binary CBOR encoded attestation object. :param _: The binary representation of the attestation object. :ivar fmt: The type of attestation used. :ivar auth_data: The attested authenticator data. :ivar att_statement: The attestation statement. """ fmt: str auth_data: AuthenticatorData att_stmt: Mapping[str, Any] def __init__(self, _: bytes): super().__init__() data = cast(Mapping[str, Any], cbor.decode(bytes(self))) object.__setattr__(self, "fmt", data["fmt"]) object.__setattr__(self, "auth_data", AuthenticatorData(data["authData"])) object.__setattr__(self, "att_stmt", data["attStmt"]) def __str__(self): # Override default implementation from bytes. return repr(self) @classmethod def create( cls, fmt: str, auth_data: AuthenticatorData, att_stmt: Mapping[str, Any] ) -> AttestationObject: return cls( cbor.encode({"fmt": fmt, "authData": auth_data, "attStmt": att_stmt}) ) @classmethod def from_ctap1(cls, app_param: bytes, registration) -> AttestationObject: """Create an AttestationObject from a CTAP1 RegistrationData instance. :param app_param: SHA256 hash of the RP ID used for the CTAP1 request. :type app_param: bytes :param registration: The CTAP1 registration data. :type registration: RegistrationData :return: The attestation object, using the "fido-u2f" format. :rtype: AttestationObject """ return cls.create( "fido-u2f", AuthenticatorData.create( app_param, AuthenticatorData.FLAG.AT | AuthenticatorData.FLAG.UP, 0, AttestedCredentialData.from_ctap1( registration.key_handle, registration.public_key ), ), {"x5c": [registration.certificate], "sig": registration.signature}, ) @dataclass(init=False, frozen=True) class CollectedClientData(bytes): @unique class TYPE(str, Enum): CREATE = "webauthn.create" GET = "webauthn.get" type: str challenge: bytes origin: str cross_origin: bool = False def __init__(self, _: bytes): super().__init__() data = json.loads(self.decode()) object.__setattr__(self, "type", data["type"]) object.__setattr__(self, "challenge", websafe_decode(data["challenge"])) object.__setattr__(self, "origin", data["origin"]) object.__setattr__(self, "cross_origin", data.get("crossOrigin", False)) @classmethod def create( cls, type: str, challenge: Union[bytes, str], origin: str, cross_origin: bool = False, **kwargs, ) -> CollectedClientData: if isinstance(challenge, bytes): encoded_challenge = websafe_encode(challenge) else: encoded_challenge = challenge return cls( json.dumps( { "type": type, "challenge": encoded_challenge, "origin": origin, "crossOrigin": cross_origin, **kwargs, }, separators=(",", ":"), ).encode() ) def __str__(self): # Override default implementation from bytes. return repr(self) @property def b64(self) -> str: return websafe_encode(self) @property def hash(self) -> bytes: return sha256(self) class _StringEnumMeta(EnumMeta): def _get_value(cls, value): return None def __call__(cls, value, *args, **kwargs): try: return super().__call__(value, *args, **kwargs) except ValueError: return cls._get_value(value) class _StringEnum(str, Enum, metaclass=_StringEnumMeta): """Enum of strings for WebAuthn types. Unrecognized values are treated as missing. """ @unique class AttestationConveyancePreference(_StringEnum): NONE = "none" INDIRECT = "indirect" DIRECT = "direct" ENTERPRISE = "enterprise" @unique class UserVerificationRequirement(_StringEnum): REQUIRED = "required" PREFERRED = "preferred" DISCOURAGED = "discouraged" @unique class ResidentKeyRequirement(_StringEnum): REQUIRED = "required" PREFERRED = "preferred" DISCOURAGED = "discouraged" @unique class AuthenticatorAttachment(_StringEnum): PLATFORM = "platform" CROSS_PLATFORM = "cross-platform" @unique class AuthenticatorTransport(_StringEnum): USB = "usb" NFC = "nfc" BLE = "ble" HYBRID = "hybrid" INTERNAL = "internal" @unique class PublicKeyCredentialType(_StringEnum): PUBLIC_KEY = "public-key" class _WebAuthnDataObject(_JsonDataObject): def __getitem__(self, key): if webauthn_json_mapping.enabled: return super().__getitem__(key) return super(_JsonDataObject, self).__getitem__(key) @classmethod def _parse_value(cls, t, value): if webauthn_json_mapping.enabled: return super()._parse_value(t, value) return super(_JsonDataObject, cls)._parse_value(t, value) @classmethod def from_dict(cls, data): webauthn_json_mapping.warn() return super().from_dict(data) def _as_cbor(data: _WebAuthnDataObject) -> Mapping[str, Any]: return {k: super(_JsonDataObject, data).__getitem__(k) for k in data} @dataclass(eq=False, frozen=True) class PublicKeyCredentialRpEntity(_WebAuthnDataObject): name: str id: Optional[str] = None @property def id_hash(self) -> Optional[bytes]: """Return SHA256 hash of the identifier.""" return sha256(self.id.encode("utf8")) if self.id else None @dataclass(eq=False, frozen=True) class PublicKeyCredentialUserEntity(_WebAuthnDataObject): name: str id: bytes display_name: Optional[str] = None @dataclass(eq=False, frozen=True) class PublicKeyCredentialParameters(_WebAuthnDataObject): type: PublicKeyCredentialType alg: int @dataclass(eq=False, frozen=True) class PublicKeyCredentialDescriptor(_WebAuthnDataObject): type: PublicKeyCredentialType id: bytes transports: Optional[Sequence[AuthenticatorTransport]] = None @dataclass(eq=False, frozen=True) class AuthenticatorSelectionCriteria(_WebAuthnDataObject): authenticator_attachment: Optional[AuthenticatorAttachment] = None resident_key: Optional[ResidentKeyRequirement] = None user_verification: Optional[UserVerificationRequirement] = None require_resident_key: Optional[bool] = False def __post_init__(self): super().__post_init__() if self.resident_key is None: object.__setattr__( self, "resident_key", ( ResidentKeyRequirement.REQUIRED if self.require_resident_key else ResidentKeyRequirement.DISCOURAGED ), ) object.__setattr__( self, "require_resident_key", self.resident_key == ResidentKeyRequirement.REQUIRED, ) @dataclass(eq=False, frozen=True) class PublicKeyCredentialCreationOptions(_WebAuthnDataObject): rp: PublicKeyCredentialRpEntity user: PublicKeyCredentialUserEntity challenge: bytes pub_key_cred_params: Sequence[PublicKeyCredentialParameters] timeout: Optional[int] = None exclude_credentials: Optional[Sequence[PublicKeyCredentialDescriptor]] = None authenticator_selection: Optional[AuthenticatorSelectionCriteria] = None attestation: Optional[AttestationConveyancePreference] = None extensions: Optional[Mapping[str, Any]] = None @dataclass(eq=False, frozen=True) class PublicKeyCredentialRequestOptions(_WebAuthnDataObject): challenge: bytes timeout: Optional[int] = None rp_id: Optional[str] = None allow_credentials: Optional[Sequence[PublicKeyCredentialDescriptor]] = None user_verification: Optional[UserVerificationRequirement] = None extensions: Optional[Mapping[str, Any]] = None # TODO 2.0: Move extension results to RegistrationResponse, remove methods @dataclass(eq=False, frozen=True) class AuthenticatorAttestationResponse(_WebAuthnDataObject): client_data: CollectedClientData = field(metadata=dict(name="clientDataJSON")) attestation_object: AttestationObject extension_results: Optional[Mapping[str, Any]] = None def __getitem__(self, key): if key == "clientData" and not webauthn_json_mapping.enabled: return self.client_data return super().__getitem__(key) @classmethod def from_dict(cls, data): if data is not None and not webauthn_json_mapping.enabled: value = dict(data) value["clientDataJSON"] = value.pop("clientData", None) data = value return super().from_dict(data) @classmethod def _parse_value(cls, t, value): if t == Optional[Mapping[str, Any]]: # Don't convert extension_results return value return super()._parse_value(t, value) # TODO 2.0: Move extension results to AuthenticationResponse, remove methods @dataclass(eq=False, frozen=True) class AuthenticatorAssertionResponse(_WebAuthnDataObject): client_data: CollectedClientData = field(metadata=dict(name="clientDataJSON")) authenticator_data: AuthenticatorData signature: bytes user_handle: Optional[bytes] = None credential_id: Optional[bytes] = None extension_results: Optional[Mapping[str, Any]] = None def __getitem__(self, key): if key == "clientData" and not webauthn_json_mapping.enabled: return self.client_data return super().__getitem__(key) @classmethod def from_dict(cls, data): if data is not None and not webauthn_json_mapping.enabled: value = dict(data) value["clientDataJSON"] = value.pop("clientData", None) data = value return super().from_dict(data) @classmethod def _parse_value(cls, t, value): if t == Optional[Mapping[str, Any]]: # Don't convert extension_results return value return super()._parse_value(t, value) # TODO 2.0: Re-align against WebAuthn spec and use in client @dataclass(eq=False, frozen=True) class RegistrationResponse(_WebAuthnDataObject): id: bytes response: AuthenticatorAttestationResponse authenticator_attachment: Optional[AuthenticatorAttachment] = None client_extension_results: Optional[AuthenticationExtensionsClientOutputs] = None type: Optional[PublicKeyCredentialType] = None def __post_init__(self): webauthn_json_mapping.require() super().__post_init__() # TODO 2.0: Re-align against WebAuthn spec and use in client @dataclass(eq=False, frozen=True) class AuthenticationResponse(_WebAuthnDataObject): id: bytes response: AuthenticatorAssertionResponse authenticator_attachment: Optional[AuthenticatorAttachment] = None client_extension_results: Optional[AuthenticationExtensionsClientOutputs] = None type: Optional[PublicKeyCredentialType] = None def __post_init__(self): webauthn_json_mapping.require() super().__post_init__() @dataclass(eq=False, frozen=True) class CredentialCreationOptions(_WebAuthnDataObject): public_key: PublicKeyCredentialCreationOptions @dataclass(eq=False, frozen=True) class CredentialRequestOptions(_WebAuthnDataObject): public_key: PublicKeyCredentialRequestOptions class AuthenticationExtensionsClientOutputs(Mapping[str, Any]): """Holds extension output from a call to MakeCredential or GetAssertion. When accessed as a dict, all bytes values will be serialized to base64url encoding, capable of being serialized to JSON. When accessed using attributes, richer types will instead be returned. """ def __init__(self, outputs: Mapping[str, Any]): self._members = {k: v for k, v in outputs.items() if v is not None} def __iter__(self): return iter(self._members) def __len__(self): return len(self._members) def __getitem__(self, key): value = self._members[key] if isinstance(value, bytes): return websafe_encode(value) elif isinstance(value, Mapping) and not isinstance(value, dict): return dict(value) return value def __getattr__(self, key): parts = key.split("_") name = parts[0] + "".join(p.title() for p in parts[1:]) return self._members.get(name) def __repr__(self): return repr(dict(self)) fido2-1.2.0/fido2/hid/0000775000175000017500000000000014741676716013655 5ustar winniewinniefido2-1.2.0/fido2/hid/linux.py0000644000175000017500000000642114721556664015364 0ustar winniewinnie# Original work 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. # # Modified work Copyright 2020 Yubico AB. All Rights Reserved. # This file, with modifications, is licensed under the above Apache License. from __future__ import annotations from .base import HidDescriptor, FileCtapHidConnection, parse_report_descriptor import glob import fcntl import struct from array import array from typing import Set import logging import sys # Don't typecheck this file on Windows assert sys.platform != "win32" # nosec logger = logging.getLogger(__name__) # hidraw.h HIDIOCGRAWINFO = 0x80084803 HIDIOCGRDESCSIZE = 0x80044801 HIDIOCGRDESC = 0x90044802 HIDIOCGRAWNAME = 0x90044804 HIDIOCGRAWUNIQ = 0x90044808 class LinuxCtapHidConnection(FileCtapHidConnection): def write_packet(self, packet): # Prepend the report ID super().write_packet(b"\0" + packet) def open_connection(descriptor): return LinuxCtapHidConnection(descriptor) def get_descriptor(path): with open(path, "rb") as f: # Read VID, PID buf = array("B", [0] * (4 + 2 + 2)) fcntl.ioctl(f, HIDIOCGRAWINFO, buf, True) _, vid, pid = struct.unpack(" 1 else None # Read unique ID try: buf = array("B", [0] * 64) length = fcntl.ioctl(f, HIDIOCGRAWUNIQ, buf, True) serial = ( bytearray(buf[: (length - 1)]).decode("utf-8") if length > 1 else None ) except OSError: serial = None # Read report descriptor buf = array("B", [0] * 4) fcntl.ioctl(f, HIDIOCGRDESCSIZE, buf, True) size = struct.unpack(" HIDIOCGRAWINFO = 0x40085520 HIDIOCGRDESC = 0x2000551F HIDIOCGRDESCSIZE = 0x4004551E HIDIOCGRAWNAME_128 = 0x40805521 HIDIOCGRAWUNIQ_64 = 0x40405525 class usb_gen_descriptor(ctypes.Structure): _fields_ = [ ( "ugd_data", ctypes.c_void_p, ), # TODO: check what COMPAT_32BIT in C header means ("ugd_lang_id", ctypes.c_uint16), ("ugd_maxlen", ctypes.c_uint16), ("ugd_actlen", ctypes.c_uint16), ("ugd_offset", ctypes.c_uint16), ("ugd_config_index", ctypes.c_uint8), ("ugd_string_index", ctypes.c_uint8), ("ugd_iface_index", ctypes.c_uint8), ("ugd_altif_index", ctypes.c_uint8), ("ugd_endpt_index", ctypes.c_uint8), ("ugd_report_type", ctypes.c_uint8), ("reserved", ctypes.c_uint8 * 8), ] class HidrawCtapHidConnection(FileCtapHidConnection): def write_packet(self, packet): # Prepend the report ID super(HidrawCtapHidConnection, self).write_packet(b"\0" + packet) def open_connection(descriptor): if descriptor.path.find(devdir + "hidraw") == 0: return HidrawCtapHidConnection(descriptor) else: return FileCtapHidConnection(descriptor) def _get_report_data(fd, report_type): data = ctypes.create_string_buffer(4096) desc = usb_gen_descriptor( ugd_data=ctypes.addressof(data), ugd_maxlen=ctypes.sizeof(data), ugd_report_type=report_type, ) ret = libc.ioctl(fd, USB_GET_REPORT_DESC, ctypes.byref(desc)) if ret != 0: raise ValueError("ioctl failed") return data.raw[: desc.ugd_actlen] def _read_descriptor(vid, pid, name, serial, path): fd = os.open(path, os.O_RDONLY) data = _get_report_data(fd, 3) os.close(fd) max_in_size, max_out_size = parse_report_descriptor(data) return HidDescriptor(path, vid, pid, max_in_size, max_out_size, name, serial) def _enumerate(): for uhid in glob.glob(devdir + "uhid?*"): index = uhid[len(devdir) + len("uhid") :] if not index.isdigit(): continue pnpinfo = ("dev.uhid." + index + ".%pnpinfo").encode() desc = ("dev.uhid." + index + ".%desc").encode() ovalue = ctypes.create_string_buffer(1024) olen = ctypes.c_size_t(ctypes.sizeof(ovalue)) key = ctypes.c_char_p(pnpinfo) retval = libc.sysctlbyname(key, ovalue, ctypes.byref(olen), None, None) if retval != 0: continue dev: Dict[str, Optional[Union[str, int]]] = {} dev["name"] = uhid[len(devdir) :] dev["path"] = uhid value = ovalue.value[: olen.value].decode() m = vendor_re.search(value) dev["vendor_id"] = int(m.group(1), 16) if m else None m = product_re.search(value) dev["product_id"] = int(m.group(1), 16) if m else None m = sernum_re.search(value) dev["serial_number"] = m.group(1) if m else None key = ctypes.c_char_p(desc) retval = libc.sysctlbyname(key, ovalue, ctypes.byref(olen), None, None) if retval == 0: dev["product_desc"] = ovalue.value[: olen.value].decode() or None yield dev def get_hidraw_descriptor(path): with open(path, "rb") as f: # Read VID, PID buf = array("B", [0] * (4 + 2 + 2)) fcntl.ioctl(f, HIDIOCGRAWINFO, buf, True) _, vid, pid = struct.unpack(" 1 else None # Read unique ID try: buf = array("B", [0] * 65) fcntl.ioctl(f, HIDIOCGRAWUNIQ_64, buf, True) length = buf.index(0) + 1 # emulate ioctl return value serial = ( bytearray(buf[: (length - 1)]).decode("utf-8") if length > 1 else None ) except OSError: serial = None # Read report descriptor buf = array("B", [0] * 4) fcntl.ioctl(f, HIDIOCGRDESCSIZE, buf, True) size = struct.unpack(" bytes: """Reads a CTAP HID packet""" @abc.abstractmethod def write_packet(self, data: bytes) -> None: """Writes a CTAP HID packet""" @abc.abstractmethod def close(self) -> None: """Closes the connection""" class FileCtapHidConnection(CtapHidConnection): """Basic CtapHidConnection implementation which uses a path to a file descriptor""" def __init__(self, descriptor): self.handle = os.open(descriptor.path, os.O_RDWR) self.descriptor = descriptor def close(self): os.close(self.handle) def write_packet(self, packet): if os.write(self.handle, packet) != len(packet): raise OSError("failed to write entire packet") def read_packet(self): return os.read(self.handle, self.descriptor.report_size_in) REPORT_DESCRIPTOR_KEY_MASK = 0xFC SIZE_MASK = ~REPORT_DESCRIPTOR_KEY_MASK OUTPUT_ITEM = 0x90 INPUT_ITEM = 0x80 COLLECTION_ITEM = 0xA0 REPORT_COUNT = 0x94 REPORT_SIZE = 0x74 USAGE_PAGE = 0x04 USAGE = 0x08 def parse_report_descriptor(data: bytes) -> Tuple[int, int]: # Parse report descriptor data usage, usage_page = None, None max_input_size, max_output_size = None, None report_count, report_size = None, None remaining = 4 while data and remaining: head, data = struct.unpack_from(">B", data)[0], data[1:] key, size = REPORT_DESCRIPTOR_KEY_MASK & head, SIZE_MASK & head value = struct.unpack_from(" bool: return bool(flags & self) TYPE_INIT = 0x80 class CtapHidDevice(CtapDevice): """ CtapDevice implementation using the HID transport. :cvar descriptor: Device descriptor. """ def __init__(self, descriptor: HidDescriptor, connection): self.descriptor = descriptor self._packet_size = descriptor.report_size_out self._connection = connection nonce = os.urandom(8) self._channel_id = 0xFFFFFFFF response = self.call(CTAPHID.INIT, nonce) r_nonce, response = response[:8], response[8:] if r_nonce != nonce: raise ConnectionFailure("Wrong nonce") ( self._channel_id, self._u2fhid_version, v1, v2, v3, self._capabilities, ) = struct.unpack_from(">IBBBBB", response) self._device_version = (v1, v2, v3) def __repr__(self): return f"CtapHidDevice({self.descriptor.path!r})" @property def version(self) -> int: """CTAP HID protocol version.""" return self._u2fhid_version @property def device_version(self) -> Tuple[int, int, int]: """Device version number.""" return self._device_version @property def capabilities(self) -> int: """Capabilities supported by the device.""" return self._capabilities @property def product_name(self) -> Optional[str]: """Product name of device.""" return self.descriptor.product_name @property def serial_number(self) -> Optional[str]: """Serial number of device.""" return self.descriptor.serial_number def _send_cancel(self): packet = struct.pack(">IB", self._channel_id, TYPE_INIT | CTAPHID.CANCEL).ljust( self._packet_size, b"\0" ) logger.log(LOG_LEVEL_TRAFFIC, "SEND: %s", packet.hex()) self._connection.write_packet(packet) def call( self, cmd: int, data: bytes = b"", event: Optional[Event] = None, on_keepalive: Optional[Callable[[STATUS], None]] = None, ) -> bytes: event = event or Event() while True: try: return self._do_call(cmd, data, event, on_keepalive) except CtapError as e: if e.code == CtapError.ERR.CHANNEL_BUSY: if not event.wait(0.1): logger.warning("CTAP channel busy, trying again...") continue # Keep retrying on BUSY while not cancelled raise def _do_call(self, cmd, data, event, on_keepalive): remaining = data seq = 0 # Send request header = struct.pack(">IBH", self._channel_id, TYPE_INIT | cmd, len(remaining)) while remaining or seq == 0: size = min(len(remaining), self._packet_size - len(header)) body, remaining = remaining[:size], remaining[size:] packet = header + body logger.log(LOG_LEVEL_TRAFFIC, "SEND: %s", packet.hex()) self._connection.write_packet(packet.ljust(self._packet_size, b"\0")) header = struct.pack(">IB", self._channel_id, 0x7F & seq) seq += 1 try: # Read response seq = 0 response = b"" last_ka = None while True: if event.is_set(): # Cancel logger.debug("Sending cancel...") self._send_cancel() recv = self._connection.read_packet() logger.log(LOG_LEVEL_TRAFFIC, "RECV: %s", recv.hex()) r_channel = struct.unpack_from(">I", recv)[0] recv = recv[4:] if r_channel != self._channel_id: raise ConnectionFailure("Wrong channel") if not response: # Initialization packet r_cmd, r_len = struct.unpack_from(">BH", recv) recv = recv[3:] if r_cmd == TYPE_INIT | cmd: pass # first data packet elif r_cmd == TYPE_INIT | CTAPHID.KEEPALIVE: try: ka_status = STATUS(struct.unpack_from(">B", recv)[0]) logger.debug(f"Got keepalive status: {ka_status:02x}") except ValueError: raise ConnectionFailure("Invalid keepalive status") if on_keepalive and ka_status != last_ka: last_ka = ka_status on_keepalive(ka_status) continue elif r_cmd == TYPE_INIT | CTAPHID.ERROR: raise CtapError(struct.unpack_from(">B", recv)[0]) else: raise CtapError(CtapError.ERR.INVALID_COMMAND) else: # Continuation packet r_seq = struct.unpack_from(">B", recv)[0] recv = recv[1:] if r_seq != seq: raise ConnectionFailure("Wrong sequence number") seq += 1 response += recv if len(response) >= r_len: break return response[:r_len] except KeyboardInterrupt: logger.debug("Keyboard interrupt, cancelling...") self._send_cancel() raise def wink(self) -> None: """Causes the authenticator to blink.""" self.call(CTAPHID.WINK) def ping(self, msg: bytes = b"Hello FIDO") -> bytes: """Sends data to the authenticator, which echoes it back. :param msg: The data to send. :return: The response from the authenticator. """ return self.call(CTAPHID.PING, msg) def lock(self, lock_time: int = 10) -> None: """Locks the channel.""" self.call(CTAPHID.LOCK, struct.pack(">B", lock_time)) def close(self) -> None: if self._connection: self._connection.close() self._connection = None @classmethod def list_devices(cls) -> Iterator[CtapHidDevice]: for d in list_descriptors(): yield cls(d, open_connection(d)) def list_devices() -> Iterator[CtapHidDevice]: return CtapHidDevice.list_devices() def open_device(path) -> CtapHidDevice: descriptor = get_descriptor(path) return CtapHidDevice(descriptor, open_connection(descriptor)) fido2-1.2.0/fido2/hid/netbsd.py0000644000175000017500000001176314721556664015511 0ustar winniewinnie# 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 NetBSD.""" from __future__ import absolute_import import errno import logging import os import select import struct import sys from ctypes import ( Structure, c_char, c_int, c_ubyte, c_uint16, c_uint32, c_uint8, ) from typing import Set from . import base # Don't typecheck this file on Windows assert sys.platform != "win32" # nosec from fcntl import ioctl # noqa: E402 logger = logging.getLogger(__name__) USB_MAX_DEVNAMELEN = 16 USB_MAX_DEVNAMES = 4 USB_MAX_STRING_LEN = 128 USB_MAX_ENCODED_STRING_LEN = USB_MAX_STRING_LEN * 3 class usb_ctl_report_desc(Structure): _fields_ = [ ("ucrd_size", c_int), ("ucrd_data", c_ubyte * 1024), ] class usb_device_info(Structure): _fields_ = [ ("udi_bus", c_uint8), ("udi_addr", c_uint8), ("udi_pad0", c_uint8 * 2), ("udi_cookie", c_uint32), ("udi_product", c_char * USB_MAX_ENCODED_STRING_LEN), ("udi_vendor", c_char * USB_MAX_ENCODED_STRING_LEN), ("udi_release", c_char * 8), ("udi_serial", c_char * USB_MAX_ENCODED_STRING_LEN), ("udi_productNo", c_uint16), ("udi_vendorNo", c_uint16), ("udi_releaseNo", c_uint16), ("udi_class", c_uint8), ("udi_subclass", c_uint8), ("udi_protocol", c_uint8), ("udi_config", c_uint8), ("udi_speed", c_uint8), ("udi_pad1", c_uint8), ("udi_power", c_int), ("udi_nports", c_int), ("udi_devnames", c_char * USB_MAX_DEVNAMES * USB_MAX_DEVNAMELEN), ("udi_ports", c_uint8 * 16), ] USB_GET_DEVICE_INFO = 0x44F45570 # _IOR('U', 112, struct usb_device_info) USB_GET_REPORT_DESC = 0x44045515 # _IOR('U', 21, struct usb_ctl_report_desc) USB_HID_SET_RAW = 0x80046802 # _IOW('h', 2, int) # Cache for continuously failing devices # XXX not thread-safe _failed_cache: Set[str] = set() def list_descriptors(): stale = set(_failed_cache) descriptors = [] for i in range(100): path = "/dev/uhid%d" % (i,) stale.discard(path) try: desc = get_descriptor(path) except OSError as e: if e.errno == errno.ENOENT: break if path not in _failed_cache: logger.debug("Failed opening FIDO device %s", path, exc_info=True) _failed_cache.add(path) continue except Exception: if path not in _failed_cache: logger.debug("Failed opening FIDO device %s", path, exc_info=True) _failed_cache.add(path) continue descriptors.append(desc) _failed_cache.difference_update(stale) return descriptors def get_descriptor(path): fd = None try: fd = os.open(path, os.O_RDONLY | os.O_CLOEXEC) devinfo = usb_device_info() ioctl(fd, USB_GET_DEVICE_INFO, devinfo) ucrd = usb_ctl_report_desc() ioctl(fd, USB_GET_REPORT_DESC, ucrd) report_desc = bytearray(ucrd.ucrd_data[: ucrd.ucrd_size]) maxin, maxout = base.parse_report_descriptor(report_desc) vid = devinfo.udi_vendorNo pid = devinfo.udi_productNo try: name = devinfo.udi_product.decode("utf-8") except UnicodeDecodeError: name = None try: serial = devinfo.udi_serial.decode("utf-8") except UnicodeDecodeError: serial = None return base.HidDescriptor(path, vid, pid, maxin, maxout, name, serial) finally: if fd is not None: os.close(fd) def open_connection(descriptor): return NetBSDCtapHidConnection(descriptor) class NetBSDCtapHidConnection(base.FileCtapHidConnection): def __init__(self, descriptor): # XXX racy -- device can change identity now that it has been # closed super().__init__(descriptor) try: ioctl(self.handle, USB_HID_SET_RAW, struct.pack("@i", 1)) ping = bytearray(64) ping[0:7] = bytearray([0xFF, 0xFF, 0xFF, 0xFF, 0x81, 0, 1]) for i in range(10): self.write_packet(ping) poll = select.poll() poll.register(self.handle, select.POLLIN) if poll.poll(100): self.read_packet() break else: raise Exception("u2f ping timeout") except Exception: self.close() raise fido2-1.2.0/fido2/pcsc.py0000644000175000017500000002130714721556664014411 0ustar winniewinnie# Copyright (c) 2019 Yubico AB # Copyright (c) 2019 Oleg Moiseenko # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations from .ctap import CtapDevice, CtapError, STATUS from .hid import CAPABILITY, CTAPHID from .utils import LOG_LEVEL_TRAFFIC from smartcard import System from smartcard.CardConnection import CardConnection from smartcard.pcsc.PCSCExceptions import ListReadersException from smartcard.pcsc.PCSCContext import PCSCContext from threading import Event from typing import Tuple, Optional, Callable, Iterator import struct import logging AID_FIDO = b"\xa0\x00\x00\x06\x47\x2f\x00\x01" SW_SUCCESS = (0x90, 0x00) SW_UPDATE = (0x91, 0x00) SW1_MORE_DATA = 0x61 logger = logging.getLogger(__name__) class CtapPcscDevice(CtapDevice): """ CtapDevice implementation using pyscard (PCSC). This class is intended for use with NFC readers. """ def __init__(self, connection: CardConnection, name: str): self._name = name self._capabilities = CAPABILITY(0) self.use_ext_apdu = False self._conn = connection self.connect() try: # Probe for CTAP2 by calling GET_INFO self.call(CTAPHID.CBOR, b"\x04") self._capabilities |= CAPABILITY.CBOR except CtapError: if not self._capabilities: raise ValueError("Unsupported device") def connect(self): self._conn.connect() self._select() def __repr__(self): return f"CtapPcscDevice({self._name})" @property def version(self) -> int: """CTAPHID protocol version.""" return 2 if CAPABILITY.CBOR in self._capabilities else 1 @property def capabilities(self) -> CAPABILITY: """Capabilities supported by the device.""" return self._capabilities @property def product_name(self) -> Optional[str]: """Product name of device.""" return None @property def serial_number(self) -> Optional[int]: """Serial number of device.""" return None def get_atr(self) -> bytes: """Get the ATR/ATS of the connected card.""" return bytes(self._conn.getATR()) def apdu_exchange( self, apdu: bytes, protocol: Optional[int] = None ) -> Tuple[bytes, int, int]: """Exchange data with smart card. :param apdu: byte string. data to exchange with card :return: byte string. response from card """ logger.log(LOG_LEVEL_TRAFFIC, "SEND: %s", apdu.hex()) resp, sw1, sw2 = self._conn.transmit(list(apdu), protocol) response = bytes(resp) logger.log(LOG_LEVEL_TRAFFIC, "RECV: %s SW=%02X%02X", response.hex(), sw1, sw2) return response, sw1, sw2 def control_exchange(self, control_code: int, control_data: bytes = b"") -> bytes: """Sends control sequence to reader's driver. :param control_code: int. code to send to reader driver. :param control_data: byte string. data to send to driver :return: byte string. response """ logger.log(LOG_LEVEL_TRAFFIC, "Send control: %s", control_data.hex()) response = self._conn.control(control_code, list(control_data)) response = bytes(response) logger.log(LOG_LEVEL_TRAFFIC, "Control response: %s", response.hex()) return response def _select(self) -> None: apdu = b"\x00\xa4\x04\x00" + struct.pack("!B", len(AID_FIDO)) + AID_FIDO resp, sw1, sw2 = self._chained_apdu_exchange(apdu) if (sw1, sw2) != SW_SUCCESS: raise ValueError("FIDO applet selection failure.") if resp == b"U2F_V2": self._capabilities |= CAPABILITY.NMSG def _chain_apdus( self, cla: int, ins: int, p1: int, p2: int, data: bytes = b"" ) -> Tuple[bytes, int, int]: if self.use_ext_apdu: header = struct.pack("!BBBBBH", cla, ins, p1, p2, 0x00, len(data)) resp, sw1, sw2 = self.apdu_exchange(header + data) return resp, sw1, sw2 else: while len(data) > 250: to_send, data = data[:250], data[250:] header = struct.pack("!BBBBB", 0x10 | cla, ins, p1, p2, len(to_send)) resp, sw1, sw2 = self.apdu_exchange(header + to_send) if (sw1, sw2) != SW_SUCCESS: return resp, sw1, sw2 apdu = struct.pack("!BBBB", cla, ins, p1, p2) if data: apdu += struct.pack("!B", len(data)) + data resp, sw1, sw2 = self.apdu_exchange(apdu + b"\x00") while sw1 == SW1_MORE_DATA: apdu = b"\x00\xc0\x00\x00" + struct.pack("!B", sw2) # sw2 == le lres, sw1, sw2 = self.apdu_exchange(apdu) resp += lres return resp, sw1, sw2 def _chained_apdu_exchange(self, apdu: bytes) -> Tuple[bytes, int, int]: if len(apdu) >= 7 and apdu[4] == 0: # Extended APDU data_len = struct.unpack("!H", apdu[5:7])[0] data = apdu[7 : 7 + data_len] elif len(apdu) == 4: data = b"" else: # Short APDU data_len = apdu[4] data = apdu[5 : 5 + data_len] (cla, ins, p1, p2) = apdu[:4] return self._chain_apdus(cla, ins, p1, p2, data) def _call_apdu(self, apdu: bytes) -> bytes: resp, sw1, sw2 = self._chained_apdu_exchange(apdu) return resp + struct.pack("!BB", sw1, sw2) def _call_cbor( self, data: bytes = b"", event: Optional[Event] = None, on_keepalive: Optional[Callable[[STATUS], None]] = None, ) -> bytes: event = event or Event() # NFCCTAP_MSG resp, sw1, sw2 = self._chain_apdus(0x80, 0x10, 0x80, 0x00, data) last_ka = None while not event.is_set(): while (sw1, sw2) == SW_UPDATE: ka_status = STATUS(resp[0]) if on_keepalive and last_ka != ka_status: last_ka = ka_status on_keepalive(ka_status) # NFCCTAP_GETRESPONSE resp, sw1, sw2 = self._chain_apdus(0x80, 0x11, 0x00, 0x00) if (sw1, sw2) != SW_SUCCESS: raise CtapError(CtapError.ERR.OTHER) # TODO: Map from SW error return resp raise CtapError(CtapError.ERR.KEEPALIVE_CANCEL) def call( self, cmd: int, data: bytes = b"", event: Optional[Event] = None, on_keepalive: Optional[Callable[[STATUS], None]] = None, ) -> bytes: if cmd == CTAPHID.CBOR: return self._call_cbor(data, event, on_keepalive) elif cmd == CTAPHID.MSG: return self._call_apdu(data) else: raise CtapError(CtapError.ERR.INVALID_COMMAND) def close(self) -> None: self._conn.disconnect() @classmethod def list_devices(cls, name: str = "") -> Iterator[CtapPcscDevice]: for reader in _list_readers(): if name in reader.name: try: yield cls(reader.createConnection(), reader.name) except Exception as e: logger.debug("Error %r", e) def _list_readers(): try: return System.readers() except ListReadersException: # If the PCSC system has restarted the context might be stale, try # forcing a new context (This happens on Windows if the last reader is # removed): PCSCContext.instance = None return System.readers() fido2-1.2.0/fido2/rpid.py0000644000175000017500000000572314721556664014423 0ustar winniewinnie# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """ These functions validate RP_ID and APP_ID according to simplified TLD+1 rules, using a bundled copy of the public suffix list fetched from: https://publicsuffix.org/list/public_suffix_list.dat Advanced APP_ID values pointing to JSON files containing valid facets are not supported by this implementation. """ from __future__ import annotations import os from urllib.parse import urlparse tld_fname = os.path.join(os.path.dirname(__file__), "public_suffix_list.dat") with open(tld_fname, "rb") as f: suffixes = [ entry for entry in (line.decode("utf8").strip() for line in f.readlines()) if entry and not entry.startswith("//") ] def verify_rp_id(rp_id: str, origin: str) -> bool: """Checks if a Webauthn RP ID is usable for a given origin. :param rp_id: The RP ID to validate. :param origin: The origin of the request. :return: True if the RP ID is usable by the origin, False if not. """ if not rp_id: return False url = urlparse(origin) host = url.hostname # Note that Webauthn requires a secure context, i.e. an origin with https scheme. # However, most browsers also treat http://localhost as a secure context. See # https://groups.google.com/a/chromium.org/g/blink-dev/c/RC9dSw-O3fE/m/E3_0XaT0BAAJ if ( url.scheme != "https" and (url.scheme, host) != ("http", "localhost") and not (url.scheme == "http" and host and host.endswith(".localhost")) ): return False if host == rp_id: return True if host and host.endswith("." + rp_id) and rp_id not in suffixes: return True return False fido2-1.2.0/fido2/public_suffix_list.dat0000644000175000017500000115336014721556664017504 0ustar winniewinnie// 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. // VERSION: 2024-11-23_08-19-30_UTC // COMMIT: 931546b3beb45b544d0692aa116b420fb34b9dfa // Instructions on pulling and using this list can be found at https://publicsuffix.org/list/. // ===BEGIN ICANN DOMAINS=== // ac : http://nic.ac/rules.htm ac com.ac edu.ac gov.ac net.ac mil.ac org.ac // ad : https://www.iana.org/domains/root/db/ad.html // Confirmed by Amadeu Abril i Abril (CORE) 2024-11-17 ad // ae : https://tdra.gov.ae/en/aeda/ae-policies ae co.ae net.ae org.ae sch.ae ac.ae gov.ae mil.ae // aero : https://information.aero/registration/policies/dmp aero // 2LDs airline.aero airport.aero // 2LDs (currently not accepting registration, seemingly never have) // As of 2024-07, these are marked as reserved for potential 3LD // registrations (clause 11 "allocated subdomains" in the 2006 TLD // policy), but the relevant industry partners have not opened them up // for registration. Current status can be determined from the TLD's // policy document: 2LDs that are open for registration must list // their policy in the TLD's policy. Any 2LD without such a policy is // not open for registrations. accident-investigation.aero accident-prevention.aero aerobatic.aero aeroclub.aero aerodrome.aero agents.aero air-surveillance.aero air-traffic-control.aero aircraft.aero airtraffic.aero ambulance.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 marketplace.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 taxi.aero trader.aero trading.aero trainer.aero union.aero workinggroup.aero works.aero // af : https://www.nic.af/domain-price af com.af edu.af gov.af net.af org.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://www.iana.org/domains/root/db/ao.html // http://www.dns.ao/REGISTR.DOC ao ed.ao edu.ao gov.ao gv.ao og.ao org.ao co.ao pb.ao it.ao // aq : https://www.iana.org/domains/root/db/aq.html aq // ar : https://nic.ar/es/nic-argentina/normativa ar bet.ar com.ar coop.ar edu.ar gob.ar gov.ar int.ar mil.ar musica.ar mutual.ar net.ar org.ar senasa.ar tur.ar // arpa : https://www.iana.org/domains/root/db/arpa.html // Confirmed by registry 2008-06-18 arpa e164.arpa home.arpa in-addr.arpa ip6.arpa iris.arpa uri.arpa urn.arpa // as : https://www.iana.org/domains/root/db/as.html as gov.as // asia : https://www.iana.org/domains/root/db/asia.html asia // at : https://www.iana.org/domains/root/db/at.html // Confirmed by registry 2008-06-17 at ac.at co.at gv.at or.at sth.ac.at // au : https://www.iana.org/domains/root/db/au.html // http://www.auda.org.au/ // Confirmed by registry 2024-11-17 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) conf.au oz.au // CGDNs - http://www.cgdn.org.au/ act.au nsw.au nt.au qld.au sa.au tas.au vic.au wa.au // 3LDs act.edu.au catholic.edu.au // eq.edu.au - Removed at the request of the Queensland Department of Education nsw.edu.au nt.edu.au qld.edu.au sa.edu.au tas.edu.au vic.edu.au wa.edu.au // act.gov.au Bug 984824 - Removed at request of Greg Tankard // nsw.gov.au Bug 547985 - Removed at request of // nt.gov.au Bug 940478 - Removed at request of Greg Connors qld.gov.au sa.gov.au tas.gov.au vic.gov.au wa.gov.au // 4LDs // education.tas.edu.au - Removed at the request of the Department of Education Tasmania schools.nsw.edu.au // aw : https://www.iana.org/domains/root/db/aw.html aw com.aw // ax : https://www.iana.org/domains/root/db/ax.html ax // az : https://www.iana.org/domains/root/db/az.html // https://whois.az/?page_id=10 az biz.az com.az edu.az gov.az info.az int.az mil.az name.az net.az org.az pp.az pro.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://www.iana.org/domains/root/db/bb.html bb biz.bb co.bb com.bb edu.bb gov.bb info.bb net.bb org.bb store.bb tv.bb // bd : https://www.iana.org/domains/root/db/bd.html *.bd // be : https://www.iana.org/domains/root/db/be.html // Confirmed by registry 2008-06-08 be ac.be // bf : https://www.iana.org/domains/root/db/bf.html bf gov.bf // bg : https://www.iana.org/domains/root/db/bg.html // 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://www.iana.org/domains/root/db/bh.html bh com.bh edu.bh net.bh org.bh gov.bh // bi : https://www.iana.org/domains/root/db/bi.html // http://whois.nic.bi/ bi co.bi com.bi edu.bi or.bi org.bi // biz : https://www.iana.org/domains/root/db/biz.html biz // bj : https://nic.bj/bj-suffixes.txt // submitted by registry bj africa.bj agro.bj architectes.bj assur.bj avocats.bj co.bj com.bj eco.bj econo.bj edu.bj info.bj loisirs.bj money.bj net.bj org.bj ote.bj resto.bj restaurant.bj tourism.bj univ.bj // bm : http://www.bermudanic.bm/dnr-text.txt bm com.bm edu.bm gov.bm net.bm org.bm // bn : http://www.bnnic.bn/faqs bn com.bn edu.bn gov.bn net.bn org.bn // bo : https://nic.bo // Confirmed by registry 2024-11-19 bo com.bo edu.bo gob.bo int.bo mil.bo net.bo org.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 plurinacional.bo politica.bo profesional.bo pueblo.bo revista.bo salud.bo tecnologia.bo tksat.bo transporte.bo wiki.bo // br : http://registro.br/dominio/categoria.html // Submitted by registry br 9guacu.br abc.br adm.br adv.br agr.br aju.br am.br anani.br aparecida.br app.br arq.br art.br ato.br b.br barueri.br belem.br bet.br bhz.br bib.br bio.br blog.br bmd.br boavista.br bsb.br campinagrande.br campinas.br caxias.br cim.br cng.br cnt.br com.br contagem.br coop.br coz.br cri.br cuiaba.br curitiba.br def.br des.br det.br dev.br ecn.br eco.br edu.br emp.br enf.br eng.br esp.br etc.br eti.br far.br feira.br flog.br floripa.br fm.br fnd.br fortal.br fot.br foz.br fst.br g12.br geo.br ggf.br goiania.br gov.br // gov.br 26 states + df https://en.wikipedia.org/wiki/States_of_Brazil ac.gov.br al.gov.br am.gov.br ap.gov.br ba.gov.br ce.gov.br df.gov.br es.gov.br go.gov.br ma.gov.br mg.gov.br ms.gov.br mt.gov.br pa.gov.br pb.gov.br pe.gov.br pi.gov.br pr.gov.br rj.gov.br rn.gov.br ro.gov.br rr.gov.br rs.gov.br sc.gov.br se.gov.br sp.gov.br to.gov.br gru.br imb.br ind.br inf.br jab.br jampa.br jdf.br joinville.br jor.br jus.br leg.br leilao.br lel.br log.br londrina.br macapa.br maceio.br manaus.br maringa.br mat.br med.br mil.br morena.br mp.br mus.br natal.br net.br niteroi.br *.nom.br not.br ntr.br odo.br ong.br org.br osasco.br palmas.br poa.br ppg.br pro.br psc.br psi.br pvh.br qsl.br radio.br rec.br recife.br rep.br ribeirao.br rio.br riobranco.br riopreto.br salvador.br sampa.br santamaria.br santoandre.br saobernardo.br saogonca.br seg.br sjc.br slg.br slz.br sorocaba.br srv.br taxi.br tc.br tec.br teo.br the.br tmp.br trd.br tur.br tv.br udi.br vet.br vix.br vlog.br wiki.br zlg.br // bs : http://www.nic.bs/rules.html bs com.bs net.bs org.bs edu.bs gov.bs // bt : https://www.iana.org/domains/root/db/bt.html bt com.bt edu.bt gov.bt net.bt org.bt // bv : No registrations at this time. // Submitted by registry bv // bw : https://www.iana.org/domains/root/db/bw.html // http://www.gobin.info/domainname/bw.doc // list of other 2nd level tlds ? bw co.bw org.bw // by : https://www.iana.org/domains/root/db/by.html // 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://www.iana.org/domains/root/db/bz.html // http://www.belizenic.bz/ bz co.bz com.bz net.bz org.bz edu.bz gov.bz // ca : https://www.iana.org/domains/root/db/ca.html 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://www.iana.org/domains/root/db/cat.html cat // cc : https://www.iana.org/domains/root/db/cc.html cc // cd : https://www.iana.org/domains/root/db/cd.html // see also: https://www.nic.cd/domain/insertDomain_2.jsp?act=1 cd gov.cd // cf : https://www.iana.org/domains/root/db/cf.html cf // cg : https://www.iana.org/domains/root/db/cg.html cg // ch : https://www.iana.org/domains/root/db/ch.html ch // ci : https://www.iana.org/domains/root/db/ci.html // 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 gouv.ci // ck : https://www.iana.org/domains/root/db/ck.html *.ck !www.ck // cl : https://www.nic.cl // Confirmed by .CL registry cl co.cl gob.cl gov.cl mil.cl // cm : https://www.iana.org/domains/root/db/cm.html plus bug 981927 cm co.cm com.cm gov.cm net.cm // cn : https://www.iana.org/domains/root/db/cn.html // 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://www.iana.org/domains/root/db/co.html // https://www.cointernet.com.co/registra // https://www.cointernet.com.co/como-funciona-un-dominio-restringido // Confirmed by registry 2024-11-18 co com.co edu.co gov.co mil.co net.co nom.co org.co // com : https://www.iana.org/domains/root/db/com.html com // coop : https://www.iana.org/domains/root/db/coop.html 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://www.iana.org/domains/root/db/cu.html cu com.cu edu.cu gob.cu inf.cu nat.cu net.cu org.cu // cv : https://www.iana.org/domains/root/db/cv.html // cv : http://www.dns.cv/tldcv_portal/do?com=DS;5446457100;111;+PAGE(4000018)+K-CAT-CODIGO(RDOM)+RCNT(100); <- registration rules cv com.cv edu.cv int.cv nome.cv org.cv // cw : https://www.uoc.cw/cw-registry // Confirmed by registry 2024-11-19 cw com.cw edu.cw net.cw org.cw // cx : https://www.iana.org/domains/root/db/cx.html // list of other 2nd level tlds ? cx gov.cx // cy : http://www.nic.cy/ // Submitted by registry Panayiotou Fotia // namespace policies URL https://www.nic.cy/portal//sites/default/files/symfonia_gia_eggrafi.pdf cy ac.cy biz.cy com.cy ekloges.cy gov.cy ltd.cy mil.cy net.cy org.cy press.cy pro.cy tm.cy // cz : https://www.iana.org/domains/root/db/cz.html cz // de : https://www.iana.org/domains/root/db/de.html // Confirmed by registry (with technical // reservations) 2008-07-01 de // dj : https://www.iana.org/domains/root/db/dj.html dj // dk : https://www.iana.org/domains/root/db/dk.html // Confirmed by registry 2008-06-17 dk // dm : https://www.iana.org/domains/root/db/dm.html // https://nic.dm/policies/pdf/DMRulesandGuidelines2024v1.pdf // Confirmed by registry 2024-11-19 dm co.dm com.dm edu.dm gov.dm net.dm org.dm // do : https://www.iana.org/domains/root/db/do.html do art.do com.do edu.do gob.do gov.do mil.do net.do org.do sld.do web.do // dz : http://www.nic.dz/images/pdf_nic/charte.pdf dz art.dz asso.dz com.dz edu.dz gov.dz org.dz net.dz pol.dz soc.dz tm.dz // ec : http://www.nic.ec/reg/paso1.asp // Submitted by registry ec com.ec info.ec net.ec fin.ec k12.ec med.ec pro.ec org.ec edu.ec gov.ec gob.ec mil.ec // edu : https://www.iana.org/domains/root/db/edu.html 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://www.iana.org/domains/root/db/eg.html eg com.eg edu.eg eun.eg gov.eg mil.eg name.eg net.eg org.eg sci.eg // er : https://www.iana.org/domains/root/db/er.html *.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://www.iana.org/domains/root/db/et.html et com.et gov.et org.et edu.et biz.et name.et info.et net.et // eu : https://www.iana.org/domains/root/db/eu.html eu // fi : https://www.iana.org/domains/root/db/fi.html fi // aland.fi : https://www.iana.org/domains/root/db/ax.html // 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. aland.fi // fj : http://domains.fj/ // Submitted by registry 2020-02-11 fj ac.fj biz.fj com.fj gov.fj info.fj mil.fj name.fj net.fj org.fj pro.fj // fk : https://www.iana.org/domains/root/db/fk.html *.fk // fm : https://www.iana.org/domains/root/db/fm.html com.fm edu.fm net.fm org.fm fm // fo : https://www.iana.org/domains/root/db/fo.html fo // fr : https://www.afnic.fr/ https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf fr asso.fr com.fr gouv.fr nom.fr prd.fr tm.fr // Other SLDs now selfmanaged out of AFNIC range. Former "domaines sectoriels", still registration suffixes avoues.fr cci.fr greta.fr huissier-justice.fr // ga : https://www.iana.org/domains/root/db/ga.html ga // gb : This registry is effectively dormant // Submitted by registry gb // gd : https://www.iana.org/domains/root/db/gd.html edu.gd gov.gd gd // ge : https://nic.ge/en/administrator/the-ge-domain-regulations // Confirmed by registry 2024-11-20 ge com.ge edu.ge gov.ge net.ge org.ge pvt.ge school.ge // gf : https://www.iana.org/domains/root/db/gf.html gf // gg : http://www.channelisles.net/register-domains/ // Confirmed by registry 2013-11-28 gg co.gg net.gg org.gg // gh : https://www.iana.org/domains/root/db/gh.html // 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://www.iana.org/domains/root/db/gl.html // 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://www.iana.org/domains/root/db/gov.html 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://www.iana.org/domains/root/db/gq.html 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://www.iana.org/domains/root/db/gs.html gs // gt : https://www.gt/sitio/registration_policy.php?lang=en gt com.gt edu.gt gob.gt ind.gt mil.gt net.gt org.gt // gu : http://gadao.gov.gu/register.html // University of Guam : https://www.uog.edu // Submitted by uognoc@triton.uog.edu gu com.gu edu.gu gov.gu guam.gu info.gu net.gu org.gu web.gu // gw : https://www.iana.org/domains/root/db/gw.html // gw : https://nic.gw/regras/ gw // gy : https://www.iana.org/domains/root/db/gy.html // 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://www.iana.org/domains/root/db/hm.html 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://www.iana.org/domains/root/db/ie.html ie gov.ie // il : http://www.isoc.org.il/domains/ // see also: https://en.isoc.org.il/il-cctld/registration-rules // ISOC-IL (operated by .il Registry) il ac.il co.il gov.il idf.il k12.il muni.il net.il org.il // xn--4dbrk0ce ("Israel", Hebrew) : IL ישראל // xn--4dbgdty6c.xn--4dbrk0ce. אקדמיה.ישראל // xn--5dbhl8d.xn--4dbrk0ce. ישוב.ישראל // xn--8dbq2a.xn--4dbrk0ce. צהל.ישראל // xn--hebda8b.xn--4dbrk0ce. ממשל.ישראל // im : https://www.nic.im/ // Submitted by registry im ac.im co.im com.im ltd.co.im net.im org.im plc.co.im tt.im tv.im // in : https://www.iana.org/domains/root/db/in.html // see also: https://registry.in/policies // Please note, that nic.in is not an official eTLD, but used by most // government institutions. in 5g.in 6g.in ac.in ai.in am.in bihar.in biz.in business.in ca.in cn.in co.in com.in coop.in cs.in delhi.in dr.in edu.in er.in firm.in gen.in gov.in gujarat.in ind.in info.in int.in internet.in io.in me.in mil.in net.in nic.in org.in pg.in post.in pro.in res.in travel.in tv.in uk.in up.in us.in // info : https://www.iana.org/domains/root/db/info.html info // int : https://www.iana.org/domains/root/db/int.html // Confirmed by registry 2008-06-18 int eu.int // io : http://www.nic.io/rules.htm io co.io com.io edu.io gov.io mil.io net.io nom.io org.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 2024-11-17 is // it : https://www.iana.org/domains/root/db/it.html 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 : https://www.dns.jo/JoFamily.aspx // Confirmed by registry 2024-11-17 jo agri.jo ai.jo com.jo edu.jo eng.jo fm.jo gov.jo mil.jo net.jo org.jo per.jo phd.jo sch.jo tv.jo // jobs : https://www.iana.org/domains/root/db/jobs.html jobs // jp : https://www.iana.org/domains/root/db/jp.html // 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 !city.kawasaki.jp *.kitakyushu.jp !city.kitakyushu.jp *.kobe.jp !city.kobe.jp *.nagoya.jp !city.nagoya.jp *.sapporo.jp !city.sapporo.jp *.sendai.jp !city.sendai.jp *.yokohama.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://www.iana.org/domains/root/db/km.html // 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://www.iana.org/domains/root/db/km.html says they're available for registration: coop.km asso.km presse.km medecin.km notaires.km pharmaciens.km veterinaire.km gouv.km // kn : https://www.iana.org/domains/root/db/kn.html // 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://www.iana.org/domains/root/db/kr.html // see also: http://domain.nida.or.kr/eng/registration.jsp kr ac.kr co.kr es.kr go.kr hs.kr kg.kr mil.kr ms.kr ne.kr or.kr pe.kr re.kr sc.kr // kr geographical names busan.kr chungbuk.kr chungnam.kr daegu.kr daejeon.kr gangwon.kr gwangju.kr gyeongbuk.kr gyeonggi.kr gyeongnam.kr incheon.kr jeju.kr jeonbuk.kr jeonnam.kr seoul.kr ulsan.kr // kw : https://www.nic.kw/policies/ // Confirmed by registry kw com.kw edu.kw emb.kw gov.kw ind.kw net.kw org.kw // ky : http://www.icta.ky/da_ky_reg_dom.php // Confirmed by registry 2008-06-17 ky com.ky edu.ky net.ky org.ky // kz : https://www.iana.org/domains/root/db/kz.html // see also: http://www.nic.kz/rules/index.jsp kz org.kz edu.kz net.kz gov.kz mil.kz com.kz // la : https://www.iana.org/domains/root/db/la.html // Submitted by registry la int.la net.la info.la edu.la gov.la per.la com.la org.la // lb : https://www.iana.org/domains/root/db/lb.html // Submitted by registry lb com.lb edu.lb gov.lb net.lb org.lb // lc : https://www.iana.org/domains/root/db/lc.html // see also: http://www.nic.lc/rules.htm lc com.lc net.lc co.lc org.lc edu.lc gov.lc // li : https://www.iana.org/domains/root/db/li.html li // lk : https://www.nic.lk/index.php/domain-registration/lk-domain-naming-structure lk gov.lk sch.lk net.lk int.lk com.lk org.lk edu.lk ngo.lk soc.lk web.lk ltd.lk assn.lk grp.lk hotel.lk ac.lk // lr : http://psg.com/dns/lr/lr.txt // Submitted by registry lr com.lr edu.lr gov.lr org.lr net.lr // ls : http://www.nic.ls/ // Confirmed by registry ls ac.ls biz.ls co.ls edu.ls gov.ls info.ls net.ls org.ls sc.ls // lt : https://www.iana.org/domains/root/db/lt.html 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://www.iana.org/domains/root/db/ma.html // 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://www.iana.org/domains/root/db/md.html md // me : https://www.iana.org/domains/root/db/me.html me co.me net.me org.me edu.me ac.me gov.me its.me priv.me // mg : https://nic.mg mg co.mg com.mg edu.mg gov.mg mil.mg nom.mg org.mg prd.mg // mh : https://www.iana.org/domains/root/db/mh.html mh // mil : https://www.iana.org/domains/root/db/mil.html mil // mk : https://www.iana.org/domains/root/db/mk.html // 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://www.iana.org/domains/root/db/ml.html ml com.ml edu.ml gouv.ml gov.ml net.ml org.ml presse.ml // mm : https://www.iana.org/domains/root/db/mm.html *.mm // mn : https://www.iana.org/domains/root/db/mn.html 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://www.iana.org/domains/root/db/mobi.html mobi // mp : http://www.dot.mp/ // Confirmed by registry 2008-06-17 mp // mq : https://www.iana.org/domains/root/db/mq.html mq // mr : https://www.iana.org/domains/root/db/mr.html 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://www.iana.org/domains/root/db/mu.html mu com.mu net.mu org.mu gov.mu ac.mu co.mu or.mu // museum : https://welcome.museum/wp-content/uploads/2018/05/20180525-Registration-Policy-MUSEUM-EN_VF-2.pdf https://welcome.museum/buy-your-dot-museum-2/ museum // mv : https://www.iana.org/domains/root/db/mv.html // "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 net.mw org.mw // mx : http://www.nic.mx/ // Submitted by registry mx com.mx org.mx gob.mx edu.mx net.mx // my : http://www.mynic.my/ // Available strings: https://mynic.my/resources/domains/buying-a-domain/ my biz.my com.my edu.my gov.my mil.my name.my net.my org.my // mz : http://www.uem.mz/ // Submitted by registry mz ac.mz adv.mz co.mz edu.mz gov.mz mil.mz net.mz org.mz // na : http://www.na-nic.com.na/ na alt.na co.na com.na gov.na net.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://www.iana.org/domains/root/db/ne.html ne // net : https://www.iana.org/domains/root/db/net.html net // nf : https://www.iana.org/domains/root/db/nf.html 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://www.iana.org/domains/root/db/nl.html // https://www.sidn.nl/ // ccTLD for the Netherlands nl // no : https://www.norid.no/en/om-domenenavn/regelverk-for-no/ // Norid geographical second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-b/ // Norid category second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-c/ // Norid category second-level domains managed by parties other than Norid : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-d/ // RSS feed: https://teknisk.norid.no/en/feed/ no // Norid category second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-c/ fhs.no vgs.no fylkesbibl.no folkebibl.no museum.no idrett.no priv.no // Norid category second-level domains managed by parties other than Norid : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-d/ mil.no stat.no dep.no kommune.no herad.no // Norid geographical second level domains : https://www.norid.no/en/om-domenenavn/regelverk-for-no/vedlegg-b/ // counties aa.no ah.no bu.no fm.no hl.no hm.no jan-mayen.no mr.no nl.no nt.no of.no ol.no oslo.no rl.no sf.no st.no svalbard.no tm.no tr.no va.no vf.no // primary and lower secondary schools per county gs.aa.no gs.ah.no gs.bu.no gs.fm.no gs.hl.no gs.hm.no gs.jan-mayen.no gs.mr.no gs.nl.no gs.nt.no gs.of.no gs.ol.no gs.oslo.no gs.rl.no gs.sf.no gs.st.no gs.svalbard.no gs.tm.no gs.tr.no gs.va.no gs.vf.no // cities akrehamn.no åkrehamn.no algard.no ålgård.no arna.no brumunddal.no bryne.no bronnoysund.no brønnøysund.no drobak.no drøbak.no egersund.no fetsund.no floro.no florø.no fredrikstad.no hokksund.no honefoss.no hønefoss.no jessheim.no jorpeland.no jørpeland.no kirkenes.no kopervik.no krokstadelva.no langevag.no langevåg.no leirvik.no mjondalen.no mjøndalen.no mo-i-rana.no mosjoen.no mosjøen.no nesoddtangen.no orkanger.no osoyro.no osøyro.no raholt.no råholt.no sandnessjoen.no sandnessjøen.no skedsmokorset.no slattum.no spjelkavik.no stathelle.no stavern.no stjordalshalsen.no stjørdalshalsen.no tananger.no tranby.no vossevangen.no // communities afjord.no åfjord.no agdenes.no al.no ål.no alesund.no ålesund.no alstahaug.no alta.no áltá.no alaheadju.no álaheadju.no alvdal.no amli.no åmli.no amot.no åmot.no andebu.no andoy.no andøy.no andasuolo.no ardal.no årdal.no aremark.no arendal.no ås.no aseral.no åseral.no asker.no askim.no askvoll.no askoy.no askøy.no asnes.no åsnes.no audnedaln.no aukra.no aure.no aurland.no aurskog-holand.no aurskog-høland.no austevoll.no austrheim.no averoy.no averøy.no balestrand.no ballangen.no balat.no bálát.no balsfjord.no bahccavuotna.no báhccavuotna.no bamble.no bardu.no beardu.no beiarn.no bajddar.no bájddar.no baidar.no báidár.no berg.no bergen.no berlevag.no berlevåg.no bearalvahki.no bearalváhki.no bindal.no birkenes.no bjarkoy.no bjarkøy.no bjerkreim.no bjugn.no bodo.no bodø.no badaddja.no bådåddjå.no budejju.no bokn.no bremanger.no bronnoy.no brønnøy.no bygland.no bykle.no barum.no bærum.no bo.telemark.no bø.telemark.no bo.nordland.no bø.nordland.no bievat.no bievát.no bomlo.no bømlo.no batsfjord.no båtsfjord.no bahcavuotna.no báhcavuotna.no dovre.no drammen.no drangedal.no dyroy.no dyrøy.no donna.no dønna.no eid.no eidfjord.no eidsberg.no eidskog.no eidsvoll.no eigersund.no elverum.no enebakk.no engerdal.no etne.no etnedal.no evenes.no evenassi.no evenášši.no evje-og-hornnes.no farsund.no fauske.no fuossko.no fuoisku.no fedje.no fet.no finnoy.no finnøy.no fitjar.no fjaler.no fjell.no flakstad.no flatanger.no flekkefjord.no flesberg.no flora.no fla.no flå.no folldal.no forsand.no fosnes.no frei.no frogn.no froland.no frosta.no frana.no fræna.no froya.no frøya.no fusa.no fyresdal.no forde.no førde.no gamvik.no gangaviika.no gáŋgaviika.no gaular.no gausdal.no gildeskal.no gildeskål.no giske.no gjemnes.no gjerdrum.no gjerstad.no gjesdal.no gjovik.no gjøvik.no gloppen.no gol.no gran.no grane.no granvin.no gratangen.no grimstad.no grong.no kraanghke.no kråanghke.no grue.no gulen.no hadsel.no halden.no halsa.no hamar.no hamaroy.no habmer.no hábmer.no hapmir.no hápmir.no hammerfest.no hammarfeasta.no hámmárfeasta.no haram.no hareid.no harstad.no hasvik.no aknoluokta.no ákŋoluokta.no hattfjelldal.no aarborte.no haugesund.no hemne.no hemnes.no hemsedal.no heroy.more-og-romsdal.no herøy.møre-og-romsdal.no heroy.nordland.no herøy.nordland.no hitra.no hjartdal.no hjelmeland.no hobol.no hobøl.no hof.no hol.no hole.no holmestrand.no holtalen.no holtålen.no hornindal.no horten.no hurdal.no hurum.no hvaler.no hyllestad.no hagebostad.no hægebostad.no hoyanger.no høyanger.no hoylandet.no høylandet.no ha.no hå.no ibestad.no inderoy.no inderøy.no iveland.no jevnaker.no jondal.no jolster.no jølster.no karasjok.no karasjohka.no kárášjohka.no karlsoy.no galsa.no gálsá.no karmoy.no karmøy.no kautokeino.no guovdageaidnu.no klepp.no klabu.no klæbu.no kongsberg.no kongsvinger.no kragero.no kragerø.no kristiansand.no kristiansund.no krodsherad.no krødsherad.no kvalsund.no rahkkeravju.no ráhkkerávju.no kvam.no kvinesdal.no kvinnherad.no kviteseid.no kvitsoy.no kvitsøy.no kvafjord.no kvæfjord.no giehtavuoatna.no kvanangen.no kvænangen.no navuotna.no návuotna.no kafjord.no kåfjord.no gaivuotna.no gáivuotna.no larvik.no lavangen.no lavagis.no loabat.no loabát.no lebesby.no davvesiida.no leikanger.no leirfjord.no leka.no leksvik.no lenvik.no leangaviika.no leaŋgaviika.no lesja.no levanger.no lier.no lierne.no lillehammer.no lillesand.no lindesnes.no lindas.no lindås.no lom.no loppa.no lahppi.no láhppi.no lund.no lunner.no luroy.no lurøy.no luster.no lyngdal.no lyngen.no ivgu.no lardal.no lerdal.no lærdal.no lodingen.no lødingen.no lorenskog.no lørenskog.no loten.no løten.no malvik.no masoy.no måsøy.no muosat.no muosát.no mandal.no marker.no marnardal.no masfjorden.no meland.no meldal.no melhus.no meloy.no meløy.no meraker.no meråker.no moareke.no moåreke.no midsund.no midtre-gauldal.no modalen.no modum.no molde.no moskenes.no moss.no mosvik.no malselv.no målselv.no malatvuopmi.no málatvuopmi.no namdalseid.no aejrie.no namsos.no namsskogan.no naamesjevuemie.no nååmesjevuemie.no laakesvuemie.no nannestad.no narvik.no narviika.no naustdal.no nedre-eiker.no nes.akershus.no nes.buskerud.no nesna.no nesodden.no nesseby.no unjarga.no unjárga.no nesset.no nissedal.no nittedal.no nord-aurdal.no nord-fron.no nord-odal.no norddal.no nordkapp.no davvenjarga.no davvenjárga.no nordre-land.no nordreisa.no raisa.no ráisa.no nore-og-uvdal.no notodden.no naroy.no nærøy.no notteroy.no nøtterøy.no odda.no oksnes.no øksnes.no oppdal.no oppegard.no oppegård.no orkdal.no orland.no ørland.no orskog.no ørskog.no orsta.no ørsta.no os.hedmark.no os.hordaland.no osen.no osteroy.no osterøy.no ostre-toten.no østre-toten.no overhalla.no ovre-eiker.no øvre-eiker.no oyer.no øyer.no oygarden.no øygarden.no oystre-slidre.no øystre-slidre.no porsanger.no porsangu.no porsáŋgu.no porsgrunn.no radoy.no radøy.no rakkestad.no rana.no ruovat.no randaberg.no rauma.no rendalen.no rennebu.no rennesoy.no rennesøy.no rindal.no ringebu.no ringerike.no ringsaker.no rissa.no risor.no risør.no roan.no rollag.no rygge.no ralingen.no rælingen.no rodoy.no rødøy.no romskog.no rømskog.no roros.no røros.no rost.no røst.no royken.no røyken.no royrvik.no røyrvik.no rade.no råde.no salangen.no siellak.no saltdal.no salat.no sálát.no sálat.no samnanger.no sande.more-og-romsdal.no sande.møre-og-romsdal.no sande.vestfold.no sandefjord.no sandnes.no sandoy.no sandøy.no sarpsborg.no sauda.no sauherad.no sel.no selbu.no selje.no seljord.no sigdal.no siljan.no sirdal.no skaun.no skedsmo.no ski.no skien.no skiptvet.no skjervoy.no skjervøy.no skierva.no skiervá.no skjak.no skjåk.no skodje.no skanland.no skånland.no skanit.no skánit.no smola.no smøla.no snillfjord.no snasa.no snåsa.no snoasa.no snaase.no snåase.no sogndal.no sokndal.no sola.no solund.no songdalen.no sortland.no spydeberg.no stange.no stavanger.no steigen.no steinkjer.no stjordal.no stjørdal.no stokke.no stor-elvdal.no stord.no stordal.no storfjord.no omasvuotna.no strand.no stranda.no stryn.no sula.no suldal.no sund.no sunndal.no surnadal.no sveio.no svelvik.no sykkylven.no sogne.no søgne.no somna.no sømna.no sondre-land.no søndre-land.no sor-aurdal.no sør-aurdal.no sor-fron.no sør-fron.no sor-odal.no sør-odal.no sor-varanger.no sør-varanger.no matta-varjjat.no mátta-várjjat.no sorfold.no sørfold.no sorreisa.no sørreisa.no sorum.no sørum.no tana.no deatnu.no time.no tingvoll.no tinn.no tjeldsund.no dielddanuorri.no tjome.no tjøme.no tokke.no tolga.no torsken.no tranoy.no tranøy.no tromso.no tromsø.no tromsa.no romsa.no trondheim.no troandin.no trysil.no trana.no træna.no trogstad.no trøgstad.no tvedestrand.no tydal.no tynset.no tysfjord.no divtasvuodna.no divttasvuotna.no tysnes.no tysvar.no tysvær.no tonsberg.no tønsberg.no ullensaker.no ullensvang.no ulvik.no utsira.no vadso.no vadsø.no cahcesuolo.no čáhcesuolo.no vaksdal.no valle.no vang.no vanylven.no vardo.no vardø.no varggat.no várggát.no vefsn.no vaapste.no vega.no vegarshei.no vegårshei.no vennesla.no verdal.no verran.no vestby.no vestnes.no vestre-slidre.no vestre-toten.no vestvagoy.no vestvågøy.no vevelstad.no vik.no vikna.no vindafjord.no volda.no voss.no varoy.no værøy.no vagan.no vågan.no voagat.no vagsoy.no vågsøy.no vaga.no vågå.no valer.ostfold.no våler.østfold.no valer.hedmark.no våler.hedmark.no // np : http://www.mos.com.np/register.html *.np // nr : http://cenpac.net.nr/dns/index.html // Submitted by registry nr biz.nr info.nr gov.nr edu.nr org.nr net.nr com.nr // nu : https://www.iana.org/domains/root/db/nu.html nu // nz : https://www.iana.org/domains/root/db/nz.html // 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://www.iana.org/domains/root/db/om.html 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://www.iana.org/domains/root/db/org.html 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://www.iana.org/domains/root/db/pg.html *.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 : https://pknic.net.pk // pk : http://pk5.pknic.net.pk/pk5/msgNamepk.PK + grandfathered old gon.pk // Contact Email: staff@pknic.net.pk PKNIC .PK Registry pk ac.pk biz.pk com.pk edu.pk fam.pk gkp.pk gob.pk gog.pk gok.pk gon.pk gop.pk gos.pk gov.pk net.pk org.pk web.pk // pl : https://www.dns.pl/en/ // Confirmed by registry 2024-11-18 pl com.pl net.pl org.pl // pl functional domains : https://www.dns.pl/en/list_of_functional_domain_names agro.pl aid.pl atm.pl auto.pl biz.pl edu.pl gmina.pl gsm.pl info.pl mail.pl media.pl miasta.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 : https://www.dns.pl/informacje_o_rejestracji_domen_gov_pl // In accordance with the .gov.pl Domain Name Regulations : https://www.dns.pl/regulamin_gov_pl gov.pl ap.gov.pl griw.gov.pl ic.gov.pl is.gov.pl kmpsp.gov.pl konsulat.gov.pl kppsp.gov.pl kwp.gov.pl kwpsp.gov.pl mup.gov.pl mw.gov.pl oia.gov.pl oirm.gov.pl oke.gov.pl oow.gov.pl oschr.gov.pl oum.gov.pl pa.gov.pl pinb.gov.pl piw.gov.pl po.gov.pl pr.gov.pl psp.gov.pl psse.gov.pl pup.gov.pl rzgw.gov.pl sa.gov.pl sdn.gov.pl sko.gov.pl so.gov.pl sr.gov.pl starostwo.gov.pl ug.gov.pl ugim.gov.pl um.gov.pl umig.gov.pl upow.gov.pl uppo.gov.pl us.gov.pl uw.gov.pl uzs.gov.pl wif.gov.pl wiih.gov.pl winb.gov.pl wios.gov.pl witd.gov.pl wiw.gov.pl wkz.gov.pl wsa.gov.pl wskr.gov.pl wsse.gov.pl wuoz.gov.pl wzmiuw.gov.pl zp.gov.pl zpisdn.gov.pl // pl regional domains : https://www.dns.pl/en/list_of_regional_domain_names 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 karpacz.pl kartuzy.pl kaszuby.pl katowice.pl kazimierz-dolny.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 pomorskie.pl pomorze.pl prochowice.pl pruszkow.pl przeworsk.pl pulawy.pl radom.pl rawa-maz.pl rybnik.pl rzeszow.pl sanok.pl sejny.pl skoczow.pl slask.pl slupsk.pl sosnowiec.pl stalowa-wola.pl starachowice.pl stargard.pl suwalki.pl swidnica.pl swiebodzin.pl swinoujscie.pl szczecin.pl szczytno.pl tarnobrzeg.pl tgory.pl turek.pl tychy.pl ustka.pl walbrzych.pl warmia.pl warszawa.pl waw.pl wegrow.pl wielun.pl wlocl.pl wloclawek.pl wodzislaw.pl wolomin.pl wroclaw.pl zachpomor.pl zagan.pl zarow.pl zgora.pl zgorzelec.pl // pm : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf pm // pn : http://www.government.pn/PnRegistry/policies.htm pn gov.pn co.pn org.pn edu.pn net.pn // post : https://www.iana.org/domains/root/db/post.html 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://www.iana.org/domains/root/db/pr.html 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://www.iana.org/domains/root/db/ps.html // http://www.nic.ps/registration/policy.html#reg ps edu.ps gov.ps sec.ps plo.ps com.ps org.ps net.ps // pt : https://www.dns.pt/en/domain/pt-terms-and-conditions-registration-rules/ pt net.pt gov.pt org.pt edu.pt int.pt publ.pt com.pt nome.pt // pw : https://www.iana.org/domains/root/db/pw.html pw co.pw or.pw ed.pw go.pw belau.pw // py : http://www.nic.py/pautas.html#seccion_9 // Submitted by registry py com.py coop.py edu.py gov.py mil.py net.py org.py // qa : http://domains.qa/en/ qa com.qa edu.qa gov.qa mil.qa name.qa net.qa org.qa sch.qa // re : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf // Confirmed by registry 2024-11-18 re // Closed for registration on 2013-03-15 but domains are still maintained asso.re com.re // ro : http://www.rotld.ro/ ro arts.ro com.ro firm.ro info.ro nom.ro nt.ro org.ro rec.ro store.ro tm.ro www.ro // rs : https://www.rnids.rs/en/domains/national-domains rs ac.rs co.rs edu.rs gov.rs in.rs org.rs // ru : https://cctld.ru/files/pdf/docs/en/rules_ru-rf.pdf // Submitted by George Georgievsky ru // rw : https://www.ricta.org.rw/sites/default/files/resources/registry_registrar_contract_0.pdf rw ac.rw co.rw coop.rw gov.rw mil.rw net.rw org.rw // sa : http://www.nic.net.sa/ sa com.sa net.sa org.sa gov.sa med.sa pub.sa edu.sa sch.sa // sb : http://www.sbnic.net.sb/ // Submitted by registry sb com.sb edu.sb gov.sb net.sb org.sb // sc : http://www.nic.sc/ sc com.sc gov.sc net.sc org.sc edu.sc // sd : http://www.isoc.sd/sudanic.isoc.sd/billing_pricing.htm // Submitted by registry sd com.sd net.sd org.sd edu.sd med.sd tv.sd gov.sd info.sd // se : https://www.iana.org/domains/root/db/se.html // 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 : https://www.sgnic.sg/domain-registration/sg-categories-rules // Confirmed by registry 2024-11-19 sg com.sg net.sg org.sg gov.sg edu.sg // sh : http://nic.sh/rules.htm sh com.sh net.sh gov.sh org.sh mil.sh // si : https://www.iana.org/domains/root/db/si.html si // sj : No registrations at this time. // Submitted by registry sj // sk : https://www.iana.org/domains/root/db/sk.html sk // sl : http://www.nic.sl // Submitted by registry sl com.sl net.sl edu.sl gov.sl org.sl // sm : https://www.iana.org/domains/root/db/sm.html sm // sn : https://www.iana.org/domains/root/db/sn.html 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://www.iana.org/domains/root/db/sr.html sr // ss : https://registry.nic.ss/ // Submitted by registry ss biz.ss co.ss com.ss edu.ss gov.ss me.ss net.ss org.ss sch.ss // st : http://www.nic.st/html/policyrules/ st co.st com.st consulado.st edu.st embaixada.st mil.st net.st org.st principe.st saotome.st store.st // su : https://www.iana.org/domains/root/db/su.html su // sv : http://www.svnet.org.sv/niveldos.pdf sv com.sv edu.sv gob.sv org.sv red.sv // sx : https://www.iana.org/domains/root/db/sx.html // Submitted by registry sx gov.sx // sy : https://www.iana.org/domains/root/db/sy.html // see also: http://www.gobin.info/domainname/sy.doc sy edu.sy gov.sy net.sy mil.sy com.sy org.sy // sz : https://www.iana.org/domains/root/db/sz.html // http://www.sispa.org.sz/ sz co.sz ac.sz org.sz // tc : https://www.iana.org/domains/root/db/tc.html tc // td : https://www.iana.org/domains/root/db/td.html td // tel: https://www.iana.org/domains/root/db/tel.html // http://www.telnic.org/ tel // tf : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf tf // tg : https://www.iana.org/domains/root/db/tg.html // http://www.nic.tg/ tg // th : https://www.iana.org/domains/root/db/th.html // 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://www.iana.org/domains/root/db/tk.html tk // tl : https://www.iana.org/domains/root/db/tl.html tl gov.tl // tm : https://www.nic.tm/local.html // Confirmed by registry - 2024-11-19 tm co.tm com.tm edu.tm gov.tm mil.tm net.tm nom.tm org.tm // tn : http://www.registre.tn/fr/ // https://whois.ati.tn/ tn com.tn ens.tn fin.tn gov.tn ind.tn info.tn intl.tn mincom.tn nat.tn net.tn org.tn perso.tn tourism.tn // to : https://www.iana.org/domains/root/db/to.html // 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 : https://www.nic.tt/ // Confirmed by registry - 2024-11-19 tt biz.tt co.tt com.tt edu.tt gov.tt info.tt mil.tt name.tt net.tt org.tt pro.tt // tv : https://www.iana.org/domains/root/db/tv.html // Not listing any 2LDs as reserved since none seem to exist in practice, // Wikipedia notwithstanding. tv // tw : https://www.iana.org/domains/root/db/tw.html tw edu.tw gov.tw mil.tw com.tw net.tw org.tw idv.tw game.tw ebiz.tw club.tw 網路.tw 組織.tw 商業.tw // tz : http://www.tznic.or.tz/index.php/domains // Submitted by registry tz ac.tz co.tz go.tz hotel.tz info.tz me.tz mil.tz mobi.tz ne.tz or.tz sc.tz tv.tz // ua : https://hostmaster.ua/policy/?ua // Submitted by registry ua // ua 2LD com.ua edu.ua gov.ua in.ua net.ua org.ua // ua geographic names // https://hostmaster.ua/2ld/ cherkassy.ua cherkasy.ua chernigov.ua chernihiv.ua chernivtsi.ua chernovtsy.ua ck.ua cn.ua cr.ua crimea.ua cv.ua dn.ua dnepropetrovsk.ua dnipropetrovsk.ua donetsk.ua dp.ua if.ua ivano-frankivsk.ua kh.ua kharkiv.ua kharkov.ua kherson.ua khmelnitskiy.ua khmelnytskyi.ua kiev.ua kirovograd.ua km.ua kr.ua kropyvnytskyi.ua krym.ua ks.ua kv.ua kyiv.ua lg.ua lt.ua lugansk.ua luhansk.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 uzhhorod.ua vinnica.ua vinnytsia.ua vn.ua volyn.ua yalta.ua zakarpattia.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://www.iana.org/domains/root/db/uk.html // 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://www.iana.org/domains/root/db/us.html 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.fl.us k12.ga.us k12.gu.us // k12.hi.us Bug 614565 - Hawaii has a state-wide DOE login k12.ia.us k12.id.us k12.il.us k12.in.us k12.ks.us k12.ky.us k12.la.us k12.ma.us k12.md.us k12.me.us k12.mi.us k12.mn.us k12.mo.us k12.ms.us k12.mt.us k12.nc.us // k12.nd.us Bug 1028347 - Removed at request of Travis Rosso k12.ne.us k12.nh.us k12.nj.us k12.nm.us k12.nv.us k12.ny.us k12.oh.us k12.ok.us k12.or.us k12.pa.us k12.pr.us // k12.ri.us Removed at request of Kim Cournoyer k12.sc.us // k12.sd.us Bug 934131 - Removed at request of James Booze k12.tn.us k12.tx.us k12.ut.us k12.vi.us k12.vt.us k12.va.us k12.wa.us k12.wi.us // k12.wv.us Bug 947705 - Removed at request of Verne Britton k12.wy.us cc.ak.us cc.al.us cc.ar.us cc.as.us cc.az.us cc.ca.us cc.co.us cc.ct.us cc.dc.us cc.de.us cc.fl.us cc.ga.us cc.gu.us cc.hi.us cc.ia.us cc.id.us cc.il.us cc.in.us cc.ks.us cc.ky.us cc.la.us cc.ma.us cc.md.us cc.me.us cc.mi.us cc.mn.us cc.mo.us cc.ms.us cc.mt.us cc.nc.us cc.nd.us cc.ne.us cc.nh.us cc.nj.us cc.nm.us cc.nv.us cc.ny.us cc.oh.us cc.ok.us cc.or.us cc.pa.us cc.pr.us cc.ri.us cc.sc.us cc.sd.us cc.tn.us cc.tx.us cc.ut.us cc.vi.us cc.vt.us cc.va.us cc.wa.us cc.wi.us cc.wv.us cc.wy.us lib.ak.us lib.al.us lib.ar.us lib.as.us lib.az.us lib.ca.us lib.co.us lib.ct.us lib.dc.us // lib.de.us Issue #243 - Moved to Private section at request of Ed Moore lib.fl.us lib.ga.us lib.gu.us lib.hi.us lib.ia.us lib.id.us lib.il.us lib.in.us lib.ks.us lib.ky.us lib.la.us lib.ma.us lib.md.us lib.me.us lib.mi.us lib.mn.us lib.mo.us lib.ms.us lib.mt.us lib.nc.us lib.nd.us lib.ne.us lib.nh.us lib.nj.us lib.nm.us lib.nv.us lib.ny.us lib.oh.us lib.ok.us lib.or.us lib.pa.us lib.pr.us lib.ri.us lib.sc.us lib.sd.us lib.tn.us lib.tx.us lib.ut.us lib.vi.us lib.vt.us lib.va.us lib.wa.us lib.wi.us // lib.wv.us Bug 941670 - Removed at request of Larry W Arnold lib.wy.us // k12.ma.us contains school districts in Massachusetts. The 4LDs are // managed independently except for private (PVT), charter (CHTR) and // parochial (PAROCH) schools. Those are delegated directly to the // 5LD operators. pvt.k12.ma.us chtr.k12.ma.us paroch.k12.ma.us // Merit Network, Inc. maintains the registry for =~ /(k12|cc|lib).mi.us/ and the following // see also: http://domreg.merit.edu // see also: whois -h whois.domreg.merit.edu help ann-arbor.mi.us cog.mi.us dst.mi.us eaton.mi.us gen.mi.us mus.mi.us tec.mi.us washtenaw.mi.us // uy : http://www.nic.org.uy/ uy com.uy edu.uy gub.uy mil.uy net.uy org.uy // uz : http://www.reg.uz/ uz co.uz com.uz net.uz org.uz // va : https://www.iana.org/domains/root/db/va.html va // vc : https://www.iana.org/domains/root/db/vc.html // Submitted by registry vc com.vc net.vc org.vc gov.vc mil.vc edu.vc // ve : https://registro.nic.ve/ // Submitted by registry nic@nic.ve and nicve@conatel.gob.ve ve arts.ve bib.ve co.ve com.ve e12.ve edu.ve firm.ve gob.ve gov.ve info.ve int.ve mil.ve net.ve nom.ve org.ve rar.ve rec.ve store.ve tec.ve web.ve // vg : https://www.iana.org/domains/root/db/vg.html vg // vi : http://www.nic.vi/newdomainform.htm // http://www.nic.vi/Domain_Rules/body_domain_rules.html indicates some other // TLDs are "reserved", such as edu.vi and gov.vi, but doesn't actually say they // are available for registration (which they do not seem to be). vi co.vi com.vi k12.vi net.vi org.vi // vn : https://www.vnnic.vn/en/domain/cctld-vn // https://vnnic.vn/sites/default/files/tailieu/vn.cctld.domains.txt vn ac.vn ai.vn biz.vn com.vn edu.vn gov.vn health.vn id.vn info.vn int.vn io.vn name.vn net.vn org.vn pro.vn // vn geographical names angiang.vn bacgiang.vn backan.vn baclieu.vn bacninh.vn baria-vungtau.vn bentre.vn binhdinh.vn binhduong.vn binhphuoc.vn binhthuan.vn camau.vn cantho.vn caobang.vn daklak.vn daknong.vn danang.vn dienbien.vn dongnai.vn dongthap.vn gialai.vn hagiang.vn haiduong.vn haiphong.vn hanam.vn hanoi.vn hatinh.vn haugiang.vn hoabinh.vn hungyen.vn khanhhoa.vn kiengiang.vn kontum.vn laichau.vn lamdong.vn langson.vn laocai.vn longan.vn namdinh.vn nghean.vn ninhbinh.vn ninhthuan.vn phutho.vn phuyen.vn quangbinh.vn quangnam.vn quangngai.vn quangninh.vn quangtri.vn soctrang.vn sonla.vn tayninh.vn thaibinh.vn thainguyen.vn thanhhoa.vn thanhphohochiminh.vn thuathienhue.vn tiengiang.vn travinh.vn tuyenquang.vn vinhlong.vn vinhphuc.vn yenbai.vn // vu : https://www.iana.org/domains/root/db/vu.html // http://www.vunic.vu/ vu com.vu edu.vu net.vu org.vu // wf : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf wf // ws : https://www.iana.org/domains/root/db/ws.html // http://samoanic.ws/index.dhtml ws com.ws net.ws org.ws gov.ws edu.ws // yt : https://www.afnic.fr/wp-media/uploads/2022/12/afnic-naming-policy-2023-01-01.pdf yt // IDN ccTLDs // When submitting patches, please maintain a sort by ISO 3166 ccTLD, then // U-label, and follow this format: // // A-Label ("", [, variant info]) : // // [sponsoring org] // U-Label // xn--mgbaam7a8h ("Emerat", Arabic) : AE // http://nic.ae/english/arabicdomain/rules.jsp امارات // xn--y9a3aq ("hye", Armenian) : AM // ISOC AM (operated by .am Registry) հայ // xn--54b7fta0cc ("Bangla", Bangla) : BD বাংলা // xn--90ae ("bg", Bulgarian) : BG бг // xn--mgbcpq6gpa1a ("albahrain", Arabic) : BH البحرين // xn--90ais ("bel", Belarusian/Russian Cyrillic) : BY // Operated by .by registry бел // xn--fiqs8s ("Zhongguo/China", Chinese, Simplified) : CN // CNNIC // http://cnnic.cn/html/Dir/2005/10/11/3218.htm 中国 // xn--fiqz9s ("Zhongguo/China", Chinese, Traditional) : CN // CNNIC // http://cnnic.cn/html/Dir/2005/10/11/3218.htm 中國 // xn--lgbbat1ad8j ("Algeria/Al Jazair", Arabic) : DZ الجزائر // xn--wgbh1c ("Egypt/Masr", Arabic) : EG // http://www.dotmasr.eg/ مصر // xn--e1a4c ("eu", Cyrillic) : EU // https://eurid.eu ею // xn--qxa6a ("eu", Greek) : EU // https://eurid.eu ευ // xn--mgbah1a3hjkrd ("Mauritania", Arabic) : MR موريتانيا // xn--node ("ge", Georgian Mkhedruli) : GE გე // xn--qxam ("el", Greek) : GR // Hellenic Ministry of Infrastructure, Transport, and Networks ελ // xn--j6w193g ("Hong Kong", Chinese) : HK // https://www.hkirc.hk // Submitted by registry // https://www.hkirc.hk/content.jsp?id=30#!/34 香港 公司.香港 教育.香港 政府.香港 個人.香港 網絡.香港 組織.香港 // xn--2scrj9c ("Bharat", Kannada) : IN // India ಭಾರತ // xn--3hcrj9c ("Bharat", Oriya) : IN // India ଭାରତ // xn--45br5cyl ("Bharatam", Assamese) : IN // India ভাৰত // xn--h2breg3eve ("Bharatam", Sanskrit) : IN // India भारतम् // xn--h2brj9c8c ("Bharot", Santali) : IN // India भारोत // xn--mgbgu82a ("Bharat", Sindhi) : IN // India ڀارت // xn--rvc1e0am3e ("Bharatam", Malayalam) : IN // India ഭാരതം // xn--h2brj9c ("Bharat", Devanagari) : IN // India भारत // xn--mgbbh1a ("Bharat", Kashmiri) : IN // India بارت // xn--mgbbh1a71e ("Bharat", Arabic) : IN // India بھارت // xn--fpcrj9c3d ("Bharat", Telugu) : IN // India భారత్ // xn--gecrj9c ("Bharat", Gujarati) : IN // India ભારત // xn--s9brj9c ("Bharat", Gurmukhi) : IN // India ਭਾਰਤ // xn--45brj9c ("Bharat", Bengali) : IN // India ভারত // xn--xkc2dl3a5ee0h ("India", Tamil) : IN // India இந்தியா // xn--mgba3a4f16a ("Iran", Persian) : IR ایران // xn--mgba3a4fra ("Iran", Arabic) : IR ايران // xn--mgbtx2b ("Iraq", Arabic) : IQ // Communications and Media Commission عراق // xn--mgbayh7gpa ("al-Ordon", Arabic) : JO // National Information Technology Center (NITC) // Royal Scientific Society, Al-Jubeiha الاردن // xn--3e0b707e ("Republic of Korea", Hangul) : KR 한국 // xn--80ao21a ("Kaz", Kazakh) : KZ қаз // xn--q7ce6a ("Lao", Lao) : LA ລາວ // xn--fzc2c9e2c ("Lanka", Sinhalese-Sinhala) : LK // https://nic.lk ලංකා // xn--xkc2al3hye2a ("Ilangai", Tamil) : LK // https://nic.lk இலங்கை // xn--mgbc0a9azcg ("Morocco/al-Maghrib", Arabic) : MA المغرب // xn--d1alf ("mkd", Macedonian) : MK // MARnet мкд // xn--l1acc ("mon", Mongolian) : MN мон // xn--mix891f ("Macao", Chinese, Traditional) : MO // MONIC / HNET Asia (Registry Operator for .mo) 澳門 // xn--mix082f ("Macao", Chinese, Simplified) : MO 澳门 // xn--mgbx4cd0ab ("Malaysia", Malay) : MY مليسيا // xn--mgb9awbf ("Oman", Arabic) : OM عمان // xn--mgbai9azgqp6j ("Pakistan", Urdu/Arabic) : PK پاکستان // xn--mgbai9a5eva00b ("Pakistan", Urdu/Arabic, variant) : PK پاكستان // xn--ygbi2ammx ("Falasteen", Arabic) : PS // The Palestinian National Internet Naming Authority (PNINA) // http://www.pnina.ps فلسطين // xn--90a3ac ("srb", Cyrillic) : RS // https://www.rnids.rs/en/domains/national-domains срб пр.срб орг.срб обр.срб од.срб упр.срб ак.срб // xn--p1ai ("rf", Russian-Cyrillic) : RU // https://cctld.ru/files/pdf/docs/en/rules_ru-rf.pdf // Submitted by George Georgievsky рф // xn--wgbl6a ("Qatar", Arabic) : QA // http://www.ict.gov.qa/ قطر // xn--mgberp4a5d4ar ("AlSaudiah", Arabic) : SA // http://www.nic.net.sa/ السعودية // xn--mgberp4a5d4a87g ("AlSaudiah", Arabic, variant) : SA السعودیة // xn--mgbqly7c0a67fbc ("AlSaudiah", Arabic, variant) : SA السعودیۃ // xn--mgbqly7cvafr ("AlSaudiah", Arabic, variant) : SA السعوديه // xn--mgbpl2fh ("sudan", Arabic) : SD // Operated by .sd registry سودان // xn--yfro4i67o Singapore ("Singapore", Chinese) : SG 新加坡 // xn--clchc0ea0b2g2a9gcd ("Singapore", Tamil) : SG சிங்கப்பூர் // xn--ogbpf8fl ("Syria", Arabic) : SY سورية // xn--mgbtf8fl ("Syria", Arabic, variant) : SY سوريا // xn--o3cw4h ("Thai", Thai) : TH // http://www.thnic.co.th ไทย ศึกษา.ไทย ธุรกิจ.ไทย รัฐบาล.ไทย ทหาร.ไทย เน็ต.ไทย องค์กร.ไทย // xn--pgbs0dh ("Tunisia", Arabic) : TN // http://nic.tn تونس // xn--kpry57d ("Taiwan", Chinese, Traditional) : TW // http://www.twnic.net/english/dn/dn_07a.htm 台灣 // xn--kprw13d ("Taiwan", Chinese, Simplified) : TW // http://www.twnic.net/english/dn/dn_07a.htm 台湾 // xn--nnx388a ("Taiwan", Chinese, variant) : TW 臺灣 // xn--j1amh ("ukr", Cyrillic) : UA укр // xn--mgb2ddes ("AlYemen", Arabic) : YE اليمن // xxx : http://icmregistry.com xxx // ye : http://www.y.net.ye/services/domain_name.htm ye com.ye edu.ye gov.ye net.ye mil.ye org.ye // za : https://www.zadna.org.za/content/page/domain-information/ ac.za agric.za alt.za co.za edu.za gov.za grondar.za law.za mil.za net.za ngo.za nic.za nis.za nom.za org.za school.za tm.za web.za // zm : https://zicta.zm/ // Submitted by registry zm ac.zm biz.zm co.zm com.zm edu.zm gov.zm info.zm mil.zm net.zm org.zm sch.zm // zw : https://www.potraz.gov.zw/ // Confirmed by registry 2017-01-25 zw ac.zw co.zw gov.zw mil.zw org.zw // newGTLDs // List of new gTLDs imported from https://www.icann.org/resources/registries/gtlds/v2/gtlds.json on 2024-10-31T15:17:42Z // This list is auto-generated, don't edit it manually. // aaa : American Automobile Association, Inc. // https://www.iana.org/domains/root/db/aaa.html aaa // aarp : AARP // https://www.iana.org/domains/root/db/aarp.html aarp // abb : ABB Ltd // https://www.iana.org/domains/root/db/abb.html abb // abbott : Abbott Laboratories, Inc. // https://www.iana.org/domains/root/db/abbott.html abbott // abbvie : AbbVie Inc. // https://www.iana.org/domains/root/db/abbvie.html abbvie // abc : Disney Enterprises, Inc. // https://www.iana.org/domains/root/db/abc.html abc // able : Able Inc. // https://www.iana.org/domains/root/db/able.html able // abogado : Registry Services, LLC // https://www.iana.org/domains/root/db/abogado.html abogado // abudhabi : Abu Dhabi Systems and Information Centre // https://www.iana.org/domains/root/db/abudhabi.html abudhabi // academy : Binky Moon, LLC // https://www.iana.org/domains/root/db/academy.html academy // accenture : Accenture plc // https://www.iana.org/domains/root/db/accenture.html accenture // accountant : dot Accountant Limited // https://www.iana.org/domains/root/db/accountant.html accountant // accountants : Binky Moon, LLC // https://www.iana.org/domains/root/db/accountants.html accountants // aco : ACO Severin Ahlmann GmbH & Co. KG // https://www.iana.org/domains/root/db/aco.html aco // actor : Dog Beach, LLC // https://www.iana.org/domains/root/db/actor.html actor // ads : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/ads.html ads // adult : ICM Registry AD LLC // https://www.iana.org/domains/root/db/adult.html adult // aeg : Aktiebolaget Electrolux // https://www.iana.org/domains/root/db/aeg.html aeg // aetna : Aetna Life Insurance Company // https://www.iana.org/domains/root/db/aetna.html aetna // afl : Australian Football League // https://www.iana.org/domains/root/db/afl.html afl // africa : ZA Central Registry NPC trading as Registry.Africa // https://www.iana.org/domains/root/db/africa.html africa // agakhan : Fondation Aga Khan (Aga Khan Foundation) // https://www.iana.org/domains/root/db/agakhan.html agakhan // agency : Binky Moon, LLC // https://www.iana.org/domains/root/db/agency.html agency // aig : American International Group, Inc. // https://www.iana.org/domains/root/db/aig.html aig // airbus : Airbus S.A.S. // https://www.iana.org/domains/root/db/airbus.html airbus // airforce : Dog Beach, LLC // https://www.iana.org/domains/root/db/airforce.html airforce // airtel : Bharti Airtel Limited // https://www.iana.org/domains/root/db/airtel.html airtel // akdn : Fondation Aga Khan (Aga Khan Foundation) // https://www.iana.org/domains/root/db/akdn.html akdn // alibaba : Alibaba Group Holding Limited // https://www.iana.org/domains/root/db/alibaba.html alibaba // alipay : Alibaba Group Holding Limited // https://www.iana.org/domains/root/db/alipay.html alipay // allfinanz : Allfinanz Deutsche Vermögensberatung Aktiengesellschaft // https://www.iana.org/domains/root/db/allfinanz.html allfinanz // allstate : Allstate Fire and Casualty Insurance Company // https://www.iana.org/domains/root/db/allstate.html allstate // ally : Ally Financial Inc. // https://www.iana.org/domains/root/db/ally.html ally // alsace : Region Grand Est // https://www.iana.org/domains/root/db/alsace.html alsace // alstom : ALSTOM // https://www.iana.org/domains/root/db/alstom.html alstom // amazon : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/amazon.html amazon // americanexpress : American Express Travel Related Services Company, Inc. // https://www.iana.org/domains/root/db/americanexpress.html americanexpress // americanfamily : AmFam, Inc. // https://www.iana.org/domains/root/db/americanfamily.html americanfamily // amex : American Express Travel Related Services Company, Inc. // https://www.iana.org/domains/root/db/amex.html amex // amfam : AmFam, Inc. // https://www.iana.org/domains/root/db/amfam.html amfam // amica : Amica Mutual Insurance Company // https://www.iana.org/domains/root/db/amica.html amica // amsterdam : Gemeente Amsterdam // https://www.iana.org/domains/root/db/amsterdam.html amsterdam // analytics : Campus IP LLC // https://www.iana.org/domains/root/db/analytics.html analytics // android : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/android.html android // anquan : Beijing Qihu Keji Co., Ltd. // https://www.iana.org/domains/root/db/anquan.html anquan // anz : Australia and New Zealand Banking Group Limited // https://www.iana.org/domains/root/db/anz.html anz // aol : Yahoo Inc. // https://www.iana.org/domains/root/db/aol.html aol // apartments : Binky Moon, LLC // https://www.iana.org/domains/root/db/apartments.html apartments // app : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/app.html app // apple : Apple Inc. // https://www.iana.org/domains/root/db/apple.html apple // aquarelle : Aquarelle.com // https://www.iana.org/domains/root/db/aquarelle.html aquarelle // arab : League of Arab States // https://www.iana.org/domains/root/db/arab.html arab // aramco : Aramco Services Company // https://www.iana.org/domains/root/db/aramco.html aramco // archi : Identity Digital Limited // https://www.iana.org/domains/root/db/archi.html archi // army : Dog Beach, LLC // https://www.iana.org/domains/root/db/army.html army // art : UK Creative Ideas Limited // https://www.iana.org/domains/root/db/art.html art // arte : Association Relative à la Télévision Européenne G.E.I.E. // https://www.iana.org/domains/root/db/arte.html arte // asda : Wal-Mart Stores, Inc. // https://www.iana.org/domains/root/db/asda.html asda // associates : Binky Moon, LLC // https://www.iana.org/domains/root/db/associates.html associates // athleta : The Gap, Inc. // https://www.iana.org/domains/root/db/athleta.html athleta // attorney : Dog Beach, LLC // https://www.iana.org/domains/root/db/attorney.html attorney // auction : Dog Beach, LLC // https://www.iana.org/domains/root/db/auction.html auction // audi : AUDI Aktiengesellschaft // https://www.iana.org/domains/root/db/audi.html audi // audible : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/audible.html audible // audio : XYZ.COM LLC // https://www.iana.org/domains/root/db/audio.html audio // auspost : Australian Postal Corporation // https://www.iana.org/domains/root/db/auspost.html auspost // author : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/author.html author // auto : XYZ.COM LLC // https://www.iana.org/domains/root/db/auto.html auto // autos : XYZ.COM LLC // https://www.iana.org/domains/root/db/autos.html autos // aws : AWS Registry LLC // https://www.iana.org/domains/root/db/aws.html aws // axa : AXA Group Operations SAS // https://www.iana.org/domains/root/db/axa.html axa // azure : Microsoft Corporation // https://www.iana.org/domains/root/db/azure.html azure // baby : XYZ.COM LLC // https://www.iana.org/domains/root/db/baby.html baby // baidu : Baidu, Inc. // https://www.iana.org/domains/root/db/baidu.html baidu // banamex : Citigroup Inc. // https://www.iana.org/domains/root/db/banamex.html banamex // band : Dog Beach, LLC // https://www.iana.org/domains/root/db/band.html band // bank : fTLD Registry Services LLC // https://www.iana.org/domains/root/db/bank.html bank // bar : Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable // https://www.iana.org/domains/root/db/bar.html bar // barcelona : Municipi de Barcelona // https://www.iana.org/domains/root/db/barcelona.html barcelona // barclaycard : Barclays Bank PLC // https://www.iana.org/domains/root/db/barclaycard.html barclaycard // barclays : Barclays Bank PLC // https://www.iana.org/domains/root/db/barclays.html barclays // barefoot : Gallo Vineyards, Inc. // https://www.iana.org/domains/root/db/barefoot.html barefoot // bargains : Binky Moon, LLC // https://www.iana.org/domains/root/db/bargains.html bargains // baseball : MLB Advanced Media DH, LLC // https://www.iana.org/domains/root/db/baseball.html baseball // basketball : Fédération Internationale de Basketball (FIBA) // https://www.iana.org/domains/root/db/basketball.html basketball // bauhaus : Werkhaus GmbH // https://www.iana.org/domains/root/db/bauhaus.html bauhaus // bayern : Bayern Connect GmbH // https://www.iana.org/domains/root/db/bayern.html bayern // bbc : British Broadcasting Corporation // https://www.iana.org/domains/root/db/bbc.html bbc // bbt : BB&T Corporation // https://www.iana.org/domains/root/db/bbt.html bbt // bbva : BANCO BILBAO VIZCAYA ARGENTARIA, S.A. // https://www.iana.org/domains/root/db/bbva.html bbva // bcg : The Boston Consulting Group, Inc. // https://www.iana.org/domains/root/db/bcg.html bcg // bcn : Municipi de Barcelona // https://www.iana.org/domains/root/db/bcn.html bcn // beats : Beats Electronics, LLC // https://www.iana.org/domains/root/db/beats.html beats // beauty : XYZ.COM LLC // https://www.iana.org/domains/root/db/beauty.html beauty // beer : Registry Services, LLC // https://www.iana.org/domains/root/db/beer.html beer // bentley : Bentley Motors Limited // https://www.iana.org/domains/root/db/bentley.html bentley // berlin : dotBERLIN GmbH & Co. KG // https://www.iana.org/domains/root/db/berlin.html berlin // best : BestTLD Pty Ltd // https://www.iana.org/domains/root/db/best.html best // bestbuy : BBY Solutions, Inc. // https://www.iana.org/domains/root/db/bestbuy.html bestbuy // bet : Identity Digital Limited // https://www.iana.org/domains/root/db/bet.html bet // bharti : Bharti Enterprises (Holding) Private Limited // https://www.iana.org/domains/root/db/bharti.html bharti // bible : American Bible Society // https://www.iana.org/domains/root/db/bible.html bible // bid : dot Bid Limited // https://www.iana.org/domains/root/db/bid.html bid // bike : Binky Moon, LLC // https://www.iana.org/domains/root/db/bike.html bike // bing : Microsoft Corporation // https://www.iana.org/domains/root/db/bing.html bing // bingo : Binky Moon, LLC // https://www.iana.org/domains/root/db/bingo.html bingo // bio : Identity Digital Limited // https://www.iana.org/domains/root/db/bio.html bio // black : Identity Digital Limited // https://www.iana.org/domains/root/db/black.html black // blackfriday : Registry Services, LLC // https://www.iana.org/domains/root/db/blackfriday.html blackfriday // blockbuster : Dish DBS Corporation // https://www.iana.org/domains/root/db/blockbuster.html blockbuster // blog : Knock Knock WHOIS There, LLC // https://www.iana.org/domains/root/db/blog.html blog // bloomberg : Bloomberg IP Holdings LLC // https://www.iana.org/domains/root/db/bloomberg.html bloomberg // blue : Identity Digital Limited // https://www.iana.org/domains/root/db/blue.html blue // bms : Bristol-Myers Squibb Company // https://www.iana.org/domains/root/db/bms.html bms // bmw : Bayerische Motoren Werke Aktiengesellschaft // https://www.iana.org/domains/root/db/bmw.html bmw // bnpparibas : BNP Paribas // https://www.iana.org/domains/root/db/bnpparibas.html bnpparibas // boats : XYZ.COM LLC // https://www.iana.org/domains/root/db/boats.html boats // boehringer : Boehringer Ingelheim International GmbH // https://www.iana.org/domains/root/db/boehringer.html boehringer // bofa : Bank of America Corporation // https://www.iana.org/domains/root/db/bofa.html bofa // bom : Núcleo de Informação e Coordenação do Ponto BR - NIC.br // https://www.iana.org/domains/root/db/bom.html bom // bond : ShortDot SA // https://www.iana.org/domains/root/db/bond.html bond // boo : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/boo.html boo // book : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/book.html book // booking : Booking.com B.V. // https://www.iana.org/domains/root/db/booking.html booking // bosch : Robert Bosch GMBH // https://www.iana.org/domains/root/db/bosch.html bosch // bostik : Bostik SA // https://www.iana.org/domains/root/db/bostik.html bostik // boston : Registry Services, LLC // https://www.iana.org/domains/root/db/boston.html boston // bot : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/bot.html bot // boutique : Binky Moon, LLC // https://www.iana.org/domains/root/db/boutique.html boutique // box : Intercap Registry Inc. // https://www.iana.org/domains/root/db/box.html box // bradesco : Banco Bradesco S.A. // https://www.iana.org/domains/root/db/bradesco.html bradesco // bridgestone : Bridgestone Corporation // https://www.iana.org/domains/root/db/bridgestone.html bridgestone // broadway : Celebrate Broadway, Inc. // https://www.iana.org/domains/root/db/broadway.html broadway // broker : Dog Beach, LLC // https://www.iana.org/domains/root/db/broker.html broker // brother : Brother Industries, Ltd. // https://www.iana.org/domains/root/db/brother.html brother // brussels : DNS.be vzw // https://www.iana.org/domains/root/db/brussels.html brussels // build : Plan Bee LLC // https://www.iana.org/domains/root/db/build.html build // builders : Binky Moon, LLC // https://www.iana.org/domains/root/db/builders.html builders // business : Binky Moon, LLC // https://www.iana.org/domains/root/db/business.html business // buy : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/buy.html buy // buzz : DOTSTRATEGY CO. // https://www.iana.org/domains/root/db/buzz.html buzz // bzh : Association www.bzh // https://www.iana.org/domains/root/db/bzh.html bzh // cab : Binky Moon, LLC // https://www.iana.org/domains/root/db/cab.html cab // cafe : Binky Moon, LLC // https://www.iana.org/domains/root/db/cafe.html cafe // cal : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/cal.html cal // call : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/call.html call // calvinklein : PVH gTLD Holdings LLC // https://www.iana.org/domains/root/db/calvinklein.html calvinklein // cam : Cam Connecting SARL // https://www.iana.org/domains/root/db/cam.html cam // camera : Binky Moon, LLC // https://www.iana.org/domains/root/db/camera.html camera // camp : Binky Moon, LLC // https://www.iana.org/domains/root/db/camp.html camp // canon : Canon Inc. // https://www.iana.org/domains/root/db/canon.html canon // capetown : ZA Central Registry NPC trading as ZA Central Registry // https://www.iana.org/domains/root/db/capetown.html capetown // capital : Binky Moon, LLC // https://www.iana.org/domains/root/db/capital.html capital // capitalone : Capital One Financial Corporation // https://www.iana.org/domains/root/db/capitalone.html capitalone // car : XYZ.COM LLC // https://www.iana.org/domains/root/db/car.html car // caravan : Caravan International, Inc. // https://www.iana.org/domains/root/db/caravan.html caravan // cards : Binky Moon, LLC // https://www.iana.org/domains/root/db/cards.html cards // care : Binky Moon, LLC // https://www.iana.org/domains/root/db/care.html care // career : dotCareer LLC // https://www.iana.org/domains/root/db/career.html career // careers : Binky Moon, LLC // https://www.iana.org/domains/root/db/careers.html careers // cars : XYZ.COM LLC // https://www.iana.org/domains/root/db/cars.html cars // casa : Registry Services, LLC // https://www.iana.org/domains/root/db/casa.html casa // case : Digity, LLC // https://www.iana.org/domains/root/db/case.html case // cash : Binky Moon, LLC // https://www.iana.org/domains/root/db/cash.html cash // casino : Binky Moon, LLC // https://www.iana.org/domains/root/db/casino.html casino // catering : Binky Moon, LLC // https://www.iana.org/domains/root/db/catering.html catering // catholic : Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) // https://www.iana.org/domains/root/db/catholic.html catholic // cba : COMMONWEALTH BANK OF AUSTRALIA // https://www.iana.org/domains/root/db/cba.html cba // cbn : The Christian Broadcasting Network, Inc. // https://www.iana.org/domains/root/db/cbn.html cbn // cbre : CBRE, Inc. // https://www.iana.org/domains/root/db/cbre.html cbre // center : Binky Moon, LLC // https://www.iana.org/domains/root/db/center.html center // ceo : XYZ.COM LLC // https://www.iana.org/domains/root/db/ceo.html ceo // cern : European Organization for Nuclear Research ("CERN") // https://www.iana.org/domains/root/db/cern.html cern // cfa : CFA Institute // https://www.iana.org/domains/root/db/cfa.html cfa // cfd : ShortDot SA // https://www.iana.org/domains/root/db/cfd.html cfd // chanel : Chanel International B.V. // https://www.iana.org/domains/root/db/chanel.html chanel // channel : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/channel.html channel // charity : Public Interest Registry // https://www.iana.org/domains/root/db/charity.html charity // chase : JPMorgan Chase Bank, National Association // https://www.iana.org/domains/root/db/chase.html chase // chat : Binky Moon, LLC // https://www.iana.org/domains/root/db/chat.html chat // cheap : Binky Moon, LLC // https://www.iana.org/domains/root/db/cheap.html cheap // chintai : CHINTAI Corporation // https://www.iana.org/domains/root/db/chintai.html chintai // christmas : XYZ.COM LLC // https://www.iana.org/domains/root/db/christmas.html christmas // chrome : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/chrome.html chrome // church : Binky Moon, LLC // https://www.iana.org/domains/root/db/church.html church // cipriani : Hotel Cipriani Srl // https://www.iana.org/domains/root/db/cipriani.html cipriani // circle : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/circle.html circle // cisco : Cisco Technology, Inc. // https://www.iana.org/domains/root/db/cisco.html cisco // citadel : Citadel Domain LLC // https://www.iana.org/domains/root/db/citadel.html citadel // citi : Citigroup Inc. // https://www.iana.org/domains/root/db/citi.html citi // citic : CITIC Group Corporation // https://www.iana.org/domains/root/db/citic.html citic // city : Binky Moon, LLC // https://www.iana.org/domains/root/db/city.html city // claims : Binky Moon, LLC // https://www.iana.org/domains/root/db/claims.html claims // cleaning : Binky Moon, LLC // https://www.iana.org/domains/root/db/cleaning.html cleaning // click : Internet Naming Company LLC // https://www.iana.org/domains/root/db/click.html click // clinic : Binky Moon, LLC // https://www.iana.org/domains/root/db/clinic.html clinic // clinique : The Estée Lauder Companies Inc. // https://www.iana.org/domains/root/db/clinique.html clinique // clothing : Binky Moon, LLC // https://www.iana.org/domains/root/db/clothing.html clothing // cloud : Aruba PEC S.p.A. // https://www.iana.org/domains/root/db/cloud.html cloud // club : Registry Services, LLC // https://www.iana.org/domains/root/db/club.html club // clubmed : Club Méditerranée S.A. // https://www.iana.org/domains/root/db/clubmed.html clubmed // coach : Binky Moon, LLC // https://www.iana.org/domains/root/db/coach.html coach // codes : Binky Moon, LLC // https://www.iana.org/domains/root/db/codes.html codes // coffee : Binky Moon, LLC // https://www.iana.org/domains/root/db/coffee.html coffee // college : XYZ.COM LLC // https://www.iana.org/domains/root/db/college.html college // cologne : dotKoeln GmbH // https://www.iana.org/domains/root/db/cologne.html cologne // commbank : COMMONWEALTH BANK OF AUSTRALIA // https://www.iana.org/domains/root/db/commbank.html commbank // community : Binky Moon, LLC // https://www.iana.org/domains/root/db/community.html community // company : Binky Moon, LLC // https://www.iana.org/domains/root/db/company.html company // compare : Registry Services, LLC // https://www.iana.org/domains/root/db/compare.html compare // computer : Binky Moon, LLC // https://www.iana.org/domains/root/db/computer.html computer // comsec : VeriSign, Inc. // https://www.iana.org/domains/root/db/comsec.html comsec // condos : Binky Moon, LLC // https://www.iana.org/domains/root/db/condos.html condos // construction : Binky Moon, LLC // https://www.iana.org/domains/root/db/construction.html construction // consulting : Dog Beach, LLC // https://www.iana.org/domains/root/db/consulting.html consulting // contact : Dog Beach, LLC // https://www.iana.org/domains/root/db/contact.html contact // contractors : Binky Moon, LLC // https://www.iana.org/domains/root/db/contractors.html contractors // cooking : Registry Services, LLC // https://www.iana.org/domains/root/db/cooking.html cooking // cool : Binky Moon, LLC // https://www.iana.org/domains/root/db/cool.html cool // corsica : Collectivité de Corse // https://www.iana.org/domains/root/db/corsica.html corsica // country : Internet Naming Company LLC // https://www.iana.org/domains/root/db/country.html country // coupon : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/coupon.html coupon // coupons : Binky Moon, LLC // https://www.iana.org/domains/root/db/coupons.html coupons // courses : Registry Services, LLC // https://www.iana.org/domains/root/db/courses.html courses // cpa : American Institute of Certified Public Accountants // https://www.iana.org/domains/root/db/cpa.html cpa // credit : Binky Moon, LLC // https://www.iana.org/domains/root/db/credit.html credit // creditcard : Binky Moon, LLC // https://www.iana.org/domains/root/db/creditcard.html creditcard // creditunion : DotCooperation LLC // https://www.iana.org/domains/root/db/creditunion.html creditunion // cricket : dot Cricket Limited // https://www.iana.org/domains/root/db/cricket.html cricket // crown : Crown Equipment Corporation // https://www.iana.org/domains/root/db/crown.html crown // crs : Federated Co-operatives Limited // https://www.iana.org/domains/root/db/crs.html crs // cruise : Viking River Cruises (Bermuda) Ltd. // https://www.iana.org/domains/root/db/cruise.html cruise // cruises : Binky Moon, LLC // https://www.iana.org/domains/root/db/cruises.html cruises // cuisinella : SCHMIDT GROUPE S.A.S. // https://www.iana.org/domains/root/db/cuisinella.html cuisinella // cymru : Nominet UK // https://www.iana.org/domains/root/db/cymru.html cymru // cyou : ShortDot SA // https://www.iana.org/domains/root/db/cyou.html cyou // dad : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/dad.html dad // dance : Dog Beach, LLC // https://www.iana.org/domains/root/db/dance.html dance // data : Dish DBS Corporation // https://www.iana.org/domains/root/db/data.html data // date : dot Date Limited // https://www.iana.org/domains/root/db/date.html date // dating : Binky Moon, LLC // https://www.iana.org/domains/root/db/dating.html dating // datsun : NISSAN MOTOR CO., LTD. // https://www.iana.org/domains/root/db/datsun.html datsun // day : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/day.html day // dclk : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/dclk.html dclk // dds : Registry Services, LLC // https://www.iana.org/domains/root/db/dds.html dds // deal : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/deal.html deal // dealer : Intercap Registry Inc. // https://www.iana.org/domains/root/db/dealer.html dealer // deals : Binky Moon, LLC // https://www.iana.org/domains/root/db/deals.html deals // degree : Dog Beach, LLC // https://www.iana.org/domains/root/db/degree.html degree // delivery : Binky Moon, LLC // https://www.iana.org/domains/root/db/delivery.html delivery // dell : Dell Inc. // https://www.iana.org/domains/root/db/dell.html dell // deloitte : Deloitte Touche Tohmatsu // https://www.iana.org/domains/root/db/deloitte.html deloitte // delta : Delta Air Lines, Inc. // https://www.iana.org/domains/root/db/delta.html delta // democrat : Dog Beach, LLC // https://www.iana.org/domains/root/db/democrat.html democrat // dental : Binky Moon, LLC // https://www.iana.org/domains/root/db/dental.html dental // dentist : Dog Beach, LLC // https://www.iana.org/domains/root/db/dentist.html dentist // desi // https://www.iana.org/domains/root/db/desi.html desi // design : Registry Services, LLC // https://www.iana.org/domains/root/db/design.html design // dev : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/dev.html dev // dhl : Deutsche Post AG // https://www.iana.org/domains/root/db/dhl.html dhl // diamonds : Binky Moon, LLC // https://www.iana.org/domains/root/db/diamonds.html diamonds // diet : XYZ.COM LLC // https://www.iana.org/domains/root/db/diet.html diet // digital : Binky Moon, LLC // https://www.iana.org/domains/root/db/digital.html digital // direct : Binky Moon, LLC // https://www.iana.org/domains/root/db/direct.html direct // directory : Binky Moon, LLC // https://www.iana.org/domains/root/db/directory.html directory // discount : Binky Moon, LLC // https://www.iana.org/domains/root/db/discount.html discount // discover : Discover Financial Services // https://www.iana.org/domains/root/db/discover.html discover // dish : Dish DBS Corporation // https://www.iana.org/domains/root/db/dish.html dish // diy : Internet Naming Company LLC // https://www.iana.org/domains/root/db/diy.html diy // dnp : Dai Nippon Printing Co., Ltd. // https://www.iana.org/domains/root/db/dnp.html dnp // docs : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/docs.html docs // doctor : Binky Moon, LLC // https://www.iana.org/domains/root/db/doctor.html doctor // dog : Binky Moon, LLC // https://www.iana.org/domains/root/db/dog.html dog // domains : Binky Moon, LLC // https://www.iana.org/domains/root/db/domains.html domains // dot : Dish DBS Corporation // https://www.iana.org/domains/root/db/dot.html dot // download : dot Support Limited // https://www.iana.org/domains/root/db/download.html download // drive : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/drive.html drive // dtv : Dish DBS Corporation // https://www.iana.org/domains/root/db/dtv.html dtv // dubai : Dubai Smart Government Department // https://www.iana.org/domains/root/db/dubai.html dubai // dunlop : The Goodyear Tire & Rubber Company // https://www.iana.org/domains/root/db/dunlop.html dunlop // dupont : DuPont Specialty Products USA, LLC // https://www.iana.org/domains/root/db/dupont.html dupont // durban : ZA Central Registry NPC trading as ZA Central Registry // https://www.iana.org/domains/root/db/durban.html durban // dvag : Deutsche Vermögensberatung Aktiengesellschaft DVAG // https://www.iana.org/domains/root/db/dvag.html dvag // dvr : DISH Technologies L.L.C. // https://www.iana.org/domains/root/db/dvr.html dvr // earth : Interlink Systems Innovation Institute K.K. // https://www.iana.org/domains/root/db/earth.html earth // eat : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/eat.html eat // eco : Big Room Inc. // https://www.iana.org/domains/root/db/eco.html eco // edeka : EDEKA Verband kaufmännischer Genossenschaften e.V. // https://www.iana.org/domains/root/db/edeka.html edeka // education : Binky Moon, LLC // https://www.iana.org/domains/root/db/education.html education // email : Binky Moon, LLC // https://www.iana.org/domains/root/db/email.html email // emerck : Merck KGaA // https://www.iana.org/domains/root/db/emerck.html emerck // energy : Binky Moon, LLC // https://www.iana.org/domains/root/db/energy.html energy // engineer : Dog Beach, LLC // https://www.iana.org/domains/root/db/engineer.html engineer // engineering : Binky Moon, LLC // https://www.iana.org/domains/root/db/engineering.html engineering // enterprises : Binky Moon, LLC // https://www.iana.org/domains/root/db/enterprises.html enterprises // epson : Seiko Epson Corporation // https://www.iana.org/domains/root/db/epson.html epson // equipment : Binky Moon, LLC // https://www.iana.org/domains/root/db/equipment.html equipment // ericsson : Telefonaktiebolaget L M Ericsson // https://www.iana.org/domains/root/db/ericsson.html ericsson // erni : ERNI Group Holding AG // https://www.iana.org/domains/root/db/erni.html erni // esq : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/esq.html esq // estate : Binky Moon, LLC // https://www.iana.org/domains/root/db/estate.html estate // eurovision : European Broadcasting Union (EBU) // https://www.iana.org/domains/root/db/eurovision.html eurovision // eus : Puntueus Fundazioa // https://www.iana.org/domains/root/db/eus.html eus // events : Binky Moon, LLC // https://www.iana.org/domains/root/db/events.html events // exchange : Binky Moon, LLC // https://www.iana.org/domains/root/db/exchange.html exchange // expert : Binky Moon, LLC // https://www.iana.org/domains/root/db/expert.html expert // exposed : Binky Moon, LLC // https://www.iana.org/domains/root/db/exposed.html exposed // express : Binky Moon, LLC // https://www.iana.org/domains/root/db/express.html express // extraspace : Extra Space Storage LLC // https://www.iana.org/domains/root/db/extraspace.html extraspace // fage : Fage International S.A. // https://www.iana.org/domains/root/db/fage.html fage // fail : Binky Moon, LLC // https://www.iana.org/domains/root/db/fail.html fail // fairwinds : FairWinds Partners, LLC // https://www.iana.org/domains/root/db/fairwinds.html fairwinds // faith : dot Faith Limited // https://www.iana.org/domains/root/db/faith.html faith // family : Dog Beach, LLC // https://www.iana.org/domains/root/db/family.html family // fan : Dog Beach, LLC // https://www.iana.org/domains/root/db/fan.html fan // fans : ZDNS International Limited // https://www.iana.org/domains/root/db/fans.html fans // farm : Binky Moon, LLC // https://www.iana.org/domains/root/db/farm.html farm // farmers : Farmers Insurance Exchange // https://www.iana.org/domains/root/db/farmers.html farmers // fashion : Registry Services, LLC // https://www.iana.org/domains/root/db/fashion.html fashion // fast : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/fast.html fast // fedex : Federal Express Corporation // https://www.iana.org/domains/root/db/fedex.html fedex // feedback : Top Level Spectrum, Inc. // https://www.iana.org/domains/root/db/feedback.html feedback // ferrari : Fiat Chrysler Automobiles N.V. // https://www.iana.org/domains/root/db/ferrari.html ferrari // ferrero : Ferrero Trading Lux S.A. // https://www.iana.org/domains/root/db/ferrero.html ferrero // fidelity : Fidelity Brokerage Services LLC // https://www.iana.org/domains/root/db/fidelity.html fidelity // fido : Rogers Communications Canada Inc. // https://www.iana.org/domains/root/db/fido.html fido // film : Motion Picture Domain Registry Pty Ltd // https://www.iana.org/domains/root/db/film.html film // final : Núcleo de Informação e Coordenação do Ponto BR - NIC.br // https://www.iana.org/domains/root/db/final.html final // finance : Binky Moon, LLC // https://www.iana.org/domains/root/db/finance.html finance // financial : Binky Moon, LLC // https://www.iana.org/domains/root/db/financial.html financial // fire : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/fire.html fire // firestone : Bridgestone Licensing Services, Inc // https://www.iana.org/domains/root/db/firestone.html firestone // firmdale : Firmdale Holdings Limited // https://www.iana.org/domains/root/db/firmdale.html firmdale // fish : Binky Moon, LLC // https://www.iana.org/domains/root/db/fish.html fish // fishing : Registry Services, LLC // https://www.iana.org/domains/root/db/fishing.html fishing // fit : Registry Services, LLC // https://www.iana.org/domains/root/db/fit.html fit // fitness : Binky Moon, LLC // https://www.iana.org/domains/root/db/fitness.html fitness // flickr : Flickr, Inc. // https://www.iana.org/domains/root/db/flickr.html flickr // flights : Binky Moon, LLC // https://www.iana.org/domains/root/db/flights.html flights // flir : FLIR Systems, Inc. // https://www.iana.org/domains/root/db/flir.html flir // florist : Binky Moon, LLC // https://www.iana.org/domains/root/db/florist.html florist // flowers : XYZ.COM LLC // https://www.iana.org/domains/root/db/flowers.html flowers // fly : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/fly.html fly // foo : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/foo.html foo // food : Internet Naming Company LLC // https://www.iana.org/domains/root/db/food.html food // football : Binky Moon, LLC // https://www.iana.org/domains/root/db/football.html football // ford : Ford Motor Company // https://www.iana.org/domains/root/db/ford.html ford // forex : Dog Beach, LLC // https://www.iana.org/domains/root/db/forex.html forex // forsale : Dog Beach, LLC // https://www.iana.org/domains/root/db/forsale.html forsale // forum : Waterford Limited // https://www.iana.org/domains/root/db/forum.html forum // foundation : Public Interest Registry // https://www.iana.org/domains/root/db/foundation.html foundation // fox : FOX Registry, LLC // https://www.iana.org/domains/root/db/fox.html fox // free : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/free.html free // fresenius : Fresenius Immobilien-Verwaltungs-GmbH // https://www.iana.org/domains/root/db/fresenius.html fresenius // frl : FRLregistry B.V. // https://www.iana.org/domains/root/db/frl.html frl // frogans : OP3FT // https://www.iana.org/domains/root/db/frogans.html frogans // frontier : Frontier Communications Corporation // https://www.iana.org/domains/root/db/frontier.html frontier // ftr : Frontier Communications Corporation // https://www.iana.org/domains/root/db/ftr.html ftr // fujitsu : Fujitsu Limited // https://www.iana.org/domains/root/db/fujitsu.html fujitsu // fun : Radix Technologies Inc. // https://www.iana.org/domains/root/db/fun.html fun // fund : Binky Moon, LLC // https://www.iana.org/domains/root/db/fund.html fund // furniture : Binky Moon, LLC // https://www.iana.org/domains/root/db/furniture.html furniture // futbol : Dog Beach, LLC // https://www.iana.org/domains/root/db/futbol.html futbol // fyi : Binky Moon, LLC // https://www.iana.org/domains/root/db/fyi.html fyi // gal : Asociación puntoGAL // https://www.iana.org/domains/root/db/gal.html gal // gallery : Binky Moon, LLC // https://www.iana.org/domains/root/db/gallery.html gallery // gallo : Gallo Vineyards, Inc. // https://www.iana.org/domains/root/db/gallo.html gallo // gallup : Gallup, Inc. // https://www.iana.org/domains/root/db/gallup.html gallup // game : XYZ.COM LLC // https://www.iana.org/domains/root/db/game.html game // games : Dog Beach, LLC // https://www.iana.org/domains/root/db/games.html games // gap : The Gap, Inc. // https://www.iana.org/domains/root/db/gap.html gap // garden : Registry Services, LLC // https://www.iana.org/domains/root/db/garden.html garden // gay : Registry Services, LLC // https://www.iana.org/domains/root/db/gay.html gay // gbiz : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/gbiz.html gbiz // gdn : Joint Stock Company "Navigation-information systems" // https://www.iana.org/domains/root/db/gdn.html gdn // gea : GEA Group Aktiengesellschaft // https://www.iana.org/domains/root/db/gea.html gea // gent : Easyhost BV // https://www.iana.org/domains/root/db/gent.html gent // genting : Resorts World Inc Pte. Ltd. // https://www.iana.org/domains/root/db/genting.html genting // george : Wal-Mart Stores, Inc. // https://www.iana.org/domains/root/db/george.html george // ggee : GMO Internet, Inc. // https://www.iana.org/domains/root/db/ggee.html ggee // gift : DotGift, LLC // https://www.iana.org/domains/root/db/gift.html gift // gifts : Binky Moon, LLC // https://www.iana.org/domains/root/db/gifts.html gifts // gives : Public Interest Registry // https://www.iana.org/domains/root/db/gives.html gives // giving : Public Interest Registry // https://www.iana.org/domains/root/db/giving.html giving // glass : Binky Moon, LLC // https://www.iana.org/domains/root/db/glass.html glass // gle : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/gle.html gle // global : Identity Digital Limited // https://www.iana.org/domains/root/db/global.html global // globo : Globo Comunicação e Participações S.A // https://www.iana.org/domains/root/db/globo.html globo // gmail : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/gmail.html gmail // gmbh : Binky Moon, LLC // https://www.iana.org/domains/root/db/gmbh.html gmbh // gmo : GMO Internet, Inc. // https://www.iana.org/domains/root/db/gmo.html gmo // gmx : 1&1 Mail & Media GmbH // https://www.iana.org/domains/root/db/gmx.html gmx // godaddy : Go Daddy East, LLC // https://www.iana.org/domains/root/db/godaddy.html godaddy // gold : Binky Moon, LLC // https://www.iana.org/domains/root/db/gold.html gold // goldpoint : YODOBASHI CAMERA CO.,LTD. // https://www.iana.org/domains/root/db/goldpoint.html goldpoint // golf : Binky Moon, LLC // https://www.iana.org/domains/root/db/golf.html golf // goo : NTT DOCOMO, INC. // https://www.iana.org/domains/root/db/goo.html goo // goodyear : The Goodyear Tire & Rubber Company // https://www.iana.org/domains/root/db/goodyear.html goodyear // goog : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/goog.html goog // google : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/google.html google // gop : Republican State Leadership Committee, Inc. // https://www.iana.org/domains/root/db/gop.html gop // got : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/got.html got // grainger : Grainger Registry Services, LLC // https://www.iana.org/domains/root/db/grainger.html grainger // graphics : Binky Moon, LLC // https://www.iana.org/domains/root/db/graphics.html graphics // gratis : Binky Moon, LLC // https://www.iana.org/domains/root/db/gratis.html gratis // green : Identity Digital Limited // https://www.iana.org/domains/root/db/green.html green // gripe : Binky Moon, LLC // https://www.iana.org/domains/root/db/gripe.html gripe // grocery : Wal-Mart Stores, Inc. // https://www.iana.org/domains/root/db/grocery.html grocery // group : Binky Moon, LLC // https://www.iana.org/domains/root/db/group.html group // gucci : Guccio Gucci S.p.a. // https://www.iana.org/domains/root/db/gucci.html gucci // guge : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/guge.html guge // guide : Binky Moon, LLC // https://www.iana.org/domains/root/db/guide.html guide // guitars : XYZ.COM LLC // https://www.iana.org/domains/root/db/guitars.html guitars // guru : Binky Moon, LLC // https://www.iana.org/domains/root/db/guru.html guru // hair : XYZ.COM LLC // https://www.iana.org/domains/root/db/hair.html hair // hamburg : Hamburg Top-Level-Domain GmbH // https://www.iana.org/domains/root/db/hamburg.html hamburg // hangout : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/hangout.html hangout // haus : Dog Beach, LLC // https://www.iana.org/domains/root/db/haus.html haus // hbo : HBO Registry Services, Inc. // https://www.iana.org/domains/root/db/hbo.html hbo // hdfc : HDFC BANK LIMITED // https://www.iana.org/domains/root/db/hdfc.html hdfc // hdfcbank : HDFC BANK LIMITED // https://www.iana.org/domains/root/db/hdfcbank.html hdfcbank // health : Registry Services, LLC // https://www.iana.org/domains/root/db/health.html health // healthcare : Binky Moon, LLC // https://www.iana.org/domains/root/db/healthcare.html healthcare // help : Innovation service Limited // https://www.iana.org/domains/root/db/help.html help // helsinki : City of Helsinki // https://www.iana.org/domains/root/db/helsinki.html helsinki // here : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/here.html here // hermes : HERMES INTERNATIONAL // https://www.iana.org/domains/root/db/hermes.html hermes // hiphop : Dot Hip Hop, LLC // https://www.iana.org/domains/root/db/hiphop.html hiphop // hisamitsu : Hisamitsu Pharmaceutical Co.,Inc. // https://www.iana.org/domains/root/db/hisamitsu.html hisamitsu // hitachi : Hitachi, Ltd. // https://www.iana.org/domains/root/db/hitachi.html hitachi // hiv : Internet Naming Company LLC // https://www.iana.org/domains/root/db/hiv.html hiv // hkt : PCCW-HKT DataCom Services Limited // https://www.iana.org/domains/root/db/hkt.html hkt // hockey : Binky Moon, LLC // https://www.iana.org/domains/root/db/hockey.html hockey // holdings : Binky Moon, LLC // https://www.iana.org/domains/root/db/holdings.html holdings // holiday : Binky Moon, LLC // https://www.iana.org/domains/root/db/holiday.html holiday // homedepot : Home Depot Product Authority, LLC // https://www.iana.org/domains/root/db/homedepot.html homedepot // homegoods : The TJX Companies, Inc. // https://www.iana.org/domains/root/db/homegoods.html homegoods // homes : XYZ.COM LLC // https://www.iana.org/domains/root/db/homes.html homes // homesense : The TJX Companies, Inc. // https://www.iana.org/domains/root/db/homesense.html homesense // honda : Honda Motor Co., Ltd. // https://www.iana.org/domains/root/db/honda.html honda // horse : Registry Services, LLC // https://www.iana.org/domains/root/db/horse.html horse // hospital : Binky Moon, LLC // https://www.iana.org/domains/root/db/hospital.html hospital // host : Radix Technologies Inc. // https://www.iana.org/domains/root/db/host.html host // hosting : XYZ.COM LLC // https://www.iana.org/domains/root/db/hosting.html hosting // hot : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/hot.html hot // hotels : Booking.com B.V. // https://www.iana.org/domains/root/db/hotels.html hotels // hotmail : Microsoft Corporation // https://www.iana.org/domains/root/db/hotmail.html hotmail // house : Binky Moon, LLC // https://www.iana.org/domains/root/db/house.html house // how : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/how.html how // hsbc : HSBC Global Services (UK) Limited // https://www.iana.org/domains/root/db/hsbc.html hsbc // hughes : Hughes Satellite Systems Corporation // https://www.iana.org/domains/root/db/hughes.html hughes // hyatt : Hyatt GTLD, L.L.C. // https://www.iana.org/domains/root/db/hyatt.html hyatt // hyundai : Hyundai Motor Company // https://www.iana.org/domains/root/db/hyundai.html hyundai // ibm : International Business Machines Corporation // https://www.iana.org/domains/root/db/ibm.html ibm // icbc : Industrial and Commercial Bank of China Limited // https://www.iana.org/domains/root/db/icbc.html icbc // ice : IntercontinentalExchange, Inc. // https://www.iana.org/domains/root/db/ice.html ice // icu : ShortDot SA // https://www.iana.org/domains/root/db/icu.html icu // ieee : IEEE Global LLC // https://www.iana.org/domains/root/db/ieee.html ieee // ifm : ifm electronic gmbh // https://www.iana.org/domains/root/db/ifm.html ifm // ikano : Ikano S.A. // https://www.iana.org/domains/root/db/ikano.html ikano // imamat : Fondation Aga Khan (Aga Khan Foundation) // https://www.iana.org/domains/root/db/imamat.html imamat // imdb : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/imdb.html imdb // immo : Binky Moon, LLC // https://www.iana.org/domains/root/db/immo.html immo // immobilien : Dog Beach, LLC // https://www.iana.org/domains/root/db/immobilien.html immobilien // inc : Intercap Registry Inc. // https://www.iana.org/domains/root/db/inc.html inc // industries : Binky Moon, LLC // https://www.iana.org/domains/root/db/industries.html industries // infiniti : NISSAN MOTOR CO., LTD. // https://www.iana.org/domains/root/db/infiniti.html infiniti // ing : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/ing.html ing // ink : Registry Services, LLC // https://www.iana.org/domains/root/db/ink.html ink // institute : Binky Moon, LLC // https://www.iana.org/domains/root/db/institute.html institute // insurance : fTLD Registry Services LLC // https://www.iana.org/domains/root/db/insurance.html insurance // insure : Binky Moon, LLC // https://www.iana.org/domains/root/db/insure.html insure // international : Binky Moon, LLC // https://www.iana.org/domains/root/db/international.html international // intuit : Intuit Administrative Services, Inc. // https://www.iana.org/domains/root/db/intuit.html intuit // investments : Binky Moon, LLC // https://www.iana.org/domains/root/db/investments.html investments // ipiranga : Ipiranga Produtos de Petroleo S.A. // https://www.iana.org/domains/root/db/ipiranga.html ipiranga // irish : Binky Moon, LLC // https://www.iana.org/domains/root/db/irish.html irish // ismaili : Fondation Aga Khan (Aga Khan Foundation) // https://www.iana.org/domains/root/db/ismaili.html ismaili // ist : Istanbul Metropolitan Municipality // https://www.iana.org/domains/root/db/ist.html ist // istanbul : Istanbul Metropolitan Municipality // https://www.iana.org/domains/root/db/istanbul.html istanbul // itau : Itau Unibanco Holding S.A. // https://www.iana.org/domains/root/db/itau.html itau // itv : ITV Services Limited // https://www.iana.org/domains/root/db/itv.html itv // jaguar : Jaguar Land Rover Ltd // https://www.iana.org/domains/root/db/jaguar.html jaguar // java : Oracle Corporation // https://www.iana.org/domains/root/db/java.html java // jcb : JCB Co., Ltd. // https://www.iana.org/domains/root/db/jcb.html jcb // jeep : FCA US LLC. // https://www.iana.org/domains/root/db/jeep.html jeep // jetzt : Binky Moon, LLC // https://www.iana.org/domains/root/db/jetzt.html jetzt // jewelry : Binky Moon, LLC // https://www.iana.org/domains/root/db/jewelry.html jewelry // jio : Reliance Industries Limited // https://www.iana.org/domains/root/db/jio.html jio // jll : Jones Lang LaSalle Incorporated // https://www.iana.org/domains/root/db/jll.html jll // jmp : Matrix IP LLC // https://www.iana.org/domains/root/db/jmp.html jmp // jnj : Johnson & Johnson Services, Inc. // https://www.iana.org/domains/root/db/jnj.html jnj // joburg : ZA Central Registry NPC trading as ZA Central Registry // https://www.iana.org/domains/root/db/joburg.html joburg // jot : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/jot.html jot // joy : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/joy.html joy // jpmorgan : JPMorgan Chase Bank, National Association // https://www.iana.org/domains/root/db/jpmorgan.html jpmorgan // jprs : Japan Registry Services Co., Ltd. // https://www.iana.org/domains/root/db/jprs.html jprs // juegos : Dog Beach, LLC // https://www.iana.org/domains/root/db/juegos.html juegos // juniper : JUNIPER NETWORKS, INC. // https://www.iana.org/domains/root/db/juniper.html juniper // kaufen : Dog Beach, LLC // https://www.iana.org/domains/root/db/kaufen.html kaufen // kddi : KDDI CORPORATION // https://www.iana.org/domains/root/db/kddi.html kddi // kerryhotels : Kerry Trading Co. Limited // https://www.iana.org/domains/root/db/kerryhotels.html kerryhotels // kerrylogistics : Kerry Trading Co. Limited // https://www.iana.org/domains/root/db/kerrylogistics.html kerrylogistics // kerryproperties : Kerry Trading Co. Limited // https://www.iana.org/domains/root/db/kerryproperties.html kerryproperties // kfh : Kuwait Finance House // https://www.iana.org/domains/root/db/kfh.html kfh // kia : KIA MOTORS CORPORATION // https://www.iana.org/domains/root/db/kia.html kia // kids : DotKids Foundation Limited // https://www.iana.org/domains/root/db/kids.html kids // kim : Identity Digital Limited // https://www.iana.org/domains/root/db/kim.html kim // kindle : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/kindle.html kindle // kitchen : Binky Moon, LLC // https://www.iana.org/domains/root/db/kitchen.html kitchen // kiwi : DOT KIWI LIMITED // https://www.iana.org/domains/root/db/kiwi.html kiwi // koeln : dotKoeln GmbH // https://www.iana.org/domains/root/db/koeln.html koeln // komatsu : Komatsu Ltd. // https://www.iana.org/domains/root/db/komatsu.html komatsu // kosher : Kosher Marketing Assets LLC // https://www.iana.org/domains/root/db/kosher.html kosher // kpmg : KPMG International Cooperative (KPMG International Genossenschaft) // https://www.iana.org/domains/root/db/kpmg.html kpmg // kpn : Koninklijke KPN N.V. // https://www.iana.org/domains/root/db/kpn.html kpn // krd : KRG Department of Information Technology // https://www.iana.org/domains/root/db/krd.html krd // kred : KredTLD Pty Ltd // https://www.iana.org/domains/root/db/kred.html kred // kuokgroup : Kerry Trading Co. Limited // https://www.iana.org/domains/root/db/kuokgroup.html kuokgroup // kyoto : Academic Institution: Kyoto Jyoho Gakuen // https://www.iana.org/domains/root/db/kyoto.html kyoto // lacaixa : Fundación Bancaria Caixa d’Estalvis i Pensions de Barcelona, “la Caixa” // https://www.iana.org/domains/root/db/lacaixa.html lacaixa // lamborghini : Automobili Lamborghini S.p.A. // https://www.iana.org/domains/root/db/lamborghini.html lamborghini // lamer : The Estée Lauder Companies Inc. // https://www.iana.org/domains/root/db/lamer.html lamer // lancaster : LANCASTER // https://www.iana.org/domains/root/db/lancaster.html lancaster // land : Binky Moon, LLC // https://www.iana.org/domains/root/db/land.html land // landrover : Jaguar Land Rover Ltd // https://www.iana.org/domains/root/db/landrover.html landrover // lanxess : LANXESS Corporation // https://www.iana.org/domains/root/db/lanxess.html lanxess // lasalle : Jones Lang LaSalle Incorporated // https://www.iana.org/domains/root/db/lasalle.html lasalle // lat : XYZ.COM LLC // https://www.iana.org/domains/root/db/lat.html lat // latino : Dish DBS Corporation // https://www.iana.org/domains/root/db/latino.html latino // latrobe : La Trobe University // https://www.iana.org/domains/root/db/latrobe.html latrobe // law : Registry Services, LLC // https://www.iana.org/domains/root/db/law.html law // lawyer : Dog Beach, LLC // https://www.iana.org/domains/root/db/lawyer.html lawyer // lds : IRI Domain Management, LLC // https://www.iana.org/domains/root/db/lds.html lds // lease : Binky Moon, LLC // https://www.iana.org/domains/root/db/lease.html lease // leclerc : A.C.D. LEC Association des Centres Distributeurs Edouard Leclerc // https://www.iana.org/domains/root/db/leclerc.html leclerc // lefrak : LeFrak Organization, Inc. // https://www.iana.org/domains/root/db/lefrak.html lefrak // legal : Binky Moon, LLC // https://www.iana.org/domains/root/db/legal.html legal // lego : LEGO Juris A/S // https://www.iana.org/domains/root/db/lego.html lego // lexus : TOYOTA MOTOR CORPORATION // https://www.iana.org/domains/root/db/lexus.html lexus // lgbt : Identity Digital Limited // https://www.iana.org/domains/root/db/lgbt.html lgbt // lidl : Schwarz Domains und Services GmbH & Co. KG // https://www.iana.org/domains/root/db/lidl.html lidl // life : Binky Moon, LLC // https://www.iana.org/domains/root/db/life.html life // lifeinsurance : American Council of Life Insurers // https://www.iana.org/domains/root/db/lifeinsurance.html lifeinsurance // lifestyle : Internet Naming Company LLC // https://www.iana.org/domains/root/db/lifestyle.html lifestyle // lighting : Binky Moon, LLC // https://www.iana.org/domains/root/db/lighting.html lighting // like : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/like.html like // lilly : Eli Lilly and Company // https://www.iana.org/domains/root/db/lilly.html lilly // limited : Binky Moon, LLC // https://www.iana.org/domains/root/db/limited.html limited // limo : Binky Moon, LLC // https://www.iana.org/domains/root/db/limo.html limo // lincoln : Ford Motor Company // https://www.iana.org/domains/root/db/lincoln.html lincoln // link : Nova Registry Ltd // https://www.iana.org/domains/root/db/link.html link // lipsy : Lipsy Ltd // https://www.iana.org/domains/root/db/lipsy.html lipsy // live : Dog Beach, LLC // https://www.iana.org/domains/root/db/live.html live // living : Internet Naming Company LLC // https://www.iana.org/domains/root/db/living.html living // llc : Identity Digital Limited // https://www.iana.org/domains/root/db/llc.html llc // llp : Intercap Registry Inc. // https://www.iana.org/domains/root/db/llp.html llp // loan : dot Loan Limited // https://www.iana.org/domains/root/db/loan.html loan // loans : Binky Moon, LLC // https://www.iana.org/domains/root/db/loans.html loans // locker : Orange Domains LLC // https://www.iana.org/domains/root/db/locker.html locker // locus : Locus Analytics LLC // https://www.iana.org/domains/root/db/locus.html locus // lol : XYZ.COM LLC // https://www.iana.org/domains/root/db/lol.html lol // london : Dot London Domains Limited // https://www.iana.org/domains/root/db/london.html london // lotte : Lotte Holdings Co., Ltd. // https://www.iana.org/domains/root/db/lotte.html lotte // lotto : Identity Digital Limited // https://www.iana.org/domains/root/db/lotto.html lotto // love : Waterford Limited // https://www.iana.org/domains/root/db/love.html love // lpl : LPL Holdings, Inc. // https://www.iana.org/domains/root/db/lpl.html lpl // lplfinancial : LPL Holdings, Inc. // https://www.iana.org/domains/root/db/lplfinancial.html lplfinancial // ltd : Binky Moon, LLC // https://www.iana.org/domains/root/db/ltd.html ltd // ltda : InterNetX, Corp // https://www.iana.org/domains/root/db/ltda.html ltda // lundbeck : H. Lundbeck A/S // https://www.iana.org/domains/root/db/lundbeck.html lundbeck // luxe : Registry Services, LLC // https://www.iana.org/domains/root/db/luxe.html luxe // luxury : Luxury Partners, LLC // https://www.iana.org/domains/root/db/luxury.html luxury // madrid : Comunidad de Madrid // https://www.iana.org/domains/root/db/madrid.html madrid // maif : Mutuelle Assurance Instituteur France (MAIF) // https://www.iana.org/domains/root/db/maif.html maif // maison : Binky Moon, LLC // https://www.iana.org/domains/root/db/maison.html maison // makeup : XYZ.COM LLC // https://www.iana.org/domains/root/db/makeup.html makeup // man : MAN Truck & Bus SE // https://www.iana.org/domains/root/db/man.html man // management : Binky Moon, LLC // https://www.iana.org/domains/root/db/management.html management // mango : PUNTO FA S.L. // https://www.iana.org/domains/root/db/mango.html mango // map : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/map.html map // market : Dog Beach, LLC // https://www.iana.org/domains/root/db/market.html market // marketing : Binky Moon, LLC // https://www.iana.org/domains/root/db/marketing.html marketing // markets : Dog Beach, LLC // https://www.iana.org/domains/root/db/markets.html markets // marriott : Marriott Worldwide Corporation // https://www.iana.org/domains/root/db/marriott.html marriott // marshalls : The TJX Companies, Inc. // https://www.iana.org/domains/root/db/marshalls.html marshalls // mattel : Mattel Sites, Inc. // https://www.iana.org/domains/root/db/mattel.html mattel // mba : Binky Moon, LLC // https://www.iana.org/domains/root/db/mba.html mba // mckinsey : McKinsey Holdings, Inc. // https://www.iana.org/domains/root/db/mckinsey.html mckinsey // med : Medistry LLC // https://www.iana.org/domains/root/db/med.html med // media : Binky Moon, LLC // https://www.iana.org/domains/root/db/media.html media // meet : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/meet.html meet // melbourne : The Crown in right of the State of Victoria, represented by its Department of State Development, Business and Innovation // https://www.iana.org/domains/root/db/melbourne.html melbourne // meme : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/meme.html meme // memorial : Dog Beach, LLC // https://www.iana.org/domains/root/db/memorial.html memorial // men : Exclusive Registry Limited // https://www.iana.org/domains/root/db/men.html men // menu : Dot Menu Registry, LLC // https://www.iana.org/domains/root/db/menu.html menu // merck : Merck Registry Holdings, Inc. // https://www.iana.org/domains/root/db/merck.html merck // merckmsd : MSD Registry Holdings, Inc. // https://www.iana.org/domains/root/db/merckmsd.html merckmsd // miami : Registry Services, LLC // https://www.iana.org/domains/root/db/miami.html miami // microsoft : Microsoft Corporation // https://www.iana.org/domains/root/db/microsoft.html microsoft // mini : Bayerische Motoren Werke Aktiengesellschaft // https://www.iana.org/domains/root/db/mini.html mini // mint : Intuit Administrative Services, Inc. // https://www.iana.org/domains/root/db/mint.html mint // mit : Massachusetts Institute of Technology // https://www.iana.org/domains/root/db/mit.html mit // mitsubishi : Mitsubishi Corporation // https://www.iana.org/domains/root/db/mitsubishi.html mitsubishi // mlb : MLB Advanced Media DH, LLC // https://www.iana.org/domains/root/db/mlb.html mlb // mls : The Canadian Real Estate Association // https://www.iana.org/domains/root/db/mls.html mls // mma : MMA IARD // https://www.iana.org/domains/root/db/mma.html mma // mobile : Dish DBS Corporation // https://www.iana.org/domains/root/db/mobile.html mobile // moda : Dog Beach, LLC // https://www.iana.org/domains/root/db/moda.html moda // moe : Interlink Systems Innovation Institute K.K. // https://www.iana.org/domains/root/db/moe.html moe // moi : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/moi.html moi // mom : XYZ.COM LLC // https://www.iana.org/domains/root/db/mom.html mom // monash : Monash University // https://www.iana.org/domains/root/db/monash.html monash // money : Binky Moon, LLC // https://www.iana.org/domains/root/db/money.html money // monster : XYZ.COM LLC // https://www.iana.org/domains/root/db/monster.html monster // mormon : IRI Domain Management, LLC // https://www.iana.org/domains/root/db/mormon.html mormon // mortgage : Dog Beach, LLC // https://www.iana.org/domains/root/db/mortgage.html mortgage // moscow : Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) // https://www.iana.org/domains/root/db/moscow.html moscow // moto : Motorola Trademark Holdings, LLC // https://www.iana.org/domains/root/db/moto.html moto // motorcycles : XYZ.COM LLC // https://www.iana.org/domains/root/db/motorcycles.html motorcycles // mov : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/mov.html mov // movie : Binky Moon, LLC // https://www.iana.org/domains/root/db/movie.html movie // msd : MSD Registry Holdings, Inc. // https://www.iana.org/domains/root/db/msd.html msd // mtn : MTN Dubai Limited // https://www.iana.org/domains/root/db/mtn.html mtn // mtr : MTR Corporation Limited // https://www.iana.org/domains/root/db/mtr.html mtr // music : DotMusic Limited // https://www.iana.org/domains/root/db/music.html music // nab : National Australia Bank Limited // https://www.iana.org/domains/root/db/nab.html nab // nagoya : GMO Registry, Inc. // https://www.iana.org/domains/root/db/nagoya.html nagoya // navy : Dog Beach, LLC // https://www.iana.org/domains/root/db/navy.html navy // nba : NBA REGISTRY, LLC // https://www.iana.org/domains/root/db/nba.html nba // nec : NEC Corporation // https://www.iana.org/domains/root/db/nec.html nec // netbank : COMMONWEALTH BANK OF AUSTRALIA // https://www.iana.org/domains/root/db/netbank.html netbank // netflix : Netflix, Inc. // https://www.iana.org/domains/root/db/netflix.html netflix // network : Binky Moon, LLC // https://www.iana.org/domains/root/db/network.html network // neustar : NeuStar, Inc. // https://www.iana.org/domains/root/db/neustar.html neustar // new : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/new.html new // news : Dog Beach, LLC // https://www.iana.org/domains/root/db/news.html news // next : Next plc // https://www.iana.org/domains/root/db/next.html next // nextdirect : Next plc // https://www.iana.org/domains/root/db/nextdirect.html nextdirect // nexus : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/nexus.html nexus // nfl : NFL Reg Ops LLC // https://www.iana.org/domains/root/db/nfl.html nfl // ngo : Public Interest Registry // https://www.iana.org/domains/root/db/ngo.html ngo // nhk : Japan Broadcasting Corporation (NHK) // https://www.iana.org/domains/root/db/nhk.html nhk // nico : DWANGO Co., Ltd. // https://www.iana.org/domains/root/db/nico.html nico // nike : NIKE, Inc. // https://www.iana.org/domains/root/db/nike.html nike // nikon : NIKON CORPORATION // https://www.iana.org/domains/root/db/nikon.html nikon // ninja : Dog Beach, LLC // https://www.iana.org/domains/root/db/ninja.html ninja // nissan : NISSAN MOTOR CO., LTD. // https://www.iana.org/domains/root/db/nissan.html nissan // nissay : Nippon Life Insurance Company // https://www.iana.org/domains/root/db/nissay.html nissay // nokia : Nokia Corporation // https://www.iana.org/domains/root/db/nokia.html nokia // norton : Gen Digital Inc. // https://www.iana.org/domains/root/db/norton.html norton // now : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/now.html now // nowruz : Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. // https://www.iana.org/domains/root/db/nowruz.html nowruz // nowtv : Starbucks (HK) Limited // https://www.iana.org/domains/root/db/nowtv.html nowtv // nra : NRA Holdings Company, INC. // https://www.iana.org/domains/root/db/nra.html nra // nrw : Minds + Machines GmbH // https://www.iana.org/domains/root/db/nrw.html nrw // ntt : NIPPON TELEGRAPH AND TELEPHONE CORPORATION // https://www.iana.org/domains/root/db/ntt.html ntt // nyc : The City of New York by and through the New York City Department of Information Technology & Telecommunications // https://www.iana.org/domains/root/db/nyc.html nyc // obi : OBI Group Holding SE & Co. KGaA // https://www.iana.org/domains/root/db/obi.html obi // observer : Fegistry, LLC // https://www.iana.org/domains/root/db/observer.html observer // office : Microsoft Corporation // https://www.iana.org/domains/root/db/office.html office // okinawa : BRregistry, Inc. // https://www.iana.org/domains/root/db/okinawa.html okinawa // olayan : Competrol (Luxembourg) Sarl // https://www.iana.org/domains/root/db/olayan.html olayan // olayangroup : Competrol (Luxembourg) Sarl // https://www.iana.org/domains/root/db/olayangroup.html olayangroup // ollo : Dish DBS Corporation // https://www.iana.org/domains/root/db/ollo.html ollo // omega : The Swatch Group Ltd // https://www.iana.org/domains/root/db/omega.html omega // one : One.com A/S // https://www.iana.org/domains/root/db/one.html one // ong : Public Interest Registry // https://www.iana.org/domains/root/db/ong.html ong // onl : iRegistry GmbH // https://www.iana.org/domains/root/db/onl.html onl // online : Radix Technologies Inc. // https://www.iana.org/domains/root/db/online.html online // ooo : INFIBEAM AVENUES LIMITED // https://www.iana.org/domains/root/db/ooo.html ooo // open : American Express Travel Related Services Company, Inc. // https://www.iana.org/domains/root/db/open.html open // oracle : Oracle Corporation // https://www.iana.org/domains/root/db/oracle.html oracle // orange : Orange Brand Services Limited // https://www.iana.org/domains/root/db/orange.html orange // organic : Identity Digital Limited // https://www.iana.org/domains/root/db/organic.html organic // origins : The Estée Lauder Companies Inc. // https://www.iana.org/domains/root/db/origins.html origins // osaka : Osaka Registry Co., Ltd. // https://www.iana.org/domains/root/db/osaka.html osaka // otsuka : Otsuka Holdings Co., Ltd. // https://www.iana.org/domains/root/db/otsuka.html otsuka // ott : Dish DBS Corporation // https://www.iana.org/domains/root/db/ott.html ott // ovh : MédiaBC // https://www.iana.org/domains/root/db/ovh.html ovh // page : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/page.html page // panasonic : Panasonic Holdings Corporation // https://www.iana.org/domains/root/db/panasonic.html panasonic // paris : City of Paris // https://www.iana.org/domains/root/db/paris.html paris // pars : Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. // https://www.iana.org/domains/root/db/pars.html pars // partners : Binky Moon, LLC // https://www.iana.org/domains/root/db/partners.html partners // parts : Binky Moon, LLC // https://www.iana.org/domains/root/db/parts.html parts // party : Blue Sky Registry Limited // https://www.iana.org/domains/root/db/party.html party // pay : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/pay.html pay // pccw : PCCW Enterprises Limited // https://www.iana.org/domains/root/db/pccw.html pccw // pet : Identity Digital Limited // https://www.iana.org/domains/root/db/pet.html pet // pfizer : Pfizer Inc. // https://www.iana.org/domains/root/db/pfizer.html pfizer // pharmacy : National Association of Boards of Pharmacy // https://www.iana.org/domains/root/db/pharmacy.html pharmacy // phd : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/phd.html phd // philips : Koninklijke Philips N.V. // https://www.iana.org/domains/root/db/philips.html philips // phone : Dish DBS Corporation // https://www.iana.org/domains/root/db/phone.html phone // photo : Registry Services, LLC // https://www.iana.org/domains/root/db/photo.html photo // photography : Binky Moon, LLC // https://www.iana.org/domains/root/db/photography.html photography // photos : Binky Moon, LLC // https://www.iana.org/domains/root/db/photos.html photos // physio : PhysBiz Pty Ltd // https://www.iana.org/domains/root/db/physio.html physio // pics : XYZ.COM LLC // https://www.iana.org/domains/root/db/pics.html pics // pictet : Pictet Europe S.A. // https://www.iana.org/domains/root/db/pictet.html pictet // pictures : Binky Moon, LLC // https://www.iana.org/domains/root/db/pictures.html pictures // pid : Top Level Spectrum, Inc. // https://www.iana.org/domains/root/db/pid.html pid // pin : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/pin.html pin // ping : Ping Registry Provider, Inc. // https://www.iana.org/domains/root/db/ping.html ping // pink : Identity Digital Limited // https://www.iana.org/domains/root/db/pink.html pink // pioneer : Pioneer Corporation // https://www.iana.org/domains/root/db/pioneer.html pioneer // pizza : Binky Moon, LLC // https://www.iana.org/domains/root/db/pizza.html pizza // place : Binky Moon, LLC // https://www.iana.org/domains/root/db/place.html place // play : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/play.html play // playstation : Sony Interactive Entertainment Inc. // https://www.iana.org/domains/root/db/playstation.html playstation // plumbing : Binky Moon, LLC // https://www.iana.org/domains/root/db/plumbing.html plumbing // plus : Binky Moon, LLC // https://www.iana.org/domains/root/db/plus.html plus // pnc : PNC Domain Co., LLC // https://www.iana.org/domains/root/db/pnc.html pnc // pohl : Deutsche Vermögensberatung Aktiengesellschaft DVAG // https://www.iana.org/domains/root/db/pohl.html pohl // poker : Identity Digital Limited // https://www.iana.org/domains/root/db/poker.html poker // politie : Politie Nederland // https://www.iana.org/domains/root/db/politie.html politie // porn : ICM Registry PN LLC // https://www.iana.org/domains/root/db/porn.html porn // pramerica : Prudential Financial, Inc. // https://www.iana.org/domains/root/db/pramerica.html pramerica // praxi : Praxi S.p.A. // https://www.iana.org/domains/root/db/praxi.html praxi // press : Radix Technologies Inc. // https://www.iana.org/domains/root/db/press.html press // prime : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/prime.html prime // prod : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/prod.html prod // productions : Binky Moon, LLC // https://www.iana.org/domains/root/db/productions.html productions // prof : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/prof.html prof // progressive : Progressive Casualty Insurance Company // https://www.iana.org/domains/root/db/progressive.html progressive // promo : Identity Digital Limited // https://www.iana.org/domains/root/db/promo.html promo // properties : Binky Moon, LLC // https://www.iana.org/domains/root/db/properties.html properties // property : Digital Property Infrastructure Limited // https://www.iana.org/domains/root/db/property.html property // protection : XYZ.COM LLC // https://www.iana.org/domains/root/db/protection.html protection // pru : Prudential Financial, Inc. // https://www.iana.org/domains/root/db/pru.html pru // prudential : Prudential Financial, Inc. // https://www.iana.org/domains/root/db/prudential.html prudential // pub : Dog Beach, LLC // https://www.iana.org/domains/root/db/pub.html pub // pwc : PricewaterhouseCoopers LLP // https://www.iana.org/domains/root/db/pwc.html pwc // qpon : dotQPON LLC // https://www.iana.org/domains/root/db/qpon.html qpon // quebec : PointQuébec Inc // https://www.iana.org/domains/root/db/quebec.html quebec // quest : XYZ.COM LLC // https://www.iana.org/domains/root/db/quest.html quest // racing : Premier Registry Limited // https://www.iana.org/domains/root/db/racing.html racing // radio : European Broadcasting Union (EBU) // https://www.iana.org/domains/root/db/radio.html radio // read : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/read.html read // realestate : dotRealEstate LLC // https://www.iana.org/domains/root/db/realestate.html realestate // realtor : Real Estate Domains LLC // https://www.iana.org/domains/root/db/realtor.html realtor // realty : Waterford Limited // https://www.iana.org/domains/root/db/realty.html realty // recipes : Binky Moon, LLC // https://www.iana.org/domains/root/db/recipes.html recipes // red : Identity Digital Limited // https://www.iana.org/domains/root/db/red.html red // redstone : Redstone Haute Couture Co., Ltd. // https://www.iana.org/domains/root/db/redstone.html redstone // redumbrella : Travelers TLD, LLC // https://www.iana.org/domains/root/db/redumbrella.html redumbrella // rehab : Dog Beach, LLC // https://www.iana.org/domains/root/db/rehab.html rehab // reise : Binky Moon, LLC // https://www.iana.org/domains/root/db/reise.html reise // reisen : Binky Moon, LLC // https://www.iana.org/domains/root/db/reisen.html reisen // reit : National Association of Real Estate Investment Trusts, Inc. // https://www.iana.org/domains/root/db/reit.html reit // reliance : Reliance Industries Limited // https://www.iana.org/domains/root/db/reliance.html reliance // ren : ZDNS International Limited // https://www.iana.org/domains/root/db/ren.html ren // rent : XYZ.COM LLC // https://www.iana.org/domains/root/db/rent.html rent // rentals : Binky Moon, LLC // https://www.iana.org/domains/root/db/rentals.html rentals // repair : Binky Moon, LLC // https://www.iana.org/domains/root/db/repair.html repair // report : Binky Moon, LLC // https://www.iana.org/domains/root/db/report.html report // republican : Dog Beach, LLC // https://www.iana.org/domains/root/db/republican.html republican // rest : Punto 2012 Sociedad Anonima Promotora de Inversion de Capital Variable // https://www.iana.org/domains/root/db/rest.html rest // restaurant : Binky Moon, LLC // https://www.iana.org/domains/root/db/restaurant.html restaurant // review : dot Review Limited // https://www.iana.org/domains/root/db/review.html review // reviews : Dog Beach, LLC // https://www.iana.org/domains/root/db/reviews.html reviews // rexroth : Robert Bosch GMBH // https://www.iana.org/domains/root/db/rexroth.html rexroth // rich : iRegistry GmbH // https://www.iana.org/domains/root/db/rich.html rich // richardli : Pacific Century Asset Management (HK) Limited // https://www.iana.org/domains/root/db/richardli.html richardli // ricoh : Ricoh Company, Ltd. // https://www.iana.org/domains/root/db/ricoh.html ricoh // ril : Reliance Industries Limited // https://www.iana.org/domains/root/db/ril.html ril // rio : Empresa Municipal de Informática SA - IPLANRIO // https://www.iana.org/domains/root/db/rio.html rio // rip : Dog Beach, LLC // https://www.iana.org/domains/root/db/rip.html rip // rocks : Dog Beach, LLC // https://www.iana.org/domains/root/db/rocks.html rocks // rodeo : Registry Services, LLC // https://www.iana.org/domains/root/db/rodeo.html rodeo // rogers : Rogers Communications Canada Inc. // https://www.iana.org/domains/root/db/rogers.html rogers // room : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/room.html room // rsvp : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/rsvp.html rsvp // rugby : World Rugby Strategic Developments Limited // https://www.iana.org/domains/root/db/rugby.html rugby // ruhr : dotSaarland GmbH // https://www.iana.org/domains/root/db/ruhr.html ruhr // run : Binky Moon, LLC // https://www.iana.org/domains/root/db/run.html run // rwe : RWE AG // https://www.iana.org/domains/root/db/rwe.html rwe // ryukyu : BRregistry, Inc. // https://www.iana.org/domains/root/db/ryukyu.html ryukyu // saarland : dotSaarland GmbH // https://www.iana.org/domains/root/db/saarland.html saarland // safe : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/safe.html safe // safety : Safety Registry Services, LLC. // https://www.iana.org/domains/root/db/safety.html safety // sakura : SAKURA Internet Inc. // https://www.iana.org/domains/root/db/sakura.html sakura // sale : Dog Beach, LLC // https://www.iana.org/domains/root/db/sale.html sale // salon : Binky Moon, LLC // https://www.iana.org/domains/root/db/salon.html salon // samsclub : Wal-Mart Stores, Inc. // https://www.iana.org/domains/root/db/samsclub.html samsclub // samsung : SAMSUNG SDS CO., LTD // https://www.iana.org/domains/root/db/samsung.html samsung // sandvik : Sandvik AB // https://www.iana.org/domains/root/db/sandvik.html sandvik // sandvikcoromant : Sandvik AB // https://www.iana.org/domains/root/db/sandvikcoromant.html sandvikcoromant // sanofi : Sanofi // https://www.iana.org/domains/root/db/sanofi.html sanofi // sap : SAP AG // https://www.iana.org/domains/root/db/sap.html sap // sarl : Binky Moon, LLC // https://www.iana.org/domains/root/db/sarl.html sarl // sas : Research IP LLC // https://www.iana.org/domains/root/db/sas.html sas // save : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/save.html save // saxo : Saxo Bank A/S // https://www.iana.org/domains/root/db/saxo.html saxo // sbi : STATE BANK OF INDIA // https://www.iana.org/domains/root/db/sbi.html sbi // sbs : ShortDot SA // https://www.iana.org/domains/root/db/sbs.html sbs // scb : The Siam Commercial Bank Public Company Limited ("SCB") // https://www.iana.org/domains/root/db/scb.html scb // schaeffler : Schaeffler Technologies AG & Co. KG // https://www.iana.org/domains/root/db/schaeffler.html schaeffler // schmidt : SCHMIDT GROUPE S.A.S. // https://www.iana.org/domains/root/db/schmidt.html schmidt // scholarships : Scholarships.com, LLC // https://www.iana.org/domains/root/db/scholarships.html scholarships // school : Binky Moon, LLC // https://www.iana.org/domains/root/db/school.html school // schule : Binky Moon, LLC // https://www.iana.org/domains/root/db/schule.html schule // schwarz : Schwarz Domains und Services GmbH & Co. KG // https://www.iana.org/domains/root/db/schwarz.html schwarz // science : dot Science Limited // https://www.iana.org/domains/root/db/science.html science // scot : Dot Scot Registry Limited // https://www.iana.org/domains/root/db/scot.html scot // search : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/search.html search // seat : SEAT, S.A. (Sociedad Unipersonal) // https://www.iana.org/domains/root/db/seat.html seat // secure : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/secure.html secure // security : XYZ.COM LLC // https://www.iana.org/domains/root/db/security.html security // seek : Seek Limited // https://www.iana.org/domains/root/db/seek.html seek // select : Registry Services, LLC // https://www.iana.org/domains/root/db/select.html select // sener : Sener Ingeniería y Sistemas, S.A. // https://www.iana.org/domains/root/db/sener.html sener // services : Binky Moon, LLC // https://www.iana.org/domains/root/db/services.html services // seven : Seven West Media Ltd // https://www.iana.org/domains/root/db/seven.html seven // sew : SEW-EURODRIVE GmbH & Co KG // https://www.iana.org/domains/root/db/sew.html sew // sex : ICM Registry SX LLC // https://www.iana.org/domains/root/db/sex.html sex // sexy : Internet Naming Company LLC // https://www.iana.org/domains/root/db/sexy.html sexy // sfr : Societe Francaise du Radiotelephone - SFR // https://www.iana.org/domains/root/db/sfr.html sfr // shangrila : Shangri‐La International Hotel Management Limited // https://www.iana.org/domains/root/db/shangrila.html shangrila // sharp : Sharp Corporation // https://www.iana.org/domains/root/db/sharp.html sharp // shell : Shell Information Technology International Inc // https://www.iana.org/domains/root/db/shell.html shell // shia : Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. // https://www.iana.org/domains/root/db/shia.html shia // shiksha : Identity Digital Limited // https://www.iana.org/domains/root/db/shiksha.html shiksha // shoes : Binky Moon, LLC // https://www.iana.org/domains/root/db/shoes.html shoes // shop : GMO Registry, Inc. // https://www.iana.org/domains/root/db/shop.html shop // shopping : Binky Moon, LLC // https://www.iana.org/domains/root/db/shopping.html shopping // shouji : Beijing Qihu Keji Co., Ltd. // https://www.iana.org/domains/root/db/shouji.html shouji // show : Binky Moon, LLC // https://www.iana.org/domains/root/db/show.html show // silk : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/silk.html silk // sina : Sina Corporation // https://www.iana.org/domains/root/db/sina.html sina // singles : Binky Moon, LLC // https://www.iana.org/domains/root/db/singles.html singles // site : Radix Technologies Inc. // https://www.iana.org/domains/root/db/site.html site // ski : Identity Digital Limited // https://www.iana.org/domains/root/db/ski.html ski // skin : XYZ.COM LLC // https://www.iana.org/domains/root/db/skin.html skin // sky : Sky UK Limited // https://www.iana.org/domains/root/db/sky.html sky // skype : Microsoft Corporation // https://www.iana.org/domains/root/db/skype.html skype // sling : DISH Technologies L.L.C. // https://www.iana.org/domains/root/db/sling.html sling // smart : Smart Communications, Inc. (SMART) // https://www.iana.org/domains/root/db/smart.html smart // smile : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/smile.html smile // sncf : Société Nationale SNCF // https://www.iana.org/domains/root/db/sncf.html sncf // soccer : Binky Moon, LLC // https://www.iana.org/domains/root/db/soccer.html soccer // social : Dog Beach, LLC // https://www.iana.org/domains/root/db/social.html social // softbank : SoftBank Group Corp. // https://www.iana.org/domains/root/db/softbank.html softbank // software : Dog Beach, LLC // https://www.iana.org/domains/root/db/software.html software // sohu : Sohu.com Limited // https://www.iana.org/domains/root/db/sohu.html sohu // solar : Binky Moon, LLC // https://www.iana.org/domains/root/db/solar.html solar // solutions : Binky Moon, LLC // https://www.iana.org/domains/root/db/solutions.html solutions // song : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/song.html song // sony : Sony Corporation // https://www.iana.org/domains/root/db/sony.html sony // soy : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/soy.html soy // spa : Asia Spa and Wellness Promotion Council Limited // https://www.iana.org/domains/root/db/spa.html spa // space : Radix Technologies Inc. // https://www.iana.org/domains/root/db/space.html space // sport : SportAccord // https://www.iana.org/domains/root/db/sport.html sport // spot : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/spot.html spot // srl : InterNetX, Corp // https://www.iana.org/domains/root/db/srl.html srl // stada : STADA Arzneimittel AG // https://www.iana.org/domains/root/db/stada.html stada // staples : Staples, Inc. // https://www.iana.org/domains/root/db/staples.html staples // star : Star India Private Limited // https://www.iana.org/domains/root/db/star.html star // statebank : STATE BANK OF INDIA // https://www.iana.org/domains/root/db/statebank.html statebank // statefarm : State Farm Mutual Automobile Insurance Company // https://www.iana.org/domains/root/db/statefarm.html statefarm // stc : Saudi Telecom Company // https://www.iana.org/domains/root/db/stc.html stc // stcgroup : Saudi Telecom Company // https://www.iana.org/domains/root/db/stcgroup.html stcgroup // stockholm : Stockholms kommun // https://www.iana.org/domains/root/db/stockholm.html stockholm // storage : XYZ.COM LLC // https://www.iana.org/domains/root/db/storage.html storage // store : Radix Technologies Inc. // https://www.iana.org/domains/root/db/store.html store // stream : dot Stream Limited // https://www.iana.org/domains/root/db/stream.html stream // studio : Dog Beach, LLC // https://www.iana.org/domains/root/db/studio.html studio // study : Registry Services, LLC // https://www.iana.org/domains/root/db/study.html study // style : Binky Moon, LLC // https://www.iana.org/domains/root/db/style.html style // sucks : Vox Populi Registry Ltd. // https://www.iana.org/domains/root/db/sucks.html sucks // supplies : Binky Moon, LLC // https://www.iana.org/domains/root/db/supplies.html supplies // supply : Binky Moon, LLC // https://www.iana.org/domains/root/db/supply.html supply // support : Binky Moon, LLC // https://www.iana.org/domains/root/db/support.html support // surf : Registry Services, LLC // https://www.iana.org/domains/root/db/surf.html surf // surgery : Binky Moon, LLC // https://www.iana.org/domains/root/db/surgery.html surgery // suzuki : SUZUKI MOTOR CORPORATION // https://www.iana.org/domains/root/db/suzuki.html suzuki // swatch : The Swatch Group Ltd // https://www.iana.org/domains/root/db/swatch.html swatch // swiss : Swiss Confederation // https://www.iana.org/domains/root/db/swiss.html swiss // sydney : State of New South Wales, Department of Premier and Cabinet // https://www.iana.org/domains/root/db/sydney.html sydney // systems : Binky Moon, LLC // https://www.iana.org/domains/root/db/systems.html systems // tab : Tabcorp Holdings Limited // https://www.iana.org/domains/root/db/tab.html tab // taipei : Taipei City Government // https://www.iana.org/domains/root/db/taipei.html taipei // talk : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/talk.html talk // taobao : Alibaba Group Holding Limited // https://www.iana.org/domains/root/db/taobao.html taobao // target : Target Domain Holdings, LLC // https://www.iana.org/domains/root/db/target.html target // tatamotors : Tata Motors Ltd // https://www.iana.org/domains/root/db/tatamotors.html tatamotors // tatar : Limited Liability Company "Coordination Center of Regional Domain of Tatarstan Republic" // https://www.iana.org/domains/root/db/tatar.html tatar // tattoo : Registry Services, LLC // https://www.iana.org/domains/root/db/tattoo.html tattoo // tax : Binky Moon, LLC // https://www.iana.org/domains/root/db/tax.html tax // taxi : Binky Moon, LLC // https://www.iana.org/domains/root/db/taxi.html taxi // tci : Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. // https://www.iana.org/domains/root/db/tci.html tci // tdk : TDK Corporation // https://www.iana.org/domains/root/db/tdk.html tdk // team : Binky Moon, LLC // https://www.iana.org/domains/root/db/team.html team // tech : Radix Technologies Inc. // https://www.iana.org/domains/root/db/tech.html tech // technology : Binky Moon, LLC // https://www.iana.org/domains/root/db/technology.html technology // temasek : Temasek Holdings (Private) Limited // https://www.iana.org/domains/root/db/temasek.html temasek // tennis : Binky Moon, LLC // https://www.iana.org/domains/root/db/tennis.html tennis // teva : Teva Pharmaceutical Industries Limited // https://www.iana.org/domains/root/db/teva.html teva // thd : Home Depot Product Authority, LLC // https://www.iana.org/domains/root/db/thd.html thd // theater : Binky Moon, LLC // https://www.iana.org/domains/root/db/theater.html theater // theatre : XYZ.COM LLC // https://www.iana.org/domains/root/db/theatre.html theatre // tiaa : Teachers Insurance and Annuity Association of America // https://www.iana.org/domains/root/db/tiaa.html tiaa // tickets : XYZ.COM LLC // https://www.iana.org/domains/root/db/tickets.html tickets // tienda : Binky Moon, LLC // https://www.iana.org/domains/root/db/tienda.html tienda // tips : Binky Moon, LLC // https://www.iana.org/domains/root/db/tips.html tips // tires : Binky Moon, LLC // https://www.iana.org/domains/root/db/tires.html tires // tirol : punkt Tirol GmbH // https://www.iana.org/domains/root/db/tirol.html tirol // tjmaxx : The TJX Companies, Inc. // https://www.iana.org/domains/root/db/tjmaxx.html tjmaxx // tjx : The TJX Companies, Inc. // https://www.iana.org/domains/root/db/tjx.html tjx // tkmaxx : The TJX Companies, Inc. // https://www.iana.org/domains/root/db/tkmaxx.html tkmaxx // tmall : Alibaba Group Holding Limited // https://www.iana.org/domains/root/db/tmall.html tmall // today : Binky Moon, LLC // https://www.iana.org/domains/root/db/today.html today // tokyo : GMO Registry, Inc. // https://www.iana.org/domains/root/db/tokyo.html tokyo // tools : Binky Moon, LLC // https://www.iana.org/domains/root/db/tools.html tools // top : .TOP Registry // https://www.iana.org/domains/root/db/top.html top // toray : Toray Industries, Inc. // https://www.iana.org/domains/root/db/toray.html toray // toshiba : TOSHIBA Corporation // https://www.iana.org/domains/root/db/toshiba.html toshiba // total : TotalEnergies SE // https://www.iana.org/domains/root/db/total.html total // tours : Binky Moon, LLC // https://www.iana.org/domains/root/db/tours.html tours // town : Binky Moon, LLC // https://www.iana.org/domains/root/db/town.html town // toyota : TOYOTA MOTOR CORPORATION // https://www.iana.org/domains/root/db/toyota.html toyota // toys : Binky Moon, LLC // https://www.iana.org/domains/root/db/toys.html toys // trade : Elite Registry Limited // https://www.iana.org/domains/root/db/trade.html trade // trading : Dog Beach, LLC // https://www.iana.org/domains/root/db/trading.html trading // training : Binky Moon, LLC // https://www.iana.org/domains/root/db/training.html training // travel : Dog Beach, LLC // https://www.iana.org/domains/root/db/travel.html travel // travelers : Travelers TLD, LLC // https://www.iana.org/domains/root/db/travelers.html travelers // travelersinsurance : Travelers TLD, LLC // https://www.iana.org/domains/root/db/travelersinsurance.html travelersinsurance // trust : Internet Naming Company LLC // https://www.iana.org/domains/root/db/trust.html trust // trv : Travelers TLD, LLC // https://www.iana.org/domains/root/db/trv.html trv // tube : Latin American Telecom LLC // https://www.iana.org/domains/root/db/tube.html tube // tui : TUI AG // https://www.iana.org/domains/root/db/tui.html tui // tunes : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/tunes.html tunes // tushu : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/tushu.html tushu // tvs : T V SUNDRAM IYENGAR & SONS LIMITED // https://www.iana.org/domains/root/db/tvs.html tvs // ubank : National Australia Bank Limited // https://www.iana.org/domains/root/db/ubank.html ubank // ubs : UBS AG // https://www.iana.org/domains/root/db/ubs.html ubs // unicom : China United Network Communications Corporation Limited // https://www.iana.org/domains/root/db/unicom.html unicom // university : Binky Moon, LLC // https://www.iana.org/domains/root/db/university.html university // uno : Radix Technologies Inc. // https://www.iana.org/domains/root/db/uno.html uno // uol : UBN INTERNET LTDA. // https://www.iana.org/domains/root/db/uol.html uol // ups : UPS Market Driver, Inc. // https://www.iana.org/domains/root/db/ups.html ups // vacations : Binky Moon, LLC // https://www.iana.org/domains/root/db/vacations.html vacations // vana : D3 Registry LLC // https://www.iana.org/domains/root/db/vana.html vana // vanguard : The Vanguard Group, Inc. // https://www.iana.org/domains/root/db/vanguard.html vanguard // vegas : Dot Vegas, Inc. // https://www.iana.org/domains/root/db/vegas.html vegas // ventures : Binky Moon, LLC // https://www.iana.org/domains/root/db/ventures.html ventures // verisign : VeriSign, Inc. // https://www.iana.org/domains/root/db/verisign.html verisign // versicherung : tldbox GmbH // https://www.iana.org/domains/root/db/versicherung.html versicherung // vet : Dog Beach, LLC // https://www.iana.org/domains/root/db/vet.html vet // viajes : Binky Moon, LLC // https://www.iana.org/domains/root/db/viajes.html viajes // video : Dog Beach, LLC // https://www.iana.org/domains/root/db/video.html video // vig : VIENNA INSURANCE GROUP AG Wiener Versicherung Gruppe // https://www.iana.org/domains/root/db/vig.html vig // viking : Viking River Cruises (Bermuda) Ltd. // https://www.iana.org/domains/root/db/viking.html viking // villas : Binky Moon, LLC // https://www.iana.org/domains/root/db/villas.html villas // vin : Binky Moon, LLC // https://www.iana.org/domains/root/db/vin.html vin // vip : Registry Services, LLC // https://www.iana.org/domains/root/db/vip.html vip // virgin : Virgin Enterprises Limited // https://www.iana.org/domains/root/db/virgin.html virgin // visa : Visa Worldwide Pte. Limited // https://www.iana.org/domains/root/db/visa.html visa // vision : Binky Moon, LLC // https://www.iana.org/domains/root/db/vision.html vision // viva : Saudi Telecom Company // https://www.iana.org/domains/root/db/viva.html viva // vivo : Telefonica Brasil S.A. // https://www.iana.org/domains/root/db/vivo.html vivo // vlaanderen : DNS.be vzw // https://www.iana.org/domains/root/db/vlaanderen.html vlaanderen // vodka : Registry Services, LLC // https://www.iana.org/domains/root/db/vodka.html vodka // volvo : Volvo Holding Sverige Aktiebolag // https://www.iana.org/domains/root/db/volvo.html volvo // vote : Monolith Registry LLC // https://www.iana.org/domains/root/db/vote.html vote // voting : Valuetainment Corp. // https://www.iana.org/domains/root/db/voting.html voting // voto : Monolith Registry LLC // https://www.iana.org/domains/root/db/voto.html voto // voyage : Binky Moon, LLC // https://www.iana.org/domains/root/db/voyage.html voyage // wales : Nominet UK // https://www.iana.org/domains/root/db/wales.html wales // walmart : Wal-Mart Stores, Inc. // https://www.iana.org/domains/root/db/walmart.html walmart // walter : Sandvik AB // https://www.iana.org/domains/root/db/walter.html walter // wang : Zodiac Wang Limited // https://www.iana.org/domains/root/db/wang.html wang // wanggou : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/wanggou.html wanggou // watch : Binky Moon, LLC // https://www.iana.org/domains/root/db/watch.html watch // watches : Identity Digital Limited // https://www.iana.org/domains/root/db/watches.html watches // weather : International Business Machines Corporation // https://www.iana.org/domains/root/db/weather.html weather // weatherchannel : International Business Machines Corporation // https://www.iana.org/domains/root/db/weatherchannel.html weatherchannel // webcam : dot Webcam Limited // https://www.iana.org/domains/root/db/webcam.html webcam // weber : Saint-Gobain Weber SA // https://www.iana.org/domains/root/db/weber.html weber // website : Radix Technologies Inc. // https://www.iana.org/domains/root/db/website.html website // wed // https://www.iana.org/domains/root/db/wed.html wed // wedding : Registry Services, LLC // https://www.iana.org/domains/root/db/wedding.html wedding // weibo : Sina Corporation // https://www.iana.org/domains/root/db/weibo.html weibo // weir : Weir Group IP Limited // https://www.iana.org/domains/root/db/weir.html weir // whoswho : Who's Who Registry // https://www.iana.org/domains/root/db/whoswho.html whoswho // wien : punkt.wien GmbH // https://www.iana.org/domains/root/db/wien.html wien // wiki : Registry Services, LLC // https://www.iana.org/domains/root/db/wiki.html wiki // williamhill : William Hill Organization Limited // https://www.iana.org/domains/root/db/williamhill.html williamhill // win : First Registry Limited // https://www.iana.org/domains/root/db/win.html win // windows : Microsoft Corporation // https://www.iana.org/domains/root/db/windows.html windows // wine : Binky Moon, LLC // https://www.iana.org/domains/root/db/wine.html wine // winners : The TJX Companies, Inc. // https://www.iana.org/domains/root/db/winners.html winners // wme : William Morris Endeavor Entertainment, LLC // https://www.iana.org/domains/root/db/wme.html wme // wolterskluwer : Wolters Kluwer N.V. // https://www.iana.org/domains/root/db/wolterskluwer.html wolterskluwer // woodside : Woodside Petroleum Limited // https://www.iana.org/domains/root/db/woodside.html woodside // work : Registry Services, LLC // https://www.iana.org/domains/root/db/work.html work // works : Binky Moon, LLC // https://www.iana.org/domains/root/db/works.html works // world : Binky Moon, LLC // https://www.iana.org/domains/root/db/world.html world // wow : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/wow.html wow // wtc : World Trade Centers Association, Inc. // https://www.iana.org/domains/root/db/wtc.html wtc // wtf : Binky Moon, LLC // https://www.iana.org/domains/root/db/wtf.html wtf // xbox : Microsoft Corporation // https://www.iana.org/domains/root/db/xbox.html xbox // xerox : Xerox DNHC LLC // https://www.iana.org/domains/root/db/xerox.html xerox // xihuan : Beijing Qihu Keji Co., Ltd. // https://www.iana.org/domains/root/db/xihuan.html xihuan // xin : Elegant Leader Limited // https://www.iana.org/domains/root/db/xin.html xin // xn--11b4c3d : VeriSign Sarl // https://www.iana.org/domains/root/db/xn--11b4c3d.html कॉम // xn--1ck2e1b : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/xn--1ck2e1b.html セール // xn--1qqw23a : Guangzhou YU Wei Information Technology Co., Ltd. // https://www.iana.org/domains/root/db/xn--1qqw23a.html 佛山 // xn--30rr7y : Excellent First Limited // https://www.iana.org/domains/root/db/xn--30rr7y.html 慈善 // xn--3bst00m : Eagle Horizon Limited // https://www.iana.org/domains/root/db/xn--3bst00m.html 集团 // xn--3ds443g : TLD REGISTRY LIMITED OY // https://www.iana.org/domains/root/db/xn--3ds443g.html 在线 // xn--3pxu8k : VeriSign Sarl // https://www.iana.org/domains/root/db/xn--3pxu8k.html 点看 // xn--42c2d9a : VeriSign Sarl // https://www.iana.org/domains/root/db/xn--42c2d9a.html คอม // xn--45q11c : Zodiac Gemini Ltd // https://www.iana.org/domains/root/db/xn--45q11c.html 八卦 // xn--4gbrim : Helium TLDs Ltd // https://www.iana.org/domains/root/db/xn--4gbrim.html موقع // xn--55qw42g : China Organizational Name Administration Center // https://www.iana.org/domains/root/db/xn--55qw42g.html 公益 // xn--55qx5d : China Internet Network Information Center (CNNIC) // https://www.iana.org/domains/root/db/xn--55qx5d.html 公司 // xn--5su34j936bgsg : Shangri‐La International Hotel Management Limited // https://www.iana.org/domains/root/db/xn--5su34j936bgsg.html 香格里拉 // xn--5tzm5g : Global Website TLD Asia Limited // https://www.iana.org/domains/root/db/xn--5tzm5g.html 网站 // xn--6frz82g : Identity Digital Limited // https://www.iana.org/domains/root/db/xn--6frz82g.html 移动 // xn--6qq986b3xl : Tycoon Treasure Limited // https://www.iana.org/domains/root/db/xn--6qq986b3xl.html 我爱你 // xn--80adxhks : Foundation for Assistance for Internet Technologies and Infrastructure Development (FAITID) // https://www.iana.org/domains/root/db/xn--80adxhks.html москва // xn--80aqecdr1a : Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) // https://www.iana.org/domains/root/db/xn--80aqecdr1a.html католик // xn--80asehdb : CORE Association // https://www.iana.org/domains/root/db/xn--80asehdb.html онлайн // xn--80aswg : CORE Association // https://www.iana.org/domains/root/db/xn--80aswg.html сайт // xn--8y0a063a : China United Network Communications Corporation Limited // https://www.iana.org/domains/root/db/xn--8y0a063a.html 联通 // xn--9dbq2a : VeriSign Sarl // https://www.iana.org/domains/root/db/xn--9dbq2a.html קום // xn--9et52u : RISE VICTORY LIMITED // https://www.iana.org/domains/root/db/xn--9et52u.html 时尚 // xn--9krt00a : Sina Corporation // https://www.iana.org/domains/root/db/xn--9krt00a.html 微博 // xn--b4w605ferd : Temasek Holdings (Private) Limited // https://www.iana.org/domains/root/db/xn--b4w605ferd.html 淡马锡 // xn--bck1b9a5dre4c : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/xn--bck1b9a5dre4c.html ファッション // xn--c1avg : Public Interest Registry // https://www.iana.org/domains/root/db/xn--c1avg.html орг // xn--c2br7g : VeriSign Sarl // https://www.iana.org/domains/root/db/xn--c2br7g.html नेट // xn--cck2b3b : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/xn--cck2b3b.html ストア // xn--cckwcxetd : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/xn--cckwcxetd.html アマゾン // xn--cg4bki : SAMSUNG SDS CO., LTD // https://www.iana.org/domains/root/db/xn--cg4bki.html 삼성 // xn--czr694b : Internet DotTrademark Organisation Limited // https://www.iana.org/domains/root/db/xn--czr694b.html 商标 // xn--czrs0t : Binky Moon, LLC // https://www.iana.org/domains/root/db/xn--czrs0t.html 商店 // xn--czru2d : Zodiac Aquarius Limited // https://www.iana.org/domains/root/db/xn--czru2d.html 商城 // xn--d1acj3b : The Foundation for Network Initiatives “The Smart Internet” // https://www.iana.org/domains/root/db/xn--d1acj3b.html дети // xn--eckvdtc9d : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/xn--eckvdtc9d.html ポイント // xn--efvy88h : Guangzhou YU Wei Information Technology Co., Ltd. // https://www.iana.org/domains/root/db/xn--efvy88h.html 新闻 // xn--fct429k : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/xn--fct429k.html 家電 // xn--fhbei : VeriSign Sarl // https://www.iana.org/domains/root/db/xn--fhbei.html كوم // xn--fiq228c5hs : TLD REGISTRY LIMITED OY // https://www.iana.org/domains/root/db/xn--fiq228c5hs.html 中文网 // xn--fiq64b : CITIC Group Corporation // https://www.iana.org/domains/root/db/xn--fiq64b.html 中信 // xn--fjq720a : Binky Moon, LLC // https://www.iana.org/domains/root/db/xn--fjq720a.html 娱乐 // xn--flw351e : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/xn--flw351e.html 谷歌 // xn--fzys8d69uvgm : PCCW Enterprises Limited // https://www.iana.org/domains/root/db/xn--fzys8d69uvgm.html 電訊盈科 // xn--g2xx48c : Nawang Heli(Xiamen) Network Service Co., LTD. // https://www.iana.org/domains/root/db/xn--g2xx48c.html 购物 // xn--gckr3f0f : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/xn--gckr3f0f.html クラウド // xn--gk3at1e : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/xn--gk3at1e.html 通販 // xn--hxt814e : Zodiac Taurus Limited // https://www.iana.org/domains/root/db/xn--hxt814e.html 网店 // xn--i1b6b1a6a2e : Public Interest Registry // https://www.iana.org/domains/root/db/xn--i1b6b1a6a2e.html संगठन // xn--imr513n : Internet DotTrademark Organisation Limited // https://www.iana.org/domains/root/db/xn--imr513n.html 餐厅 // xn--io0a7i : China Internet Network Information Center (CNNIC) // https://www.iana.org/domains/root/db/xn--io0a7i.html 网络 // xn--j1aef : VeriSign Sarl // https://www.iana.org/domains/root/db/xn--j1aef.html ком // xn--jlq480n2rg : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/xn--jlq480n2rg.html 亚马逊 // xn--jvr189m : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/xn--jvr189m.html 食品 // xn--kcrx77d1x4a : Koninklijke Philips N.V. // https://www.iana.org/domains/root/db/xn--kcrx77d1x4a.html 飞利浦 // xn--kput3i : Beijing RITT-Net Technology Development Co., Ltd // https://www.iana.org/domains/root/db/xn--kput3i.html 手机 // xn--mgba3a3ejt : Aramco Services Company // https://www.iana.org/domains/root/db/xn--mgba3a3ejt.html ارامكو // xn--mgba7c0bbn0a : Competrol (Luxembourg) Sarl // https://www.iana.org/domains/root/db/xn--mgba7c0bbn0a.html العليان // xn--mgbab2bd : CORE Association // https://www.iana.org/domains/root/db/xn--mgbab2bd.html بازار // xn--mgbca7dzdo : Abu Dhabi Systems and Information Centre // https://www.iana.org/domains/root/db/xn--mgbca7dzdo.html ابوظبي // xn--mgbi4ecexp : Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) // https://www.iana.org/domains/root/db/xn--mgbi4ecexp.html كاثوليك // xn--mgbt3dhd : Asia Green IT System Bilgisayar San. ve Tic. Ltd. Sti. // https://www.iana.org/domains/root/db/xn--mgbt3dhd.html همراه // xn--mk1bu44c : VeriSign Sarl // https://www.iana.org/domains/root/db/xn--mk1bu44c.html 닷컴 // xn--mxtq1m : Net-Chinese Co., Ltd. // https://www.iana.org/domains/root/db/xn--mxtq1m.html 政府 // xn--ngbc5azd : International Domain Registry Pty. Ltd. // https://www.iana.org/domains/root/db/xn--ngbc5azd.html شبكة // xn--ngbe9e0a : Kuwait Finance House // https://www.iana.org/domains/root/db/xn--ngbe9e0a.html بيتك // xn--ngbrx : League of Arab States // https://www.iana.org/domains/root/db/xn--ngbrx.html عرب // xn--nqv7f : Public Interest Registry // https://www.iana.org/domains/root/db/xn--nqv7f.html 机构 // xn--nqv7fs00ema : Public Interest Registry // https://www.iana.org/domains/root/db/xn--nqv7fs00ema.html 组织机构 // xn--nyqy26a : Stable Tone Limited // https://www.iana.org/domains/root/db/xn--nyqy26a.html 健康 // xn--otu796d : Jiang Yu Liang Cai Technology Company Limited // https://www.iana.org/domains/root/db/xn--otu796d.html 招聘 // xn--p1acf : Rusnames Limited // https://www.iana.org/domains/root/db/xn--p1acf.html рус // xn--pssy2u : VeriSign Sarl // https://www.iana.org/domains/root/db/xn--pssy2u.html 大拿 // xn--q9jyb4c : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/xn--q9jyb4c.html みんな // xn--qcka1pmc : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/xn--qcka1pmc.html グーグル // xn--rhqv96g : Stable Tone Limited // https://www.iana.org/domains/root/db/xn--rhqv96g.html 世界 // xn--rovu88b : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/xn--rovu88b.html 書籍 // xn--ses554g : KNET Co., Ltd. // https://www.iana.org/domains/root/db/xn--ses554g.html 网址 // xn--t60b56a : VeriSign Sarl // https://www.iana.org/domains/root/db/xn--t60b56a.html 닷넷 // xn--tckwe : VeriSign Sarl // https://www.iana.org/domains/root/db/xn--tckwe.html コム // xn--tiq49xqyj : Pontificium Consilium de Comunicationibus Socialibus (PCCS) (Pontifical Council for Social Communication) // https://www.iana.org/domains/root/db/xn--tiq49xqyj.html 天主教 // xn--unup4y : Binky Moon, LLC // https://www.iana.org/domains/root/db/xn--unup4y.html 游戏 // xn--vermgensberater-ctb : Deutsche Vermögensberatung Aktiengesellschaft DVAG // https://www.iana.org/domains/root/db/xn--vermgensberater-ctb.html vermögensberater // xn--vermgensberatung-pwb : Deutsche Vermögensberatung Aktiengesellschaft DVAG // https://www.iana.org/domains/root/db/xn--vermgensberatung-pwb.html vermögensberatung // xn--vhquv : Binky Moon, LLC // https://www.iana.org/domains/root/db/xn--vhquv.html 企业 // xn--vuq861b : Beijing Tele-info Technology Co., Ltd. // https://www.iana.org/domains/root/db/xn--vuq861b.html 信息 // xn--w4r85el8fhu5dnra : Kerry Trading Co. Limited // https://www.iana.org/domains/root/db/xn--w4r85el8fhu5dnra.html 嘉里大酒店 // xn--w4rs40l : Kerry Trading Co. Limited // https://www.iana.org/domains/root/db/xn--w4rs40l.html 嘉里 // xn--xhq521b : Guangzhou YU Wei Information Technology Co., Ltd. // https://www.iana.org/domains/root/db/xn--xhq521b.html 广东 // xn--zfr164b : China Organizational Name Administration Center // https://www.iana.org/domains/root/db/xn--zfr164b.html 政务 // xyz : XYZ.COM LLC // https://www.iana.org/domains/root/db/xyz.html xyz // yachts : XYZ.COM LLC // https://www.iana.org/domains/root/db/yachts.html yachts // yahoo : Yahoo Inc. // https://www.iana.org/domains/root/db/yahoo.html yahoo // yamaxun : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/yamaxun.html yamaxun // yandex : Yandex Europe B.V. // https://www.iana.org/domains/root/db/yandex.html yandex // yodobashi : YODOBASHI CAMERA CO.,LTD. // https://www.iana.org/domains/root/db/yodobashi.html yodobashi // yoga : Registry Services, LLC // https://www.iana.org/domains/root/db/yoga.html yoga // yokohama : GMO Registry, Inc. // https://www.iana.org/domains/root/db/yokohama.html yokohama // you : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/you.html you // youtube : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/youtube.html youtube // yun : Beijing Qihu Keji Co., Ltd. // https://www.iana.org/domains/root/db/yun.html yun // zappos : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/zappos.html zappos // zara : Industria de Diseño Textil, S.A. (INDITEX, S.A.) // https://www.iana.org/domains/root/db/zara.html zara // zero : Amazon Registry Services, Inc. // https://www.iana.org/domains/root/db/zero.html zero // zip : Charleston Road Registry Inc. // https://www.iana.org/domains/root/db/zip.html zip // zone : Binky Moon, LLC // https://www.iana.org/domains/root/db/zone.html zone // zuerich : Kanton Zürich (Canton of Zurich) // https://www.iana.org/domains/root/db/zuerich.html zuerich // ===END ICANN DOMAINS=== // ===BEGIN PRIVATE DOMAINS=== // (Note: these are in alphabetical order by company name) // .KRD : http://nic.krd/data/krd/Registration%20Policy.pdf co.krd edu.krd // .pl domains (grandfathered) art.pl gliwice.pl krakow.pl poznan.pl wroc.pl zakopane.pl // .US // Submitted by Ed Moore lib.de.us // 12CHARS: https://12chars.com // Submitted by Kenny Niehage 12chars.dev 12chars.it 12chars.pro // 1GB LLC : https://www.1gb.ua/ // Submitted by 1GB LLC cc.ua inf.ua ltd.ua // 611 blockchain domain name system : https://611project.net/ 611.to // A2 Hosting // Submitted by Tyler Hall a2hosted.com cpserver.com // AAA workspace : https://aaa.vodka // Submitted by Kirill Rezraf aaa.vodka // Acorn Labs : https://acorn.io // Submitted by Craig Jellick *.on-acorn.io // ActiveTrail: https://www.activetrail.biz/ // Submitted by Ofer Kalaora activetrail.biz // Adaptable.io : https://adaptable.io // Submitted by Mark Terrel adaptable.app // Adobe : https://www.adobe.com/ // Submitted by Ian Boston and Lars Trieloff adobeaemcloud.com *.dev.adobeaemcloud.com aem.live hlx.live adobeaemcloud.net aem.page hlx.page hlx3.page // Adobe Developer Platform : https://developer.adobe.com // Submitted by Jesse MacFadyen adobeio-static.net adobeioruntime.net // Africa.com Web Solutions Ltd : https://registry.africa.com // Submitted by Gavin Brown africa.com // Agnat sp. z o.o. : https://domena.pl // Submitted by Przemyslaw Plewa beep.pl // Airkit : https://www.airkit.com/ // Submitted by Grant Cooksey airkitapps.com airkitapps-au.com airkitapps.eu // Aiven: https://aiven.io/ // Submitted by Etienne Stalmans aivencloud.com // Akamai : https://www.akamai.com/ // Submitted by Akamai Team akadns.net akamai.net akamai-staging.net akamaiedge.net akamaiedge-staging.net akamaihd.net akamaihd-staging.net akamaiorigin.net akamaiorigin-staging.net akamaized.net akamaized-staging.net edgekey.net edgekey-staging.net edgesuite.net edgesuite-staging.net // alboto.ca : http://alboto.ca // Submitted by Anton Avramov barsy.ca // Alces Software Ltd : http://alces-software.com // Submitted by Mark J. Titorenko *.compute.estate *.alces.network // all-inkl.com : https://all-inkl.com // Submitted by Werner Kaltofen kasserver.com // Altervista: https://www.altervista.org // Submitted by Carlo Cannas altervista.org // alwaysdata : https://www.alwaysdata.com // Submitted by Cyril alwaysdata.net // Amaze Software : https://amaze.co // Submitted by Domain Admin myamaze.net // Amazon : https://www.amazon.com/ // Submitted by AWS Security // Subsections of Amazon/subsidiaries will appear until "concludes" tag // Amazon API Gateway // Submitted by AWS Security // Reference: 6a4f5a95-8c7d-4077-a7af-9cf1abec0a53 execute-api.cn-north-1.amazonaws.com.cn execute-api.cn-northwest-1.amazonaws.com.cn execute-api.af-south-1.amazonaws.com execute-api.ap-east-1.amazonaws.com execute-api.ap-northeast-1.amazonaws.com execute-api.ap-northeast-2.amazonaws.com execute-api.ap-northeast-3.amazonaws.com execute-api.ap-south-1.amazonaws.com execute-api.ap-south-2.amazonaws.com execute-api.ap-southeast-1.amazonaws.com execute-api.ap-southeast-2.amazonaws.com execute-api.ap-southeast-3.amazonaws.com execute-api.ap-southeast-4.amazonaws.com execute-api.ap-southeast-5.amazonaws.com execute-api.ca-central-1.amazonaws.com execute-api.ca-west-1.amazonaws.com execute-api.eu-central-1.amazonaws.com execute-api.eu-central-2.amazonaws.com execute-api.eu-north-1.amazonaws.com execute-api.eu-south-1.amazonaws.com execute-api.eu-south-2.amazonaws.com execute-api.eu-west-1.amazonaws.com execute-api.eu-west-2.amazonaws.com execute-api.eu-west-3.amazonaws.com execute-api.il-central-1.amazonaws.com execute-api.me-central-1.amazonaws.com execute-api.me-south-1.amazonaws.com execute-api.sa-east-1.amazonaws.com execute-api.us-east-1.amazonaws.com execute-api.us-east-2.amazonaws.com execute-api.us-gov-east-1.amazonaws.com execute-api.us-gov-west-1.amazonaws.com execute-api.us-west-1.amazonaws.com execute-api.us-west-2.amazonaws.com // Amazon CloudFront // Submitted by Donavan Miller // Reference: 54144616-fd49-4435-8535-19c6a601bdb3 cloudfront.net // Amazon Cognito // Submitted by AWS Security // Reference: cb38c251-c93d-4cda-81ec-e72c4f0fdb72 auth.af-south-1.amazoncognito.com auth.ap-east-1.amazoncognito.com auth.ap-northeast-1.amazoncognito.com auth.ap-northeast-2.amazoncognito.com auth.ap-northeast-3.amazoncognito.com auth.ap-south-1.amazoncognito.com auth.ap-south-2.amazoncognito.com auth.ap-southeast-1.amazoncognito.com auth.ap-southeast-2.amazoncognito.com auth.ap-southeast-3.amazoncognito.com auth.ap-southeast-4.amazoncognito.com auth.ca-central-1.amazoncognito.com auth.ca-west-1.amazoncognito.com auth.eu-central-1.amazoncognito.com auth.eu-central-2.amazoncognito.com auth.eu-north-1.amazoncognito.com auth.eu-south-1.amazoncognito.com auth.eu-south-2.amazoncognito.com auth.eu-west-1.amazoncognito.com auth.eu-west-2.amazoncognito.com auth.eu-west-3.amazoncognito.com auth.il-central-1.amazoncognito.com auth.me-central-1.amazoncognito.com auth.me-south-1.amazoncognito.com auth.sa-east-1.amazoncognito.com auth.us-east-1.amazoncognito.com auth-fips.us-east-1.amazoncognito.com auth.us-east-2.amazoncognito.com auth-fips.us-east-2.amazoncognito.com auth-fips.us-gov-west-1.amazoncognito.com auth.us-west-1.amazoncognito.com auth-fips.us-west-1.amazoncognito.com auth.us-west-2.amazoncognito.com auth-fips.us-west-2.amazoncognito.com // Amazon EC2 // Submitted by Luke Wells // Reference: 4c38fa71-58ac-4768-99e5-689c1767e537 *.compute.amazonaws.com.cn *.compute.amazonaws.com *.compute-1.amazonaws.com us-east-1.amazonaws.com // Amazon EMR // Submitted by AWS Security // Reference: 82f43f9f-bbb8-400e-8349-854f5a62f20d emrappui-prod.cn-north-1.amazonaws.com.cn emrnotebooks-prod.cn-north-1.amazonaws.com.cn emrstudio-prod.cn-north-1.amazonaws.com.cn emrappui-prod.cn-northwest-1.amazonaws.com.cn emrnotebooks-prod.cn-northwest-1.amazonaws.com.cn emrstudio-prod.cn-northwest-1.amazonaws.com.cn emrappui-prod.af-south-1.amazonaws.com emrnotebooks-prod.af-south-1.amazonaws.com emrstudio-prod.af-south-1.amazonaws.com emrappui-prod.ap-east-1.amazonaws.com emrnotebooks-prod.ap-east-1.amazonaws.com emrstudio-prod.ap-east-1.amazonaws.com emrappui-prod.ap-northeast-1.amazonaws.com emrnotebooks-prod.ap-northeast-1.amazonaws.com emrstudio-prod.ap-northeast-1.amazonaws.com emrappui-prod.ap-northeast-2.amazonaws.com emrnotebooks-prod.ap-northeast-2.amazonaws.com emrstudio-prod.ap-northeast-2.amazonaws.com emrappui-prod.ap-northeast-3.amazonaws.com emrnotebooks-prod.ap-northeast-3.amazonaws.com emrstudio-prod.ap-northeast-3.amazonaws.com emrappui-prod.ap-south-1.amazonaws.com emrnotebooks-prod.ap-south-1.amazonaws.com emrstudio-prod.ap-south-1.amazonaws.com emrappui-prod.ap-south-2.amazonaws.com emrnotebooks-prod.ap-south-2.amazonaws.com emrstudio-prod.ap-south-2.amazonaws.com emrappui-prod.ap-southeast-1.amazonaws.com emrnotebooks-prod.ap-southeast-1.amazonaws.com emrstudio-prod.ap-southeast-1.amazonaws.com emrappui-prod.ap-southeast-2.amazonaws.com emrnotebooks-prod.ap-southeast-2.amazonaws.com emrstudio-prod.ap-southeast-2.amazonaws.com emrappui-prod.ap-southeast-3.amazonaws.com emrnotebooks-prod.ap-southeast-3.amazonaws.com emrstudio-prod.ap-southeast-3.amazonaws.com emrappui-prod.ap-southeast-4.amazonaws.com emrnotebooks-prod.ap-southeast-4.amazonaws.com emrstudio-prod.ap-southeast-4.amazonaws.com emrappui-prod.ca-central-1.amazonaws.com emrnotebooks-prod.ca-central-1.amazonaws.com emrstudio-prod.ca-central-1.amazonaws.com emrappui-prod.ca-west-1.amazonaws.com emrnotebooks-prod.ca-west-1.amazonaws.com emrstudio-prod.ca-west-1.amazonaws.com emrappui-prod.eu-central-1.amazonaws.com emrnotebooks-prod.eu-central-1.amazonaws.com emrstudio-prod.eu-central-1.amazonaws.com emrappui-prod.eu-central-2.amazonaws.com emrnotebooks-prod.eu-central-2.amazonaws.com emrstudio-prod.eu-central-2.amazonaws.com emrappui-prod.eu-north-1.amazonaws.com emrnotebooks-prod.eu-north-1.amazonaws.com emrstudio-prod.eu-north-1.amazonaws.com emrappui-prod.eu-south-1.amazonaws.com emrnotebooks-prod.eu-south-1.amazonaws.com emrstudio-prod.eu-south-1.amazonaws.com emrappui-prod.eu-south-2.amazonaws.com emrnotebooks-prod.eu-south-2.amazonaws.com emrstudio-prod.eu-south-2.amazonaws.com emrappui-prod.eu-west-1.amazonaws.com emrnotebooks-prod.eu-west-1.amazonaws.com emrstudio-prod.eu-west-1.amazonaws.com emrappui-prod.eu-west-2.amazonaws.com emrnotebooks-prod.eu-west-2.amazonaws.com emrstudio-prod.eu-west-2.amazonaws.com emrappui-prod.eu-west-3.amazonaws.com emrnotebooks-prod.eu-west-3.amazonaws.com emrstudio-prod.eu-west-3.amazonaws.com emrappui-prod.il-central-1.amazonaws.com emrnotebooks-prod.il-central-1.amazonaws.com emrstudio-prod.il-central-1.amazonaws.com emrappui-prod.me-central-1.amazonaws.com emrnotebooks-prod.me-central-1.amazonaws.com emrstudio-prod.me-central-1.amazonaws.com emrappui-prod.me-south-1.amazonaws.com emrnotebooks-prod.me-south-1.amazonaws.com emrstudio-prod.me-south-1.amazonaws.com emrappui-prod.sa-east-1.amazonaws.com emrnotebooks-prod.sa-east-1.amazonaws.com emrstudio-prod.sa-east-1.amazonaws.com emrappui-prod.us-east-1.amazonaws.com emrnotebooks-prod.us-east-1.amazonaws.com emrstudio-prod.us-east-1.amazonaws.com emrappui-prod.us-east-2.amazonaws.com emrnotebooks-prod.us-east-2.amazonaws.com emrstudio-prod.us-east-2.amazonaws.com emrappui-prod.us-gov-east-1.amazonaws.com emrnotebooks-prod.us-gov-east-1.amazonaws.com emrstudio-prod.us-gov-east-1.amazonaws.com emrappui-prod.us-gov-west-1.amazonaws.com emrnotebooks-prod.us-gov-west-1.amazonaws.com emrstudio-prod.us-gov-west-1.amazonaws.com emrappui-prod.us-west-1.amazonaws.com emrnotebooks-prod.us-west-1.amazonaws.com emrstudio-prod.us-west-1.amazonaws.com emrappui-prod.us-west-2.amazonaws.com emrnotebooks-prod.us-west-2.amazonaws.com emrstudio-prod.us-west-2.amazonaws.com // Amazon Managed Workflows for Apache Airflow // Submitted by AWS Security // Reference: f5ea5d0a-ec6a-4f23-ac1c-553fbff13f5c *.cn-north-1.airflow.amazonaws.com.cn *.cn-northwest-1.airflow.amazonaws.com.cn *.af-south-1.airflow.amazonaws.com *.ap-east-1.airflow.amazonaws.com *.ap-northeast-1.airflow.amazonaws.com *.ap-northeast-2.airflow.amazonaws.com *.ap-northeast-3.airflow.amazonaws.com *.ap-south-1.airflow.amazonaws.com *.ap-south-2.airflow.amazonaws.com *.ap-southeast-1.airflow.amazonaws.com *.ap-southeast-2.airflow.amazonaws.com *.ap-southeast-3.airflow.amazonaws.com *.ap-southeast-4.airflow.amazonaws.com *.ca-central-1.airflow.amazonaws.com *.ca-west-1.airflow.amazonaws.com *.eu-central-1.airflow.amazonaws.com *.eu-central-2.airflow.amazonaws.com *.eu-north-1.airflow.amazonaws.com *.eu-south-1.airflow.amazonaws.com *.eu-south-2.airflow.amazonaws.com *.eu-west-1.airflow.amazonaws.com *.eu-west-2.airflow.amazonaws.com *.eu-west-3.airflow.amazonaws.com *.il-central-1.airflow.amazonaws.com *.me-central-1.airflow.amazonaws.com *.me-south-1.airflow.amazonaws.com *.sa-east-1.airflow.amazonaws.com *.us-east-1.airflow.amazonaws.com *.us-east-2.airflow.amazonaws.com *.us-west-1.airflow.amazonaws.com *.us-west-2.airflow.amazonaws.com // Amazon S3 // Submitted by AWS Security // Reference: ada5c9df-55e1-4195-a1ce-732d6c81e357 s3.dualstack.cn-north-1.amazonaws.com.cn s3-accesspoint.dualstack.cn-north-1.amazonaws.com.cn s3-website.dualstack.cn-north-1.amazonaws.com.cn s3.cn-north-1.amazonaws.com.cn s3-accesspoint.cn-north-1.amazonaws.com.cn s3-deprecated.cn-north-1.amazonaws.com.cn s3-object-lambda.cn-north-1.amazonaws.com.cn s3-website.cn-north-1.amazonaws.com.cn s3.dualstack.cn-northwest-1.amazonaws.com.cn s3-accesspoint.dualstack.cn-northwest-1.amazonaws.com.cn s3.cn-northwest-1.amazonaws.com.cn s3-accesspoint.cn-northwest-1.amazonaws.com.cn s3-object-lambda.cn-northwest-1.amazonaws.com.cn s3-website.cn-northwest-1.amazonaws.com.cn s3.dualstack.af-south-1.amazonaws.com s3-accesspoint.dualstack.af-south-1.amazonaws.com s3-website.dualstack.af-south-1.amazonaws.com s3.af-south-1.amazonaws.com s3-accesspoint.af-south-1.amazonaws.com s3-object-lambda.af-south-1.amazonaws.com s3-website.af-south-1.amazonaws.com s3.dualstack.ap-east-1.amazonaws.com s3-accesspoint.dualstack.ap-east-1.amazonaws.com s3.ap-east-1.amazonaws.com s3-accesspoint.ap-east-1.amazonaws.com s3-object-lambda.ap-east-1.amazonaws.com s3-website.ap-east-1.amazonaws.com s3.dualstack.ap-northeast-1.amazonaws.com s3-accesspoint.dualstack.ap-northeast-1.amazonaws.com s3-website.dualstack.ap-northeast-1.amazonaws.com s3.ap-northeast-1.amazonaws.com s3-accesspoint.ap-northeast-1.amazonaws.com s3-object-lambda.ap-northeast-1.amazonaws.com s3-website.ap-northeast-1.amazonaws.com s3.dualstack.ap-northeast-2.amazonaws.com s3-accesspoint.dualstack.ap-northeast-2.amazonaws.com s3-website.dualstack.ap-northeast-2.amazonaws.com s3.ap-northeast-2.amazonaws.com s3-accesspoint.ap-northeast-2.amazonaws.com s3-object-lambda.ap-northeast-2.amazonaws.com s3-website.ap-northeast-2.amazonaws.com s3.dualstack.ap-northeast-3.amazonaws.com s3-accesspoint.dualstack.ap-northeast-3.amazonaws.com s3-website.dualstack.ap-northeast-3.amazonaws.com s3.ap-northeast-3.amazonaws.com s3-accesspoint.ap-northeast-3.amazonaws.com s3-object-lambda.ap-northeast-3.amazonaws.com s3-website.ap-northeast-3.amazonaws.com s3.dualstack.ap-south-1.amazonaws.com s3-accesspoint.dualstack.ap-south-1.amazonaws.com s3-website.dualstack.ap-south-1.amazonaws.com s3.ap-south-1.amazonaws.com s3-accesspoint.ap-south-1.amazonaws.com s3-object-lambda.ap-south-1.amazonaws.com s3-website.ap-south-1.amazonaws.com s3.dualstack.ap-south-2.amazonaws.com s3-accesspoint.dualstack.ap-south-2.amazonaws.com s3-website.dualstack.ap-south-2.amazonaws.com s3.ap-south-2.amazonaws.com s3-accesspoint.ap-south-2.amazonaws.com s3-object-lambda.ap-south-2.amazonaws.com s3-website.ap-south-2.amazonaws.com s3.dualstack.ap-southeast-1.amazonaws.com s3-accesspoint.dualstack.ap-southeast-1.amazonaws.com s3-website.dualstack.ap-southeast-1.amazonaws.com s3.ap-southeast-1.amazonaws.com s3-accesspoint.ap-southeast-1.amazonaws.com s3-object-lambda.ap-southeast-1.amazonaws.com s3-website.ap-southeast-1.amazonaws.com s3.dualstack.ap-southeast-2.amazonaws.com s3-accesspoint.dualstack.ap-southeast-2.amazonaws.com s3-website.dualstack.ap-southeast-2.amazonaws.com s3.ap-southeast-2.amazonaws.com s3-accesspoint.ap-southeast-2.amazonaws.com s3-object-lambda.ap-southeast-2.amazonaws.com s3-website.ap-southeast-2.amazonaws.com s3.dualstack.ap-southeast-3.amazonaws.com s3-accesspoint.dualstack.ap-southeast-3.amazonaws.com s3-website.dualstack.ap-southeast-3.amazonaws.com s3.ap-southeast-3.amazonaws.com s3-accesspoint.ap-southeast-3.amazonaws.com s3-object-lambda.ap-southeast-3.amazonaws.com s3-website.ap-southeast-3.amazonaws.com s3.dualstack.ap-southeast-4.amazonaws.com s3-accesspoint.dualstack.ap-southeast-4.amazonaws.com s3-website.dualstack.ap-southeast-4.amazonaws.com s3.ap-southeast-4.amazonaws.com s3-accesspoint.ap-southeast-4.amazonaws.com s3-object-lambda.ap-southeast-4.amazonaws.com s3-website.ap-southeast-4.amazonaws.com s3.dualstack.ap-southeast-5.amazonaws.com s3-accesspoint.dualstack.ap-southeast-5.amazonaws.com s3-website.dualstack.ap-southeast-5.amazonaws.com s3.ap-southeast-5.amazonaws.com s3-accesspoint.ap-southeast-5.amazonaws.com s3-deprecated.ap-southeast-5.amazonaws.com s3-object-lambda.ap-southeast-5.amazonaws.com s3-website.ap-southeast-5.amazonaws.com s3.dualstack.ca-central-1.amazonaws.com s3-accesspoint.dualstack.ca-central-1.amazonaws.com s3-accesspoint-fips.dualstack.ca-central-1.amazonaws.com s3-fips.dualstack.ca-central-1.amazonaws.com s3-website.dualstack.ca-central-1.amazonaws.com s3.ca-central-1.amazonaws.com s3-accesspoint.ca-central-1.amazonaws.com s3-accesspoint-fips.ca-central-1.amazonaws.com s3-fips.ca-central-1.amazonaws.com s3-object-lambda.ca-central-1.amazonaws.com s3-website.ca-central-1.amazonaws.com s3.dualstack.ca-west-1.amazonaws.com s3-accesspoint.dualstack.ca-west-1.amazonaws.com s3-accesspoint-fips.dualstack.ca-west-1.amazonaws.com s3-fips.dualstack.ca-west-1.amazonaws.com s3-website.dualstack.ca-west-1.amazonaws.com s3.ca-west-1.amazonaws.com s3-accesspoint.ca-west-1.amazonaws.com s3-accesspoint-fips.ca-west-1.amazonaws.com s3-fips.ca-west-1.amazonaws.com s3-object-lambda.ca-west-1.amazonaws.com s3-website.ca-west-1.amazonaws.com s3.dualstack.eu-central-1.amazonaws.com s3-accesspoint.dualstack.eu-central-1.amazonaws.com s3-website.dualstack.eu-central-1.amazonaws.com s3.eu-central-1.amazonaws.com s3-accesspoint.eu-central-1.amazonaws.com s3-object-lambda.eu-central-1.amazonaws.com s3-website.eu-central-1.amazonaws.com s3.dualstack.eu-central-2.amazonaws.com s3-accesspoint.dualstack.eu-central-2.amazonaws.com s3-website.dualstack.eu-central-2.amazonaws.com s3.eu-central-2.amazonaws.com s3-accesspoint.eu-central-2.amazonaws.com s3-object-lambda.eu-central-2.amazonaws.com s3-website.eu-central-2.amazonaws.com s3.dualstack.eu-north-1.amazonaws.com s3-accesspoint.dualstack.eu-north-1.amazonaws.com s3.eu-north-1.amazonaws.com s3-accesspoint.eu-north-1.amazonaws.com s3-object-lambda.eu-north-1.amazonaws.com s3-website.eu-north-1.amazonaws.com s3.dualstack.eu-south-1.amazonaws.com s3-accesspoint.dualstack.eu-south-1.amazonaws.com s3-website.dualstack.eu-south-1.amazonaws.com s3.eu-south-1.amazonaws.com s3-accesspoint.eu-south-1.amazonaws.com s3-object-lambda.eu-south-1.amazonaws.com s3-website.eu-south-1.amazonaws.com s3.dualstack.eu-south-2.amazonaws.com s3-accesspoint.dualstack.eu-south-2.amazonaws.com s3-website.dualstack.eu-south-2.amazonaws.com s3.eu-south-2.amazonaws.com s3-accesspoint.eu-south-2.amazonaws.com s3-object-lambda.eu-south-2.amazonaws.com s3-website.eu-south-2.amazonaws.com s3.dualstack.eu-west-1.amazonaws.com s3-accesspoint.dualstack.eu-west-1.amazonaws.com s3-website.dualstack.eu-west-1.amazonaws.com s3.eu-west-1.amazonaws.com s3-accesspoint.eu-west-1.amazonaws.com s3-deprecated.eu-west-1.amazonaws.com s3-object-lambda.eu-west-1.amazonaws.com s3-website.eu-west-1.amazonaws.com s3.dualstack.eu-west-2.amazonaws.com s3-accesspoint.dualstack.eu-west-2.amazonaws.com s3.eu-west-2.amazonaws.com s3-accesspoint.eu-west-2.amazonaws.com s3-object-lambda.eu-west-2.amazonaws.com s3-website.eu-west-2.amazonaws.com s3.dualstack.eu-west-3.amazonaws.com s3-accesspoint.dualstack.eu-west-3.amazonaws.com s3-website.dualstack.eu-west-3.amazonaws.com s3.eu-west-3.amazonaws.com s3-accesspoint.eu-west-3.amazonaws.com s3-object-lambda.eu-west-3.amazonaws.com s3-website.eu-west-3.amazonaws.com s3.dualstack.il-central-1.amazonaws.com s3-accesspoint.dualstack.il-central-1.amazonaws.com s3-website.dualstack.il-central-1.amazonaws.com s3.il-central-1.amazonaws.com s3-accesspoint.il-central-1.amazonaws.com s3-object-lambda.il-central-1.amazonaws.com s3-website.il-central-1.amazonaws.com s3.dualstack.me-central-1.amazonaws.com s3-accesspoint.dualstack.me-central-1.amazonaws.com s3-website.dualstack.me-central-1.amazonaws.com s3.me-central-1.amazonaws.com s3-accesspoint.me-central-1.amazonaws.com s3-object-lambda.me-central-1.amazonaws.com s3-website.me-central-1.amazonaws.com s3.dualstack.me-south-1.amazonaws.com s3-accesspoint.dualstack.me-south-1.amazonaws.com s3.me-south-1.amazonaws.com s3-accesspoint.me-south-1.amazonaws.com s3-object-lambda.me-south-1.amazonaws.com s3-website.me-south-1.amazonaws.com s3.amazonaws.com s3-1.amazonaws.com s3-ap-east-1.amazonaws.com s3-ap-northeast-1.amazonaws.com s3-ap-northeast-2.amazonaws.com s3-ap-northeast-3.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-north-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-east-1.amazonaws.com s3-fips-us-gov-west-1.amazonaws.com mrap.accesspoint.s3-global.amazonaws.com s3-me-south-1.amazonaws.com s3-sa-east-1.amazonaws.com s3-us-east-2.amazonaws.com s3-us-gov-east-1.amazonaws.com s3-us-gov-west-1.amazonaws.com s3-us-west-1.amazonaws.com s3-us-west-2.amazonaws.com s3-website-ap-northeast-1.amazonaws.com s3-website-ap-southeast-1.amazonaws.com s3-website-ap-southeast-2.amazonaws.com s3-website-eu-west-1.amazonaws.com s3-website-sa-east-1.amazonaws.com s3-website-us-east-1.amazonaws.com s3-website-us-gov-west-1.amazonaws.com s3-website-us-west-1.amazonaws.com s3-website-us-west-2.amazonaws.com s3.dualstack.sa-east-1.amazonaws.com s3-accesspoint.dualstack.sa-east-1.amazonaws.com s3-website.dualstack.sa-east-1.amazonaws.com s3.sa-east-1.amazonaws.com s3-accesspoint.sa-east-1.amazonaws.com s3-object-lambda.sa-east-1.amazonaws.com s3-website.sa-east-1.amazonaws.com s3.dualstack.us-east-1.amazonaws.com s3-accesspoint.dualstack.us-east-1.amazonaws.com s3-accesspoint-fips.dualstack.us-east-1.amazonaws.com s3-fips.dualstack.us-east-1.amazonaws.com s3-website.dualstack.us-east-1.amazonaws.com s3.us-east-1.amazonaws.com s3-accesspoint.us-east-1.amazonaws.com s3-accesspoint-fips.us-east-1.amazonaws.com s3-deprecated.us-east-1.amazonaws.com s3-fips.us-east-1.amazonaws.com s3-object-lambda.us-east-1.amazonaws.com s3-website.us-east-1.amazonaws.com s3.dualstack.us-east-2.amazonaws.com s3-accesspoint.dualstack.us-east-2.amazonaws.com s3-accesspoint-fips.dualstack.us-east-2.amazonaws.com s3-fips.dualstack.us-east-2.amazonaws.com s3-website.dualstack.us-east-2.amazonaws.com s3.us-east-2.amazonaws.com s3-accesspoint.us-east-2.amazonaws.com s3-accesspoint-fips.us-east-2.amazonaws.com s3-deprecated.us-east-2.amazonaws.com s3-fips.us-east-2.amazonaws.com s3-object-lambda.us-east-2.amazonaws.com s3-website.us-east-2.amazonaws.com s3.dualstack.us-gov-east-1.amazonaws.com s3-accesspoint.dualstack.us-gov-east-1.amazonaws.com s3-accesspoint-fips.dualstack.us-gov-east-1.amazonaws.com s3-fips.dualstack.us-gov-east-1.amazonaws.com s3.us-gov-east-1.amazonaws.com s3-accesspoint.us-gov-east-1.amazonaws.com s3-accesspoint-fips.us-gov-east-1.amazonaws.com s3-fips.us-gov-east-1.amazonaws.com s3-object-lambda.us-gov-east-1.amazonaws.com s3-website.us-gov-east-1.amazonaws.com s3.dualstack.us-gov-west-1.amazonaws.com s3-accesspoint.dualstack.us-gov-west-1.amazonaws.com s3-accesspoint-fips.dualstack.us-gov-west-1.amazonaws.com s3-fips.dualstack.us-gov-west-1.amazonaws.com s3.us-gov-west-1.amazonaws.com s3-accesspoint.us-gov-west-1.amazonaws.com s3-accesspoint-fips.us-gov-west-1.amazonaws.com s3-fips.us-gov-west-1.amazonaws.com s3-object-lambda.us-gov-west-1.amazonaws.com s3-website.us-gov-west-1.amazonaws.com s3.dualstack.us-west-1.amazonaws.com s3-accesspoint.dualstack.us-west-1.amazonaws.com s3-accesspoint-fips.dualstack.us-west-1.amazonaws.com s3-fips.dualstack.us-west-1.amazonaws.com s3-website.dualstack.us-west-1.amazonaws.com s3.us-west-1.amazonaws.com s3-accesspoint.us-west-1.amazonaws.com s3-accesspoint-fips.us-west-1.amazonaws.com s3-fips.us-west-1.amazonaws.com s3-object-lambda.us-west-1.amazonaws.com s3-website.us-west-1.amazonaws.com s3.dualstack.us-west-2.amazonaws.com s3-accesspoint.dualstack.us-west-2.amazonaws.com s3-accesspoint-fips.dualstack.us-west-2.amazonaws.com s3-fips.dualstack.us-west-2.amazonaws.com s3-website.dualstack.us-west-2.amazonaws.com s3.us-west-2.amazonaws.com s3-accesspoint.us-west-2.amazonaws.com s3-accesspoint-fips.us-west-2.amazonaws.com s3-deprecated.us-west-2.amazonaws.com s3-fips.us-west-2.amazonaws.com s3-object-lambda.us-west-2.amazonaws.com s3-website.us-west-2.amazonaws.com // Amazon SageMaker Ground Truth // Submitted by AWS Security // Reference: 98dbfde4-7802-48c3-8751-b60f204e0d9c labeling.ap-northeast-1.sagemaker.aws labeling.ap-northeast-2.sagemaker.aws labeling.ap-south-1.sagemaker.aws labeling.ap-southeast-1.sagemaker.aws labeling.ap-southeast-2.sagemaker.aws labeling.ca-central-1.sagemaker.aws labeling.eu-central-1.sagemaker.aws labeling.eu-west-1.sagemaker.aws labeling.eu-west-2.sagemaker.aws labeling.us-east-1.sagemaker.aws labeling.us-east-2.sagemaker.aws labeling.us-west-2.sagemaker.aws // Amazon SageMaker Notebook Instances // Submitted by AWS Security // Reference: b5ea56df-669e-43cc-9537-14aa172f5dfc notebook.af-south-1.sagemaker.aws notebook.ap-east-1.sagemaker.aws notebook.ap-northeast-1.sagemaker.aws notebook.ap-northeast-2.sagemaker.aws notebook.ap-northeast-3.sagemaker.aws notebook.ap-south-1.sagemaker.aws notebook.ap-south-2.sagemaker.aws notebook.ap-southeast-1.sagemaker.aws notebook.ap-southeast-2.sagemaker.aws notebook.ap-southeast-3.sagemaker.aws notebook.ap-southeast-4.sagemaker.aws notebook.ca-central-1.sagemaker.aws notebook-fips.ca-central-1.sagemaker.aws notebook.ca-west-1.sagemaker.aws notebook-fips.ca-west-1.sagemaker.aws notebook.eu-central-1.sagemaker.aws notebook.eu-central-2.sagemaker.aws notebook.eu-north-1.sagemaker.aws notebook.eu-south-1.sagemaker.aws notebook.eu-south-2.sagemaker.aws notebook.eu-west-1.sagemaker.aws notebook.eu-west-2.sagemaker.aws notebook.eu-west-3.sagemaker.aws notebook.il-central-1.sagemaker.aws notebook.me-central-1.sagemaker.aws notebook.me-south-1.sagemaker.aws notebook.sa-east-1.sagemaker.aws notebook.us-east-1.sagemaker.aws notebook-fips.us-east-1.sagemaker.aws notebook.us-east-2.sagemaker.aws notebook-fips.us-east-2.sagemaker.aws notebook.us-gov-east-1.sagemaker.aws notebook-fips.us-gov-east-1.sagemaker.aws notebook.us-gov-west-1.sagemaker.aws notebook-fips.us-gov-west-1.sagemaker.aws notebook.us-west-1.sagemaker.aws notebook-fips.us-west-1.sagemaker.aws notebook.us-west-2.sagemaker.aws notebook-fips.us-west-2.sagemaker.aws notebook.cn-north-1.sagemaker.com.cn notebook.cn-northwest-1.sagemaker.com.cn // Amazon SageMaker Studio // Submitted by AWS Security // Reference: 69c723d9-6e1a-4bff-a203-48eecd203183 studio.af-south-1.sagemaker.aws studio.ap-east-1.sagemaker.aws studio.ap-northeast-1.sagemaker.aws studio.ap-northeast-2.sagemaker.aws studio.ap-northeast-3.sagemaker.aws studio.ap-south-1.sagemaker.aws studio.ap-southeast-1.sagemaker.aws studio.ap-southeast-2.sagemaker.aws studio.ap-southeast-3.sagemaker.aws studio.ca-central-1.sagemaker.aws studio.eu-central-1.sagemaker.aws studio.eu-north-1.sagemaker.aws studio.eu-south-1.sagemaker.aws studio.eu-south-2.sagemaker.aws studio.eu-west-1.sagemaker.aws studio.eu-west-2.sagemaker.aws studio.eu-west-3.sagemaker.aws studio.il-central-1.sagemaker.aws studio.me-central-1.sagemaker.aws studio.me-south-1.sagemaker.aws studio.sa-east-1.sagemaker.aws studio.us-east-1.sagemaker.aws studio.us-east-2.sagemaker.aws studio.us-gov-east-1.sagemaker.aws studio-fips.us-gov-east-1.sagemaker.aws studio.us-gov-west-1.sagemaker.aws studio-fips.us-gov-west-1.sagemaker.aws studio.us-west-1.sagemaker.aws studio.us-west-2.sagemaker.aws studio.cn-north-1.sagemaker.com.cn studio.cn-northwest-1.sagemaker.com.cn // Amazon SageMaker with MLflow // Submited by: AWS Security // Reference: c19f92b3-a82a-452d-8189-831b572eea7e *.experiments.sagemaker.aws // Analytics on AWS // Submitted by AWS Security // Reference: 955f9f40-a495-4e73-ae85-67b77ac9cadd analytics-gateway.ap-northeast-1.amazonaws.com analytics-gateway.ap-northeast-2.amazonaws.com analytics-gateway.ap-south-1.amazonaws.com analytics-gateway.ap-southeast-1.amazonaws.com analytics-gateway.ap-southeast-2.amazonaws.com analytics-gateway.eu-central-1.amazonaws.com analytics-gateway.eu-west-1.amazonaws.com analytics-gateway.us-east-1.amazonaws.com analytics-gateway.us-east-2.amazonaws.com analytics-gateway.us-west-2.amazonaws.com // AWS Amplify // Submitted by AWS Security // Reference: c35bed18-6f4f-424f-9298-5756f2f7d72b amplifyapp.com // AWS App Runner // Submitted by AWS Security // Reference: 6828c008-ba5d-442f-ade5-48da4e7c2316 *.awsapprunner.com // AWS Cloud9 // Submitted by: AWS Security // Reference: 30717f72-4007-4f0f-8ed4-864c6f2efec9 webview-assets.aws-cloud9.af-south-1.amazonaws.com vfs.cloud9.af-south-1.amazonaws.com webview-assets.cloud9.af-south-1.amazonaws.com webview-assets.aws-cloud9.ap-east-1.amazonaws.com vfs.cloud9.ap-east-1.amazonaws.com webview-assets.cloud9.ap-east-1.amazonaws.com webview-assets.aws-cloud9.ap-northeast-1.amazonaws.com vfs.cloud9.ap-northeast-1.amazonaws.com webview-assets.cloud9.ap-northeast-1.amazonaws.com webview-assets.aws-cloud9.ap-northeast-2.amazonaws.com vfs.cloud9.ap-northeast-2.amazonaws.com webview-assets.cloud9.ap-northeast-2.amazonaws.com webview-assets.aws-cloud9.ap-northeast-3.amazonaws.com vfs.cloud9.ap-northeast-3.amazonaws.com webview-assets.cloud9.ap-northeast-3.amazonaws.com webview-assets.aws-cloud9.ap-south-1.amazonaws.com vfs.cloud9.ap-south-1.amazonaws.com webview-assets.cloud9.ap-south-1.amazonaws.com webview-assets.aws-cloud9.ap-southeast-1.amazonaws.com vfs.cloud9.ap-southeast-1.amazonaws.com webview-assets.cloud9.ap-southeast-1.amazonaws.com webview-assets.aws-cloud9.ap-southeast-2.amazonaws.com vfs.cloud9.ap-southeast-2.amazonaws.com webview-assets.cloud9.ap-southeast-2.amazonaws.com webview-assets.aws-cloud9.ca-central-1.amazonaws.com vfs.cloud9.ca-central-1.amazonaws.com webview-assets.cloud9.ca-central-1.amazonaws.com webview-assets.aws-cloud9.eu-central-1.amazonaws.com vfs.cloud9.eu-central-1.amazonaws.com webview-assets.cloud9.eu-central-1.amazonaws.com webview-assets.aws-cloud9.eu-north-1.amazonaws.com vfs.cloud9.eu-north-1.amazonaws.com webview-assets.cloud9.eu-north-1.amazonaws.com webview-assets.aws-cloud9.eu-south-1.amazonaws.com vfs.cloud9.eu-south-1.amazonaws.com webview-assets.cloud9.eu-south-1.amazonaws.com webview-assets.aws-cloud9.eu-west-1.amazonaws.com vfs.cloud9.eu-west-1.amazonaws.com webview-assets.cloud9.eu-west-1.amazonaws.com webview-assets.aws-cloud9.eu-west-2.amazonaws.com vfs.cloud9.eu-west-2.amazonaws.com webview-assets.cloud9.eu-west-2.amazonaws.com webview-assets.aws-cloud9.eu-west-3.amazonaws.com vfs.cloud9.eu-west-3.amazonaws.com webview-assets.cloud9.eu-west-3.amazonaws.com webview-assets.aws-cloud9.il-central-1.amazonaws.com vfs.cloud9.il-central-1.amazonaws.com webview-assets.aws-cloud9.me-south-1.amazonaws.com vfs.cloud9.me-south-1.amazonaws.com webview-assets.cloud9.me-south-1.amazonaws.com webview-assets.aws-cloud9.sa-east-1.amazonaws.com vfs.cloud9.sa-east-1.amazonaws.com webview-assets.cloud9.sa-east-1.amazonaws.com webview-assets.aws-cloud9.us-east-1.amazonaws.com vfs.cloud9.us-east-1.amazonaws.com webview-assets.cloud9.us-east-1.amazonaws.com webview-assets.aws-cloud9.us-east-2.amazonaws.com vfs.cloud9.us-east-2.amazonaws.com webview-assets.cloud9.us-east-2.amazonaws.com webview-assets.aws-cloud9.us-west-1.amazonaws.com vfs.cloud9.us-west-1.amazonaws.com webview-assets.cloud9.us-west-1.amazonaws.com webview-assets.aws-cloud9.us-west-2.amazonaws.com vfs.cloud9.us-west-2.amazonaws.com webview-assets.cloud9.us-west-2.amazonaws.com // AWS Directory Service // Submitted by AWS Security // Reference: a13203e8-42dc-4045-a0d2-2ee67bed1068 awsapps.com // AWS Elastic Beanstalk // Submitted by AWS Security // Reference: bb5a965c-dec3-4967-aa22-e306ad064797 cn-north-1.eb.amazonaws.com.cn cn-northwest-1.eb.amazonaws.com.cn elasticbeanstalk.com af-south-1.elasticbeanstalk.com ap-east-1.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 ap-southeast-3.elasticbeanstalk.com ca-central-1.elasticbeanstalk.com eu-central-1.elasticbeanstalk.com eu-north-1.elasticbeanstalk.com eu-south-1.elasticbeanstalk.com eu-west-1.elasticbeanstalk.com eu-west-2.elasticbeanstalk.com eu-west-3.elasticbeanstalk.com il-central-1.elasticbeanstalk.com me-south-1.elasticbeanstalk.com sa-east-1.elasticbeanstalk.com us-east-1.elasticbeanstalk.com us-east-2.elasticbeanstalk.com us-gov-east-1.elasticbeanstalk.com us-gov-west-1.elasticbeanstalk.com us-west-1.elasticbeanstalk.com us-west-2.elasticbeanstalk.com // (AWS) Elastic Load Balancing // Submitted by Luke Wells // Reference: 12a3d528-1bac-4433-a359-a395867ffed2 *.elb.amazonaws.com.cn *.elb.amazonaws.com // AWS Global Accelerator // Submitted by Daniel Massaguer // Reference: d916759d-a08b-4241-b536-4db887383a6a awsglobalaccelerator.com // AWS re:Post Private // Submitted by AWS Security // Reference: 83385945-225f-416e-9aa0-ad0632bfdcee *.private.repost.aws // eero // Submitted by Yue Kang // Reference: 264afe70-f62c-4c02-8ab9-b5281ed24461 eero.online eero-stage.online // concludes Amazon // Apigee : https://apigee.com/ // Submitted by Apigee Security Team apigee.io // Apis Networks : https://apisnetworks.com // Submitted by Matt Saladna panel.dev // Apphud : https://apphud.com // Submitted by Alexander Selivanov siiites.com // Appspace : https://www.appspace.com // Submitted by Appspace Security Team appspacehosted.com appspaceusercontent.com // Appudo UG (haftungsbeschränkt) : https://www.appudo.com // Submitted by Alexander Hochbaum appudo.net // Aptible : https://www.aptible.com/ // Submitted by Thomas Orozco on-aptible.com // Aquapal : https://aquapal.net/ // Submitted by Aki Ueno f5.si // ArvanCloud EdgeCompute // Submitted by ArvanCloud CDN arvanedge.ir // ASEINet : https://www.aseinet.com/ // Submitted by Asei SEKIGUCHI user.aseinet.ne.jp gv.vc d.gv.vc // Asociación Amigos de la Informática "Euskalamiga" : http://encounter.eus/ // Submitted by Hector Martin user.party.eus // Association potager.org : https://potager.org/ // Submitted by Lunar pimienta.org poivron.org potager.org sweetpepper.org // ASUSTOR Inc. : http://www.asustor.com // Submitted by Vincent Tseng myasustor.com // Atlassian : https://atlassian.com // Submitted by Sam Smyth cdn.prod.atlassian-dev.net // Authentick UG (haftungsbeschränkt) : https://authentick.net // Submitted by Lukas Reschke translated.page // AVM : https://avm.de // Submitted by Andreas Weise myfritz.link myfritz.net // AVStack Pte. Ltd. : https://avstack.io // Submitted by Jasper Hugo onavstack.net // AW AdvisorWebsites.com Software Inc : https://advisorwebsites.com // Submitted by James Kennedy *.awdev.ca *.advisor.ws // AZ.pl sp. z.o.o : https://az.pl // Submitted by Krzysztof Wolski ecommerce-shop.pl // b-data GmbH : https://www.b-data.io // Submitted by Olivier Benz b-data.io // Balena : https://www.balena.io // Submitted by Petros Angelatos balena-devices.com // BASE, Inc. : https://binc.jp // Submitted by Yuya NAGASAWA base.ec official.ec buyshop.jp fashionstore.jp handcrafted.jp kawaiishop.jp supersale.jp theshop.jp shopselect.net base.shop // BeagleBoard.org Foundation : https://beagleboard.org // Submitted by Jason Kridner beagleboard.io // Beget Ltd // Submitted by Lev Nekrasov *.beget.app // Besties : https://besties.house // Submitted by Hazel Cora pages.gay // BinaryLane : http://www.binarylane.com // Submitted by Nathan O'Sullivan bnr.la // Bitbucket : http://bitbucket.org // Submitted by Andy Ortlieb bitbucket.io // Blackbaud, Inc. : https://www.blackbaud.com // Submitted by Paul Crowder blackbaudcdn.net // Blatech : http://www.blatech.net // Submitted by Luke Bratch of.je // Blue Bite, LLC : https://bluebite.com // Submitted by Joshua Weiss bluebite.io // Boomla : https://boomla.com // Submitted by Tibor Halter boomla.net // Boutir : https://www.boutir.com // Submitted by Eric Ng Ka Ka boutir.com // Boxfuse : https://boxfuse.com // Submitted by Axel Fontaine boxfuse.io // bplaced : https://www.bplaced.net/ // Submitted by Miroslav Bozic square7.ch bplaced.com bplaced.de square7.de bplaced.net square7.net // Brave : https://brave.com // Submitted by Andrea Brancaleoni *.s.brave.io // Brendly : https://brendly.rs // Submitted by Dusan Radovanovic shop.brendly.hr shop.brendly.rs // BrowserSafetyMark // Submitted by Dave Tharp browsersafetymark.io // BRS Media : https://brsmedia.com/ // Submitted by Gavin Brown radio.am radio.fm // Bytemark Hosting : https://www.bytemark.co.uk // Submitted by Paul Cammish uk0.bigv.io dh.bytemark.co.uk vm.bytemark.co.uk // Caf.js Labs LLC : https://www.cafjs.com // Submitted by Antonio Lain cafjs.com // Canva Pty Ltd : https://canva.com/ // Submitted by Joel Aquilina canva-apps.cn *.my.canvasite.cn canva-apps.com *.my.canva.site // Carrd : https://carrd.co // Submitted by AJ drr.ac uwu.ai carrd.co crd.co ju.mp // CDDO : https://www.gov.uk/guidance/get-an-api-domain-on-govuk // Submitted by Jamie Tanna api.gov.uk // CDN77.com : http://www.cdn77.com // Submitted by Jan Krpes cdn77-storage.com rsc.contentproxy9.cz r.cdn77.net cdn77-ssl.net c.cdn77.org rsc.cdn77.org ssl.origin.cdn77-secure.org // CentralNic : http://www.centralnic.com/names/domains // Submitted by registry za.bz br.com cn.com de.com eu.com jpn.com mex.com ru.com sa.com uk.com us.com za.com com.de gb.net hu.net jp.net se.net uk.net ae.org com.se // Cityhost LLC : https://cityhost.ua // Submitted by Maksym Rivtin cx.ua // Civilized Discourse Construction Kit, Inc. : https://www.discourse.org/ // Submitted by Rishabh Nambiar & Michael Brown discourse.group discourse.team // Clerk : https://www.clerk.dev // Submitted by Colin Sidoti clerk.app clerkstage.app *.lcl.dev *.lclstage.dev *.stg.dev *.stgstage.dev // Clever Cloud : https://www.clever-cloud.com/ // Submitted by Quentin Adam cleverapps.cc *.services.clever-cloud.com cleverapps.io cleverapps.tech // ClickRising : https://clickrising.com/ // Submitted by Umut Gumeli clickrising.net // Cloud DNS Ltd : http://www.cloudns.net // Submitted by Aleksander Hristov & Boyan Peychev cloudns.asia cloudns.be cloud-ip.biz cloudns.biz cloudns.cc cloudns.ch cloudns.cl cloudns.club dnsabr.com ip-ddns.com cloudns.cx cloudns.eu cloudns.in cloudns.info ddns-ip.net dns-cloud.net dns-dynamic.net cloudns.nz cloudns.org ip-dynamic.org cloudns.ph cloudns.pro cloudns.pw cloudns.us // 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 // Cloudera, Inc. : https://www.cloudera.com/ // Submitted by Kedarnath Waikar *.cloudera.site // Cloudflare, Inc. : https://www.cloudflare.com/ // Submitted by Cloudflare Team cf-ipfs.com cloudflare-ipfs.com trycloudflare.com pages.dev r2.dev workers.dev cloudflare.net cdn.cloudflare.net cdn.cloudflareanycast.net cdn.cloudflarecn.net cdn.cloudflareglobal.net // cloudscale.ch AG : https://www.cloudscale.ch/ // Submitted by Gaudenz Steinlin cust.cloudscale.ch objects.lpg.cloudscale.ch objects.rma.cloudscale.ch // Clovyr : https://clovyr.io // Submitted by Patrick Nielsen wnext.app // CNPY : https://cnpy.gdn // Submitted by Angelo Gladding cnpy.gdn // Co & Co : https://co-co.nl/ // Submitted by Govert Versluis *.otap.co // co.ca : http://registry.co.ca/ co.ca // co.com Registry, LLC : https://registry.co.com // Submitted by Gavin Brown co.com // Codeberg e. V. : https://codeberg.org // Submitted by Moritz Marquardt codeberg.page // CodeSandbox B.V. : https://codesandbox.io // Submitted by Ives van Hoorne csb.app preview.csb.app // CoDNS B.V. co.nl co.no // Combell.com : https://www.combell.com // Submitted by Thomas Wouters webhosting.be hosting-cluster.nl // Contentful GmbH : https://www.contentful.com // Submitted by Contentful Developer Experience Team ctfcloud.net // Convex : https://convex.dev/ // Submitted by James Cowling convex.site // Coordination Center for TLD RU and XN--P1AI : https://cctld.ru/en/domains/domens_ru/reserved/ // Submitted by George Georgievsky ac.ru edu.ru gov.ru int.ru mil.ru test.ru // COSIMO GmbH : http://www.cosimo.de // Submitted by Rene Marticke dyn.cosidns.de dnsupdater.de dynamisches-dns.de internet-dns.de l-o-g-i-n.de dynamic-dns.info feste-ip.net knx-server.net static-access.net // Craft Docs Ltd : https://www.craft.do/ // Submitted by Zsombor Fuszenecker craft.me // Craynic, s.r.o. : http://www.craynic.com/ // Submitted by Ales Krajnik realm.cz // Crisp IM SAS : https://crisp.chat/ // Submitted by Baptiste Jamin on.crisp.email // Cryptonomic : https://cryptonomic.net/ // Submitted by Andrew Cady *.cryptonomic.net // Curv UG : https://curv-labs.de/ // Submitted by Marvin Wiesner curv.dev // cyber_Folks S.A. : https://cyberfolks.pl // Submitted by Bartlomiej Kida cfolks.pl // cyon GmbH : https://www.cyon.ch/ // Submitted by Dominic Luechinger cyon.link cyon.site // Danger Science Group: https://dangerscience.com/ // Submitted by Skylar MacDonald platform0.app fnwk.site folionetwork.site // Dansk.net : http://www.dansk.net/ // Submitted by Anani Voule biz.dk co.dk firm.dk reg.dk store.dk // dappnode.io : https://dappnode.io/ // Submitted by Abel Boldu / DAppNode Team dyndns.dappnode.io // Dark, Inc. : https://darklang.com // Submitted by Paul Biggar builtwithdark.com darklang.io // DataDetect, LLC. : https://datadetect.com // Submitted by Andrew Banchich demo.datadetect.com instance.datadetect.com // Datawire, Inc : https://www.datawire.io // Submitted by Richard Li edgestack.me // Datto, Inc. : https://www.datto.com/ // Submitted by Philipp Heckel dattolocal.com dattorelay.com dattoweb.com mydatto.com dattolocal.net mydatto.net // ddnss.de : https://www.ddnss.de/ // Submitted by Robert Niedziela ddnss.de dyn.ddnss.de dyndns.ddnss.de dyn-ip24.de dyndns1.de home-webserver.de dyn.home-webserver.de myhome-server.de ddnss.org // Debian : https://www.debian.org/ // Submitted by Peter Palfrader / Debian Sysadmin Team debian.net // Definima : http://www.definima.com/ // Submitted by Maxence Bitterli definima.io definima.net // Deno Land Inc : https://deno.com/ // Submitted by Luca Casonato deno.dev deno-staging.dev // deSEC : https://desec.io/ // Submitted by Peter Thomassen dedyn.io // Deta: https://www.deta.sh/ // Submitted by Aavash Shrestha deta.app deta.dev // dhosting.pl Sp. z o.o.: https://dhosting.pl/ // Submitted by Michal Kokoszkiewicz dfirma.pl dkonto.pl you2.pl // DigitalOcean App Platform : https://www.digitalocean.com/products/app-platform/ // Submitted by Braxton Huggins ondigitalocean.app // DigitalOcean Spaces : https://www.digitalocean.com/products/spaces/ // Submitted by Robin H. Johnson *.digitaloceanspaces.com // DigitalPlat : https://www.digitalplat.org/ // Submitted by Edward Hsing us.kg // Diher Solutions : https://diher.solutions // Submitted by Didi Hermawan rss.my.id diher.solutions // Discord Inc : https://discord.com // Submitted by Sahn Lam discordsays.com discordsez.com // DNS Africa Ltd https://dns.business // Submitted by Calvin Browne jozi.biz // DNShome : https://www.dnshome.de/ // Submitted by Norbert Auler dnshome.de // DotArai : https://www.dotarai.com/ // Submitted by Atsadawat Netcharadsang online.th shop.th // DrayTek Corp. : https://www.draytek.com/ // Submitted by Paul Fang drayddns.com // DreamCommerce : https://shoper.pl/ // Submitted by Konrad Kotarba shoparena.pl // DreamHost : http://www.dreamhost.com/ // Submitted by Andrew Farmer dreamhosters.com // Dreamyoungs, Inc. : https://durumis.com // Submitted by Infra Team durumis.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.biz for-better.biz for-more.biz for-some.biz for-the.biz selfip.biz webhop.biz ftpaccess.cc game-server.cc myphotos.cc scrapping.cc blogdns.com cechire.com dnsalias.com dnsdojo.com doesntexist.com dontexist.com doomdns.com dyn-o-saur.com dynalias.com 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 est-a-la-maison.com est-a-la-masion.com est-le-patron.com est-mon-blogueur.com from-ak.com from-al.com from-ar.com from-ca.com 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-ma.com from-md.com 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-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 getmyip.com gotdns.com hobby-site.com homelinux.com homeunix.com iamallama.com is-a-anarchist.com is-a-blogger.com is-a-bookkeeper.com is-a-bulls-fan.com is-a-caterer.com is-a-chef.com 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-green.com is-a-guru.com is-a-hard-worker.com is-a-hunter.com is-a-landscaper.com is-a-lawyer.com is-a-liberal.com is-a-libertarian.com is-a-llama.com is-a-musician.com is-a-nascarfan.com is-a-nurse.com is-a-painter.com 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-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-certified.com is-gone.com is-into-anime.com is-into-cars.com is-into-cartoons.com is-into-games.com is-leet.com is-not-certified.com is-slick.com is-uberleet.com is-with-theband.com isa-geek.com isa-hockeynut.com issmarterthanyou.com likes-pie.com likescandy.com neat-url.com saves-the-whales.com selfip.com sells-for-less.com sells-for-u.com servebbs.com simple-url.com space-to-rent.com teaches-yoga.com writesthisblog.com ath.cx fuettertdasnetz.de isteingeek.de istmein.de lebtimnetz.de leitungsen.de traeumtgerade.de barrel-of-knowledge.info barrell-of-knowledge.info dyndns.info for-our.info groks-the.info groks-this.info here-for-more.info knowsitall.info selfip.info webhop.info forgot.her.name forgot.his.name at-band-camp.net blogdns.net broke-it.net buyshouses.net dnsalias.net dnsdojo.net does-it.net dontexist.net dynalias.net dynathome.net endofinternet.net from-az.net from-co.net from-la.net from-ny.net gets-it.net ham-radio-op.net homeftp.net homeip.net homelinux.net homeunix.net in-the-band.net is-a-chef.net is-a-geek.net isa-geek.net kicks-ass.net office-on-the.net podzone.net scrapper-site.net selfip.net sells-it.net servebbs.net serveftp.net thruhere.net webhop.net merseine.nu mine.nu shacknet.nu blogdns.org blogsite.org boldlygoingnowhere.org dnsalias.org dnsdojo.org doesntexist.org dontexist.org doomdns.org dvrdns.org dynalias.org dyndns.org go.dyndns.org home.dyndns.org endofinternet.org endoftheinternet.org from-me.org game-host.org gotdns.org hobby-site.org homedns.org homeftp.org homelinux.org homeunix.org is-a-bruinsfan.org is-a-candidate.org is-a-celticsfan.org is-a-chef.org is-a-geek.org is-a-knight.org is-a-linux-user.org is-a-patsfan.org is-a-soxfan.org is-found.org is-lost.org is-saved.org is-very-bad.org is-very-evil.org is-very-good.org is-very-nice.org is-very-sweet.org isa-geek.org kicks-ass.org misconfused.org podzone.org readmyblog.org selfip.org sellsyourhome.org servebbs.org serveftp.org servegame.org stuff-4-sale.org webhop.org better-than.tv dyndns.tv on-the-web.tv worse-than.tv is-by.us land-4-sale.us stuff-4-sale.us dyndns.ws mypets.ws // 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 // dynv6 : https://dynv6.com // Submitted by Dominik Menke dynv6.net // E4YOU spol. s.r.o. : https://e4you.cz/ // Submitted by Vladimir Dudr e4.cz // Easypanel : https://easypanel.io // Submitted by Andrei Canta easypanel.app easypanel.host // EasyWP : https://www.easywp.com // Submitted by *.ewp.live // eDirect Corp. : https://hosting.url.com.tw/ // Submitted by C.S. chang twmail.cc twmail.net twmail.org mymailer.com.tw url.tw // Electromagnetic Field : https://www.emfcamp.org // Submitted by at.emf.camp // Elefunc, Inc. : https://elefunc.com // Submitted by Cetin Sert rt.ht // Elementor : Elementor Ltd. // Submitted by Anton Barkan elementor.cloud elementor.cool // En root‽ : https://en-root.org // Submitted by Emmanuel Raviart en-root.fr // Enalean SAS: https://www.enalean.com // Submitted by Enalean Security Team mytuleap.com tuleap-partners.com // Encoretivity AB: https://encore.dev // Submitted by André Eriksson encr.app encoreapi.com // encoway GmbH : https://www.encoway.de // Submitted by Marcel Daus eu.encoway.cloud // EU.org https://eu.org/ // Submitted by Pierre Beyssac eu.org al.eu.org asso.eu.org at.eu.org au.eu.org be.eu.org bg.eu.org ca.eu.org cd.eu.org ch.eu.org cn.eu.org cy.eu.org cz.eu.org de.eu.org dk.eu.org edu.eu.org ee.eu.org es.eu.org fi.eu.org fr.eu.org gr.eu.org hr.eu.org hu.eu.org ie.eu.org il.eu.org in.eu.org int.eu.org is.eu.org it.eu.org jp.eu.org kr.eu.org lt.eu.org lu.eu.org lv.eu.org 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 pl.eu.org pt.eu.org ro.eu.org ru.eu.org se.eu.org si.eu.org sk.eu.org tr.eu.org uk.eu.org us.eu.org // Eurobyte : https://eurobyte.ru // Submitted by Evgeniy Subbotin eurodir.ru // Evennode : http://www.evennode.com/ // Submitted by Michal Kralik eu-1.evennode.com eu-2.evennode.com eu-3.evennode.com eu-4.evennode.com us-1.evennode.com us-2.evennode.com us-3.evennode.com us-4.evennode.com // Evervault : https://evervault.com // Submitted by Hannah Neary relay.evervault.app relay.evervault.dev // Expo : https://expo.dev/ // Submitted by James Ide expo.app staging.expo.app // Fabrica Technologies, Inc. : https://www.fabrica.dev/ // Submitted by Eric Jiang onfabrica.com // FAITID : https://faitid.org/ // Submitted by Maxim Alzoba // https://www.flexireg.net/stat_info ru.net adygeya.ru bashkiria.ru bir.ru cbg.ru com.ru dagestan.ru grozny.ru kalmykia.ru kustanai.ru marine.ru mordovia.ru msk.ru mytis.ru nalchik.ru nov.ru pyatigorsk.ru spb.ru vladikavkaz.ru vladimir.ru abkhazia.su adygeya.su aktyubinsk.su arkhangelsk.su armenia.su ashgabad.su azerbaijan.su balashov.su bashkiria.su bryansk.su bukhara.su chimkent.su dagestan.su east-kazakhstan.su exnet.su georgia.su grozny.su ivanovo.su jambyl.su kalmykia.su kaluga.su karacol.su karaganda.su karelia.su khakassia.su krasnodar.su kurgan.su kustanai.su lenug.su mangyshlak.su mordovia.su msk.su murmansk.su nalchik.su navoi.su north-kazakhstan.su nov.su obninsk.su penza.su pokrovsk.su sochi.su spb.su tashkent.su termez.su togliatti.su troitsk.su tselinograd.su tula.su tuva.su vladikavkaz.su vladimir.su vologda.su // Fancy Bits, LLC : http://getchannels.com // Submitted by Aman Gupta channelsdvr.net u.channelsdvr.net // Fastly Inc. : http://www.fastly.com/ // Submitted by Fastly Security edgecompute.app fastly-edge.com fastly-terrarium.com 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 fastlylb.net map.fastlylb.net // Fastmail : https://www.fastmail.com/ // Submitted by Marc Bradshaw *.user.fm // FASTVPS EESTI OU : https://fastvps.ru/ // Submitted by Likhachev Vasiliy fastvps-server.com fastvps.host myfast.host fastvps.site myfast.space // FearWorks Media Ltd. : https://fearworksmedia.co.uk // submitted by Keith Fairley conn.uk copro.uk hosp.uk // 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 // FH Muenster : https://www.fh-muenster.de // Submitted by Robin Naundorf fh-muenster.io // Filegear Inc. : https://www.filegear.com // Submitted by Jason Zhu filegear.me // Firebase, Inc. // Submitted by Chris Raynor firebaseapp.com // FlashDrive : https://flashdrive.io // Submitted by Eric Chan fldrv.com // FlutterFlow : https://flutterflow.io // Submitted by Anton Emelyanov flutterflow.app // fly.io: https://fly.io // Submitted by Kurt Mackey fly.dev shw.io edgeapp.net // Forgerock : https://www.forgerock.com // Submitted by Roderick Parr forgeblocks.com id.forgerock.io // Framer : https://www.framer.com // Submitted by Koen Rouwhorst framer.ai framer.app framercanvas.com framer.media framer.photos framer.website framer.wiki // Frederik Braun : https://frederik-braun.com // Submitted by Frederik Braun 0e.vc // Freebox : http://www.freebox.fr // Submitted by Romain Fliedel freebox-os.com freeboxos.com fbx-os.fr fbxos.fr freebox-os.fr freeboxos.fr // freedesktop.org : https://www.freedesktop.org // Submitted by Daniel Stone freedesktop.org // freemyip.com : https://freemyip.com // Submitted by Cadence freemyip.com // Frusky MEDIA&PR : https://www.frusky.de // Submitted by Victor Pupynin *.frusky.de // FunkFeuer - Verein zur Förderung freier Netze : https://www.funkfeuer.at // Submitted by Daniel A. Maierhofer wien.funkfeuer.at // Future Versatile Group. : https://www.fvg-on.net/ // T.Kabu daemon.asia dix.asia mydns.bz 0am.jp 0g0.jp 0j0.jp 0t0.jp mydns.jp pgw.jp wjg.jp keyword-on.net live-on.net server-on.net mydns.tw mydns.vc // Futureweb GmbH : https://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 // GCom Internet : https://www.gcom.net.au // Submitted by Leo Julius aliases121.com // GDS : https://www.gov.uk/service-manual/technology/managing-domain-names // Submitted by Stephen Ford campaign.gov.uk service.gov.uk independent-commission.uk independent-inquest.uk independent-inquiry.uk independent-panel.uk independent-review.uk public-inquiry.uk royal-commission.uk // Gehirn Inc. : https://www.gehirn.co.jp/ // Submitted by Kohei YOSHIDA gehirn.ne.jp usercontent.jp // Gentlent, Inc. : https://www.gentlent.com // Submitted by Tom Klein gentapps.com gentlentapis.com lab.ms cdn-edges.net // GetLocalCert : https://getlocalcert.net // Submitted by William Harrison localcert.net localhostcert.net // GignoSystemJapan: http://gsj.bz // Submitted by GignoSystemJapan gsj.bz // GitHub, Inc. // Submitted by Patrick Toomey githubusercontent.com githubpreview.dev github.io // GitLab, Inc. // Submitted by Alex Hanselka gitlab.io // Gitplac.si - https://gitplac.si // Submitted by Aljaž Starc gitapp.si gitpage.si // Glitch, Inc : https://glitch.com // Submitted by Mads Hartmann glitch.me // Global NOG Alliance : https://nogalliance.org/ // Submitted by Sander Steffann nog.community // Globe Hosting SRL : https://www.globehosting.com/ // Submitted by Gavin Brown co.ro shop.ro // GMO Pepabo, Inc. : https://pepabo.com/ // Submitted by Hosting Div lolipop.io angry.jp babyblue.jp babymilk.jp backdrop.jp bambina.jp bitter.jp blush.jp boo.jp boy.jp boyfriend.jp but.jp candypop.jp capoo.jp catfood.jp cheap.jp chicappa.jp chillout.jp chips.jp chowder.jp chu.jp ciao.jp cocotte.jp coolblog.jp cranky.jp cutegirl.jp daa.jp deca.jp deci.jp digick.jp egoism.jp fakefur.jp fem.jp flier.jp floppy.jp fool.jp frenchkiss.jp girlfriend.jp girly.jp gloomy.jp gonna.jp greater.jp hacca.jp heavy.jp her.jp hiho.jp hippy.jp holy.jp hungry.jp icurus.jp itigo.jp jellybean.jp kikirara.jp kill.jp kilo.jp kuron.jp littlestar.jp lolipopmc.jp lolitapunk.jp lomo.jp lovepop.jp lovesick.jp main.jp mods.jp mond.jp mongolian.jp moo.jp namaste.jp nikita.jp nobushi.jp noor.jp oops.jp parallel.jp parasite.jp pecori.jp peewee.jp penne.jp pepper.jp perma.jp pigboat.jp pinoko.jp punyu.jp pupu.jp pussycat.jp pya.jp raindrop.jp readymade.jp sadist.jp schoolbus.jp secret.jp staba.jp stripper.jp sub.jp sunnyday.jp thick.jp tonkotsu.jp under.jp upper.jp velvet.jp verse.jp versus.jp vivian.jp watson.jp weblike.jp whitesnow.jp zombie.jp heteml.net // GoDaddy Registry : https://registry.godaddy // Submitted by Rohan Durrant graphic.design // GoIP DNS Services : http://www.goip.de // Submitted by Christian Poulter goip.de // Google, Inc. // Submitted by Shannon McCabe blogspot.ae blogspot.al blogspot.am *.hosted.app *.run.app web.app blogspot.com.ar blogspot.co.at blogspot.com.au blogspot.ba blogspot.be blogspot.bg blogspot.bj blogspot.com.br blogspot.com.by blogspot.ca blogspot.cf blogspot.ch blogspot.cl blogspot.com.co *.0emm.com appspot.com *.r.appspot.com blogspot.com codespot.com googleapis.com googlecode.com pagespeedmobilizer.com withgoogle.com withyoutube.com blogspot.cv blogspot.com.cy blogspot.cz blogspot.de *.gateway.dev blogspot.dk blogspot.com.ee blogspot.com.eg blogspot.com.es blogspot.fi blogspot.fr cloud.goog translate.goog *.usercontent.goog blogspot.gr blogspot.hk blogspot.hr blogspot.hu blogspot.co.id blogspot.ie blogspot.co.il blogspot.in blogspot.is blogspot.it blogspot.jp blogspot.co.ke blogspot.kr blogspot.li blogspot.lt blogspot.lu blogspot.md blogspot.mk blogspot.com.mt blogspot.mx blogspot.my cloudfunctions.net blogspot.com.ng blogspot.nl blogspot.no blogspot.co.nz 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.com.tr blogspot.tw blogspot.ug blogspot.co.uk blogspot.com.uy blogspot.vn blogspot.co.za // Goupile : https://goupile.fr // Submitted by Niels Martignene goupile.fr // GOV.UK Pay : https://www.payments.service.gov.uk/ // Submitted by Richard Baker pymnt.uk // GOV.UK Platform as a Service : https://www.cloud.service.gov.uk/ // Submitted by Tom Whitwell cloudapps.digital london.cloudapps.digital // Government of the Netherlands: https://www.government.nl // Submitted by gov.nl // Grafana Labs: https://grafana.com/ // Submitted by Platform Engineering grafana-dev.net // GrayJay Web Solutions Inc. : https://grayjaysports.ca // Submitted by Matt Yamkowy grayjayleagues.com // GünstigBestellen : https://günstigbestellen.de // Submitted by Furkan Akkoc günstigbestellen.de günstigliefern.de // Hakaran group: http://hakaran.cz // Submitted by Arseniy Sokolov fin.ci free.hr caa.li ua.rs conf.se // Häkkinen.fi // Submitted by Eero Häkkinen häkkinen.fi // Harrison Network : https://hrsn.net // Submitted by William Harrison hrsn.dev // Hashbang : https://hashbang.sh hashbang.sh // Hasura : https://hasura.io // Submitted by Shahidh K Muhammed hasura.app hasura-app.io // Hatena Co., Ltd. : https://hatena.co.jp // Submitted by Masato Nakamura hatenablog.com hatenadiary.com hateblo.jp hatenablog.jp hatenadiary.jp hatenadiary.org // Heilbronn University of Applied Sciences - Faculty Informatics (GitLab Pages): https://www.hs-heilbronn.de // Submitted by Richard Zowalla pages.it.hs-heilbronn.de pages-research.it.hs-heilbronn.de // HeiyuSpace: https://lazycat.cloud // Submitted by Xia Bin heiyu.space // Helio Networks : https://heliohost.org // Submitted by Ben Frede helioho.st heliohost.us // Hepforge : https://www.hepforge.org // Submitted by David Grellscheid hepforge.org // Heroku : https://www.heroku.com/ // Submitted by Tom Maher herokuapp.com herokussl.com // Heyflow : https://www.heyflow.com // Submitted by Mirko Nitschke heyflow.page heyflow.site // Hibernating Rhinos // Submitted by Oren Eini ravendb.cloud ravendb.community development.run ravendb.run // home.pl S.A.: https://home.pl // Submitted by Krzysztof Wolski homesklep.pl // Homebase : https://homebase.id/ // Submitted by Jason Babo *.kin.one *.id.pub *.kin.pub // Hong Kong Productivity Council: https://www.hkpc.org/ // Submitted by SECaaS Team secaas.hk // Hoplix : https://www.hoplix.com // Submitted by Danilo De Franco hoplix.shop // HOSTBIP REGISTRY : https://www.hostbip.com/ // Submitted by Atanunu Igbunuroghene orx.biz biz.gl biz.ng co.biz.ng dl.biz.ng go.biz.ng lg.biz.ng on.biz.ng col.ng firm.ng gen.ng ltd.ng ngo.ng plc.ng // HostFly : https://www.ie.ua // Submitted by Bohdan Dub ie.ua // HostyHosting : https://hostyhosting.com hostyhosting.io // Hugging Face: https://huggingface.co // Submitted by Eliott Coyac hf.space static.hf.space // Hypernode B.V. : https://www.hypernode.com/ // Submitted by Cipriano Groenendal hypernode.io // I-O DATA DEVICE, INC. : http://www.iodata.com/ // Submitted by Yuji Minagawa iobb.net // i-registry s.r.o. : http://www.i-registry.cz/ // Submitted by Martin Semrad co.cz // Ici la Lune : http://www.icilalune.com/ // Submitted by Simon Morvan *.moonscale.io moonscale.net // iDOT Services Limited : http://www.domain.gr.com // Submitted by Gavin Brown gr.com // iki.fi // Submitted by Hannu Aronsson iki.fi // iliad italia : https://www.iliad.it // Submitted by Marios Makassikis ibxos.it iliadboxos.it // Incsub, LLC : https://incsub.com/ // Submitted by Aaron Edwards smushcdn.com wphostedmail.com wpmucdn.com tempurl.host wpmudev.host // Individual Network Berlin e.V. : https://www.in-berlin.de/ // Submitted by Christian Seitz dyn-berlin.de in-berlin.de in-brb.de in-butter.de in-dsl.de in-vpn.de in-dsl.net in-vpn.net in-dsl.org in-vpn.org // info.at : http://www.info.at/ biz.at info.at // info.cx : http://info.cx // Submitted by June Slater info.cx // Interlegis : http://www.interlegis.leg.br // Submitted by Gabriel Ferreira ac.leg.br al.leg.br am.leg.br ap.leg.br ba.leg.br ce.leg.br df.leg.br es.leg.br go.leg.br ma.leg.br mg.leg.br ms.leg.br mt.leg.br pa.leg.br pb.leg.br pe.leg.br pi.leg.br pr.leg.br rj.leg.br rn.leg.br ro.leg.br rr.leg.br rs.leg.br sc.leg.br se.leg.br sp.leg.br to.leg.br // intermetrics GmbH : https://pixolino.com/ // Submitted by Wolfgang Schwarz pixolino.com // Internet-Pro, LLP : https://netangels.ru/ // Submitted by Vasiliy Sheredeko na4u.ru // IONOS SE : https://www.ionos.com/, // IONOS Group SE: https://www.ionos-group.com/ // submitted by Henrik Willert apps-1and1.com live-website.com apps-1and1.net websitebuilder.online app-ionos.space // iopsys software solutions AB : https://iopsys.eu/ // Submitted by Roman Azarenko iopsys.se // IPFS Project : https://ipfs.tech/ // Submitted by Interplanetary Shipyard *.dweb.link // IPiFony Systems, Inc. : https://www.ipifony.com/ // Submitted by Matthew Hardeman ipifony.net // ir.md : https://nic.ir.md // Submitted by Ali Soizi ir.md // is-a-good.dev : https://is-a-good.dev // Submitted by William Harrison is-a-good.dev // is-a.dev : https://is-a.dev // Submitted by William Harrison is-a.dev // IServ GmbH : https://iserv.de // Submitted by Mario Hoberg iservschule.de mein-iserv.de schulplattform.de schulserver.de test-iserv.de iserv.dev // Jelastic, Inc. : https://jelastic.com/ // Submitted by Ihor Kolodyuk mel.cloudlets.com.au cloud.interhostsolutions.be alp1.ae.flow.ch appengine.flow.ch es-1.axarnet.cloud diadem.cloud vip.jelastic.cloud jele.cloud it1.eur.aruba.jenv-aruba.cloud it1.jenv-aruba.cloud keliweb.cloud cs.keliweb.cloud oxa.cloud tn.oxa.cloud uk.oxa.cloud primetel.cloud uk.primetel.cloud ca.reclaim.cloud uk.reclaim.cloud us.reclaim.cloud ch.trendhosting.cloud de.trendhosting.cloud jele.club dopaas.com paas.hosted-by-previder.com rag-cloud.hosteur.com rag-cloud-ch.hosteur.com jcloud.ik-server.com jcloud-ver-jpc.ik-server.com demo.jelastic.com paas.massivegrid.com jed.wafaicloud.com ryd.wafaicloud.com j.scaleforce.com.cy jelastic.dogado.eu fi.cloudplatform.fi demo.datacenter.fi paas.datacenter.fi jele.host mircloud.host paas.beebyte.io sekd1.beebyteapp.io jele.io jc.neen.it jcloud.kz cloudjiffy.net fra1-de.cloudjiffy.net west1-us.cloudjiffy.net jls-sto1.elastx.net jls-sto2.elastx.net jls-sto3.elastx.net fr-1.paas.massivegrid.net lon-1.paas.massivegrid.net lon-2.paas.massivegrid.net ny-1.paas.massivegrid.net ny-2.paas.massivegrid.net sg-1.paas.massivegrid.net jelastic.saveincloud.net nordeste-idc.saveincloud.net j.scaleforce.net sdscloud.pl unicloud.pl mircloud.ru enscaled.sg jele.site jelastic.team orangecloud.tn j.layershift.co.uk phx.enscaled.us mircloud.us // Jino : https://www.jino.ru // Submitted by Sergey Ulyashin myjino.ru *.hosting.myjino.ru *.landing.myjino.ru *.spectrum.myjino.ru *.vps.myjino.ru // Jotelulu S.L. : https://jotelulu.com // Submitted by Daniel Fariña jotelulu.cloud // JouwWeb B.V. : https://www.jouwweb.nl // Submitted by Camilo Sperberg webadorsite.com jouwweb.site // Joyent : https://www.joyent.com/ // Submitted by Brian Bennett *.cns.joyent.com *.triton.zone // 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 // Kapsi : https://kapsi.fi // Submitted by Tomi Juntunen kapsi.fi // Katholieke Universiteit Leuven: https://www.kuleuven.be // Submitted by Abuse KU Leuven ezproxy.kuleuven.be kuleuven.cloud // Keyweb AG : https://www.keyweb.de // Submitted by Martin Dannehl keymachine.de // KingHost : https://king.host // Submitted by Felipe Keller Braz kinghost.net uni5.net // KnightPoint Systems, LLC : http://www.knightpoint.com/ // Submitted by Roy Keene knightpoint.systems // KoobinEvent, SL: https://www.koobin.com // Submitted by Iván Oliva koobin.events // Krellian Ltd. : https://krellian.com // Submitted by Ben Francis webthings.io krellian.net // KUROKU LTD : https://kuroku.ltd/ // Submitted by DisposaBoy oya.to // 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 // libp2p project : https://libp2p.io // Submitted by Interplanetary Shipyard libp2p.direct // Libre IT Ltd : https://libre.nz // Submitted by Tomas Maggio runcontainers.dev // Lifetime Hosting : https://Lifetime.Hosting/ // Submitted by Mike Fillator co.business co.education co.events co.financial co.network co.place co.technology // linkyard ldt: https://www.linkyard.ch/ // Submitted by Mario Siegenthaler linkyard-cloud.ch linkyard.cloud // Linode : https://linode.com // Submitted by members.linode.com *.nodebalancer.linode.com *.linodeobjects.com ip.linodeusercontent.com // LiquidNet Ltd : http://www.liquidnetlimited.com/ // Submitted by Victor Velchev we.bs // Listen53 : https://www.l53.net // Submitted by Gerry Keh filegear-sg.me ggff.net // Localcert : https://localcert.dev // Submitted by Lann Martin *.user.localcert.dev // Lodz University of Technology LODMAN regional domains https://www.man.lodz.pl/dns // Submitted by Piotr Wilk lodz.pl pabianice.pl plock.pl sieradz.pl skierniewice.pl zgierz.pl // Log'in Line : https://www.loginline.com/ // Submitted by Rémi Mach loginline.app loginline.dev loginline.io loginline.services loginline.site // Lõhmus Family, The // Submitted by Heiki Lõhmus lohmus.me // Lokalized : https://lokalized.nl // Submitted by Noah Taheij servers.run // LubMAN UMCS Sp. z o.o : https://lubman.pl/ // Submitted by Ireneusz Maliszewski krasnik.pl leczna.pl lubartow.pl lublin.pl poniatowa.pl swidnik.pl // Lug.org.uk : https://lug.org.uk // Submitted by Jon Spriggs glug.org.uk lug.org.uk lugs.org.uk // Lukanet Ltd : https://lukanet.com // Submitted by Anton Avramov barsy.bg barsy.club barsycenter.com barsyonline.com barsy.de barsy.dev barsy.eu barsy.gr barsy.in barsy.info barsy.io barsy.me barsy.menu barsyonline.menu barsy.mobi barsy.net barsy.online barsy.org barsy.pro barsy.pub barsy.ro barsy.rs barsy.shop barsyonline.shop barsy.site barsy.store barsy.support barsy.uk barsy.co.uk barsyonline.co.uk // Magento Commerce // Submitted by Damien Tournoud *.magentosite.cloud // Mail.Ru Group : https://hb.cldmail.ru // Submitted by Ilya Zaretskiy hb.cldmail.ru // MathWorks : https://www.mathworks.com/ // Submitted by Emily Reed matlab.cloud modelscape.com mwcloudnonprod.com polyspace.com // May First - People Link : https://mayfirst.org/ // Submitted by Jamie McClelland mayfirst.info mayfirst.org // Maze Play: https://www.mazeplay.com // Submitted by Adam Humpherys mazeplay.com // McHost : https://mchost.ru // Submitted by Evgeniy Subbotin mcdir.me mcdir.ru vps.mcdir.ru mcpre.ru // mcpe.me : https://mcpe.me // Submitted by Noa Heyl mcpe.me // Mediatech : https://mediatech.by // Submitted by Evgeniy Kozhuhovskiy mediatech.by mediatech.dev // Medicom Health : https://medicomhealth.com // Submitted by Michael Olson hra.health // MedusaJS, Inc : https://medusajs.com/ // Submitted by Stevche Radevski medusajs.app // Memset hosting : https://www.memset.com // Submitted by Tom Whitwell miniserver.com memset.net // Messerli Informatik AG : https://www.messerli.ch/ // Submitted by Ruben Schmidmeister messerli.app // Meta Platforms, Inc. : https://meta.com/ // Submitted by Jacob Cordero atmeta.com apps.fbsbx.com // MetaCentrum, CESNET z.s.p.o. : https://www.metacentrum.cz/en/ // Submitted by Zdeněk Šustr and Radim Janča *.cloud.metacentrum.cz custom.metacentrum.cz flt.cloud.muni.cz usr.cloud.muni.cz // Meteor Development Group : https://www.meteor.com/hosting // Submitted by Pierre Carrier meteorapp.com eu.meteorapp.com // Michau Enterprises Limited : http://www.co.pl/ co.pl // Microsoft Corporation : http://microsoft.com // Submitted by Public Suffix List Admin // Managed by Corporate Domains // Microsoft Azure : https://home.azure *.azurecontainer.io azure-api.net azure-mobile.net azureedge.net azurefd.net azurestaticapps.net 1.azurestaticapps.net 2.azurestaticapps.net 3.azurestaticapps.net 4.azurestaticapps.net 5.azurestaticapps.net 6.azurestaticapps.net 7.azurestaticapps.net centralus.azurestaticapps.net eastasia.azurestaticapps.net eastus2.azurestaticapps.net westeurope.azurestaticapps.net westus2.azurestaticapps.net azurewebsites.net cloudapp.net trafficmanager.net blob.core.windows.net servicebus.windows.net // MikroTik: https://mikrotik.com // Submitted by MikroTik SysAdmin Team routingthecloud.com sn.mynetname.net routingthecloud.net routingthecloud.org // minion.systems : http://minion.systems // Submitted by Robert Böttinger csx.cc // Mittwald CM Service GmbH & Co. KG : https://mittwald.de // Submitted by Marco Rieger mydbserver.com webspaceconfig.de mittwald.info mittwaldserver.info typo3server.info project.space // MODX Systems LLC : https://modx.com // Submitted by Elizabeth Southwell modx.dev // Mozilla Foundation : https://mozilla.org/ // Submitted by glob bmoattachments.org // MSK-IX : https://www.msk-ix.ru/ // Submitted by Khannanov Roman net.ru org.ru pp.ru // Mythic Beasts : https://www.mythic-beasts.com // Submitted by Paul Cammish hostedpi.com caracal.mythic-beasts.com customer.mythic-beasts.com fentiger.mythic-beasts.com lynx.mythic-beasts.com ocelot.mythic-beasts.com oncilla.mythic-beasts.com onza.mythic-beasts.com sphinx.mythic-beasts.com vs.mythic-beasts.com x.mythic-beasts.com yali.mythic-beasts.com cust.retrosnub.co.uk // Nabu Casa : https://www.nabucasa.com // Submitted by Paulus Schoutsen ui.nabu.casa // Net at Work Gmbh : https://www.netatwork.de // Submitted by Jan Jaeschke cloud.nospamproxy.com // Netfy Domains : https://netfy.domains // Submitted by Suranga Ranasinghe netfy.app // Netlify : https://www.netlify.com // Submitted by Jessica Parsons netlify.app // Neustar Inc. // Submitted by Trung Tran 4u.com // NFSN, Inc. : https://www.NearlyFreeSpeech.NET/ // Submitted by Jeff Wheelhouse nfshost.com // NFT.Storage : https://nft.storage/ // Submitted by Vasco Santos or ipfs.nftstorage.link // NGO.US Registry : https://nic.ngo.us // Submitted by Alstra Solutions Ltd. Networking Team ngo.us // ngrok : https://ngrok.com/ // Submitted by Alan Shreve ngrok.app ngrok-free.app ngrok.dev ngrok-free.dev ngrok.io ap.ngrok.io au.ngrok.io eu.ngrok.io in.ngrok.io jp.ngrok.io sa.ngrok.io us.ngrok.io ngrok.pizza ngrok.pro // Nicolaus Copernicus University in Torun - MSK TORMAN (https://www.man.torun.pl) torun.pl // Nimbus Hosting Ltd. : https://www.nimbushosting.co.uk/ // Submitted by Nicholas Ford nh-serv.co.uk nimsite.uk // No-IP.com : https://noip.com/ // Submitted by Deven Reza mmafan.biz myftp.biz no-ip.biz no-ip.ca fantasyleague.cc gotdns.ch 3utilities.com blogsyte.com ciscofreak.com damnserver.com ddnsking.com ditchyourip.com dnsiskinky.com dynns.com geekgalaxy.com health-carereform.com homesecuritymac.com homesecuritypc.com myactivedirectory.com mysecuritycamera.com myvnc.com net-freaks.com onthewifi.com point2this.com quicksytes.com securitytactics.com servebeer.com servecounterstrike.com serveexchange.com serveftp.com servegame.com servehalflife.com servehttp.com servehumour.com serveirc.com servemp3.com servep2p.com servepics.com servequake.com servesarcasm.com stufftoread.com unusualperson.com workisboring.com dvrcam.info ilovecollege.info no-ip.info brasilia.me ddns.me dnsfor.me hopto.me loginto.me noip.me webhop.me bounceme.net ddns.net eating-organic.net mydissent.net myeffect.net mymediapc.net mypsx.net mysecuritycamera.net nhlfan.net no-ip.net pgafan.net privatizehealthinsurance.net redirectme.net serveblog.net serveminecraft.net sytes.net cable-modem.org collegefan.org couchpotatofries.org hopto.org mlbfan.org myftp.org mysecuritycamera.org nflfan.org no-ip.org read-books.org ufcfan.org zapto.org no-ip.co.uk golffan.us noip.us pointto.us // NodeArt : https://nodeart.io // Submitted by Konstantin Nosov stage.nodeart.io // Noop : https://noop.app // Submitted by Nathaniel Schweinberg *.developer.app noop.app // Northflank Ltd. : https://northflank.com/ // Submitted by Marco Suter *.northflank.app *.build.run *.code.run *.database.run *.migration.run // Noticeable : https://noticeable.io // Submitted by Laurent Pellegrino noticeable.news // Notion Labs, Inc : https://www.notion.so/ // Submitted by Jess Yao notion.site // Now-DNS : https://now-dns.com // Submitted by Steve Russell dnsking.ch mypi.co n4t.co 001www.com myiphost.com forumz.info 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 // nsupdate.info : https://www.nsupdate.info/ // Submitted by Thomas Waldmann nsupdate.info nerdpol.ovh // NYC.mn : https://dot.nyc.mn/ // Submitted by NYC.mn Subdomain Service nyc.mn // O3O.Foundation : https://o3o.foundation/ // Submitted by the prvcy.page Registry Team prvcy.page // Obl.ong : // Submitted by Reese Armstrong obl.ong // Observable, Inc. : https://observablehq.com // Submitted by Mike Bostock observablehq.cloud static.observableusercontent.com // OMG.LOL : https://omg.lol // Submitted by Adam Newbold omg.lol // Omnibond Systems, LLC. : https://www.omnibond.com // Submitted by Cole Estep cloudycluster.net // OmniWe Limited: https://omniwe.com // Submitted by Vicary Archangel omniwe.site // One.com: https://www.one.com/ // Submitted by Jacob Bunk Nielsen 123webseite.at 123website.be simplesite.com.br 123website.ch simplesite.com 123webseite.de 123hjemmeside.dk 123miweb.es 123kotisivu.fi 123siteweb.fr simplesite.gr 123homepage.it 123website.lu 123website.nl 123hjemmeside.no service.one simplesite.pl 123paginaweb.pt 123minsida.se // Open Domains : https://open-domains.net // Submitted by William Harrison is-a-fullstack.dev is-cool.dev is-not-a.dev localplayer.dev is-local.org // Open Social : https://www.getopensocial.com/ // Submitted by Alexander Varwijk opensocial.site // OpenCraft GmbH : http://opencraft.com/ // Submitted by Sven Marnach opencraft.hosting // OpenHost : https://registry.openhost.uk // Submitted by OpenHost Registry Team 16-b.it 32-b.it 64-b.it // OpenResearch GmbH: https://openresearch.com/ // Submitted by Philipp Schmid orsites.com // Opera Software, A.S.A. // Submitted by Yngve Pettersen operaunite.com // Oracle Dyn : https://cloud.oracle.com/home https://dyn.com/dns/ // Submitted by Gregory Drake // Note: This is intended to also include customer-oci.com due to wildcards implicitly including the current label *.customer-oci.com *.oci.customer-oci.com *.ocp.customer-oci.com *.ocs.customer-oci.com *.oraclecloudapps.com *.oraclegovcloudapps.com *.oraclegovcloudapps.uk // Orange : https://www.orange.com // Submitted by Alexandre Linte tech.orange // OsSav Technology Ltd. : https://ossav.com/ // TLD Nic: http://nic.can.re - TLD Whois Server: whois.can.re // Submitted by OsSav Technology Ltd. can.re // Oursky Limited : https://authgear.com/, https://skygear.io/ // Submitted by Authgear Team , Skygear Developer authgear-staging.com authgearapps.com skygearapp.com // OutSystems // Submitted by Duarte Santos outsystemscloud.com // OVHcloud: https://ovhcloud.com // Submitted by Vincent Cassé *.hosting.ovh.net *.webpaas.ovh.net // OwnProvider GmbH: http://www.ownprovider.com // Submitted by Jan Moennich ownprovider.com own.pm // OwO : https://whats-th.is/ // Submitted by Dean Sheather *.owo.codes // OX : http://www.ox.rs // Submitted by Adam Grand ox.rs // oy.lc // Submitted by Charly Coste oy.lc // Pagefog : https://pagefog.com/ // Submitted by Derek Myers pgfog.com // PageXL : https://pagexl.com // Submitted by Yann Guichard pagexl.com // Pantheon Systems, Inc. : https://pantheon.io/ // Submitted by Gary Dylina gotpantheon.com pantheonsite.io // Paywhirl, Inc : https://paywhirl.com/ // Submitted by Daniel Netzer *.paywhirl.com // pcarrier.ca Software Inc: https://pcarrier.ca/ // Submitted by Pierre Carrier *.xmit.co xmit.dev madethis.site srv.us gh.srv.us gl.srv.us // PE Ulyanov Kirill Sergeevich : https://airy.host // Submitted by Kirill Ulyanov lk3.ru // Peplink | Pepwave : http://peplink.com/ // Submitted by Steve Leung mypep.link // Perspecta : https://perspecta.com/ // Submitted by Kenneth Van Alstyne perspecta.cloud // 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 *.upsun.app upsunapp.com ent.platform.sh eu.platform.sh us.platform.sh *.platformsh.site *.tst.site // Platter: https://platter.dev // Submitted by Patrick Flor platter-app.com platter-app.dev platterp.us // Pley AB : https://www.pley.com/ // Submitted by Henning Pohl pley.games // Porter : https://porter.run/ // Submitted by Rudraksh MK onporter.run // Positive Codes Technology Company : http://co.bn/faq.html // Submitted by Zulfais co.bn // Postman, Inc : https://postman.com // Submitted by Rahul Dhawan postman-echo.com pstmn.io mock.pstmn.io httpbin.org // prequalifyme.today : https://prequalifyme.today // Submitted by DeepakTiwari deepak@ivylead.io prequalifyme.today // prgmr.com : https://prgmr.com/ // Submitted by Sarah Newman xen.prgmr.com // priv.at : http://www.nic.priv.at/ // Submitted by registry priv.at // Protonet GmbH : http://protonet.io // Submitted by Martin Meier protonet.io // Publication Presse Communication SARL : https://ppcom.fr // Submitted by Yaacov Akiba Slama chirurgiens-dentistes-en-france.fr byen.site // pubtls.org: https://www.pubtls.org // Submitted by Kor Nielsen pubtls.org // PythonAnywhere LLP: https://www.pythonanywhere.com // Submitted by Giles Thomas pythonanywhere.com eu.pythonanywhere.com // QA2 // Submitted by Daniel Dent (https://www.danieldent.com/) qa2.com // QCX // Submitted by Cassandra Beelen qcx.io *.sys.qcx.io // QNAP System Inc : https://www.qnap.com // Submitted by Nick Chang myqnapcloud.cn alpha-myqnapcloud.com dev-myqnapcloud.com mycloudnas.com mynascloud.com myqnapcloud.com // QOTO, Org. // Submitted by Jeffrey Phillips Freeman qoto.io // Qualifio : https://qualifio.com/ // Submitted by Xavier De Cock qualifioapp.com // Quality Unit : https://qualityunit.com // Submitted by Vasyl Tsalko ladesk.com // QuickBackend: https://www.quickbackend.com // Submitted by Dani Biro qbuser.com // 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 // Rad Web Hosting: https://radwebhosting.com // Submitted by Scott Claeys cloudsite.builders myradweb.net servername.us // Radix FZC : http://domains.in.net // Submitted by Gavin Brown web.in in.net // Raidboxes GmbH : https://raidboxes.de // Submitted by Auke Tembrink myrdbx.io site.rb-hosting.io // Rancher Labs, Inc : https://rancher.com // Submitted by Vincent Fiduccia *.on-rancher.cloud *.on-k3s.io *.on-rio.io // RavPage : https://www.ravpage.co.il // Submitted by Roni Horowitz ravpage.co.il // Read The Docs, Inc : https://www.readthedocs.org // Submitted by David Fischer readthedocs-hosted.com readthedocs.io // Red Hat, Inc. OpenShift : https://openshift.redhat.com/ // Submitted by Tim Kramer rhcloud.com // Redgate Software: https://red-gate.com // Submitted by Andrew Farries instances.spawn.cc // Render : https://render.com // Submitted by Anurag Goel onrender.com app.render.com // Repl.it : https://repl.it // Submitted by Lincoln Bergeson replit.app id.replit.app firewalledreplit.co id.firewalledreplit.co repl.co id.repl.co replit.dev archer.replit.dev bones.replit.dev canary.replit.dev global.replit.dev hacker.replit.dev id.replit.dev janeway.replit.dev kim.replit.dev kira.replit.dev kirk.replit.dev odo.replit.dev paris.replit.dev picard.replit.dev pike.replit.dev prerelease.replit.dev reed.replit.dev riker.replit.dev sisko.replit.dev spock.replit.dev staging.replit.dev sulu.replit.dev tarpit.replit.dev teams.replit.dev tucker.replit.dev wesley.replit.dev worf.replit.dev 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 // Rico Developments Limited : https://adimo.co // Submitted by Colin Brown adimo.co.uk // Riseup Networks : https://riseup.net // Submitted by Micah Anderson itcouldbewor.se // Roar Domains LLC : https://roar.basketball/ // Submitted by Gavin Brown aus.basketball nz.basketball // Rochester Institute of Technology : http://www.rit.edu/ // Submitted by Jennifer Herting git-pages.rit.edu // Rocky Enterprise Software Foundation : https://resf.org // Submitted by Neil Hanlon rocky.page // Rusnames Limited: http://rusnames.ru/ // Submitted by Sergey Zotov биз.рус ком.рус крым.рус мир.рус мск.рус орг.рус самара.рус сочи.рус спб.рус я.рус // Russian Academy of Sciences // Submitted by Tech Support ras.ru // Sakura Frp : https://www.natfrp.com // Submitted by Bobo Liu nyat.app // SAKURA Internet Inc. : https://www.sakura.ad.jp/ // Submitted by Internet Service Department 180r.com dojin.com sakuratan.com sakuraweb.com x0.com 2-d.jp bona.jp crap.jp daynight.jp eek.jp flop.jp halfmoon.jp jeez.jp matrix.jp mimoza.jp ivory.ne.jp mail-box.ne.jp mints.ne.jp mokuren.ne.jp opal.ne.jp sakura.ne.jp sumomo.ne.jp topaz.ne.jp netgamers.jp nyanta.jp o0o0.jp rdy.jp rgr.jp rulez.jp s3.isk01.sakurastorage.jp s3.isk02.sakurastorage.jp saloon.jp sblo.jp skr.jp tank.jp uh-oh.jp undo.jp rs.webaccel.jp user.webaccel.jp websozai.jp xii.jp squares.net jpn.org kirara.st x0.to from.tv sakura.tv // Salesforce.com, Inc. https://salesforce.com/ // Submitted by Salesforce Public Suffix List Team *.builder.code.com *.dev-builder.code.com *.stg-builder.code.com *.001.test.code-builder-stg.platform.salesforce.com *.d.crm.dev *.w.crm.dev *.wa.crm.dev *.wb.crm.dev *.wc.crm.dev *.wd.crm.dev *.we.crm.dev *.wf.crm.dev // 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.com logoip.de // Scaleway : https://www.scaleway.com/ // Submitted by Rémy Léone fr-par-1.baremetal.scw.cloud fr-par-2.baremetal.scw.cloud nl-ams-1.baremetal.scw.cloud cockpit.fr-par.scw.cloud fnc.fr-par.scw.cloud functions.fnc.fr-par.scw.cloud k8s.fr-par.scw.cloud nodes.k8s.fr-par.scw.cloud s3.fr-par.scw.cloud s3-website.fr-par.scw.cloud whm.fr-par.scw.cloud priv.instances.scw.cloud pub.instances.scw.cloud k8s.scw.cloud cockpit.nl-ams.scw.cloud k8s.nl-ams.scw.cloud nodes.k8s.nl-ams.scw.cloud s3.nl-ams.scw.cloud s3-website.nl-ams.scw.cloud whm.nl-ams.scw.cloud cockpit.pl-waw.scw.cloud k8s.pl-waw.scw.cloud nodes.k8s.pl-waw.scw.cloud s3.pl-waw.scw.cloud s3-website.pl-waw.scw.cloud scalebook.scw.cloud smartlabeling.scw.cloud dedibox.fr // schokokeks.org GbR : https://schokokeks.org/ // Submitted by Hanno Böck schokokeks.net // Scottish Government: https://www.gov.scot // Submitted by Martin Ellis gov.scot service.gov.scot // Scry Security : http://www.scrysec.com // Submitted by Shante Adam scrysec.com // Scrypted : https://scrypted.app // Submitted by Koushik Dutta client.scrypted.io // Securepoint GmbH : https://www.securepoint.de // Submitted by Erik Anders firewall-gateway.com firewall-gateway.de my-gateway.de my-router.de spdns.de spdns.eu firewall-gateway.net my-firewall.org myfirewall.org spdns.org // Seidat : https://www.seidat.com // Submitted by Artem Kondratev seidat.net // Sellfy : https://sellfy.com // Submitted by Yuriy Romadin sellfy.store // Sendmsg: https://www.sendmsg.co.il // Submitted by Assaf Stern minisite.ms // Senseering GmbH : https://www.senseering.de // Submitted by Felix Mönckemeyer senseering.net // Servebolt AS: https://servebolt.com // Submitted by Daniel Kjeserud servebolt.cloud // Service Online LLC : http://drs.ua/ // Submitted by Serhii Bulakh biz.ua co.ua pp.ua // Shanghai Accounting Society : https://www.sasf.org.cn // Submitted by Information Administration as.sh.cn // Sheezy.Art : https://sheezy.art // Submitted by Nyoom sheezy.games // ShiftEdit : https://shiftedit.net/ // Submitted by Adam Jimenez shiftedit.io // Shopblocks : http://www.shopblocks.com/ // Submitted by Alex Bowers myshopblocks.com // Shopify : https://www.shopify.com // Submitted by Alex Richter myshopify.com // Shopit : https://www.shopitcommerce.com/ // Submitted by Craig McMahon shopitsite.com // shopware AG : https://shopware.com // Submitted by Jens Küper shopware.shop shopware.store // Siemens Mobility GmbH // Submitted by Oliver Graebner mo-siemens.io // SinaAppEngine : http://sae.sina.com.cn/ // Submitted by SinaAppEngine 1kapp.com appchizi.com applinzi.com sinaapp.com vipsinaapp.com // Siteleaf : https://www.siteleaf.com/ // Submitted by Skylar Challand siteleaf.net // Small Technology Foundation : https://small-tech.org // Submitted by Aral Balkan small-web.org // Smallregistry by Promopixel SARL: https://www.smallregistry.net // Former AFNIC's SLDs // Submitted by Jérôme Lipowicz aeroport.fr avocat.fr chambagri.fr chirurgiens-dentistes.fr experts-comptables.fr medecin.fr notaires.fr pharmacien.fr port.fr veterinaire.fr // Smoove.io : https://www.smoove.io/ // Submitted by Dan Kozak vp4.me // Snowflake Inc : https://www.snowflake.com/ // Submitted by Sam Haar *.snowflake.app *.privatelink.snowflake.app streamlit.app streamlitapp.com // Snowplow Analytics : https://snowplowanalytics.com/ // Submitted by Ian Streeter try-snowplow.com // Software Consulting Michal Zalewski : https://www.mafelo.com // Submitted by Michal Zalewski mafelo.net // Sony Interactive Entertainment LLC : https://sie.com/ // Submitted by David Coles playstation-cloud.com // SourceHut : https://sourcehut.org // Submitted by Drew DeVault srht.site // 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 // SparrowHost : https://sparrowhost.in/ // Submitted by Anant Pandey ind.mom // SpeedPartner GmbH: https://www.speedpartner.de/ // Submitted by Stefan Neufeind customer.speedpartner.de // Spreadshop (sprd.net AG) : https://www.spreadshop.com/ // Submitted by Martin Breest myspreadshop.at myspreadshop.com.au myspreadshop.be myspreadshop.ca myspreadshop.ch myspreadshop.com myspreadshop.de myspreadshop.dk myspreadshop.es myspreadshop.fi myspreadshop.fr myspreadshop.ie myspreadshop.it myspreadshop.net myspreadshop.nl myspreadshop.no myspreadshop.pl myspreadshop.se myspreadshop.co.uk // StackBlitz : https://stackblitz.com // Submitted by Dominic Elm w-corp-staticblitz.com w-credentialless-staticblitz.com w-staticblitz.com // Stackhero : https://www.stackhero.io // Submitted by Adrien Gillon stackhero-network.com // STACKIT GmbH & Co. KG : https://www.stackit.de/en/ // Submitted by STACKIT-DNS Team (Simon Stier) runs.onstackit.cloud stackit.gg stackit.rocks stackit.run stackit.zone // Staclar : https://staclar.com // Submitted by Q Misell // Submitted by Matthias Merkel musician.io novecore.site // Standard Library : https://stdlib.com // Submitted by Jacob Lee api.stdlib.com // stereosense GmbH : https://www.involve.me // Submitted by Florian Burmann feedback.ac forms.ac assessments.cx calculators.cx funnels.cx paynow.cx quizzes.cx researched.cx tests.cx surveys.so // Storebase : https://www.storebase.io // Submitted by Tony Schirmer storebase.store // Storipress : https://storipress.com // Submitted by Benno Liu storipress.app // Storj Labs Inc. : https://storj.io/ // Submitted by Philip Hutchins storj.farm // Strapi : https://strapi.io/ // Submitted by Florent Baldino strapiapp.com media.strapiapp.com // Strategic System Consulting (eApps Hosting): https://www.eapps.com/ // Submitted by Alex Oancea vps-host.net atl.jelastic.vps-host.net njs.jelastic.vps-host.net ric.jelastic.vps-host.net // Streak : https://streak.com // Submitted by Blake Kadatz streak-link.com streaklinks.com streakusercontent.com // Student-Run Computing Facility : https://www.srcf.net/ // Submitted by Edwin Balani soc.srcf.net user.srcf.net // Studenten Net Twente : http://www.snt.utwente.nl/ // Submitted by Silke Hofstra utwente.io // Sub 6 Limited: http://www.sub6.com // Submitted by Dan Miller temp-dns.com // Supabase : https://supabase.io // Submitted by Inian Parameshwaran supabase.co supabase.in supabase.net // Syncloud : https://syncloud.org // Submitted by Boris Rybalkin syncloud.it // Synology, Inc. : https://www.synology.com/ // Submitted by Rony Weng dscloud.biz direct.quickconnect.cn dsmynas.com familyds.com diskstation.me dscloud.me i234.me myds.me synology.me dscloud.mobi dsmynas.net familyds.net dsmynas.org familyds.org direct.quickconnect.to vpnplus.to // Tabit Technologies Ltd. : https://tabit.cloud/ // Submitted by Oren Agiv mytabit.com mytabit.co.il tabitorder.co.il // TAIFUN Software AG : http://taifun-software.de // Submitted by Bjoern Henke taifun-dns.de // Tailscale Inc. : https://www.tailscale.com // Submitted by David Anderson ts.net *.c.ts.net // TASK geographical domains (https://www.task.gda.pl/uslugi/dns) gda.pl gdansk.pl gdynia.pl med.pl sopot.pl // Tave Creative Corp : https://tave.com/ // Submitted by Adrian Ziemkowski taveusercontent.com // tawk.to, Inc : https://www.tawk.to // Submitted by tawk.to developer team p.tawk.email p.tawkto.email // team.blue https://team.blue // Submitted by Cedric Dubois site.tb-hosting.com // Teckids e.V. : https://www.teckids.org // Submitted by Dominik George edugit.io s3.teckids.org // Telebit : https://telebit.cloud // Submitted by AJ ONeal telebit.app telebit.io *.telebit.xyz // Thingdust AG : https://thingdust.com/ // Submitted by Adrian Imboden *.firenet.ch *.svc.firenet.ch reservd.com thingdustdata.com cust.dev.thingdust.io reservd.dev.thingdust.io cust.disrec.thingdust.io reservd.disrec.thingdust.io cust.prod.thingdust.io cust.testing.thingdust.io reservd.testing.thingdust.io // ticket i/O GmbH : https://ticket.io // Submitted by Christian Franke tickets.io // Tlon.io : https://tlon.io // Submitted by Mark Staarink arvo.network azimuth.network tlon.network // Tor Project, Inc. : https://torproject.org // Submitted by Antoine Beaupré torproject.net pages.torproject.net // TownNews.com : http://www.townnews.com // Submitted by Dustin Ward 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 and Cedric Dubois *.transurl.be *.transurl.eu site.transip.me *.transurl.nl // TuxFamily : http://tuxfamily.org // Submitted by TuxFamily administrators tuxfamily.org // TwoDNS : https://www.twodns.de/ // Submitted by TwoDNS-Support dd-dns.de 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 diskstation.eu diskstation.org // Typedream : https://typedream.com // Submitted by Putri Karunia typedream.app // Typeform : https://www.typeform.com // Submitted by Sergi Ferriz pro.typeform.com // Uberspace : https://uberspace.de // Submitted by Moritz Werner *.uberspace.de uber.space // UDR Limited : http://www.udr.hk.com // Submitted by registry hk.com inc.hk ltd.hk hk.org // UK Intis Telecom LTD : https://it.com // Submitted by ITComdomains it.com // Unison Computing, PBC : https://unison.cloud // Submitted by Simon Højberg unison-services.cloud // United Gameserver GmbH : https://united-gameserver.de // Submitted by Stefan Schwarz virtual-user.de virtualuser.de // UNIVERSAL DOMAIN REGISTRY : https://www.udr.org.yt/ // see also: whois -h whois.udr.org.yt help // Submitted by Atanunu Igbunuroghene name.pm sch.tf biz.wf sch.wf org.yt // University of Banja Luka : https://unibl.org // Domains for Republic of Srpska administrative entity. // Submitted by Marko Ivanovic rs.ba // University of Bielsko-Biala regional domain: http://dns.bielsko.pl/ // Submitted by Marcin bielsko.pl // Upli : https://upli.io // Submitted by Lenny Bakkalian upli.io // urown.net : https://urown.net // Submitted by Hostmaster urown.cloud dnsupdate.info // US REGISTRY LLC : http://us.org // Submitted by Gavin Brown us.org // V.UA Domain Administrator : https://domain.v.ua/ // Submitted by Serhii Rostilo v.ua // Val Town, Inc : https://val.town/ // Submitted by Tom MacWright express.val.run web.val.run // Vercel, Inc : https://vercel.com/ // Submitted by Max Leiter vercel.app v0.build vercel.dev vusercontent.net now.sh // 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 // Vultr Objects : https://www.vultr.com/products/object-storage/ // Submitted by Niels Maumenee *.vultrobjects.com // Waffle Computer Inc., Ltd. : https://docs.waffleinfo.com // Submitted by Masayuki Note wafflecell.com // Webflow, Inc. : https://www.webflow.com // Submitted by Webflow Security Team webflow.io webflowtest.io // WebHare bv : https://www.webhare.com/ // Submitted by Arnold Hendriks *.webhare.dev // WebHotelier Technologies Ltd : https://www.webhotelier.net/ // Submitted by Apostolos Tsakpinis bookonline.app hotelwithflight.com reserve-online.com reserve-online.net // WebPros International, LLC : https://webpros.com/ // Submitted by Nicolas Rochelemagne cprapid.com pleskns.com wp2.host pdns.page plesk.page wpsquared.site // WebWaddle Ltd : https://webwaddle.com/ // Submitted by Merlin Glander *.wadl.top // Western Digital Technologies, Inc : https://www.wdc.com // Submitted by Jung Jin remotewd.com // Whatbox Inc. : https://whatbox.ca/ // Submitted by Anthony Ryan box.ca // WIARD Enterprises : https://wiardweb.com // Submitted by Kidd Hustle pages.wiardweb.com // Wikimedia Labs : https://wikitech.wikimedia.org // Submitted by Arturo Borrero Gonzalez toolforge.org wmcloud.org wmflabs.org // William Harrison : https://wdh.gg // Submitted by William Harrison wdh.app // WISP : https://wisp.gg // Submitted by Stepan Fedotov panel.gg daemon.panel.gg // Wix.com, Inc. : https://www.wix.com // Submitted by Shahar Talmi / Alon Kochba wixsite.com wixstudio.com editorx.io wixstudio.io wix.run // Wizard Zines : https://wizardzines.com // Submitted by Julia Evans messwithdns.com // WoltLab GmbH : https://www.woltlab.com // Submitted by Tim Düsterhus woltlab-demo.com myforum.community community-pro.de diskussionsbereich.de community-pro.net meinforum.net // Woods Valldata : https://www.woodsvalldata.co.uk/ // Submitted by Chris Whittle affinitylottery.org.uk raffleentry.org.uk weeklylottery.org.uk // WP Engine : https://wpengine.com/ // Submitted by Michael Smith // Submitted by Brandon DuRette wpenginepowered.com js.wpenginepowered.com // 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 yombo.me // Yunohost : https://yunohost.org // Submitted by Valentin Grimaud ynh.fr nohost.me noho.st // ZaNiC : http://www.za.net/ // Submitted by registry za.net za.org // ZAP-Hosting GmbH & Co. KG : https://zap-hosting.com // Submitted by Julian Alker zap.cloud // Zeabur : https://zeabur.com/ // Submitted by Zeabur Team zeabur.app // Zine EOOD : https://zine.bg/ // Submitted by Martin Angelov bss.design // Zitcom A/S : https://www.zitcom.dk // Submitted by Emil Stahl basicserver.io virtualserver.io enterprisecloud.nu // ===END PRIVATE DOMAINS=== fido2-1.2.0/fido2/features.py0000644000175000017500000000657414721556664015310 0ustar winniewinnie# Copyright (c) 2022 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from typing import Optional import warnings class FeatureNotEnabledError(Exception): pass class _Feature: def __init__(self, name: str, desc: str): self._enabled: Optional[bool] = None self._name = name self._desc = desc @property def enabled(self) -> bool: self.warn() return self._enabled is True @enabled.setter def enabled(self, value: bool) -> None: if self._enabled is not None: raise ValueError( f"{self._name} has already been configured with {self._enabled}" ) self._enabled = value def require(self, state=True) -> None: if self._enabled != state: self.warn() raise FeatureNotEnabledError( f"Usage requires {self._name}.enabled = {state}" ) def warn(self) -> None: if self._enabled is None: warnings.warn( f"""Deprecated use of {self._name}. You are using deprecated functionality which will change in the next major version of python-fido2. You can opt-in to use the new functionality now by adding the following to your code somewhere where it gets executed prior to using the affected functionality: import fido2.features fido2.features.{self._name}.enabled = True To silence this warning but retain the current behavior, instead set enabled to False: fido2.features.{self._name}.enabled = False {self._desc} """, DeprecationWarning, ) # TODO 2.0: Remove this feature webauthn_json_mapping = _Feature( "webauthn_json_mapping", """JSON values for WebAuthn data class Mapping interface. This changes the keys and values used by the webauthn data classes when accessed using the Mapping (dict) interface (eg. user_entity["id"] and the from_dict() methods) to be JSON-friendly and align with the current draft of the next WebAuthn Level specification. For the most part, this means that binary values (bytes) are represented as URL-safe base64 encoded strings instead. """, ) fido2-1.2.0/fido2/ctap2/0000775000175000017500000000000014741676716014122 5ustar winniewinniefido2-1.2.0/fido2/ctap2/config.py0000644000175000017500000001106414721556664015736 0ustar winniewinnie# Copyright (c) 2020 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations from .. import cbor from .base import Ctap2, Info from .pin import PinProtocol, _PinUv from typing import Optional, List, Dict, Any from enum import IntEnum, unique import struct class Config: """Implementation of the CTAP2.1 Authenticator Config API. :param ctap: An instance of a CTAP2 object. :param pin_uv_protocol: An instance of a PinUvAuthProtocol. :param pin_uv_token: A valid PIN/UV Auth Token for the current CTAP session. """ @unique class CMD(IntEnum): ENABLE_ENTERPRISE_ATT = 0x01 TOGGLE_ALWAYS_UV = 0x02 SET_MIN_PIN_LENGTH = 0x03 VENDOR_PROTOTYPE = 0xFF @unique class PARAM(IntEnum): NEW_MIN_PIN_LENGTH = 0x01 MIN_PIN_LENGTH_RPIDS = 0x02 FORCE_CHANGE_PIN = 0x03 @staticmethod def is_supported(info: Info) -> bool: return info.options.get("authnrCfg") is True def __init__( self, ctap: Ctap2, pin_uv_protocol: Optional[PinProtocol] = None, pin_uv_token: Optional[bytes] = None, ): if not self.is_supported(ctap.info): raise ValueError("Authenticator does not support Config") self.ctap = ctap self.pin_uv = ( _PinUv(pin_uv_protocol, pin_uv_token) if pin_uv_protocol and pin_uv_token else None ) def _call(self, sub_cmd, params=None): if self.pin_uv: msg = b"\xff" * 32 + b"\x0d" + struct.pack(" None: """Enables Enterprise Attestation. If already enabled, this command is ignored. """ self._call(Config.CMD.ENABLE_ENTERPRISE_ATT) def toggle_always_uv(self) -> None: """Toggle the alwaysUV setting. When true, the Authenticator always requires UV for credential assertion. """ self._call(Config.CMD.TOGGLE_ALWAYS_UV) def set_min_pin_length( self, min_pin_length: Optional[int] = None, rp_ids: Optional[List[str]] = None, force_change_pin: bool = False, ) -> None: """Set the minimum PIN length allowed when setting/changing the PIN. :param min_pin_length: The minimum PIN length the Authenticator should allow. :param rp_ids: A list of RP IDs which should be allowed to get the current minimum PIN length. :param force_change_pin: True if the Authenticator should enforce changing the PIN before the next use. """ params: Dict[int, Any] = {Config.PARAM.FORCE_CHANGE_PIN: force_change_pin} if min_pin_length is not None: params[Config.PARAM.NEW_MIN_PIN_LENGTH] = min_pin_length if rp_ids is not None: params[Config.PARAM.MIN_PIN_LENGTH_RPIDS] = rp_ids self._call(Config.CMD.SET_MIN_PIN_LENGTH, params) fido2-1.2.0/fido2/ctap2/extensions.py0000644000175000017500000007703114721556664016676 0ustar winniewinnie# Copyright (c) 2020 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations from .base import AttestationResponse, AssertionResponse, Ctap2 from .pin import ClientPin, PinProtocol from .blob import LargeBlobs from ..utils import sha256, websafe_encode, _JsonDataObject from ..webauthn import ( PublicKeyCredentialDescriptor, PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions, AuthenticatorSelectionCriteria, ResidentKeyRequirement, ) from enum import Enum, unique from dataclasses import dataclass from typing import Dict, Tuple, Any, Optional, Mapping import abc import warnings class ExtensionProcessor(abc.ABC): """Base class for CTAP2 extension processing. See: :class:`RegistrationExtensionProcessor` and :class:`AuthenticationExtensionProcessor`. """ def __init__( self, permissions: ClientPin.PERMISSION = ClientPin.PERMISSION(0), inputs: Optional[Dict[str, Any]] = None, outputs: Optional[Dict[str, Any]] = None, ): self.permissions = permissions self._inputs = inputs self._outputs = outputs class RegistrationExtensionProcessor(ExtensionProcessor): """Processing state for a CTAP2 extension, for single use. The ExtensionProcessor holds state and logic for client processing of an extension, for a registration (MakeCredential) call. :param permissions: PinUvAuthToken permissions required by the extension. :param inputs: Default authenticator inputs, if prepare_inputs is not overridden. :param outputs: Default client outputs, if prepare_outputs is not overridden. """ def prepare_inputs(self, pin_token: Optional[bytes]) -> Optional[Dict[str, Any]]: "Prepare authenticator extension inputs, to be passed to the Authenenticator." return self._inputs def prepare_outputs( self, response: AttestationResponse, pin_token: Optional[bytes], ) -> Optional[Dict[str, Any]]: "Prepare client extension outputs, to be returned to the caller." return self._outputs class AuthenticationExtensionProcessor(ExtensionProcessor): """Processing state for a CTAP2 extension, for single use. The ExtensionProcessor holds state and logic for client processing of an extension, for an authentication (GetAssertion) call. :param permissions: PinUvAuthToken permissions required by the extension. :param inputs: Default authenticator inputs, if prepare_inputs is not overridden. :param outputs: Default client outputs, if prepare_outputs is not overridden. """ def prepare_inputs( self, selected: Optional[PublicKeyCredentialDescriptor], pin_token: Optional[bytes], ) -> Optional[Dict[str, Any]]: "Prepare authenticator extension inputs, to be passed to the Authenenticator." return self._inputs def prepare_outputs( self, response: AssertionResponse, pin_token: Optional[bytes], ) -> Optional[Dict[str, Any]]: "Prepare client extension outputs, to be returned to the caller." return self._outputs # TODO 2.0: Make changes as described below class Ctap2Extension(abc.ABC): """Base class for CTAP2 extensions. As of python-fido2 1.2 these instances can be used for multiple requests and should be invoked via the make_credential and get_assertion methods. Subclasses are instantiated for a single request, if the Authenticator supports the extension. From python-fido2 2.0 the following methods will be fully removed: get_create_permissions, process_create_input, process_create_output, process_create_input_with_permissions, get_get_permissions, process_get_input, process_get_output, process_get_input_with_permissions. The following changes will also be made: :func:`__init__` will no longer allow passing a ctap2 instance. :func:`is_supported` will require a ctap2 instance to be passed. :attr:`NAME` and :attr:`ctap` will be removed. """ NAME: str = None # type: ignore def __init__(self, ctap: Optional[Ctap2] = None): if ctap: warnings.warn( "Calling __init__ with a Ctap2 instance is deprecated.", DeprecationWarning, ) self._ctap = ctap @property def ctap(self) -> Ctap2: ctap = self._ctap if not ctap: raise ValueError( "Accessed self.ctap when no ctap instance has been passed to __init__" ) return ctap def is_supported(self, ctap: Optional[Ctap2] = None) -> bool: """Whether or not the extension is supported by the authenticator.""" if not ctap: warnings.warn( "Calling is_supported without a Ctap2 instance is deprecated.", DeprecationWarning, ) ctap = ctap or self._ctap if not ctap: raise ValueError("No Ctap2 instance available") return self.NAME in ctap.info.extensions def make_credential( self, ctap: Ctap2, options: PublicKeyCredentialCreationOptions, pin_protocol: Optional[PinProtocol], ) -> Optional[RegistrationExtensionProcessor]: """Start client extension processing for registration.""" # This implementation is for LEGACY PURPOSES! # Subclasses should override this method instead of: # process_create_input, process_create_output, and get_create_permissions warnings.warn( "This extension does not override make_credential, which is deprecated.", DeprecationWarning, ) inputs = dict(options.extensions or {}) self._ctap = ctap ext = self class Processor(RegistrationExtensionProcessor): def prepare_inputs(self, pin_token): processed = ext.process_create_input(inputs) self._has_input = processed is not None return {ext.NAME: processed} if self._has_input else None def prepare_outputs(self, response, pin_token): if self._has_input: processed = ext.process_create_output( response, pin_token, pin_protocol ) return processed return Processor(self.get_create_permissions(inputs)) def get_assertion( self, ctap: Ctap2, options: PublicKeyCredentialRequestOptions, pin_protocol: Optional[PinProtocol], ) -> Optional[AuthenticationExtensionProcessor]: """Start client extension processing for authentication.""" # This implementation is for LEGACY PURPOSES! # Subclasses should override this method instead of: # process_get_input, process_get_output, and get_get_permissions warnings.warn( "This extension does not override get_assertion, which is deprecated.", DeprecationWarning, ) inputs = dict(options.extensions or {}) self._ctap = ctap ext = self class Processor(AuthenticationExtensionProcessor): _has_input: bool def prepare_inputs(self, selected, pin_token): processed = ext.process_get_input(inputs) self._has_input = processed is not None return {ext.NAME: processed} if self._has_input else None def prepare_outputs(self, response, pin_token): if self._has_input: return ext.process_get_output(response, pin_token, pin_protocol) return Processor(self.get_get_permissions(inputs)) # TODO 2.0: Remove the remaining methods of this class def get_create_permissions(self, inputs: Dict[str, Any]) -> ClientPin.PERMISSION: """Get PinUvAuthToken permissions required for Registration. .. deprecated:: 1.2.0 Implement :func:`make_credential` instead. """ return ClientPin.PERMISSION(0) def process_create_input(self, inputs: Dict[str, Any]) -> Any: """Returns a value to include in the authenticator extension input, or None. .. deprecated:: 1.2.0 Implement :func:`make_credential` instead. """ return None def process_create_input_with_permissions( self, inputs: Dict[str, Any] ) -> Tuple[Any, ClientPin.PERMISSION]: """ .. deprecated:: 1.2.0 Implement :func:`make_credential` instead. """ warnings.warn( "This method is deprecated, use make_credential().", DeprecationWarning ) return self.process_create_input(inputs), self.get_create_permissions(inputs) def process_create_output( self, attestation_response: AttestationResponse, token: Optional[bytes], pin_protocol: Optional[PinProtocol], ) -> Optional[Dict[str, Any]]: """Return client extension output given attestation_response, or None. .. deprecated:: 1.2.0 Implement :func:`make_credential` instead. """ return None def get_get_permissions(self, inputs: Dict[str, Any]) -> ClientPin.PERMISSION: """ .. deprecated:: 1.2.0 Implement :func:`get_assertion` instead. """ return ClientPin.PERMISSION(0) def process_get_input(self, inputs: Dict[str, Any]) -> Any: """Returns a value to include in the authenticator extension input, or None. .. deprecated:: 1.2.0 Implement :func:`get_assertion` instead. """ return None def process_get_input_with_permissions( self, inputs: Dict[str, Any] ) -> Tuple[Any, ClientPin.PERMISSION]: """ .. deprecated:: 1.2.0 Implement :func:`get_assertion` instead. """ warnings.warn( "This method is deprecated, use get_assertion().", DeprecationWarning ) return self.process_get_input(inputs), self.get_get_permissions(inputs) def process_get_output( self, assertion_response: AssertionResponse, token: Optional[bytes], pin_protocol: Optional[PinProtocol], ) -> Optional[Dict[str, Any]]: """Return client extension output given assertion_response, or None. .. deprecated:: 1.2.0 Implement :func:`get_assertion` instead. """ return None @dataclass(eq=False, frozen=True) class HMACGetSecretInput(_JsonDataObject): """Client inputs for hmac-secret.""" salt1: bytes salt2: Optional[bytes] = None @dataclass(eq=False, frozen=True) class HMACGetSecretOutput(_JsonDataObject): """Client outputs for hmac-secret.""" output1: bytes output2: Optional[bytes] = None def _prf_salt(secret): return sha256(b"WebAuthn PRF\0" + secret) @dataclass(eq=False, frozen=True) class AuthenticatorExtensionsPRFValues(_JsonDataObject): """Salt values for use with prf.""" first: bytes second: Optional[bytes] = None @dataclass(eq=False, frozen=True) class AuthenticatorExtensionsPRFInputs(_JsonDataObject): """Client inputs for prf.""" eval: Optional[AuthenticatorExtensionsPRFValues] = None eval_by_credential: Optional[Mapping[str, AuthenticatorExtensionsPRFValues]] = None @dataclass(eq=False, frozen=True) class AuthenticatorExtensionsPRFOutputs(_JsonDataObject): """Client outputs for prf.""" enabled: Optional[bool] = None results: Optional[AuthenticatorExtensionsPRFValues] = None class HmacSecretExtension(Ctap2Extension): """ Implements the Pseudo-random function (prf) and the hmac-secret CTAP2 extensions. The hmac-secret extension is not directly available to clients by default, instead the prf extension is used. https://www.w3.org/TR/webauthn-3/#prf-extension https://fidoalliance.org/specs/fido-v2.1-rd-20201208/fido-client-to-authenticator-protocol-v2.1-rd-20201208.html#sctn-hmac-secret-extension :param allow_hmac_secret: Set to True to allow hmac-secret, in addition to prf. """ NAME = "hmac-secret" SALT_LEN = 32 def __init__(self, ctap=None, pin_protocol=None, allow_hmac_secret=False): super().__init__(ctap) if pin_protocol: warnings.warn( "Initializing HmacSecretExtension with pin_protocol is deprecated, " "pin_protocol will be ignored.", DeprecationWarning, ) self.pin_protocol = pin_protocol self._allow_hmac_secret = allow_hmac_secret def make_credential(self, ctap, options, pin_protocol): inputs = options.extensions or {} prf = inputs.get("prf") is not None hmac = self._allow_hmac_secret and inputs.get("hmacCreateSecret") is True if self.is_supported(ctap) and (prf or hmac): class Processor(RegistrationExtensionProcessor): def prepare_inputs(self, pin_token): return {HmacSecretExtension.NAME: True} def prepare_outputs(self, response, pin_token): extensions = response.auth_data.extensions or {} enabled = extensions.get(HmacSecretExtension.NAME, False) if prf: return { "prf": AuthenticatorExtensionsPRFOutputs(enabled=enabled) } else: return {"hmacCreateSecret": enabled} return Processor() def get_assertion(self, ctap, options, pin_protocol): inputs = options.extensions or {} prf = AuthenticatorExtensionsPRFInputs.from_dict(inputs.get("prf")) hmac = ( HMACGetSecretInput.from_dict(inputs.get("hmacGetSecret")) if self._allow_hmac_secret else None ) if pin_protocol and self.is_supported(ctap) and (prf or hmac): client_pin = ClientPin(ctap, pin_protocol) key_agreement, shared_secret = client_pin._get_shared_secret() class Processing(AuthenticationExtensionProcessor): def prepare_inputs(self, selected, pin_token): if prf: secrets = prf.eval by_creds = prf.eval_by_credential if by_creds: # Make sure all keys are valid IDs from allow_credentials allow_list = options.allow_credentials if not allow_list: raise ValueError( "evalByCredentials requires allowCredentials" ) ids = {websafe_encode(c.id) for c in allow_list} if not ids.issuperset(by_creds): raise ValueError( "evalByCredentials contains invalid key" ) if selected: key = websafe_encode(selected.id) if key in by_creds: secrets = by_creds[key] if not secrets: return salts = ( _prf_salt(secrets.first), ( _prf_salt(secrets.second) if secrets.second is not None else b"" ), ) else: assert hmac is not None # nosec salts = hmac.salt1, hmac.salt2 or b"" if not ( len(salts[0]) == HmacSecretExtension.SALT_LEN and ( not salts[1] or len(salts[1]) == HmacSecretExtension.SALT_LEN ) ): raise ValueError("Invalid salt length") salt_enc = pin_protocol.encrypt(shared_secret, salts[0] + salts[1]) salt_auth = pin_protocol.authenticate(shared_secret, salt_enc) return { HmacSecretExtension.NAME: { 1: key_agreement, 2: salt_enc, 3: salt_auth, 4: pin_protocol.VERSION, } } def prepare_outputs(self, response, pin_token): extensions = response.auth_data.extensions or {} value = extensions.get(HmacSecretExtension.NAME) if value: decrypted = client_pin.protocol.decrypt(shared_secret, value) output1 = decrypted[: HmacSecretExtension.SALT_LEN] output2 = decrypted[HmacSecretExtension.SALT_LEN :] or None else: return None if prf: return { "prf": AuthenticatorExtensionsPRFOutputs( results=AuthenticatorExtensionsPRFValues( output1, output2 ) ) } else: return {"hmacGetSecret": HMACGetSecretOutput(output1, output2)} return Processing() # TODO 2.0: Remove the remaining methods of this class def process_create_input(self, inputs): if self.is_supported() and inputs.get("hmacCreateSecret") is True: return True def process_create_output(self, attestation_response, *args, **kwargs): enabled = attestation_response.auth_data.extensions.get(self.NAME, False) return {"hmacCreateSecret": enabled} def process_get_input(self, inputs): if not self.is_supported(): return get_secret = HMACGetSecretInput.from_dict(inputs.get("hmacGetSecret")) if not get_secret: return salts = get_secret.salt1, get_secret.salt2 or b"" if not ( len(salts[0]) == HmacSecretExtension.SALT_LEN and (not salts[1] or len(salts[1]) == HmacSecretExtension.SALT_LEN) ): raise ValueError("Invalid salt length") if not self._ctap: raise ValueError("No Ctap2 instance available") client_pin = ClientPin(self._ctap, self.pin_protocol) key_agreement, self.shared_secret = client_pin._get_shared_secret() if self.pin_protocol is None: self.pin_protocol = client_pin.protocol salt_enc = self.pin_protocol.encrypt(self.shared_secret, salts[0] + salts[1]) salt_auth = self.pin_protocol.authenticate(self.shared_secret, salt_enc) return { 1: key_agreement, 2: salt_enc, 3: salt_auth, 4: self.pin_protocol.VERSION, } def process_get_output(self, assertion_response, *args, **kwargs): value = assertion_response.auth_data.extensions.get(self.NAME) assert self.pin_protocol is not None # nosec decrypted = self.pin_protocol.decrypt(self.shared_secret, value) output1 = decrypted[: HmacSecretExtension.SALT_LEN] output2 = decrypted[HmacSecretExtension.SALT_LEN :] or None return {"hmacGetSecret": HMACGetSecretOutput(output1, output2)} @dataclass(eq=False, frozen=True) class AuthenticatorExtensionsLargeBlobInputs(_JsonDataObject): """Client inputs for largeBlob.""" support: Optional[str] = None read: Optional[bool] = None write: Optional[bytes] = None @dataclass(eq=False, frozen=True) class AuthenticatorExtensionsLargeBlobOutputs(_JsonDataObject): """Client outputs for largeBlob.""" supported: Optional[bool] = None blob: Optional[bytes] = None written: Optional[bool] = None class LargeBlobExtension(Ctap2Extension): """ Implements the Large Blob storage (largeBlob) WebAuthn extension. https://www.w3.org/TR/webauthn-3/#sctn-large-blob-extension """ NAME = "largeBlobKey" def is_supported(self, ctap=None): ctap = ctap or self._ctap assert ctap is not None # nosec return super().is_supported(ctap) and ctap.info.options.get("largeBlobs", False) def make_credential(self, ctap, options, pin_protocol): inputs = options.extensions or {} data = AuthenticatorExtensionsLargeBlobInputs.from_dict(inputs.get("largeBlob")) if data: if data.read or data.write: raise ValueError("Invalid set of parameters") if data.support == "required" and not self.is_supported(ctap): raise ValueError("Authenticator does not support large blob storage") class Processor(RegistrationExtensionProcessor): def prepare_inputs(self, pin_token): return {LargeBlobExtension.NAME: True} def prepare_outputs(self, response, pin_token): return { "largeBlob": AuthenticatorExtensionsLargeBlobOutputs( supported=response.large_blob_key is not None ) } return Processor() def get_assertion(self, ctap, options, pin_protocol): inputs = options.extensions or {} data = AuthenticatorExtensionsLargeBlobInputs.from_dict(inputs.get("largeBlob")) if data: if data.support or (data.read and data.write): raise ValueError("Invalid set of parameters") if not self.is_supported(ctap): raise ValueError("Authenticator does not support large blob storage") class Processor(AuthenticationExtensionProcessor): def prepare_outputs(self, response, pin_token): blob_key = response.large_blob_key if blob_key: if data.read: large_blobs = LargeBlobs(ctap) blob = large_blobs.get_blob(blob_key) return { "largeBlob": AuthenticatorExtensionsLargeBlobOutputs( blob=blob ) } elif data.write: large_blobs = LargeBlobs(ctap, pin_protocol, pin_token) large_blobs.put_blob(blob_key, data.write) return { "largeBlob": AuthenticatorExtensionsLargeBlobOutputs( written=True ) } return Processor( ( ClientPin.PERMISSION.LARGE_BLOB_WRITE if data.write else ClientPin.PERMISSION(0) ), inputs={LargeBlobExtension.NAME: True}, ) # TODO 2.0: Remove the remaining methods of this class def process_create_input(self, inputs): data = AuthenticatorExtensionsLargeBlobInputs.from_dict(inputs.get("largeBlob")) if data: if data.read or data.write: raise ValueError("Invalid set of parameters") if data.support == "required" and not self.is_supported(): raise ValueError("Authenticator does not support large blob storage") return True def process_create_output(self, attestation_response, *args, **kwargs): return { "largeBlob": AuthenticatorExtensionsLargeBlobOutputs( supported=attestation_response.large_blob_key is not None ) } def get_get_permissions(self, inputs): data = AuthenticatorExtensionsLargeBlobInputs.from_dict(inputs.get("largeBlob")) if data and data.write: return ClientPin.PERMISSION.LARGE_BLOB_WRITE return ClientPin.PERMISSION(0) def process_get_input(self, inputs): data = AuthenticatorExtensionsLargeBlobInputs.from_dict(inputs.get("largeBlob")) if data: if data.support or (data.read and data.write): raise ValueError("Invalid set of parameters") if not self.is_supported(): raise ValueError("Authenticator does not support large blob storage") if data.read: self._action = True else: self._action = data.write return True def process_get_output(self, assertion_response, token, pin_protocol): blob_key = assertion_response.large_blob_key if blob_key: if self._action is True: # Read large_blobs = LargeBlobs(self.ctap) blob = large_blobs.get_blob(blob_key) return {"largeBlob": AuthenticatorExtensionsLargeBlobOutputs(blob=blob)} elif self._action: # Write large_blobs = LargeBlobs(self.ctap, pin_protocol, token) large_blobs.put_blob(blob_key, self._action) return { "largeBlob": AuthenticatorExtensionsLargeBlobOutputs(written=True) } class CredBlobExtension(Ctap2Extension): """ Implements the Credential Blob (credBlob) CTAP2 extension. https://fidoalliance.org/specs/fido-v2.1-rd-20201208/fido-client-to-authenticator-protocol-v2.1-rd-20201208.html#sctn-credBlob-extension """ NAME = "credBlob" def make_credential(self, ctap, options, pin_protocol): inputs = options.extensions or {} if self.is_supported(): blob = inputs.get("credBlob") assert ctap.info.max_cred_blob_length is not None # nosec if blob and len(blob) <= ctap.info.max_cred_blob_length: return RegistrationExtensionProcessor(inputs={self.NAME: blob}) def get_assertion(self, ctap, options, pin_protocol): inputs = options.extensions or {} if self.is_supported(ctap) and inputs.get("getCredBlob") is True: return AuthenticationExtensionProcessor(inputs={self.NAME: True}) # TODO 2.0: Remove the remaining methods of this class def process_create_input(self, inputs): if self.is_supported(): blob = inputs.get("credBlob") assert self.ctap.info.max_cred_blob_length is not None # nosec if blob and len(blob) <= self.ctap.info.max_cred_blob_length: return blob def process_get_input(self, inputs): if self.is_supported() and inputs.get("getCredBlob") is True: return True class CredProtectExtension(Ctap2Extension): """ Implements the Credential Protection CTAP2 extension. https://fidoalliance.org/specs/fido-v2.1-rd-20201208/fido-client-to-authenticator-protocol-v2.1-rd-20201208.html#sctn-credProtect-extension """ @unique class POLICY(Enum): OPTIONAL = "userVerificationOptional" OPTIONAL_WITH_LIST = "userVerificationOptionalWithCredentialIDList" REQUIRED = "userVerificationRequired" NAME = "credProtect" def make_credential(self, ctap, options, pin_protocol): inputs = options.extensions or {} policy = inputs.get("credentialProtectionPolicy") if policy: index = list(CredProtectExtension.POLICY).index( CredProtectExtension.POLICY(policy) ) enforce = inputs.get("enforceCredentialProtectionPolicy", False) if enforce and not self.is_supported(ctap) and index > 0: raise ValueError("Authenticator does not support Credential Protection") return RegistrationExtensionProcessor(inputs={self.NAME: index + 1}) # TODO 2.0: Remove the remaining methods of this class def process_create_input(self, inputs): policy = inputs.get("credentialProtectionPolicy") if policy: index = list(CredProtectExtension.POLICY).index( CredProtectExtension.POLICY(policy) ) enforce = inputs.get("enforceCredentialProtectionPolicy", False) if enforce and not self.is_supported() and index > 0: raise ValueError("Authenticator does not support Credential Protection") return index + 1 class MinPinLengthExtension(Ctap2Extension): """ Implements the Minimum PIN Length (minPinLength) CTAP2 extension. https://fidoalliance.org/specs/fido-v2.1-rd-20201208/fido-client-to-authenticator-protocol-v2.1-rd-20201208.html#sctn-minpinlength-extension """ NAME = "minPinLength" def is_supported(self, ctap=None): # NB: There is no key in the extensions field. ctap = ctap or self._ctap assert ctap is not None # nosec return "setMinPINLength" in ctap.info.options def make_credential(self, ctap, options, pin_protocol): inputs = options.extensions or {} if self.is_supported(ctap) and inputs.get(self.NAME) is True: return RegistrationExtensionProcessor(inputs={self.NAME: True}) # TODO 2.0: Remove the remaining methods of this class def process_create_input(self, inputs): if self.is_supported() and inputs.get(self.NAME) is True: return True @dataclass(eq=False, frozen=True) class CredentialPropertiesOutput(_JsonDataObject): """Client outputs for credProps.""" rk: Optional[bool] = None class CredPropsExtension(Ctap2Extension): """ Implements the Credential Properties (credProps) WebAuthn extension. https://www.w3.org/TR/webauthn-3/#sctn-authenticator-credential-properties-extension """ NAME = "credProps" def is_supported(self, ctap=None): # NB: There is no key in the extensions field. return True def make_credential(self, ctap, options, pin_protocol): inputs = options.extensions or {} if inputs.get(self.NAME) is True: selection = ( options.authenticator_selection or AuthenticatorSelectionCriteria() ) rk = selection.resident_key == ResidentKeyRequirement.REQUIRED or ( selection.resident_key == ResidentKeyRequirement.PREFERRED and ctap.info.options.get("rk") ) return RegistrationExtensionProcessor( outputs={self.NAME: CredentialPropertiesOutput(rk=rk)} ) fido2-1.2.0/fido2/ctap2/base.py0000644000175000017500000005427014721556664015411 0ustar winniewinnie# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations from .. import cbor from ..utils import _DataClassMapping from ..ctap import CtapDevice, CtapError from ..cose import CoseKey from ..hid import CTAPHID, CAPABILITY from ..webauthn import AuthenticatorData, Aaguid from enum import IntEnum, unique from dataclasses import dataclass, field, fields, Field from threading import Event from typing import Mapping, Dict, Any, List, Optional, Callable import struct import logging logger = logging.getLogger(__name__) def args(*params) -> Dict[int, Any]: """Constructs a dict from a list of arguments for sending a CBOR command. None elements will be omitted. :param params: Arguments, in order, to add to the command. :return: The input parameters as a dict. """ return {i: v for i, v in enumerate(params, 1) if v is not None} class _CborDataObject(_DataClassMapping[int]): @classmethod def _get_field_key(cls, field: Field) -> int: return fields(cls).index(field) + 1 # type: ignore @dataclass(eq=False, frozen=True) class Info(_CborDataObject): """Binary CBOR encoded response data returned by the CTAP2 GET_INFO command. :param _: The binary content of the Info data. :ivar versions: The versions supported by the authenticator. :ivar extensions: The extensions supported by the authenticator. :ivar aaguid: The AAGUID of the authenticator. :ivar options: The options supported by the authenticator. :ivar max_msg_size: The max message size supported by the authenticator. :ivar pin_uv_protocols: The PIN/UV protocol versions supported by the authenticator. :ivar max_creds_in_list: Max number of credentials supported in list at a time. :ivar max_cred_id_length: Max length of Credential ID supported. :ivar transports: List of supported transports. :ivar algorithms: List of supported algorithms for credential creation. :ivar data: The Info members, in the form of a dict. """ versions: List[str] extensions: List[str] = field(default_factory=list) aaguid: Aaguid = Aaguid.NONE options: Dict[str, bool] = field(default_factory=dict) max_msg_size: int = 1024 pin_uv_protocols: List[int] = field(default_factory=list) max_creds_in_list: Optional[int] = None max_cred_id_length: Optional[int] = None transports: List[str] = field(default_factory=list) algorithms: Optional[List[Dict[str, Any]]] = None max_large_blob: Optional[int] = None force_pin_change: bool = False min_pin_length: int = 4 firmware_version: Optional[int] = None max_cred_blob_length: Optional[int] = None max_rpids_for_min_pin: int = 0 preferred_platform_uv_attempts: Optional[int] = None uv_modality: Optional[int] = None certifications: Optional[Dict] = None remaining_disc_creds: Optional[int] = None vendor_prototype_config_commands: Optional[List[int]] = None @dataclass(eq=False, frozen=True) class AttestationResponse(_CborDataObject): """Binary CBOR encoded attestation object. :param _: The binary representation of the attestation object. :type _: bytes :ivar fmt: The type of attestation used. :type fmt: str :ivar auth_data: The attested authenticator data. :type auth_data: AuthenticatorData :ivar att_stmt: The attestation statement. :type att_stmt: Dict[str, Any] """ fmt: str auth_data: AuthenticatorData att_stmt: Dict[str, Any] ep_att: Optional[bool] = None large_blob_key: Optional[bytes] = None @dataclass(eq=False, frozen=True) class AssertionResponse(_CborDataObject): """Binary CBOR encoded assertion response. :param _: The binary representation of the assertion response. :ivar credential: The credential used for the assertion. :ivar auth_data: The authenticator data part of the response. :ivar signature: The digital signature of the assertion. :ivar user: The user data of the credential. :ivar number_of_credentials: The total number of responses available (only set for the first response, if > 1). """ credential: Mapping[str, Any] auth_data: AuthenticatorData signature: bytes user: Optional[Dict[str, Any]] = None number_of_credentials: Optional[int] = None user_selected: Optional[bool] = None large_blob_key: Optional[bytes] = None def verify(self, client_param: bytes, public_key: CoseKey): """Verify the digital signature of the response with regard to the client_param, using the given public key. :param client_param: SHA256 hash of the ClientData used for the request. :param public_key: The public key of the credential, to verify. """ public_key.verify(self.auth_data + client_param, self.signature) @classmethod def from_ctap1( cls, app_param: bytes, credential: Mapping[str, Any], authentication ) -> "AssertionResponse": """Create an AssertionResponse from a CTAP1 SignatureData instance. :param app_param: SHA256 hash of the RP ID used for the CTAP1 request. :param credential: Credential used for the CTAP1 request (from the allowList). :param authentication: The CTAP1 signature data. :return: The assertion response. """ return cls( credential=credential, auth_data=AuthenticatorData.create( app_param, authentication.user_presence & AuthenticatorData.FLAG.UP, authentication.counter, ), signature=authentication.signature, ) class Ctap2: """Implementation of the CTAP2 specification. :param device: A CtapHidDevice handle supporting CTAP2. :param strict_cbor: Validate that CBOR returned from the Authenticator is canonical, defaults to True. """ @unique class CMD(IntEnum): MAKE_CREDENTIAL = 0x01 GET_ASSERTION = 0x02 GET_INFO = 0x04 CLIENT_PIN = 0x06 RESET = 0x07 GET_NEXT_ASSERTION = 0x08 BIO_ENROLLMENT = 0x09 CREDENTIAL_MGMT = 0x0A SELECTION = 0x0B LARGE_BLOBS = 0x0C CONFIG = 0x0D BIO_ENROLLMENT_PRE = 0x40 CREDENTIAL_MGMT_PRE = 0x41 def __init__(self, device: CtapDevice, strict_cbor: bool = True): if not device.capabilities & CAPABILITY.CBOR: raise ValueError("Device does not support CTAP2.") self.device = device self._strict_cbor = strict_cbor self._info = self.get_info() @property def info(self) -> Info: """Get a cached Info object which can be used to determine capabilities. :rtype: Info :return: The response of calling GetAuthenticatorInfo. """ return self._info def send_cbor( self, cmd: int, data: Optional[Mapping[int, Any]] = None, *, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> Mapping[int, Any]: """Sends a CBOR message to the device, and waits for a response. :param cmd: The command byte of the request. :param data: The payload to send (to be CBOR encoded). :param event: Optional threading.Event used to cancel the request. :param on_keepalive: Optional function called when keep-alive is sent by the authenticator. """ request = struct.pack(">B", cmd) if data is not None: request += cbor.encode(data) response = self.device.call(CTAPHID.CBOR, request, event, on_keepalive) status = response[0] if status != 0x00: raise CtapError(status) enc = response[1:] if not enc: return {} decoded = cbor.decode(enc) if self._strict_cbor: expected = cbor.encode(decoded) if expected != enc: raise ValueError( "Non-canonical CBOR from Authenticator.\n" f"Got: {enc.hex()}\nExpected: {expected.hex()}" ) if isinstance(decoded, Mapping): return decoded raise TypeError("Decoded value of wrong type") def get_info(self) -> Info: """CTAP2 getInfo command. :return: Information about the authenticator. """ return Info.from_dict(self.send_cbor(Ctap2.CMD.GET_INFO)) def client_pin( self, pin_uv_protocol: int, sub_cmd: int, key_agreement: Optional[Mapping[int, Any]] = None, pin_uv_param: Optional[bytes] = None, new_pin_enc: Optional[bytes] = None, pin_hash_enc: Optional[bytes] = None, permissions: Optional[int] = None, permissions_rpid: Optional[str] = None, *, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> Mapping[int, Any]: """CTAP2 clientPin command, used for various PIN operations. This method is not intended to be called directly. It is intended to be used by an instance of the PinProtocolV1 class. :param pin_uv_protocol: The PIN/UV protocol version to use. :param sub_cmd: A clientPin sub command. :param key_agreement: The keyAgreement parameter. :param pin_uv_param: The pinAuth parameter. :param new_pin_enc: The newPinEnc parameter. :param pin_hash_enc: The pinHashEnc parameter. :param permissions: The permissions parameter. :param permissions_rpid: The permissions RPID parameter. :param event: Optional threading.Event used to cancel the request. :param on_keepalive: Optional callback function to handle keep-alive messages from the authenticator. :return: The response of the command, decoded. """ return self.send_cbor( Ctap2.CMD.CLIENT_PIN, args( pin_uv_protocol, sub_cmd, key_agreement, pin_uv_param, new_pin_enc, pin_hash_enc, None, None, permissions, permissions_rpid, ), event=event, on_keepalive=on_keepalive, ) def reset( self, *, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> None: """CTAP2 reset command, erases all credentials and PIN. :param event: Optional threading.Event object used to cancel the request. :param on_keepalive: Optional callback function to handle keep-alive messages from the authenticator. """ self.send_cbor(Ctap2.CMD.RESET, event=event, on_keepalive=on_keepalive) logger.info("Reset completed - All data erased") def make_credential( self, client_data_hash: bytes, rp: Mapping[str, Any], user: Mapping[str, Any], key_params: List[Mapping[str, Any]], exclude_list: Optional[List[Mapping[str, Any]]] = None, extensions: Optional[Mapping[str, Any]] = None, options: Optional[Mapping[str, Any]] = None, pin_uv_param: Optional[bytes] = None, pin_uv_protocol: Optional[int] = None, enterprise_attestation: Optional[int] = None, *, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> AttestationResponse: """CTAP2 makeCredential operation. :param client_data_hash: SHA256 hash of the ClientData. :param rp: PublicKeyCredentialRpEntity parameters. :param user: PublicKeyCredentialUserEntity parameters. :param key_params: List of acceptable credential types. :param exclude_list: Optional list of PublicKeyCredentialDescriptors. :param extensions: Optional dict of extensions. :param options: Optional dict of options. :param pin_uv_param: Optional PIN/UV auth parameter. :param pin_uv_protocol: The version of PIN/UV protocol used, if any. :param enterprise_attestation: Whether or not to request Enterprise Attestation. :param event: Optional threading.Event used to cancel the request. :param on_keepalive: Optional callback function to handle keep-alive messages from the authenticator. :return: The new credential. """ logger.debug("Calling CTAP2 make_credential") return AttestationResponse.from_dict( self.send_cbor( Ctap2.CMD.MAKE_CREDENTIAL, args( client_data_hash, rp, user, key_params, exclude_list, extensions, options, pin_uv_param, pin_uv_protocol, enterprise_attestation, ), event=event, on_keepalive=on_keepalive, ) ) def get_assertion( self, rp_id: str, client_data_hash: bytes, allow_list: Optional[List[Mapping[str, Any]]] = None, extensions: Optional[Mapping[str, Any]] = None, options: Optional[Mapping[str, Any]] = None, pin_uv_param: Optional[bytes] = None, pin_uv_protocol: Optional[int] = None, *, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> AssertionResponse: """CTAP2 getAssertion command. :param rp_id: The RP ID of the credential. :param client_data_hash: SHA256 hash of the ClientData used. :param allow_list: Optional list of PublicKeyCredentialDescriptors. :param extensions: Optional dict of extensions. :param options: Optional dict of options. :param pin_uv_param: Optional PIN/UV auth parameter. :param pin_uv_protocol: The version of PIN/UV protocol used, if any. :param event: Optional threading.Event used to cancel the request. :param on_keepalive: Optional callback function to handle keep-alive messages from the authenticator. :return: The new assertion. """ logger.debug("Calling CTAP2 get_assertion") return AssertionResponse.from_dict( self.send_cbor( Ctap2.CMD.GET_ASSERTION, args( rp_id, client_data_hash, allow_list, extensions, options, pin_uv_param, pin_uv_protocol, ), event=event, on_keepalive=on_keepalive, ) ) def get_next_assertion(self) -> AssertionResponse: """CTAP2 getNextAssertion command. :return: The next available assertion response. """ return AssertionResponse.from_dict(self.send_cbor(Ctap2.CMD.GET_NEXT_ASSERTION)) def get_assertions(self, *args, **kwargs) -> List[AssertionResponse]: """Convenience method to get list of assertions. See get_assertion and get_next_assertion for details. """ first = self.get_assertion(*args, **kwargs) rest = [ self.get_next_assertion() for _ in range(1, first.number_of_credentials or 1) ] return [first] + rest def credential_mgmt( self, sub_cmd: int, sub_cmd_params: Optional[Mapping[int, Any]] = None, pin_uv_protocol: Optional[int] = None, pin_uv_param: Optional[bytes] = None, ) -> Mapping[int, Any]: """CTAP2 credentialManagement command, used to manage resident credentials. NOTE: This implements the current draft version of the CTAP2 specification and should be considered highly experimental. This method is not intended to be called directly. It is intended to be used by an instance of the CredentialManagement class. :param sub_cmd: A CredentialManagement sub command. :param sub_cmd_params: Sub command specific parameters. :param pin_uv_protocol: PIN/UV auth protocol version used. :param pin_uv_param: PIN/UV Auth parameter. """ if "credMgmt" in self.info.options: cmd = Ctap2.CMD.CREDENTIAL_MGMT elif "credentialMgmtPreview" in self.info.options: cmd = Ctap2.CMD.CREDENTIAL_MGMT_PRE else: raise ValueError( "Credential Management not supported by this Authenticator" ) return self.send_cbor( cmd, args(sub_cmd, sub_cmd_params, pin_uv_protocol, pin_uv_param), ) def bio_enrollment( self, modality: Optional[int] = None, sub_cmd: Optional[int] = None, sub_cmd_params: Optional[Mapping[int, Any]] = None, pin_uv_protocol: Optional[int] = None, pin_uv_param: Optional[bytes] = None, get_modality: Optional[bool] = None, *, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> Mapping[int, Any]: """CTAP2 bio enrollment command. Used to provision/enumerate/delete bio enrollments in the authenticator. NOTE: This implements the current draft version of the CTAP2 specification and should be considered highly experimental. This method is not intended to be called directly. It is intended to be used by an instance of the BioEnrollment class. :param modality: The user verification modality being used. :param sub_cmd: A BioEnrollment sub command. :param sub_cmd_params: Sub command specific parameters. :param pin_uv_protocol: PIN/UV protocol version used. :param pin_uv_param: PIN/UV auth param. :param get_modality: Get the user verification type modality. """ if "bioEnroll" in self.info.options: cmd = Ctap2.CMD.BIO_ENROLLMENT elif "userVerificationMgmtPreview" in self.info.options: cmd = Ctap2.CMD.BIO_ENROLLMENT_PRE else: raise ValueError("Authenticator does not support Bio Enroll") return self.send_cbor( cmd, args( modality, sub_cmd, sub_cmd_params, pin_uv_protocol, pin_uv_param, get_modality, ), event=event, on_keepalive=on_keepalive, ) def selection( self, *, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> None: """CTAP2 authenticator selection command. This command allows the platform to let a user select a certain authenticator by asking for user presence. :param event: Optional threading.Event used to cancel the request. :param on_keepalive: Optional callback function to handle keep-alive messages from the authenticator. """ self.send_cbor(Ctap2.CMD.SELECTION, event=event, on_keepalive=on_keepalive) def large_blobs( self, offset: int, get: Optional[int] = None, set: Optional[bytes] = None, length: Optional[int] = None, pin_uv_param: Optional[bytes] = None, pin_uv_protocol: Optional[int] = None, ) -> Mapping[int, Any]: """CTAP2 authenticator large blobs command. This command is used to read and write the large blob array. This method is not intended to be called directly. It is intended to be used by an instance of the LargeBlobs class. :param offset: The offset of where to start reading/writing data. :param get: Optional (max) length of data to read. :param set: Optional data to write. :param length: Length of the payload in set. :param pin_uv_protocol: PIN/UV protocol version used. :param pin_uv_param: PIN/UV auth param. """ return self.send_cbor( Ctap2.CMD.LARGE_BLOBS, args(get, set, offset, length, pin_uv_param, pin_uv_protocol), ) def config( self, sub_cmd: int, sub_cmd_params: Optional[Mapping[int, Any]] = None, pin_uv_protocol: Optional[int] = None, pin_uv_param: Optional[bytes] = None, ) -> Mapping[int, Any]: """CTAP2 authenticator config command. This command is used to configure various authenticator features through the use of its subcommands. This method is not intended to be called directly. It is intended to be used by an instance of the Config class. :param sub_cmd: A Config sub command. :param sub_cmd_params: Sub command specific parameters. :param pin_uv_protocol: PIN/UV auth protocol version used. :param pin_uv_param: PIN/UV Auth parameter. """ return self.send_cbor( Ctap2.CMD.CONFIG, args(sub_cmd, sub_cmd_params, pin_uv_protocol, pin_uv_param), ) fido2-1.2.0/fido2/ctap2/blob.py0000644000175000017500000001633014721556664015410 0ustar winniewinnie# Copyright (c) 2020 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations from .. import cbor from ..utils import sha256 from .base import Ctap2, Info from .pin import PinProtocol, _PinUv from cryptography.hazmat.primitives.ciphers.aead import AESGCM from cryptography.exceptions import InvalidTag from typing import Optional, Any, Sequence, Mapping, cast import struct import zlib import os def _compress(data): o = zlib.compressobj(wbits=-zlib.MAX_WBITS) return o.compress(data) + o.flush() def _decompress(data): o = zlib.decompressobj(wbits=-zlib.MAX_WBITS) return o.decompress(data) + o.flush() def _lb_ad(orig_size): return b"blob" + struct.pack(" bool: return info.options.get("largeBlobs") is True def __init__( self, ctap: Ctap2, pin_uv_protocol: Optional[PinProtocol] = None, pin_uv_token: Optional[bytes] = None, ): if not self.is_supported(ctap.info): raise ValueError("Authenticator does not support LargeBlobs") self.ctap = ctap self.max_fragment_length = self.ctap.info.max_msg_size - 64 self.pin_uv = ( _PinUv(pin_uv_protocol, pin_uv_token) if pin_uv_protocol and pin_uv_token else None ) def read_blob_array(self) -> Sequence[Mapping[int, Any]]: """Gets the entire contents of the Large Blobs array. :return: The CBOR decoded list of Large Blobs. """ offset = 0 buf = b"" while True: fragment = self.ctap.large_blobs(offset, get=self.max_fragment_length)[1] buf += fragment if len(fragment) < self.max_fragment_length: break offset += self.max_fragment_length data, check = buf[:-16], buf[-16:] if check != sha256(data)[:-16]: return [] return cast(Sequence[Mapping[int, Any]], cbor.decode(data)) def write_blob_array(self, blob_array: Sequence[Mapping[int, Any]]) -> None: """Writes the entire Large Blobs array. :param blob_array: A list to write to the Authenticator. """ if not isinstance(blob_array, list): raise TypeError("large-blob array must be a list") data = cbor.encode(blob_array) data += sha256(data)[:16] offset = 0 size = len(data) while offset < size: ln = min(size - offset, self.max_fragment_length) _set = data[offset : offset + ln] if self.pin_uv: msg = ( b"\xff" * 32 + b"\x0c\x00" + struct.pack(" Optional[bytes]: """Gets the Large Blob stored for a single credential. :param large_blob_key: The largeBlobKey for the credential, or None. :returns: The decrypted and deflated value stored for the credential. """ for entry in self.read_blob_array(): try: compressed, orig_size = _lb_unpack(large_blob_key, entry) decompressed = _decompress(compressed) if len(decompressed) == orig_size: return decompressed except (ValueError, zlib.error): continue return None def put_blob(self, large_blob_key: bytes, data: Optional[bytes]) -> None: """Stores a Large Blob for a single credential. Any existing entries for the same credential will be replaced. :param large_blob_key: The largeBlobKey for the credential. :param data: The data to compress, encrypt and store. """ modified = data is not None entries = [] for entry in self.read_blob_array(): try: _lb_unpack(large_blob_key, entry) modified = True except ValueError: entries.append(entry) if data is not None: entries.append(_lb_pack(large_blob_key, data)) if modified: self.write_blob_array(entries) def delete_blob(self, large_blob_key: bytes) -> None: """Deletes any Large Blob(s) stored for a single credential. :param large_blob_key: The largeBlobKey for the credential. """ self.put_blob(large_blob_key, None) fido2-1.2.0/fido2/ctap2/credman.py0000644000175000017500000002231414721556664016102 0ustar winniewinnie# Copyright (c) 2020 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations from .. import cbor from ..ctap import CtapError from ..webauthn import ( PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity, _as_cbor, ) from .base import Ctap2, Info from .pin import PinProtocol, _PinUv from enum import IntEnum, unique from typing import Mapping, Sequence, Any import struct import logging logger = logging.getLogger(__name__) class CredentialManagement: """Implementation of a draft specification of the Credential Management API. WARNING: This specification is not final and this class is likely to change. :param ctap: An instance of a CTAP2 object. :param pin_uv_protocol: An instance of a PinUvAuthProtocol. :param pin_uv_token: A valid PIN/UV Auth Token for the current CTAP session. """ @unique class CMD(IntEnum): GET_CREDS_METADATA = 0x01 ENUMERATE_RPS_BEGIN = 0x02 ENUMERATE_RPS_NEXT = 0x03 ENUMERATE_CREDS_BEGIN = 0x04 ENUMERATE_CREDS_NEXT = 0x05 DELETE_CREDENTIAL = 0x06 UPDATE_USER_INFO = 0x07 @unique class PARAM(IntEnum): RP_ID_HASH = 0x01 CREDENTIAL_ID = 0x02 USER = 0x03 @unique class RESULT(IntEnum): EXISTING_CRED_COUNT = 0x01 MAX_REMAINING_COUNT = 0x02 RP = 0x03 RP_ID_HASH = 0x04 TOTAL_RPS = 0x05 USER = 0x06 CREDENTIAL_ID = 0x07 PUBLIC_KEY = 0x08 TOTAL_CREDENTIALS = 0x09 CRED_PROTECT = 0x0A LARGE_BLOB_KEY = 0x0B @staticmethod def is_supported(info: Info) -> bool: if info.options.get("credMgmt"): return True # We also support the Prototype command if "FIDO_2_1_PRE" in info.versions and "credentialMgmtPreview" in info.options: return True return False @staticmethod def is_update_supported(info: Info) -> bool: # Not supported in credentialMgmtPreview return bool(info.options.get("credMgmt")) def __init__( self, ctap: Ctap2, pin_uv_protocol: PinProtocol, pin_uv_token: bytes, ): if not self.is_supported(ctap.info): raise ValueError("Authenticator does not support Credential Management") self.ctap = ctap self.pin_uv = _PinUv(pin_uv_protocol, pin_uv_token) def _call(self, sub_cmd, params=None, auth=True): kwargs = {"sub_cmd": sub_cmd, "sub_cmd_params": params} if auth: msg = struct.pack(">B", sub_cmd) if params is not None: msg += cbor.encode(params) kwargs["pin_uv_protocol"] = self.pin_uv.protocol.VERSION kwargs["pin_uv_param"] = self.pin_uv.protocol.authenticate( self.pin_uv.token, msg ) return self.ctap.credential_mgmt(**kwargs) def get_metadata(self) -> Mapping[int, Any]: """Get credentials metadata. This returns the existing resident credentials count, and the max possible number of remaining resident credentials (the actual number of remaining credentials may depend on algorithm choice, etc). :return: A dict containing EXISTING_CRED_COUNT, and MAX_REMAINING_COUNT. """ return self._call(CredentialManagement.CMD.GET_CREDS_METADATA) def enumerate_rps_begin(self) -> Mapping[int, Any]: """Start enumeration of RP entities of resident credentials. This will begin enumeration of stored RP entities, returning the first entity, as well as a count of the total number of entities stored. :return: A dict containing RP, RP_ID_HASH, and TOTAL_RPS. """ return self._call(CredentialManagement.CMD.ENUMERATE_RPS_BEGIN) def enumerate_rps_next(self) -> Mapping[int, Any]: """Get the next RP entity stored. This continues enumeration of stored RP entities, returning the next entity. :return: A dict containing RP, and RP_ID_HASH. """ return self._call(CredentialManagement.CMD.ENUMERATE_RPS_NEXT, auth=False) def enumerate_rps(self) -> Sequence[Mapping[int, Any]]: """Convenience method to enumerate all RPs. See enumerate_rps_begin and enumerate_rps_next for details. """ try: first = self.enumerate_rps_begin() except CtapError as e: if e.code == CtapError.ERR.NO_CREDENTIALS: return [] raise # Other error n_rps = first[CredentialManagement.RESULT.TOTAL_RPS] if n_rps == 0: return [] rest = [self.enumerate_rps_next() for _ in range(1, n_rps)] return [first] + rest def enumerate_creds_begin(self, rp_id_hash: bytes) -> Mapping[int, Any]: """Start enumeration of resident credentials. This will begin enumeration of resident credentials for a given RP, returning the first credential, as well as a count of the total number of resident credentials stored for the given RP. :param rp_id_hash: SHA256 hash of the RP ID. :return: A dict containing USER, CREDENTIAL_ID, PUBLIC_KEY, and TOTAL_CREDENTIALS. """ return self._call( CredentialManagement.CMD.ENUMERATE_CREDS_BEGIN, {CredentialManagement.PARAM.RP_ID_HASH: rp_id_hash}, ) def enumerate_creds_next(self) -> Mapping[int, Any]: """Get the next resident credential stored. This continues enumeration of resident credentials, returning the next credential. :return: A dict containing USER, CREDENTIAL_ID, and PUBLIC_KEY. """ return self._call(CredentialManagement.CMD.ENUMERATE_CREDS_NEXT, auth=False) def enumerate_creds(self, *args, **kwargs) -> Sequence[Mapping[int, Any]]: """Convenience method to enumerate all resident credentials for an RP. See enumerate_creds_begin and enumerate_creds_next for details. """ try: first = self.enumerate_creds_begin(*args, **kwargs) except CtapError as e: if e.code == CtapError.ERR.NO_CREDENTIALS: return [] raise # Other error rest = [ self.enumerate_creds_next() for _ in range( 1, first.get(CredentialManagement.RESULT.TOTAL_CREDENTIALS, 1) ) ] return [first] + rest def delete_cred(self, cred_id: PublicKeyCredentialDescriptor) -> None: """Delete a resident credential. :param cred_id: The PublicKeyCredentialDescriptor of the credential to delete. """ cred_id = PublicKeyCredentialDescriptor.from_dict(cred_id) logger.debug(f"Deleting credential with ID: {cred_id}") self._call( CredentialManagement.CMD.DELETE_CREDENTIAL, {CredentialManagement.PARAM.CREDENTIAL_ID: _as_cbor(cred_id)}, ) logger.info("Credential deleted") def update_user_info( self, cred_id: PublicKeyCredentialDescriptor, user_info: PublicKeyCredentialUserEntity, ) -> None: """Update the user entity of a resident key. :param cred_id: The PublicKeyCredentialDescriptor of the credential to update. :param user_info: The user info update. """ if not CredentialManagement.is_update_supported(self.ctap.info): raise ValueError("Authenticator does not support update_user_info") cred_id = PublicKeyCredentialDescriptor.from_dict(cred_id) user_info = PublicKeyCredentialUserEntity.from_dict(user_info) logger.debug(f"Updating credential: {cred_id} with user info: {user_info}") self._call( CredentialManagement.CMD.UPDATE_USER_INFO, { CredentialManagement.PARAM.CREDENTIAL_ID: _as_cbor(cred_id), CredentialManagement.PARAM.USER: _as_cbor(user_info), }, ) logger.info("Credential user info updated") fido2-1.2.0/fido2/ctap2/pin.py0000644000175000017500000003621314721556664015262 0ustar winniewinnie# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations from ..utils import sha256, hmac_sha256, bytes2int, int2bytes from ..cose import CoseKey from .base import Ctap2 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.kdf.hkdf import HKDF from enum import IntEnum, IntFlag, unique from dataclasses import dataclass from threading import Event from typing import Optional, Any, Mapping, ClassVar, Tuple, Callable import abc import os import logging logger = logging.getLogger(__name__) def _pad_pin(pin: str) -> bytes: if not isinstance(pin, str): raise ValueError(f"PIN of wrong type, expecting {str}") if len(pin) < 4: raise ValueError("PIN must be >= 4 characters") pin_padded = pin.encode().ljust(64, b"\0") pin_padded += b"\0" * (-(len(pin_padded) - 16) % 16) if len(pin_padded) > 255: raise ValueError("PIN must be <= 255 bytes") return pin_padded class PinProtocol(abc.ABC): VERSION: ClassVar[int] @abc.abstractmethod def encapsulate(self, peer_cose_key: CoseKey) -> Tuple[Mapping[int, Any], bytes]: """Generates an encapsulation of the public key. Returns the message to transmit and the shared secret. """ @abc.abstractmethod def encrypt(self, key: bytes, plaintext: bytes) -> bytes: """Encrypts data""" @abc.abstractmethod def decrypt(self, key: bytes, ciphertext: bytes) -> bytes: """Decrypts encrypted data""" @abc.abstractmethod def authenticate(self, key: bytes, message: bytes) -> bytes: """Computes a MAC of the given message.""" @abc.abstractmethod def validate_token(self, token: bytes) -> bytes: """Validates that a token is well-formed. Returns the token, or if invalid, raises a ValueError. """ @dataclass class _PinUv: protocol: PinProtocol token: bytes class PinProtocolV1(PinProtocol): """Implementation of the CTAP2 PIN/UV protocol v1. :param ctap: An instance of a CTAP2 object. :cvar VERSION: The version number of the PIV/UV protocol. :cvar IV: An all-zero IV used for some cryptographic operations. """ VERSION = 1 IV = b"\x00" * 16 def kdf(self, z: bytes) -> bytes: return sha256(z) def encapsulate(self, peer_cose_key): be = default_backend() sk = ec.generate_private_key(ec.SECP256R1(), be) pn = sk.public_key().public_numbers() key_agreement = { 1: 2, 3: -25, # Per the spec, "although this is NOT the algorithm actually used" -1: 1, -2: int2bytes(pn.x, 32), -3: int2bytes(pn.y, 32), } x = bytes2int(peer_cose_key[-2]) y = bytes2int(peer_cose_key[-3]) pk = ec.EllipticCurvePublicNumbers(x, y, ec.SECP256R1()).public_key(be) shared_secret = self.kdf(sk.exchange(ec.ECDH(), pk)) # x-coordinate, 32b return key_agreement, shared_secret def _get_cipher_v1(self, secret): be = default_backend() return Cipher(algorithms.AES(secret), modes.CBC(PinProtocolV1.IV), be) def encrypt(self, key, plaintext): cipher = self._get_cipher_v1(key) enc = cipher.encryptor() return enc.update(plaintext) + enc.finalize() def decrypt(self, key, ciphertext): cipher = self._get_cipher_v1(key) dec = cipher.decryptor() return dec.update(ciphertext) + dec.finalize() def authenticate(self, key, message): return hmac_sha256(key, message)[:16] def validate_token(self, token): if len(token) not in (16, 32): raise ValueError("PIN/UV token must be 16 or 32 bytes") return token class PinProtocolV2(PinProtocolV1): """Implementation of the CTAP2 PIN/UV protocol v2. :param ctap: An instance of a CTAP2 object. :cvar VERSION: The version number of the PIV/UV protocol. :cvar IV: An all-zero IV used for some cryptographic operations. """ VERSION = 2 HKDF_SALT = b"\x00" * 32 HKDF_INFO_HMAC = b"CTAP2 HMAC key" HKDF_INFO_AES = b"CTAP2 AES key" def kdf(self, z): be = default_backend() hmac_key = HKDF( algorithm=hashes.SHA256(), length=32, salt=PinProtocolV2.HKDF_SALT, info=PinProtocolV2.HKDF_INFO_HMAC, backend=be, ).derive(z) aes_key = HKDF( algorithm=hashes.SHA256(), length=32, salt=PinProtocolV2.HKDF_SALT, info=PinProtocolV2.HKDF_INFO_AES, backend=be, ).derive(z) return hmac_key + aes_key def _get_cipher_v2(self, secret, iv): be = default_backend() return Cipher(algorithms.AES(secret), modes.CBC(iv), be) def encrypt(self, key, plaintext): aes_key = key[32:] iv = os.urandom(16) cipher = self._get_cipher_v2(aes_key, iv) enc = cipher.encryptor() return iv + enc.update(plaintext) + enc.finalize() def decrypt(self, key, ciphertext): aes_key = key[32:] iv, ciphertext = ciphertext[:16], ciphertext[16:] cipher = self._get_cipher_v2(aes_key, iv) dec = cipher.decryptor() return dec.update(ciphertext) + dec.finalize() def authenticate(self, key, message): hmac_key = key[:32] return hmac_sha256(hmac_key, message) def validate_token(self, token): if len(token) != 32: raise ValueError("PIN/UV token must be 32 bytes") return token class ClientPin: """Implementation of the CTAP2 Client PIN API. :param ctap: An instance of a CTAP2 object. :param protocol: An optional instance of a PinUvAuthProtocol object. If None is provided then the latest protocol supported by both library and Authenticator will be used. """ PROTOCOLS = [PinProtocolV2, PinProtocolV1] @unique class CMD(IntEnum): GET_PIN_RETRIES = 0x01 GET_KEY_AGREEMENT = 0x02 SET_PIN = 0x03 CHANGE_PIN = 0x04 GET_TOKEN_USING_PIN_LEGACY = 0x05 GET_TOKEN_USING_UV = 0x06 GET_UV_RETRIES = 0x07 GET_TOKEN_USING_PIN = 0x09 @unique class RESULT(IntEnum): KEY_AGREEMENT = 0x01 PIN_UV_TOKEN = 0x02 PIN_RETRIES = 0x03 POWER_CYCLE_STATE = 0x04 UV_RETRIES = 0x05 @unique class PERMISSION(IntFlag): MAKE_CREDENTIAL = 0x01 GET_ASSERTION = 0x02 CREDENTIAL_MGMT = 0x04 BIO_ENROLL = 0x08 LARGE_BLOB_WRITE = 0x10 AUTHENTICATOR_CFG = 0x20 @staticmethod def is_supported(info): """Checks if ClientPin functionality is supported. Note that the ClientPin function is still usable without support for client PIN functionality, as UV token may still be supported. """ return "clientPin" in info.options @staticmethod def is_token_supported(info): """Checks if pinUvAuthToken is supported.""" return info.options.get("pinUvAuthToken") is True def __init__(self, ctap: Ctap2, protocol: Optional[PinProtocol] = None): self.ctap = ctap if protocol is None: for proto in ClientPin.PROTOCOLS: if proto.VERSION in ctap.info.pin_uv_protocols: self.protocol: PinProtocol = proto() break else: raise ValueError("No compatible PIN/UV protocols supported!") else: self.protocol = protocol def _get_shared_secret(self): resp = self.ctap.client_pin( self.protocol.VERSION, ClientPin.CMD.GET_KEY_AGREEMENT ) pk = resp[ClientPin.RESULT.KEY_AGREEMENT] return self.protocol.encapsulate(pk) def get_pin_token( self, pin: str, permissions: Optional[ClientPin.PERMISSION] = None, permissions_rpid: Optional[str] = None, ) -> bytes: """Get a PIN/UV token from the authenticator using PIN. :param pin: The PIN of the authenticator. :param permissions: The permissions to associate with the token. :param permissions_rpid: The permissions RPID to associate with the token. :return: A PIN/UV token. """ if not ClientPin.is_supported(self.ctap.info): raise ValueError("Authenticator does not support get_pin_token") key_agreement, shared_secret = self._get_shared_secret() pin_hash = sha256(pin.encode())[:16] pin_hash_enc = self.protocol.encrypt(shared_secret, pin_hash) if ClientPin.is_token_supported(self.ctap.info) and permissions: cmd = ClientPin.CMD.GET_TOKEN_USING_PIN else: cmd = ClientPin.CMD.GET_TOKEN_USING_PIN_LEGACY # Ignore permissions if not supported permissions = None permissions_rpid = None resp = self.ctap.client_pin( self.protocol.VERSION, cmd, key_agreement=key_agreement, pin_hash_enc=pin_hash_enc, permissions=permissions, permissions_rpid=permissions_rpid, ) pin_token_enc = resp[ClientPin.RESULT.PIN_UV_TOKEN] logger.debug(f"Got PIN token for permissions: {permissions}") return self.protocol.validate_token( self.protocol.decrypt(shared_secret, pin_token_enc) ) def get_uv_token( self, permissions: Optional[ClientPin.PERMISSION] = None, permissions_rpid: Optional[str] = None, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> bytes: """Get a PIN/UV token from the authenticator using built-in UV. :param permissions: The permissions to associate with the token. :param permissions_rpid: The permissions RPID to associate with the token. :param event: An optional threading.Event which can be used to cancel the invocation. :param on_keepalive: An optional callback to handle keep-alive messages from the authenticator. The function is only called once for consecutive keep-alive messages with the same status. :return: A PIN/UV token. """ if not ClientPin.is_token_supported(self.ctap.info): raise ValueError("Authenticator does not support get_uv_token") key_agreement, shared_secret = self._get_shared_secret() resp = self.ctap.client_pin( self.protocol.VERSION, ClientPin.CMD.GET_TOKEN_USING_UV, key_agreement=key_agreement, permissions=permissions, permissions_rpid=permissions_rpid, event=event, on_keepalive=on_keepalive, ) pin_token_enc = resp[ClientPin.RESULT.PIN_UV_TOKEN] logger.debug(f"Got UV token for permissions: {permissions}") return self.protocol.validate_token( self.protocol.decrypt(shared_secret, pin_token_enc) ) def get_pin_retries(self) -> Tuple[int, Optional[int]]: """Get the number of PIN retries remaining. :return: A tuple of the number of PIN attempts remaining until the authenticator is locked, and the power cycle state, if available. """ resp = self.ctap.client_pin( self.protocol.VERSION, ClientPin.CMD.GET_PIN_RETRIES ) return ( resp[ClientPin.RESULT.PIN_RETRIES], resp.get(ClientPin.RESULT.POWER_CYCLE_STATE), ) def get_uv_retries(self) -> int: """Get the number of UV retries remaining. :return: A tuple of the number of UV attempts remaining until the authenticator is locked, and the power cycle state, if available. """ resp = self.ctap.client_pin(self.protocol.VERSION, ClientPin.CMD.GET_UV_RETRIES) return resp[ClientPin.RESULT.UV_RETRIES] def set_pin(self, pin: str) -> None: """Set the PIN of the autenticator. This only works when no PIN is set. To change the PIN when set, use change_pin. :param pin: A PIN to set. """ if not ClientPin.is_supported(self.ctap.info): raise ValueError("Authenticator does not support ClientPin") key_agreement, shared_secret = self._get_shared_secret() pin_enc = self.protocol.encrypt(shared_secret, _pad_pin(pin)) pin_uv_param = self.protocol.authenticate(shared_secret, pin_enc) self.ctap.client_pin( self.protocol.VERSION, ClientPin.CMD.SET_PIN, key_agreement=key_agreement, new_pin_enc=pin_enc, pin_uv_param=pin_uv_param, ) logger.info("PIN has been set") def change_pin(self, old_pin: str, new_pin: str) -> None: """Change the PIN of the authenticator. This only works when a PIN is already set. If no PIN is set, use set_pin. :param old_pin: The currently set PIN. :param new_pin: The new PIN to set. """ if not ClientPin.is_supported(self.ctap.info): raise ValueError("Authenticator does not support ClientPin") key_agreement, shared_secret = self._get_shared_secret() pin_hash = sha256(old_pin.encode())[:16] pin_hash_enc = self.protocol.encrypt(shared_secret, pin_hash) new_pin_enc = self.protocol.encrypt(shared_secret, _pad_pin(new_pin)) pin_uv_param = self.protocol.authenticate( shared_secret, new_pin_enc + pin_hash_enc ) self.ctap.client_pin( self.protocol.VERSION, ClientPin.CMD.CHANGE_PIN, key_agreement=key_agreement, pin_hash_enc=pin_hash_enc, new_pin_enc=new_pin_enc, pin_uv_param=pin_uv_param, ) logger.info("PIN has been changed") fido2-1.2.0/fido2/ctap2/__init__.py0000644000175000017500000000326214721556664016231 0ustar winniewinnie# Copyright (c) 2020 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from .base import ( # noqa Ctap2, Info, AttestationResponse, AssertionResponse, ) from .pin import ClientPin, PinProtocolV1, PinProtocolV2 # noqa from .credman import CredentialManagement # noqa from .bio import FPBioEnrollment, CaptureError # noqa from .blob import LargeBlobs # noqa from .config import Config # noqa fido2-1.2.0/fido2/ctap2/bio.py0000644000175000017500000003103414721556664015241 0ustar winniewinnie# Copyright (c) 2020 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations from .. import cbor from ..ctap import CtapError from .base import Ctap2, Info from .pin import PinProtocol from enum import IntEnum, unique from threading import Event from typing import Optional, Callable, Mapping, Any, Tuple, Dict import struct import logging logger = logging.getLogger(__name__) class BioEnrollment: @unique class RESULT(IntEnum): MODALITY = 0x01 FINGERPRINT_KIND = 0x02 MAX_SAMPLES_REQUIRED = 0x03 TEMPLATE_ID = 0x04 LAST_SAMPLE_STATUS = 0x05 REMAINING_SAMPLES = 0x06 TEMPLATE_INFOS = 0x07 @unique class TEMPLATE_INFO(IntEnum): ID = 0x01 NAME = 0x02 @unique class MODALITY(IntEnum): FINGERPRINT = 0x01 @staticmethod def is_supported(info: Info) -> bool: if "bioEnroll" in info.options: return True # We also support the Prototype command if ( "FIDO_2_1_PRE" in info.versions and "userVerificationMgmtPreview" in info.options ): return True return False def __init__(self, ctap: Ctap2, modality: MODALITY): if not self.is_supported(ctap.info): raise ValueError("Authenticator does not support BioEnroll") self.ctap = ctap self.modality = self.get_modality() if modality != self.modality: raise ValueError(f"Device does not support {modality:s}") def get_modality(self) -> int: """Get bio modality. :return: The type of modality supported by the authenticator. """ return self.ctap.bio_enrollment(get_modality=True)[ BioEnrollment.RESULT.MODALITY ] class CaptureError(Exception): def __init__(self, code: int): self.code = code super().__init__(f"Fingerprint capture error: {code}") class FPEnrollmentContext: """Helper object to perform fingerprint enrollment. :param bio: An instance of FPBioEnrollment. :param timeout: Optional timeout for fingerprint captures (ms). :ivar remaining: The number of (estimated) remaining samples needed. :ivar template_id: The ID of the new template (only available after the initial sample has been captured). """ def __init__(self, bio: "FPBioEnrollment", timeout: Optional[int] = None): self._bio = bio self.timeout = timeout self.template_id: Optional[bytes] = None self.remaining: Optional[int] = None def capture( self, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> Optional[bytes]: """Capture a fingerprint sample. This call will block for up to timeout milliseconds (or indefinitely, if timeout not specified) waiting for the user to scan their fingerprint to collect one sample. :return: None, if more samples are needed, or the template ID if enrollment is completed. """ if self.template_id is None: self.template_id, status, self.remaining = self._bio.enroll_begin( self.timeout, event, on_keepalive ) else: status, self.remaining = self._bio.enroll_capture_next( self.template_id, self.timeout, event, on_keepalive ) if status != FPBioEnrollment.FEEDBACK.FP_GOOD: raise CaptureError(status) if self.remaining == 0: return self.template_id return None def cancel(self) -> None: """Cancels ongoing enrollment.""" self._bio.enroll_cancel() self.template_id = None class FPBioEnrollment(BioEnrollment): """Implementation of a draft specification of the bio enrollment API. WARNING: This specification is not final and this class is likely to change. NOTE: The get_fingerprint_sensor_info method does not require authentication, and can be used by setting pin_uv_protocol and pin_uv_token to None. :param ctap: An instance of a CTAP2 object. :param pin_uv_protocol: The PIN/UV protocol version used. :param pin_uv_token: A valid PIN/UV Auth Token for the current CTAP session. """ @unique class CMD(IntEnum): ENROLL_BEGIN = 0x01 ENROLL_CAPTURE_NEXT = 0x02 ENROLL_CANCEL = 0x03 ENUMERATE_ENROLLMENTS = 0x04 SET_NAME = 0x05 REMOVE_ENROLLMENT = 0x06 GET_SENSOR_INFO = 0x07 @unique class PARAM(IntEnum): TEMPLATE_ID = 0x01 TEMPLATE_NAME = 0x02 TIMEOUT_MS = 0x03 @unique class FEEDBACK(IntEnum): FP_GOOD = 0x00 FP_TOO_HIGH = 0x01 FP_TOO_LOW = 0x02 FP_TOO_LEFT = 0x03 FP_TOO_RIGHT = 0x04 FP_TOO_FAST = 0x05 FP_TOO_SLOW = 0x06 FP_POOR_QUALITY = 0x07 FP_TOO_SKEWED = 0x08 FP_TOO_SHORT = 0x09 FP_MERGE_FAILURE = 0x0A FP_EXISTS = 0x0B FP_DATABASE_FULL = 0x0C NO_USER_ACTIVITY = 0x0D NO_UP_TRANSITION = 0x0E def __str__(self): return f"0x{self.value:02X} - {self.name}" def __init__(self, ctap: Ctap2, pin_uv_protocol: PinProtocol, pin_uv_token: bytes): super().__init__(ctap, BioEnrollment.MODALITY.FINGERPRINT) self.pin_uv_protocol = pin_uv_protocol self.pin_uv_token = pin_uv_token def _call(self, sub_cmd, params=None, auth=True, event=None, on_keepalive=None): kwargs = { "modality": self.modality, "sub_cmd": sub_cmd, "sub_cmd_params": params, "event": event, "on_keepalive": on_keepalive, } if auth: msg = struct.pack(">BB", self.modality, sub_cmd) if params is not None: msg += cbor.encode(params) kwargs["pin_uv_protocol"] = self.pin_uv_protocol.VERSION kwargs["pin_uv_param"] = self.pin_uv_protocol.authenticate( self.pin_uv_token, msg ) return self.ctap.bio_enrollment(**kwargs) def get_fingerprint_sensor_info(self) -> Mapping[int, Any]: """Get fingerprint sensor info. :return: A dict containing FINGERPRINT_KIND and MAX_SAMPLES_REQUIRES. """ return self._call(FPBioEnrollment.CMD.GET_SENSOR_INFO, auth=False) def enroll_begin( self, timeout: Optional[int] = None, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> Tuple[bytes, FPBioEnrollment.FEEDBACK, int]: """Start fingerprint enrollment. Starts the process of enrolling a new fingerprint, and will wait for the user to scan their fingerprint once to provide an initial sample. :param timeout: Optional timeout in milliseconds. :return: A tuple containing the new template ID, the sample status, and the number of samples remaining to complete the enrollment. """ logger.debug(f"Starting fingerprint enrollment (timeout={timeout})") result = self._call( FPBioEnrollment.CMD.ENROLL_BEGIN, ( {FPBioEnrollment.PARAM.TIMEOUT_MS: timeout} if timeout is not None else None ), event=event, on_keepalive=on_keepalive, ) logger.debug(f"Sample capture result: {result}") return ( result[BioEnrollment.RESULT.TEMPLATE_ID], FPBioEnrollment.FEEDBACK(result[BioEnrollment.RESULT.LAST_SAMPLE_STATUS]), result[BioEnrollment.RESULT.REMAINING_SAMPLES], ) def enroll_capture_next( self, template_id: bytes, timeout: Optional[int] = None, event: Optional[Event] = None, on_keepalive: Optional[Callable[[int], None]] = None, ) -> Tuple[FPBioEnrollment.FEEDBACK, int]: """Continue fingerprint enrollment. Continues enrolling a new fingerprint and will wait for the user to scan their fingerpring once to provide a new sample. Once the number of samples remaining is 0, the enrollment is completed. :param template_id: The template ID returned by a call to `enroll_begin`. :param timeout: Optional timeout in milliseconds. :return: A tuple containing the sample status, and the number of samples remaining to complete the enrollment. """ logger.debug(f"Capturing next sample with (timeout={timeout})") params: Dict[int, Any] = {FPBioEnrollment.PARAM.TEMPLATE_ID: template_id} if timeout is not None: params[FPBioEnrollment.PARAM.TIMEOUT_MS] = timeout result = self._call( FPBioEnrollment.CMD.ENROLL_CAPTURE_NEXT, params, event=event, on_keepalive=on_keepalive, ) logger.debug(f"Sample capture result: {result}") return ( FPBioEnrollment.FEEDBACK(result[BioEnrollment.RESULT.LAST_SAMPLE_STATUS]), result[BioEnrollment.RESULT.REMAINING_SAMPLES], ) def enroll_cancel(self) -> None: """Cancel any ongoing fingerprint enrollment.""" logger.debug("Cancelling fingerprint enrollment.") self._call(FPBioEnrollment.CMD.ENROLL_CANCEL, auth=False) def enroll(self, timeout: Optional[int] = None) -> FPEnrollmentContext: """Convenience wrapper for doing fingerprint enrollment. See FPEnrollmentContext for details. :return: An initialized FPEnrollmentContext. """ return FPEnrollmentContext(self, timeout) def enumerate_enrollments(self) -> Mapping[bytes, Optional[str]]: """Get a dict of enrolled fingerprint templates which maps template ID's to their friendly names. :return: A dict of enrolled template_id -> name pairs. """ try: return { t[BioEnrollment.TEMPLATE_INFO.ID]: t[BioEnrollment.TEMPLATE_INFO.NAME] for t in self._call(FPBioEnrollment.CMD.ENUMERATE_ENROLLMENTS)[ BioEnrollment.RESULT.TEMPLATE_INFOS ] } except CtapError as e: if e.code == CtapError.ERR.INVALID_OPTION: return {} raise def set_name(self, template_id: bytes, name: str) -> None: """Set/Change the friendly name of a previously enrolled fingerprint template. :param template_id: The ID of the template to change. :param name: A friendly name to give the template. """ logger.debug(f"Changing name of template: {template_id.hex()} to {name}") self._call( FPBioEnrollment.CMD.SET_NAME, { BioEnrollment.TEMPLATE_INFO.ID: template_id, BioEnrollment.TEMPLATE_INFO.NAME: name, }, ) logger.info("Fingerprint template renamed") def remove_enrollment(self, template_id: bytes) -> None: """Remove a previously enrolled fingerprint template. :param template_id: The Id of the template to remove. """ logger.debug(f"Deleting template: {template_id.hex()}") self._call( FPBioEnrollment.CMD.REMOVE_ENROLLMENT, {BioEnrollment.TEMPLATE_INFO.ID: template_id}, ) logger.info("Fingerprint template deleted") fido2-1.2.0/fido2/__init__.py0000644000175000017500000000256314721556664015223 0ustar winniewinnie# Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. __version__ = "1.2.0" fido2-1.2.0/fido2/ctap1.py0000644000175000017500000002173514721556664014476 0ustar winniewinnie# Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations from .hid import CTAPHID from .ctap import CtapDevice from .utils import websafe_encode, websafe_decode, bytes2int, ByteBuffer from .cose import ES256 from .attestation import FidoU2FAttestation from enum import IntEnum, unique from dataclasses import dataclass import struct @unique class APDU(IntEnum): """APDU response codes.""" OK = 0x9000 USE_NOT_SATISFIED = 0x6985 WRONG_DATA = 0x6A80 class ApduError(Exception): """An Exception thrown when a response APDU doesn't have an OK (0x9000) status. :param code: APDU response code. :param data: APDU response body. """ def __init__(self, code: int, data: bytes = b""): self.code = code self.data = data def __repr__(self): return f"APDU error: 0x{self.code:04X} {len(self.data):d} bytes of data" @dataclass(init=False) class RegistrationData(bytes): """Binary response data for a CTAP1 registration. :param _: The binary contents of the response data. :ivar public_key: Binary representation of the credential public key. :ivar key_handle: Binary key handle of the credential. :ivar certificate: Attestation certificate of the authenticator, DER encoded. :ivar signature: Attestation signature. """ public_key: bytes key_handle: bytes certificate: bytes signature: bytes def __init__(self, _: bytes): super().__init__() reader = ByteBuffer(self) if reader.unpack("B") != 0x05: raise ValueError("Reserved byte != 0x05") self.public_key = reader.read(65) self.key_handle = reader.read(reader.unpack("B")) cert_buf = reader.read(2) # Tag and first length byte cert_len = cert_buf[1] if cert_len > 0x80: # Multi-byte length n_bytes = cert_len - 0x80 len_bytes = reader.read(n_bytes) cert_buf += len_bytes cert_len = bytes2int(len_bytes) self.certificate = cert_buf + reader.read(cert_len) self.signature = reader.read() @property def b64(self) -> str: """Websafe base64 encoded string of the RegistrationData.""" return websafe_encode(self) def verify(self, app_param: bytes, client_param: bytes) -> None: """Verify the included signature with regard to the given app and client params. :param app_param: SHA256 hash of the app ID used for the request. :param client_param: SHA256 hash of the ClientData used for the request. """ FidoU2FAttestation.verify_signature( app_param, client_param, self.key_handle, self.public_key, self.certificate, self.signature, ) @classmethod def from_b64(cls, data: str) -> RegistrationData: """Parse a RegistrationData from a websafe base64 encoded string. :param data: Websafe base64 encoded string. :return: The decoded and parsed RegistrationData. """ return cls(websafe_decode(data)) @dataclass(init=False) class SignatureData(bytes): """Binary response data for a CTAP1 authentication. :param _: The binary contents of the response data. :ivar user_presence: User presence byte. :ivar counter: Signature counter. :ivar signature: Cryptographic signature. """ user_presence: int counter: int signature: bytes def __init__(self, _: bytes): super().__init__() reader = ByteBuffer(self) self.user_presence = reader.unpack("B") self.counter = reader.unpack(">I") self.signature = reader.read() @property def b64(self) -> str: """str: Websafe base64 encoded string of the SignatureData.""" return websafe_encode(self) def verify(self, app_param: bytes, client_param: bytes, public_key: bytes) -> None: """Verify the included signature with regard to the given app and client params, using the given public key. :param app_param: SHA256 hash of the app ID used for the request. :param client_param: SHA256 hash of the ClientData used for the request. :param public_key: Binary representation of the credential public key. """ m = app_param + self[:5] + client_param ES256.from_ctap1(public_key).verify(m, self.signature) @classmethod def from_b64(cls, data: str) -> SignatureData: """Parse a SignatureData from a websafe base64 encoded string. :param data: Websafe base64 encoded string. :return: The decoded and parsed SignatureData. """ return cls(websafe_decode(data)) class Ctap1: """Implementation of the CTAP1 specification. :param device: A CtapHidDevice handle supporting CTAP1. """ @unique class INS(IntEnum): REGISTER = 0x01 AUTHENTICATE = 0x02 VERSION = 0x03 def __init__(self, device: CtapDevice): self.device = device def send_apdu( self, cla: int = 0, ins: int = 0, p1: int = 0, p2: int = 0, data: bytes = b"" ) -> bytes: """Packs and sends an APDU for use in CTAP1 commands. This is a low-level method mainly used internally. Avoid calling it directly if possible, and use the get_version, register, and authenticate methods if possible instead. :param cla: The CLA parameter of the request. :param ins: The INS parameter of the request. :param p1: The P1 parameter of the request. :param p2: The P2 parameter of the request. :param data: The body of the request. :return: The response APDU data of a successful request. :raise: ApduError """ apdu = struct.pack(">BBBBBH", cla, ins, p1, p2, 0, len(data)) + data + b"\0\0" response = self.device.call(CTAPHID.MSG, apdu) status = struct.unpack(">H", response[-2:])[0] data = response[:-2] if status != APDU.OK: raise ApduError(status, data) return data def get_version(self) -> str: """Get the U2F version implemented by the authenticator. The only version specified is "U2F_V2". :return: A U2F version string. """ return self.send_apdu(ins=Ctap1.INS.VERSION).decode() def register(self, client_param: bytes, app_param: bytes) -> RegistrationData: """Register a new U2F credential. :param client_param: SHA256 hash of the ClientData used for the request. :param app_param: SHA256 hash of the app ID used for the request. :return: The registration response from the authenticator. """ data = client_param + app_param response = self.send_apdu(ins=Ctap1.INS.REGISTER, data=data) return RegistrationData(response) def authenticate( self, client_param: bytes, app_param: bytes, key_handle: bytes, check_only: bool = False, ) -> SignatureData: """Authenticate a previously registered credential. :param client_param: SHA256 hash of the ClientData used for the request. :param app_param: SHA256 hash of the app ID used for the request. :param key_handle: The binary key handle of the credential. :param check_only: True to send a "check-only" request, which is used to determine if a key handle is known. :return: The authentication response from the authenticator. """ data = ( client_param + app_param + struct.pack(">B", len(key_handle)) + key_handle ) p1 = 0x07 if check_only else 0x03 response = self.send_apdu(ins=Ctap1.INS.AUTHENTICATE, p1=p1, data=data) return SignatureData(response) fido2-1.2.0/fido2/cose.py0000644000175000017500000002407114721556664014413 0ustar winniewinnie# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations from .utils import bytes2int, int2bytes from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import ec, rsa, padding, ed25519, types from typing import Sequence, Type, Mapping, Any, TypeVar class CoseKey(dict): """A COSE formatted public key. :param _: The COSE key paramters. :cvar ALGORITHM: COSE algorithm identifier. """ ALGORITHM: int = None # type: ignore def verify(self, message: bytes, signature: bytes) -> None: """Validates a digital signature over a given message. :param message: The message which was signed. :param signature: The signature to check. """ raise NotImplementedError("Signature verification not supported.") @classmethod def from_cryptography_key( cls: Type[T_CoseKey], public_key: types.PublicKeyTypes ) -> T_CoseKey: """Converts a PublicKey object from Cryptography into a COSE key. :param public_key: Either an EC or RSA public key. :return: A CoseKey. """ raise NotImplementedError("Creation from cryptography not supported.") @staticmethod def for_alg(alg: int) -> Type[CoseKey]: """Get a subclass of CoseKey corresponding to an algorithm identifier. :param alg: The COSE identifier of the algorithm. :return: A CoseKey. """ for cls in CoseKey.__subclasses__(): if cls.ALGORITHM == alg: return cls return UnsupportedKey @staticmethod def for_name(name: str) -> Type[CoseKey]: """Get a subclass of CoseKey corresponding to an algorithm identifier. :param alg: The COSE identifier of the algorithm. :return: A CoseKey. """ for cls in CoseKey.__subclasses__(): if cls.__name__ == name: return cls return UnsupportedKey @staticmethod def parse(cose: Mapping[int, Any]) -> CoseKey: """Create a CoseKey from a dict""" alg = cose.get(3) if not alg: raise ValueError("COSE alg identifier must be provided.") return CoseKey.for_alg(alg)(cose) @staticmethod def supported_algorithms() -> Sequence[int]: """Get a list of all supported algorithm identifiers""" algs: Sequence[Type[CoseKey]] = [ ES256, EdDSA, ES384, ES512, PS256, RS256, ES256K, ] return [cls.ALGORITHM for cls in algs] T_CoseKey = TypeVar("T_CoseKey", bound=CoseKey) class UnsupportedKey(CoseKey): """A COSE key with an unsupported algorithm.""" class ES256(CoseKey): ALGORITHM = -7 _HASH_ALG = hashes.SHA256() def verify(self, message, signature): if self[-1] != 1: raise ValueError("Unsupported elliptic curve") ec.EllipticCurvePublicNumbers( bytes2int(self[-2]), bytes2int(self[-3]), ec.SECP256R1() ).public_key(default_backend()).verify( signature, message, ec.ECDSA(self._HASH_ALG) ) @classmethod def from_cryptography_key(cls, public_key): assert isinstance(public_key, ec.EllipticCurvePublicKey) # nosec pn = public_key.public_numbers() return cls( { 1: 2, 3: cls.ALGORITHM, -1: 1, -2: int2bytes(pn.x, 32), -3: int2bytes(pn.y, 32), } ) @classmethod def from_ctap1(cls, data): """Creates an ES256 key from a CTAP1 formatted public key byte string. :param data: A 65 byte SECP256R1 public key. :return: A ES256 key. """ return cls({1: 2, 3: cls.ALGORITHM, -1: 1, -2: data[1:33], -3: data[33:65]}) class ES384(CoseKey): ALGORITHM = -35 _HASH_ALG = hashes.SHA384() def verify(self, message, signature): if self[-1] != 2: raise ValueError("Unsupported elliptic curve") ec.EllipticCurvePublicNumbers( bytes2int(self[-2]), bytes2int(self[-3]), ec.SECP384R1() ).public_key(default_backend()).verify( signature, message, ec.ECDSA(self._HASH_ALG) ) @classmethod def from_cryptography_key(cls, public_key): assert isinstance(public_key, ec.EllipticCurvePublicKey) # nosec pn = public_key.public_numbers() return cls( { 1: 2, 3: cls.ALGORITHM, -1: 2, -2: int2bytes(pn.x, 48), -3: int2bytes(pn.y, 48), } ) class ES512(CoseKey): ALGORITHM = -36 _HASH_ALG = hashes.SHA512() def verify(self, message, signature): if self[-1] != 3: raise ValueError("Unsupported elliptic curve") ec.EllipticCurvePublicNumbers( bytes2int(self[-2]), bytes2int(self[-3]), ec.SECP521R1() ).public_key(default_backend()).verify( signature, message, ec.ECDSA(self._HASH_ALG) ) @classmethod def from_cryptography_key(cls, public_key): assert isinstance(public_key, ec.EllipticCurvePublicKey) # nosec pn = public_key.public_numbers() return cls( { 1: 2, 3: cls.ALGORITHM, -1: 3, -2: int2bytes(pn.x, 66), -3: int2bytes(pn.y, 66), } ) 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): assert isinstance(public_key, rsa.RSAPublicKey) # nosec 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): assert isinstance(public_key, rsa.RSAPublicKey) # nosec 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): assert isinstance(public_key, ed25519.Ed25519PublicKey) # nosec return cls( { 1: 1, 3: cls.ALGORITHM, -1: 6, -2: public_key.public_bytes( serialization.Encoding.Raw, serialization.PublicFormat.Raw ), } ) class RS1(CoseKey): ALGORITHM = -65535 _HASH_ALG = hashes.SHA1() # nosec def verify(self, message, signature): rsa.RSAPublicNumbers(bytes2int(self[-2]), bytes2int(self[-1])).public_key( default_backend() ).verify(signature, message, padding.PKCS1v15(), self._HASH_ALG) @classmethod def from_cryptography_key(cls, public_key): assert isinstance(public_key, rsa.RSAPublicKey) # nosec pn = public_key.public_numbers() return cls({1: 3, 3: cls.ALGORITHM, -1: int2bytes(pn.n), -2: int2bytes(pn.e)}) class ES256K(CoseKey): ALGORITHM = -47 _HASH_ALG = hashes.SHA256() def verify(self, message, signature): if self[-1] != 8: raise ValueError("Unsupported elliptic curve") ec.EllipticCurvePublicNumbers( bytes2int(self[-2]), bytes2int(self[-3]), ec.SECP256K1() ).public_key(default_backend()).verify( signature, message, ec.ECDSA(self._HASH_ALG) ) @classmethod def from_cryptography_key(cls, public_key): assert isinstance(public_key, ec.EllipticCurvePublicKey) # nosec pn = public_key.public_numbers() return cls( { 1: 2, 3: cls.ALGORITHM, -1: 8, -2: int2bytes(pn.x, 32), -3: int2bytes(pn.y, 32), } ) fido2-1.2.0/fido2/client.py0000644000175000017500000012332714721556664014744 0ustar winniewinnie# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations from .hid import STATUS from .ctap import CtapDevice, CtapError from .ctap1 import Ctap1, APDU, ApduError from .ctap2 import Ctap2, AssertionResponse, Info from .ctap2.pin import ClientPin, PinProtocol from .ctap2.extensions import ( Ctap2Extension, AuthenticationExtensionProcessor, ) from .webauthn import ( Aaguid, AttestationObject, CollectedClientData, PublicKeyCredentialRpEntity, PublicKeyCredentialDescriptor, PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions, AuthenticationExtensionsClientOutputs, AuthenticatorSelectionCriteria, UserVerificationRequirement, AuthenticatorAttestationResponse, AuthenticatorAssertionResponse, AttestationConveyancePreference, ResidentKeyRequirement, _as_cbor, ) from .cose import ES256 from .rpid import verify_rp_id from .utils import sha256 from enum import IntEnum, unique from dataclasses import replace from urllib.parse import urlparse from threading import Timer, Event from typing import ( Type, Any, Callable, Optional, Mapping, Sequence, Tuple, overload, ) import abc import platform import inspect import logging logger = logging.getLogger(__name__) class ClientError(Exception): """Base error raised by clients.""" @unique class ERR(IntEnum): """Error codes for ClientError.""" OTHER_ERROR = 1 BAD_REQUEST = 2 CONFIGURATION_UNSUPPORTED = 3 DEVICE_INELIGIBLE = 4 TIMEOUT = 5 def __call__(self, cause=None): return ClientError(self, cause) def __init__(self, code, cause=None): self.code = ClientError.ERR(code) self.cause = cause def __repr__(self): r = "Client error: {0} - {0.name}".format(self.code) if self.cause: r += f" (cause: {self.cause})" return r def _ctap2client_err(e, err_cls=ClientError): if e.code in [CtapError.ERR.CREDENTIAL_EXCLUDED, CtapError.ERR.NO_CREDENTIALS]: ce = ClientError.ERR.DEVICE_INELIGIBLE elif e.code in [ CtapError.ERR.KEEPALIVE_CANCEL, CtapError.ERR.ACTION_TIMEOUT, CtapError.ERR.USER_ACTION_TIMEOUT, ]: ce = ClientError.ERR.TIMEOUT elif e.code in [ CtapError.ERR.UNSUPPORTED_ALGORITHM, CtapError.ERR.UNSUPPORTED_OPTION, CtapError.ERR.KEY_STORE_FULL, ]: ce = ClientError.ERR.CONFIGURATION_UNSUPPORTED elif e.code in [ CtapError.ERR.INVALID_COMMAND, CtapError.ERR.CBOR_UNEXPECTED_TYPE, CtapError.ERR.INVALID_CBOR, CtapError.ERR.MISSING_PARAMETER, CtapError.ERR.INVALID_OPTION, CtapError.ERR.PUAT_REQUIRED, CtapError.ERR.PIN_INVALID, CtapError.ERR.PIN_BLOCKED, CtapError.ERR.PIN_NOT_SET, CtapError.ERR.PIN_POLICY_VIOLATION, CtapError.ERR.PIN_TOKEN_EXPIRED, CtapError.ERR.PIN_AUTH_INVALID, CtapError.ERR.PIN_AUTH_BLOCKED, CtapError.ERR.REQUEST_TOO_LARGE, CtapError.ERR.OPERATION_DENIED, ]: ce = ClientError.ERR.BAD_REQUEST else: ce = ClientError.ERR.OTHER_ERROR return err_cls(ce, e) class PinRequiredError(ClientError): """Raised when a call cannot be completed without providing PIN.""" def __init__( self, code=ClientError.ERR.BAD_REQUEST, cause="PIN required but not provided" ): super().__init__(code, cause) def _call_polling(poll_delay, event, on_keepalive, func, *args, **kwargs): event = event or Event() while not event.is_set(): try: return func(*args, **kwargs) except ApduError as e: if e.code == APDU.USE_NOT_SATISFIED: if on_keepalive: on_keepalive(STATUS.UPNEEDED) on_keepalive = None event.wait(poll_delay) else: raise ClientError.ERR.OTHER_ERROR(e) except CtapError as e: raise _ctap2client_err(e) raise ClientError.ERR.TIMEOUT() class _BaseClient: def __init__(self, origin: str, verify: Callable[[str, str], bool]): self.origin = origin self._verify = verify def _verify_rp_id(self, rp_id): try: if self._verify(rp_id, self.origin): return except Exception: # nosec pass # Fall through to ClientError raise ClientError.ERR.BAD_REQUEST() def _build_client_data(self, typ, challenge): return CollectedClientData.create( type=typ, origin=self.origin, challenge=challenge, ) class AssertionSelection: """GetAssertion result holding one or more assertions. Since multiple assertions may be retured by Fido2Client.get_assertion, this result is returned which can be used to select a specific response to get. """ def __init__( self, client_data: CollectedClientData, assertions: Sequence[AssertionResponse], extension_results=None, ): self._client_data = client_data self._assertions = assertions self._extension_results = extension_results def get_assertions(self) -> Sequence[AssertionResponse]: """Get the raw AssertionResponses available to inspect before selecting one.""" return self._assertions def _get_extension_results( self, assertion: AssertionResponse ) -> Optional[Mapping[str, Any]]: return self._extension_results def get_response(self, index: int) -> AuthenticatorAssertionResponse: """Get a single response.""" assertion = self._assertions[index] return AuthenticatorAssertionResponse( self._client_data, assertion.auth_data, assertion.signature, assertion.user["id"] if assertion.user else None, assertion.credential["id"] if assertion.credential else None, self._get_extension_results(assertion), ) class WebAuthnClient(abc.ABC): """Base class for a WebAuthn client, supporting registration and authentication.""" @abc.abstractmethod def make_credential( self, options: PublicKeyCredentialCreationOptions, event: Optional[Event] = None, ) -> AuthenticatorAttestationResponse: """Creates a credential. :param options: PublicKeyCredentialCreationOptions data. :param threading.Event event: (optional) Signal to abort the operation. """ raise NotImplementedError() @abc.abstractmethod def get_assertion( self, options: PublicKeyCredentialRequestOptions, event: Optional[Event] = None, ) -> AssertionSelection: """Get an assertion. :param options: PublicKeyCredentialRequestOptions data. :param threading.Event event: (optional) Signal to abort the operation. """ raise NotImplementedError() def _default_extensions() -> Sequence[Type[Ctap2Extension]]: return [ cls for cls in Ctap2Extension.__subclasses__() if not inspect.isabstract(cls) ] class UserInteraction: """Provides user interaction to the Client. Users of Fido2Client should subclass this to implement asking the user to perform specific actions, such as entering a PIN or touching their""" def prompt_up(self) -> None: """Called when the authenticator is awaiting a user presence check.""" logger.info("User Presence check required.") def request_pin( self, permissions: ClientPin.PERMISSION, rp_id: Optional[str] ) -> Optional[str]: """Called when the client requires a PIN from the user. Should return a PIN, or None/Empty to cancel.""" logger.info("PIN requested, but UserInteraction does not support it.") return None def request_uv( self, permissions: ClientPin.PERMISSION, rp_id: Optional[str] ) -> bool: """Called when the client is about to request UV from the user. Should return True if allowed, or False to cancel.""" logger.info("User Verification requested.") return True def _user_keepalive(user_interaction): def on_keepalive(status): if status == STATUS.UPNEEDED: # Waiting for touch user_interaction.prompt_up() return on_keepalive class _ClientBackend(abc.ABC): info: Info @abc.abstractmethod def selection(self, event: Optional[Event]) -> None: raise NotImplementedError() @abc.abstractmethod def do_make_credential( self, options: PublicKeyCredentialCreationOptions, client_data: CollectedClientData, rp: PublicKeyCredentialRpEntity, rp_id: str, enterprise_rpid_list: Optional[Sequence[str]], event: Event, ) -> AuthenticatorAttestationResponse: raise NotImplementedError() @abc.abstractmethod def do_get_assertion( self, options: PublicKeyCredentialRequestOptions, client_data: CollectedClientData, rp_id: str, event: Event, ) -> AssertionSelection: raise NotImplementedError() class _Ctap1ClientBackend(_ClientBackend): def __init__(self, device: CtapDevice, user_interaction: UserInteraction): self.ctap1 = Ctap1(device) self.info = Info(versions=["U2F_V2"], extensions=[], aaguid=Aaguid.NONE) self._poll_delay = 0.25 self._on_keepalive = _user_keepalive(user_interaction) def selection(self, event): _call_polling( self._poll_delay, event, None, self.ctap1.register, b"\0" * 32, b"\0" * 32, ) def do_make_credential( self, options, client_data, rp, rp_id, enterprise_rpid_list, event, ): key_params = options.pub_key_cred_params exclude_list = options.exclude_credentials selection = options.authenticator_selection or AuthenticatorSelectionCriteria() rk = selection.require_resident_key user_verification = selection.user_verification if ( rk or user_verification == UserVerificationRequirement.REQUIRED or ES256.ALGORITHM not in [p.alg for p in key_params] or options.attestation == AttestationConveyancePreference.ENTERPRISE ): raise CtapError(CtapError.ERR.UNSUPPORTED_OPTION) app_param = sha256(rp_id.encode()) dummy_param = b"\0" * 32 for cred in exclude_list or []: key_handle = cred.id try: self.ctap1.authenticate(dummy_param, app_param, key_handle, True) raise ClientError.ERR.OTHER_ERROR() # Shouldn't happen except ApduError as e: if e.code == APDU.USE_NOT_SATISFIED: _call_polling( self._poll_delay, event, self._on_keepalive, self.ctap1.register, dummy_param, dummy_param, ) raise ClientError.ERR.DEVICE_INELIGIBLE() att_obj = AttestationObject.from_ctap1( app_param, _call_polling( self._poll_delay, event, self._on_keepalive, self.ctap1.register, client_data.hash, app_param, ), ) return AuthenticatorAttestationResponse( client_data, AttestationObject.create(att_obj.fmt, att_obj.auth_data, att_obj.att_stmt), AuthenticationExtensionsClientOutputs({}), ) def do_get_assertion( self, options, client_data, rp_id, event, ): allow_list = options.allow_credentials user_verification = options.user_verification if user_verification == UserVerificationRequirement.REQUIRED or not allow_list: raise CtapError(CtapError.ERR.UNSUPPORTED_OPTION) app_param = sha256(rp_id.encode()) client_param = client_data.hash for cred in allow_list: try: auth_resp = _call_polling( self._poll_delay, event, self._on_keepalive, self.ctap1.authenticate, client_param, app_param, cred.id, ) assertions = [AssertionResponse.from_ctap1(app_param, cred, auth_resp)] return AssertionSelection(client_data, assertions) except ClientError as e: if e.code == ClientError.ERR.TIMEOUT: raise # Other errors are ignored so we move to the next. raise ClientError.ERR.DEVICE_INELIGIBLE() class _Ctap2ClientAssertionSelection(AssertionSelection): def __init__( self, client_data: CollectedClientData, assertions: Sequence[AssertionResponse], extensions: Sequence[AuthenticationExtensionProcessor], pin_token: Optional[bytes], ): super().__init__(client_data, assertions) self._extensions = extensions self._pin_token = pin_token def _get_extension_results(self, assertion): # Process extenstion outputs extension_outputs = {} try: for ext in self._extensions: output = ext.prepare_outputs(assertion, self._pin_token) if output: extension_outputs.update(output) except ValueError as e: raise ClientError.ERR.CONFIGURATION_UNSUPPORTED(e) return AuthenticationExtensionsClientOutputs(extension_outputs) @overload def _cbor_list(values: Sequence) -> list: ... @overload def _cbor_list(values: None) -> None: ... def _cbor_list(values): if not values: return None return [_as_cbor(v) for v in values] class _Ctap2ClientBackend(_ClientBackend): def __init__( self, device: CtapDevice, user_interaction: UserInteraction, extension_types: Sequence[Type[Ctap2Extension]], extensions: Sequence[Ctap2Extension], ): self.ctap2 = Ctap2(device) self.info = self.ctap2.info self._extension_types = extension_types self._extensions = extensions self.user_interaction = user_interaction def _get_extensions(self) -> Sequence[Ctap2Extension]: if self._extensions: return self._extensions return [ext(self.ctap2) for ext in self._extension_types] def _filter_creds( self, rp_id, cred_list, pin_protocol, pin_token, event, on_keepalive ): # Filter out credential IDs which are too long max_len = self.info.max_cred_id_length if max_len: cred_list = [c for c in cred_list if len(c.id) <= max_len] max_creds = self.info.max_creds_in_list or 1 chunks = [ cred_list[i : i + max_creds] for i in range(0, len(cred_list), max_creds) ] client_data_hash = b"\0" * 32 if pin_token: pin_auth = pin_protocol.authenticate(pin_token, client_data_hash) version = pin_protocol.VERSION else: pin_auth = None version = None for chunk in chunks: try: assertions = self.ctap2.get_assertions( rp_id, client_data_hash, _cbor_list(chunk), None, {"up": False}, pin_auth, version, event=event, on_keepalive=on_keepalive, ) if len(chunk) == 1: # Credential ID might be omitted from assertions return chunk[0] else: return PublicKeyCredentialDescriptor(**assertions[0].credential) except CtapError as e: if e.code == CtapError.ERR.NO_CREDENTIALS: # All creds in chunk are discarded continue raise # No matches found return None def selection(self, event): if "FIDO_2_1" in self.info.versions: self.ctap2.selection(event=event) else: # Selection not supported, make dummy credential instead try: self.ctap2.make_credential( b"\0" * 32, {"id": "example.com", "name": "example.com"}, {"id": b"dummy", "name": "dummy"}, [{"type": "public-key", "alg": -7}], pin_uv_param=b"", event=event, ) except CtapError as e: if e.code in ( CtapError.ERR.PIN_NOT_SET, CtapError.ERR.PIN_INVALID, CtapError.ERR.PIN_AUTH_INVALID, ): return raise def _should_use_uv(self, user_verification, permissions): uv_supported = any( k in self.info.options for k in ("uv", "clientPin", "bioEnroll") ) uv_configured = any( self.info.options.get(k) for k in ("uv", "clientPin", "bioEnroll") ) mc = ClientPin.PERMISSION.MAKE_CREDENTIAL & permissions != 0 additional_perms = permissions & ~( ClientPin.PERMISSION.MAKE_CREDENTIAL | ClientPin.PERMISSION.GET_ASSERTION ) if ( user_verification == UserVerificationRequirement.REQUIRED or ( user_verification == UserVerificationRequirement.PREFERRED and uv_supported ) or self.info.options.get("alwaysUv") ): if not uv_configured: raise ClientError.ERR.CONFIGURATION_UNSUPPORTED( "User verification not configured/supported" ) return True elif mc and uv_configured and not self.info.options.get("makeCredUvNotRqd"): return True elif uv_configured and additional_perms: return True return False def _get_token( self, client_pin, permissions, rp_id, event, on_keepalive, allow_internal_uv ): # Prefer UV if self.info.options.get("uv"): if ClientPin.is_token_supported(self.info): if self.user_interaction.request_uv(permissions, rp_id): return client_pin.get_uv_token( permissions, rp_id, event, on_keepalive ) elif allow_internal_uv: if self.user_interaction.request_uv(permissions, rp_id): return None # No token, use uv=True # PIN if UV not supported/allowed. if self.info.options.get("clientPin"): pin = self.user_interaction.request_pin(permissions, rp_id) if pin: return client_pin.get_pin_token(pin, permissions, rp_id) raise PinRequiredError() # Client PIN not configured. raise ClientError.ERR.CONFIGURATION_UNSUPPORTED( "User verification not configured" ) def _get_auth_params( self, pin_protocol, rp_id, user_verification, permissions, event, on_keepalive ): self.info = self.ctap2.get_info() # Make sure we have "fresh" info pin_token = None internal_uv = False if self._should_use_uv(user_verification, permissions): client_pin = ClientPin(self.ctap2, pin_protocol) allow_internal_uv = ( permissions & ~( ClientPin.PERMISSION.MAKE_CREDENTIAL | ClientPin.PERMISSION.GET_ASSERTION ) == 0 ) pin_token = self._get_token( client_pin, permissions, rp_id, event, on_keepalive, allow_internal_uv ) if not pin_token: internal_uv = True return pin_token, internal_uv def do_make_credential( self, options, client_data, rp, rp_id, enterprise_rpid_list, event, ): user = options.user key_params = options.pub_key_cred_params exclude_list = options.exclude_credentials selection = options.authenticator_selection or AuthenticatorSelectionCriteria() user_verification = selection.user_verification on_keepalive = _user_keepalive(self.user_interaction) # Handle enterprise attestation enterprise_attestation = None if options.attestation == AttestationConveyancePreference.ENTERPRISE: if self.info.options.get("ep"): if enterprise_rpid_list is not None: # Platform facilitated if rp_id in enterprise_rpid_list: enterprise_attestation = 2 else: # Vendor facilitated enterprise_attestation = 1 # Negotiate PIN/UV protocol version for proto in ClientPin.PROTOCOLS: if proto.VERSION in self.info.pin_uv_protocols: pin_protocol: Optional[PinProtocol] = proto() break else: pin_protocol = None # Gather UV permissions permissions = ClientPin.PERMISSION.MAKE_CREDENTIAL if exclude_list: # We need this for filtering the exclude_list permissions |= ClientPin.PERMISSION.GET_ASSERTION # Initialize extensions and add extension permissions used_extensions = [] for e in self._get_extensions(): ext = e.make_credential(self.ctap2, options, pin_protocol) if ext: used_extensions.append(ext) permissions |= ext.permissions def _do_make(): # Handle auth pin_token, internal_uv = self._get_auth_params( pin_protocol, rp_id, user_verification, permissions, event, on_keepalive ) if exclude_list: exclude_cred = self._filter_creds( rp_id, exclude_list, pin_protocol, pin_token, event, on_keepalive ) # We know the request will fail if exclude_cred is not None here # BUT DO NOT FAIL EARLY! We still need to prompt for UP, so we keep # processing the request else: exclude_cred = None # Process extensions extension_inputs = {} try: for ext in used_extensions: auth_input = ext.prepare_inputs(pin_token) if auth_input: extension_inputs.update(auth_input) except ValueError as e: raise ClientError.ERR.CONFIGURATION_UNSUPPORTED(e) can_rk = self.info.options.get("rk") rk = selection.resident_key == ResidentKeyRequirement.REQUIRED or ( selection.resident_key == ResidentKeyRequirement.PREFERRED and can_rk ) if not (rk or internal_uv): options = None else: options = {} if rk: if not can_rk: raise ClientError.ERR.CONFIGURATION_UNSUPPORTED( "Resident key not supported" ) options["rk"] = True if internal_uv: options["uv"] = True # Calculate pin_auth client_data_hash = client_data.hash if pin_protocol and pin_token: pin_auth: Tuple[Optional[bytes], Optional[int]] = ( pin_protocol.authenticate(pin_token, client_data_hash), pin_protocol.VERSION, ) else: pin_auth = (None, None) # Perform make credential return ( self.ctap2.make_credential( client_data_hash, _as_cbor(replace(rp, id=rp_id)), _as_cbor(user), _cbor_list(key_params), [_as_cbor(exclude_cred)] if exclude_cred else None, extension_inputs or None, options, *pin_auth, enterprise_attestation, event=event, on_keepalive=on_keepalive, ), pin_token, ) dev = self.ctap2.device reconnected = False while True: try: att_obj, pin_token = _do_make() break except CtapError as e: # The Authenticator may still require UV, try again if ( e.code == CtapError.ERR.PUAT_REQUIRED and user_verification == UserVerificationRequirement.DISCOURAGED ): user_verification = UserVerificationRequirement.REQUIRED continue # NFC may require reconnect connect = getattr(dev, "connect", None) if ( e.code == CtapError.ERR.PIN_AUTH_BLOCKED and connect and not reconnected ): dev.close() connect() reconnected = True # We only want to try this once continue raise # Process extenstion outputs extension_outputs = {} try: for ext in used_extensions: output = ext.prepare_outputs(att_obj, pin_token) if output is not None: extension_outputs.update(output) except ValueError as e: raise ClientError.ERR.CONFIGURATION_UNSUPPORTED(e) return AuthenticatorAttestationResponse( client_data, AttestationObject.create(att_obj.fmt, att_obj.auth_data, att_obj.att_stmt), AuthenticationExtensionsClientOutputs(extension_outputs), ) def do_get_assertion( self, options, client_data, rp_id, event, ): rp_id = options.rp_id allow_list = options.allow_credentials user_verification = options.user_verification on_keepalive = _user_keepalive(self.user_interaction) # Negotiate PIN/UV protocol version for proto in ClientPin.PROTOCOLS: if proto.VERSION in self.info.pin_uv_protocols: pin_protocol: Optional[PinProtocol] = proto() break else: pin_protocol = None # Gather UV permissions permissions = ClientPin.PERMISSION.GET_ASSERTION # Initialize extensions and add extension permissions used_extensions = [] for e in self._get_extensions(): ext = e.get_assertion(self.ctap2, options, pin_protocol) if ext: used_extensions.append(ext) permissions |= ext.permissions def _do_auth(): # Handle auth pin_token, internal_uv = self._get_auth_params( pin_protocol, rp_id, user_verification, permissions, event, on_keepalive ) if allow_list: selected_cred = self._filter_creds( rp_id, allow_list, pin_protocol, pin_token, event, on_keepalive ) # We know the request will fail if selected_cred is None here # BUT DO NOT FAIL EARLY! We still need to prompt for UP, so we keep # processing the request else: selected_cred = None # Process extensions extension_inputs = {} try: for ext in used_extensions: inputs = ext.prepare_inputs(selected_cred, pin_token) if inputs: extension_inputs.update(inputs) except ValueError as e: raise ClientError.ERR.CONFIGURATION_UNSUPPORTED(e) options = {"uv": True} if internal_uv else None # Calculate pin_auth client_data_hash = client_data.hash if pin_protocol and pin_token: pin_auth: Tuple[Optional[bytes], Optional[int]] = ( pin_protocol.authenticate(pin_token, client_data_hash), pin_protocol.VERSION, ) else: pin_auth = (None, None) if allow_list and not selected_cred: # We still need to send a dummy value if there was an allow_list # but no matches were found: selected_cred = PublicKeyCredentialDescriptor(allow_list[0].type, b"\0") # Perform get assertion assertions = self.ctap2.get_assertions( rp_id, client_data_hash, [_as_cbor(selected_cred)] if selected_cred else None, extension_inputs or None, options, *pin_auth, event=event, on_keepalive=on_keepalive, ) return _Ctap2ClientAssertionSelection( client_data, assertions, used_extensions, pin_token ) dev = self.ctap2.device reconnected = False while True: try: return _do_auth() except CtapError as e: # The Authenticator may still require UV, try again if ( e.code == CtapError.ERR.PUAT_REQUIRED and user_verification == UserVerificationRequirement.DISCOURAGED ): user_verification = UserVerificationRequirement.REQUIRED continue # NFC may require reconnect connect = getattr(dev, "connect", None) if ( e.code == CtapError.ERR.PIN_AUTH_BLOCKED and connect and not reconnected ): dev.close() connect() reconnected = True # We only want to try this once continue raise class Fido2Client(WebAuthnClient, _BaseClient): """WebAuthn-like client implementation. The client allows registration and authentication of WebAuthn credentials against an Authenticator using CTAP (1 or 2). :param device: CtapDevice to use. :param str origin: The origin to use. :param verify: Function to verify an RP ID for a given origin. """ def __init__( self, device: CtapDevice, origin: str, verify: Callable[[str, str], bool] = verify_rp_id, # TODO 2.0: Replace extension_types with extensions extension_types: Sequence[Type[Ctap2Extension]] = _default_extensions(), user_interaction: UserInteraction = UserInteraction(), extensions: Sequence[Ctap2Extension] = [], ): super().__init__(origin, verify) # TODO: Decide how to configure this list. self._enterprise_rpid_list: Optional[Sequence[str]] = None try: self._backend: _ClientBackend = _Ctap2ClientBackend( device, user_interaction, extension_types, extensions ) except (ValueError, CtapError): self._backend = _Ctap1ClientBackend(device, user_interaction) @property def info(self) -> Info: return self._backend.info def selection(self, event: Optional[Event] = None) -> None: try: self._backend.selection(event) except CtapError as e: raise _ctap2client_err(e) def _get_rp_id(self, rp_id: Optional[str]) -> str: if rp_id is None: url = urlparse(self.origin) if url.scheme != "https" or not url.netloc: raise ClientError.ERR.BAD_REQUEST( "RP ID required for non-https origin." ) return url.netloc else: return rp_id def make_credential( self, options: PublicKeyCredentialCreationOptions, event: Optional[Event] = None, ) -> AuthenticatorAttestationResponse: """Creates a credential. :param options: PublicKeyCredentialCreationOptions data. :param threading.Event event: (optional) Signal to abort the operation. """ options = PublicKeyCredentialCreationOptions.from_dict(options) event = event or Event() if options.timeout: timer = Timer(options.timeout / 1000, event.set) timer.daemon = True timer.start() rp = options.rp rp_id = self._get_rp_id(rp.id) logger.debug(f"Register a new credential for RP ID: {rp_id}") self._verify_rp_id(rp_id) client_data = self._build_client_data( CollectedClientData.TYPE.CREATE, options.challenge ) try: return self._backend.do_make_credential( options, client_data, rp, rp_id, self._enterprise_rpid_list, event, ) except CtapError as e: raise _ctap2client_err(e) finally: if options.timeout: timer.cancel() def get_assertion( self, options: PublicKeyCredentialRequestOptions, event: Optional[Event] = None, ) -> AssertionSelection: """Get an assertion. :param options: PublicKeyCredentialRequestOptions data. :param threading.Event event: (optional) Signal to abort the operation. """ options = PublicKeyCredentialRequestOptions.from_dict(options) event = event or Event() if options.timeout: timer = Timer(options.timeout / 1000, event.set) timer.daemon = True timer.start() rp_id = self._get_rp_id(options.rp_id) logger.debug(f"Assert a credential for RP ID: {rp_id}") self._verify_rp_id(rp_id) client_data = self._build_client_data( CollectedClientData.TYPE.GET, options.challenge ) try: return self._backend.do_get_assertion( options, client_data, rp_id, event, ) except CtapError as e: raise _ctap2client_err(e) finally: if options.timeout: timer.cancel() if platform.system().lower() == "windows": try: from .win_api import ( WinAPI, WebAuthNAuthenticatorAttachment, WebAuthNUserVerificationRequirement, WebAuthNAttestationConveyancePreference, WebAuthNEnterpriseAttestation, ) except Exception: # nosec # TODO: Make this less generic pass class WindowsClient(WebAuthnClient, _BaseClient): """Fido2Client-like class using the Windows WebAuthn API. Note: This class only works on Windows 10 19H1 or later. This is also when Windows started restricting access to FIDO devices, causing the standard client classes to require admin priveleges to run (unlike this one). The make_credential and get_assertion methods are intended to work as a drop-in replacement for the Fido2Client methods of the same name. :param str origin: The origin to use. :param verify: Function to verify an RP ID for a given origin. :param ctypes.wintypes.HWND handle: (optional) Window reference to use. """ def __init__( self, origin: str, verify: Callable[[str, str], bool] = verify_rp_id, handle=None, allow_hmac_secret=False, ): super().__init__(origin, verify) self.api = WinAPI( handle, return_extensions=True, allow_hmac_secret=allow_hmac_secret ) self.info = Info( versions=["U2F_V2", "FIDO_2_0"], extensions=[], aaguid=Aaguid.NONE ) # TODO: Decide how to configure this list. self._enterprise_rpid_list: Optional[Sequence[str]] = None @staticmethod def is_available() -> bool: return platform.system().lower() == "windows" and WinAPI.version > 0 def make_credential(self, options, event=None): """Create a credential using Windows WebAuthN APIs. :param options: PublicKeyCredentialCreationOptions data. :param threading.Event event: (optional) Signal to abort the operation. """ options = PublicKeyCredentialCreationOptions.from_dict(options) logger.debug(f"Register a new credential for RP ID: {options.rp.id}") self._verify_rp_id(options.rp.id) client_data = self._build_client_data( CollectedClientData.TYPE.CREATE, options.challenge ) selection = options.authenticator_selection or AuthenticatorSelectionCriteria() enterprise_attestation = WebAuthNEnterpriseAttestation.NONE if options.attestation == AttestationConveyancePreference.ENTERPRISE: attestation = WebAuthNAttestationConveyancePreference.ANY if self.info.options.get("ep"): if self._enterprise_rpid_list is not None: # Platform facilitated if options.rp.id in self._enterprise_rpid_list: enterprise_attestation = ( WebAuthNEnterpriseAttestation.PLATFORM_MANAGED ) else: # Vendor facilitated enterprise_attestation = ( WebAuthNEnterpriseAttestation.VENDOR_FACILITATED ) else: attestation = WebAuthNAttestationConveyancePreference.from_string( options.attestation or "none" ) try: att_obj, extensions = self.api.make_credential( options.rp, options.user, options.pub_key_cred_params, client_data, options.timeout or 0, selection.resident_key, WebAuthNAuthenticatorAttachment.from_string( selection.authenticator_attachment or "any" ), WebAuthNUserVerificationRequirement.from_string( selection.user_verification or "discouraged" ), attestation, options.exclude_credentials, options.extensions, event, enterprise_attestation, ) except OSError as e: raise ClientError.ERR.OTHER_ERROR(e) logger.info("New credential registered") return AuthenticatorAttestationResponse( client_data, att_obj, AuthenticationExtensionsClientOutputs(extensions) ) def get_assertion(self, options, event=None): """Get assertion using Windows WebAuthN APIs. :param options: PublicKeyCredentialRequestOptions data. :param threading.Event event: (optional) Signal to abort the operation. """ options = PublicKeyCredentialRequestOptions.from_dict(options) logger.debug(f"Assert a credential for RP ID: {options.rp_id}") self._verify_rp_id(options.rp_id) client_data = self._build_client_data( CollectedClientData.TYPE.GET, options.challenge ) try: (credential, auth_data, signature, user_id, extensions) = ( 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, event, ) ) except OSError as e: raise ClientError.ERR.OTHER_ERROR(e) user = {"id": user_id} if user_id else None return AssertionSelection( client_data, [ AssertionResponse( credential=credential, auth_data=auth_data, signature=signature, user=user, ) ], AuthenticationExtensionsClientOutputs(extensions), ) fido2-1.2.0/fido2/server.py0000644000175000017500000004771114721556664014776 0ustar winniewinnie# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations from .rpid import verify_rp_id from .cose import CoseKey from .utils import websafe_encode, websafe_decode from .webauthn import ( CollectedClientData, AuthenticatorData, AttestationObject, AttestedCredentialData, AttestationConveyancePreference, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity, AuthenticatorSelectionCriteria, PublicKeyCredentialDescriptor, PublicKeyCredentialType, PublicKeyCredentialParameters, PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions, UserVerificationRequirement, ResidentKeyRequirement, AuthenticatorAttachment, RegistrationResponse, AuthenticationResponse, CredentialCreationOptions, CredentialRequestOptions, ) from cryptography.hazmat.primitives import constant_time from cryptography.exceptions import InvalidSignature as _InvalidSignature from dataclasses import replace from urllib.parse import urlparse from typing import Sequence, Mapping, Optional, Callable, Union, Tuple, Any, overload import os import logging logger = logging.getLogger(__name__) VerifyAttestation = Callable[[AttestationObject, bytes], None] VerifyOrigin = Callable[[str], bool] def _verify_origin_for_rp(rp_id: str) -> VerifyOrigin: return lambda o: verify_rp_id(rp_id, o) def _validata_challenge(challenge: Optional[bytes]) -> bytes: if challenge is None: challenge = os.urandom(32) else: if not isinstance(challenge, bytes): raise TypeError("Custom challenge must be of type 'bytes'.") if len(challenge) < 16: raise ValueError("Custom challenge length must be >= 16.") return challenge def to_descriptor( credential: AttestedCredentialData, transports=None ) -> PublicKeyCredentialDescriptor: """Converts an AttestedCredentialData to a PublicKeyCredentialDescriptor. :param credential: AttestedCredentialData containing the credential ID to use. :param transports: Optional list of AuthenticatorTransport strings to add to the descriptor. :return: A descriptor of the credential, for use with register_begin or authenticate_begin. :rtype: PublicKeyCredentialDescriptor """ return PublicKeyCredentialDescriptor( PublicKeyCredentialType.PUBLIC_KEY, credential.credential_id, transports ) def _wrap_credentials( creds: Optional[ Sequence[Union[AttestedCredentialData, PublicKeyCredentialDescriptor]] ], ) -> Optional[Sequence[PublicKeyCredentialDescriptor]]: if creds is None: return None return [ ( to_descriptor(c) if isinstance(c, AttestedCredentialData) else PublicKeyCredentialDescriptor.from_dict(c) ) for c in creds ] def _ignore_attestation( attestation_object: AttestationObject, client_data_hash: bytes ) -> None: """Ignore attestation.""" class Fido2Server: """FIDO2 server. :param rp: Relying party data as `PublicKeyCredentialRpEntity` instance. :param attestation: (optional) Requirement on authenticator attestation. :param verify_origin: (optional) Alternative function to validate an origin. :param verify_attestation: (optional) function to validate attestation, which is invoked with attestation_object and client_data_hash. It should return nothing and raise an exception on failure. By default, attestation is ignored. Attestation is also ignored if `attestation` is set to `none`. """ def __init__( self, rp: PublicKeyCredentialRpEntity, attestation: Optional[AttestationConveyancePreference] = None, verify_origin: Optional[VerifyOrigin] = None, verify_attestation: Optional[VerifyAttestation] = None, ): self.rp = PublicKeyCredentialRpEntity.from_dict(rp) self._verify = verify_origin or _verify_origin_for_rp(self.rp.id) self.timeout = None self.attestation = AttestationConveyancePreference(attestation) self.allowed_algorithms = [ PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, alg) for alg in CoseKey.supported_algorithms() ] self._verify_attestation = verify_attestation or _ignore_attestation logger.debug(f"Fido2Server initialized for RP: {self.rp}") def register_begin( self, user: PublicKeyCredentialUserEntity, credentials: Optional[ Sequence[Union[AttestedCredentialData, PublicKeyCredentialDescriptor]] ] = None, resident_key_requirement: Optional[ResidentKeyRequirement] = None, user_verification: Optional[UserVerificationRequirement] = None, authenticator_attachment: Optional[AuthenticatorAttachment] = None, challenge: Optional[bytes] = None, extensions=None, ) -> Tuple[CredentialCreationOptions, Any]: """Return a PublicKeyCredentialCreationOptions registration object and the internal state dictionary that needs to be passed as is to the corresponding `register_complete` call. :param user: The dict containing the user data. :param credentials: The list of previously registered credentials, these can be of type AttestedCredentialData, or PublicKeyCredentialDescriptor. :param resident_key_requirement: The desired RESIDENT_KEY_REQUIREMENT level. :param user_verification: The desired USER_VERIFICATION level. :param authenticator_attachment: The desired AUTHENTICATOR_ATTACHMENT or None to not provide a preference (and get both types). :param challenge: A custom challenge to sign and verify or None to use OS-specific random bytes. :return: Registration data, internal state.""" if not self.allowed_algorithms: raise ValueError("Server has no allowed algorithms.") challenge = _validata_challenge(challenge) descriptors = _wrap_credentials(credentials) state = self._make_internal_state(challenge, user_verification) logger.debug( "Starting new registration, existing credentials: " + ", ".join(d.id.hex() for d in descriptors or []) ) return ( CredentialCreationOptions( PublicKeyCredentialCreationOptions( self.rp, PublicKeyCredentialUserEntity.from_dict(user), challenge, self.allowed_algorithms, self.timeout, descriptors, ( AuthenticatorSelectionCriteria( authenticator_attachment, resident_key_requirement, user_verification, ) if any( ( authenticator_attachment, resident_key_requirement, user_verification, ) ) else None ), self.attestation, extensions, ) ), state, ) @overload def register_complete( self, state, response: Union[RegistrationResponse, Mapping[str, Any]], ) -> AuthenticatorData: pass @overload def register_complete( self, state, client_data: CollectedClientData, attestation_object: AttestationObject, ) -> AuthenticatorData: pass def register_complete(self, state, *args, **kwargs): """Verify the correctness of the registration data received from the client. :param state: The state data returned by the corresponding `register_begin`. :param client_data: The client data. :param attestation_object: The attestation object. :return: The authenticator data """ response = None if len(args) == 1 and not kwargs: response = args[0] elif set(kwargs) == {"response"} and not args: response = kwargs["response"] if response: registration = RegistrationResponse.from_dict(response) client_data = registration.response.client_data attestation_object = registration.response.attestation_object else: names = ["client_data", "attestation_object"] pos = dict(zip(names, args)) data = {**kwargs, **pos} if set(kwargs) & set(pos) or set(data) != set(names): raise TypeError("incorrect arguments passed to register_complete()") client_data = data[names[0]] attestation_object = data[names[1]] if client_data.type != CollectedClientData.TYPE.CREATE: raise ValueError("Incorrect type in CollectedClientData.") if not self._verify(client_data.origin): raise ValueError("Invalid origin in CollectedClientData.") if not constant_time.bytes_eq( websafe_decode(state["challenge"]), client_data.challenge ): raise ValueError("Wrong challenge in response.") if not constant_time.bytes_eq( self.rp.id_hash, attestation_object.auth_data.rp_id_hash ): raise ValueError("Wrong RP ID hash in response.") if not attestation_object.auth_data.is_user_present(): raise ValueError("User Present flag not set.") if ( state["user_verification"] == UserVerificationRequirement.REQUIRED and not attestation_object.auth_data.is_user_verified() ): raise ValueError( "User verification required, but User Verified flag not set." ) if self.attestation not in (None, AttestationConveyancePreference.NONE): logger.debug(f"Verifying attestation of type {attestation_object.fmt}") self._verify_attestation(attestation_object, client_data.hash) # We simply ignore attestation if self.attestation == 'none', as not all # clients strip the attestation. auth_data = attestation_object.auth_data assert auth_data.credential_data is not None # nosec logger.info( "New credential registered: " + auth_data.credential_data.credential_id.hex() ) return auth_data def authenticate_begin( self, credentials: Optional[ Sequence[Union[AttestedCredentialData, PublicKeyCredentialDescriptor]] ] = None, user_verification: Optional[UserVerificationRequirement] = None, challenge: Optional[bytes] = None, extensions=None, ) -> Tuple[CredentialRequestOptions, Any]: """Return a PublicKeyCredentialRequestOptions assertion object and the internal state dictionary that needs to be passed as is to the corresponding `authenticate_complete` call. :param credentials: The list of previously registered credentials, these can be of type AttestedCredentialData, or PublicKeyCredentialDescriptor. :param user_verification: The desired USER_VERIFICATION level. :param challenge: A custom challenge to sign and verify or None to use OS-specific random bytes. :return: Assertion data, internal state.""" challenge = _validata_challenge(challenge) descriptors = _wrap_credentials(credentials) state = self._make_internal_state(challenge, user_verification) if descriptors is None: logger.debug("Starting new authentication without credentials") else: logger.debug( "Starting new authentication, for credentials: " + ", ".join(d.id.hex() for d in descriptors) ) return ( CredentialRequestOptions( PublicKeyCredentialRequestOptions( challenge, self.timeout, self.rp.id, descriptors, user_verification, extensions, ) ), state, ) @overload def authenticate_complete( self, state, credentials: Sequence[AttestedCredentialData], response: Union[AuthenticationResponse, Mapping[str, Any]], ) -> AttestedCredentialData: pass @overload def authenticate_complete( self, state, credentials: Sequence[AttestedCredentialData], credential_id: bytes, client_data: CollectedClientData, auth_data: AuthenticatorData, signature: bytes, ) -> AttestedCredentialData: pass def authenticate_complete(self, state, credentials, *args, **kwargs): """Verify the correctness of the assertion data received from the client. :param state: The state data returned by the corresponding `register_begin`. :param credentials: The list of previously registered credentials. :param credential_id: The credential id from the client response. :param client_data: The client data. :param auth_data: The authenticator data. :param signature: The signature provided by the client.""" response = None if len(args) == 1 and not kwargs: response = args[0] elif set(kwargs) == {"response"} and not args: response = kwargs["response"] if response: authentication = AuthenticationResponse.from_dict(response) credential_id = authentication.id client_data = authentication.response.client_data auth_data = authentication.response.authenticator_data signature = authentication.response.signature else: names = ["credential_id", "client_data", "auth_data", "signature"] pos = dict(zip(names, args)) data = {**kwargs, **pos} if set(kwargs) & set(pos) or set(data) != set(names): raise TypeError("incorrect arguments passed to authenticate_complete()") credential_id = data[names[0]] client_data = data[names[1]] auth_data = data[names[2]] signature = data[names[3]] if client_data.type != CollectedClientData.TYPE.GET: raise ValueError("Incorrect type in CollectedClientData.") if not self._verify(client_data.origin): raise ValueError("Invalid origin in CollectedClientData.") if websafe_decode(state["challenge"]) != client_data.challenge: raise ValueError("Wrong challenge in response.") if not constant_time.bytes_eq(self.rp.id_hash, auth_data.rp_id_hash): raise ValueError("Wrong RP ID hash in response.") if not auth_data.is_user_present(): raise ValueError("User Present flag not set.") if ( state["user_verification"] == UserVerificationRequirement.REQUIRED and not auth_data.is_user_verified() ): raise ValueError( "User verification required, but user verified flag not set." ) for cred in credentials: if cred.credential_id == credential_id: try: cred.public_key.verify(auth_data + client_data.hash, signature) except _InvalidSignature: raise ValueError("Invalid signature.") logger.info(f"Credential authenticated: {credential_id.hex()}") return cred raise ValueError("Unknown credential ID.") @staticmethod def _make_internal_state( challenge: bytes, user_verification: Optional[UserVerificationRequirement] ): return { "challenge": websafe_encode(challenge), "user_verification": user_verification, } # TODO 2.0: Delete, or move to an example def verify_app_id(app_id: str, origin: str) -> bool: """Checks if a FIDO U2F App ID is usable for a given origin. :param app_id: The App ID to validate. :param origin: The origin of the request. :return: True if the App ID is usable by the origin, False if not. .. deprecated:: 1.2.0 This will be removed in python-fido2 2.0. """ url = urlparse(app_id) hostname = url.hostname # Note that FIDO U2F requires a secure context, i.e. an origin with https scheme. # However, most browsers also treat http://localhost as a secure context. See # https://groups.google.com/a/chromium.org/g/blink-dev/c/RC9dSw-O3fE/m/E3_0XaT0BAAJ if url.scheme != "https" and (url.scheme, hostname) != ("http", "localhost"): return False if not hostname: return False return verify_rp_id(hostname, origin) # TODO 2.0: Delete, or move to an example 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. .. deprecated:: 1.2.0 This will be removed in python-fido2 2.0. """ def __init__( self, app_id: str, rp: PublicKeyCredentialRpEntity, verify_u2f_origin: Optional[VerifyOrigin] = None, *args, **kwargs, ): super().__init__(rp, *args, **kwargs) if verify_u2f_origin: kwargs["verify_origin"] = verify_u2f_origin else: kwargs["verify_origin"] = lambda o: verify_app_id(app_id, o) self._app_id = app_id self._app_id_server = Fido2Server( replace(PublicKeyCredentialRpEntity.from_dict(rp), id=app_id), *args, **kwargs, ) def register_begin(self, *args, **kwargs): kwargs.setdefault("extensions", {})["appidExclude"] = self._app_id req, state = super().register_begin(*args, **kwargs) return req, state def authenticate_begin(self, *args, **kwargs): kwargs.setdefault("extensions", {})["appid"] = self._app_id req, state = super().authenticate_begin(*args, **kwargs) return req, state def authenticate_complete(self, *args, **kwargs): try: return super().authenticate_complete(*args, **kwargs) except ValueError: return self._app_id_server.authenticate_complete(*args, **kwargs) fido2-1.2.0/fido2/py.typed0000644000175000017500000000000014721556664014571 0ustar winniewinniefido2-1.2.0/fido2/mds3.py0000644000175000017500000004272014721556664014331 0ustar winniewinnie# Copyright (c) 2022 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations from .webauthn import AttestationObject, Aaguid from .attestation import ( Attestation, UntrustedAttestation, verify_x509_chain, AttestationVerifier, ) from .utils import websafe_decode, _JsonDataObject from .cose import CoseKey from cryptography import x509 from cryptography.hazmat.backends import default_backend from dataclasses import dataclass, field from enum import Enum, unique from datetime import date from base64 import b64decode, b64encode from contextvars import ContextVar from typing import Sequence, Mapping, Any, Optional, Callable import json import logging logger = logging.getLogger(__name__) @dataclass(eq=False, frozen=True) class Version(_JsonDataObject): major: int minor: int @dataclass(eq=False, frozen=True) class RogueListEntry(_JsonDataObject): sk: bytes date: int @dataclass(eq=False, frozen=True) class BiometricStatusReport(_JsonDataObject): cert_level: int modality: str effective_date: int certification_descriptor: str certificate_number: str certification_policy_version: str certification_requirements_version: str @dataclass(eq=False, frozen=True) class CodeAccuracyDescriptor(_JsonDataObject): base: int min_length: int max_retries: Optional[int] = None block_slowdown: Optional[int] = None @dataclass(eq=False, frozen=True) class BiometricAccuracyDescriptor(_JsonDataObject): self_attested_frr: Optional[float] = field( default=None, metadata=dict(name="selfAttestedFRR") ) self_attested_far: Optional[float] = field( default=None, metadata=dict(name="selfAttestedFAR") ) max_templates: Optional[int] = None max_retries: Optional[int] = None block_slowdown: Optional[int] = None @dataclass(eq=False, frozen=True) class PatternAccuracyDescriptor(_JsonDataObject): min_complexity: int max_retries: Optional[int] = None block_slowdown: Optional[int] = None @dataclass(eq=False, frozen=True) class VerificationMethodDescriptor(_JsonDataObject): user_verification_method: Optional[str] = None ca_desc: Optional[CodeAccuracyDescriptor] = None ba_desc: Optional[BiometricAccuracyDescriptor] = None pa_desc: Optional[PatternAccuracyDescriptor] = None @dataclass(eq=False, frozen=True) class RgbPaletteEntry(_JsonDataObject): r: int g: int b: int @dataclass(eq=False, frozen=True) class DisplayPngCharacteristicsDescriptor(_JsonDataObject): width: int height: int bit_depth: int color_type: int compression: int filter: int interlace: int plte: Optional[Sequence[RgbPaletteEntry]] = None @dataclass(eq=False, frozen=True) class EcdaaTrustAnchor(_JsonDataObject): x: str = field(metadata=dict(name="X")) y: str = field(metadata=dict(name="Y")) c: str sx: str sy: str g1_curve: str = field(metadata=dict(name="G1Curve")) @unique class AuthenticatorStatus(str, Enum): """Status of an Authenitcator.""" NOT_FIDO_CERTIFIED = "NOT_FIDO_CERTIFIED" FIDO_CERTIFIED = "FIDO_CERTIFIED" USER_VERIFICATION_BYPASS = "USER_VERIFICATION_BYPASS" ATTESTATION_KEY_COMPROMISE = "ATTESTATION_KEY_COMPROMISE" USER_KEY_REMOTE_COMPROMISE = "USER_KEY_REMOTE_COMPROMISE" USER_KEY_PHYSICAL_COMPROMISE = "USER_KEY_PHYSICAL_COMPROMISE" UPDATE_AVAILABLE = "UPDATE_AVAILABLE" REVOKED = "REVOKED" SELF_ASSERTION_SUBMITTED = "SELF_ASSERTION_SUBMITTED" FIDO_CERTIFIED_L1 = "FIDO_CERTIFIED_L1" FIDO_CERTIFIED_L1plus = "FIDO_CERTIFIED_L1plus" FIDO_CERTIFIED_L2 = "FIDO_CERTIFIED_L2" FIDO_CERTIFIED_L2plus = "FIDO_CERTIFIED_L2plus" FIDO_CERTIFIED_L3 = "FIDO_CERTIFIED_L3" FIDO_CERTIFIED_L3plus = "FIDO_CERTIFIED_L3plus" @dataclass(eq=False, frozen=True) class StatusReport(_JsonDataObject): status: AuthenticatorStatus effective_date: Optional[date] = field( metadata=dict( deserialize=date.fromisoformat, serialize=lambda x: x.isoformat(), ), default=None, ) authenticator_version: Optional[int] = None certificate: Optional[bytes] = field( metadata=dict(deserialize=b64decode, serialize=lambda x: b64encode(x).decode()), default=None, ) url: Optional[str] = None certification_descriptor: Optional[str] = None certificate_number: Optional[str] = None certification_policy_version: Optional[str] = None certification_requirements_version: Optional[str] = None @dataclass(eq=False, frozen=True) class ExtensionDescriptor(_JsonDataObject): fail_if_unknown: bool = field(metadata=dict(name="fail_if_unknown")) id: str tag: Optional[int] = None data: Optional[str] = None @dataclass(eq=False, frozen=True) class MetadataStatement(_JsonDataObject): description: str authenticator_version: int schema: int upv: Sequence[Version] attestation_types: Sequence[str] user_verification_details: Sequence[Sequence[VerificationMethodDescriptor]] = field( metadata=dict(serialize=lambda xss: [[dict(x) for x in xs] for xs in xss]) ) key_protection: Sequence[str] matcher_protection: Sequence[str] attachment_hint: Sequence[str] tc_display: Sequence[str] attestation_root_certificates: Sequence[bytes] = field( metadata=dict( deserialize=lambda xs: [b64decode(x) for x in xs], serialize=lambda xs: [b64encode(x).decode() for x in xs], ) ) legal_header: Optional[str] = None aaid: Optional[str] = None aaguid: Optional[Aaguid] = field( metadata=dict( deserialize=Aaguid.parse, serialize=lambda x: str(x), ), default=None, ) attestation_certificate_key_identifiers: Optional[Sequence[bytes]] = field( metadata=dict( deserialize=lambda xs: [bytes.fromhex(x) for x in xs], serialize=lambda xs: [x.hex() for x in xs], ), default=None, ) alternative_descriptions: Optional[Mapping[str, str]] = None protocol_family: Optional[str] = None authentication_algorithms: Optional[Sequence[str]] = None public_key_alg_and_encodings: Optional[Sequence[str]] = None is_key_restricted: Optional[bool] = None is_fresh_user_verification_required: Optional[bool] = None crypto_strength: Optional[int] = None operating_env: Optional[str] = None tc_display_content_type: Optional[str] = None tc_display_png_characteristics: Optional[ Sequence[DisplayPngCharacteristicsDescriptor] ] = field( metadata=dict(name="tcDisplayPNGCharacteristics"), default=None, ) ecdaa_trust_anchors: Optional[Sequence[EcdaaTrustAnchor]] = None icon: Optional[str] = None supported_extensions: Optional[Sequence[ExtensionDescriptor]] = None authenticator_get_info: Optional[Mapping[str, Any]] = None @dataclass(eq=False, frozen=True) class MetadataBlobPayloadEntry(_JsonDataObject): status_reports: Sequence[StatusReport] time_of_last_status_change: date = field( metadata=dict( deserialize=date.fromisoformat, serialize=lambda x: x.isoformat(), ) ) aaid: Optional[str] = None aaguid: Optional[Aaguid] = field( metadata=dict( deserialize=Aaguid.parse, serialize=lambda x: str(x), ), default=None, ) attestation_certificate_key_identifiers: Optional[Sequence[bytes]] = field( metadata=dict( deserialize=lambda xs: [bytes.fromhex(x) for x in xs], serialize=lambda xs: [x.hex() for x in xs], ), default=None, ) metadata_statement: Optional[MetadataStatement] = None biometric_status_reports: Optional[Sequence[BiometricStatusReport]] = None rogue_list_url: Optional[str] = field( metadata=dict(name="rogueListURL"), default=None ) rogue_list_hash: Optional[bytes] = field( metadata=dict( deserialize=bytes.fromhex, serialize=lambda x: x.hex(), ), default=None, ) @dataclass(eq=False, frozen=True) class MetadataBlobPayload(_JsonDataObject): legal_header: str no: int next_update: date = field( metadata=dict( deserialize=date.fromisoformat, serialize=lambda x: x.isoformat(), ) ) entries: Sequence[MetadataBlobPayloadEntry] EntryFilter = Callable[[MetadataBlobPayloadEntry], bool] LookupFilter = Callable[[MetadataBlobPayloadEntry, Sequence[bytes]], bool] def filter_revoked(entry: MetadataBlobPayloadEntry) -> bool: """Filters out any revoked metadata entry. This filter will remove any metadata entry which has a status_report with the REVOKED status. """ return not any( r.status == AuthenticatorStatus.REVOKED for r in entry.status_reports ) def filter_attestation_key_compromised( entry: MetadataBlobPayloadEntry, certificate_chain: Sequence[bytes] ) -> bool: """Denies any attestation that has a compromised attestation key. This filter checks the status reports of a metadata entry and ensures the attestation isn't signed by a key which is marked as compromised. """ for r in entry.status_reports: if r.status == AuthenticatorStatus.ATTESTATION_KEY_COMPROMISE: if r.certificate in certificate_chain: return False return True _last_entry: ContextVar[Optional[MetadataBlobPayloadEntry]] = ContextVar("_last_entry") class MdsAttestationVerifier(AttestationVerifier): """MDS3 implementation of an AttestationVerifier. The entry_filter is an optional predicate used to filter which metadata entries to include in the lookup for verification. By default, a filter that removes any entries that have a status report indicating the authenticator is REVOKED is used. See: filter_revoked The attestation_filter is an optional predicate used to filter metadata entries while performing attestation validation, and may take into account the Authenticators attestation trust_chain. By default, a filter that will fail any verification that has a trust_chain where one of the certificates is marked as compromised by the metadata statement is used. See: filter_attestation_key_compromised NOTE: The attestation_filter is not used when calling find_entry_by_aaguid nor find_entry_by_chain as no attestation is being verified! Setting either filter (including setting it to None) will replace it, removing the default behavior. :param blob: The MetadataBlobPayload to query for device metadata. :param entry_filter: An optional filter to exclude entries from lookup. :param attestation_filter: An optional filter to fail verification for a given attestation. :param attestation_types: A list of Attestation types to support. """ def __init__( self, blob: MetadataBlobPayload, entry_filter: Optional[EntryFilter] = filter_revoked, attestation_filter: Optional[LookupFilter] = filter_attestation_key_compromised, attestation_types: Optional[Sequence[Attestation]] = None, ): super().__init__(attestation_types) self._attestation_filter = attestation_filter or ( lambda a, b: True ) # No-op for None entries = ( [e for e in blob.entries if entry_filter(e)] if entry_filter else blob.entries ) self._aaguid_table = {e.aaguid: e for e in entries if e.aaguid} self._ski_table = { ski: e for e in entries for ski in e.attestation_certificate_key_identifiers or [] } def find_entry_by_aaguid( self, aaguid: Aaguid ) -> Optional[MetadataBlobPayloadEntry]: """Find an entry by AAGUID. Returns a MetadataBlobPayloadEntry with a matching aaguid field, if found. This method does not take the attestation_filter into account. """ return self._aaguid_table.get(aaguid) def find_entry_by_chain( self, certificate_chain: Sequence[bytes] ) -> Optional[MetadataBlobPayloadEntry]: """Find an entry by trust chain. Returns a MetadataBlobPayloadEntry containing an attestationCertificateKeyIdentifier which matches one of the certificates in the given chain, if found. This method does not take the attestation_filter into account. """ for der in certificate_chain: cert = x509.load_der_x509_certificate(der, default_backend()) ski = x509.SubjectKeyIdentifier.from_public_key(cert.public_key()).digest if ski in self._ski_table: return self._ski_table[ski] return None def ca_lookup(self, attestation_result, auth_data): assert auth_data.credential_data is not None # nosec aaguid = auth_data.credential_data.aaguid if aaguid: logging.debug(f"Using AAGUID: {aaguid} to look up metadata") entry = self.find_entry_by_aaguid(aaguid) else: logging.debug("Using trust_path chain to look up metadata") entry = self.find_entry_by_chain(attestation_result.trust_path) if entry: logging.debug(f"Found entry: {entry}") # Check attestation filter if not self._attestation_filter(entry, attestation_result.trust_path): logging.debug("Matched entry did not pass attestation filter") return None # Figure out which root to use if not entry.metadata_statement: logging.warning( "Matched entry has no metadata_statement, can't validate!" ) return None issuer = x509.load_der_x509_certificate( attestation_result.trust_path[-1], default_backend() ).issuer for root in entry.metadata_statement.attestation_root_certificates: subject = x509.load_der_x509_certificate( root, default_backend() ).subject if subject == issuer: _last_entry.set(entry) return root logger.info(f"No attestation root matching subject: {issuer}") return None def find_entry( self, attestation_object: AttestationObject, client_data_hash: bytes ) -> Optional[MetadataBlobPayloadEntry]: """Lookup a Metadata entry based on an Attestation. Returns the first Metadata entry matching the given attestation and verifies it, including checking it against the attestation_filter. """ token = _last_entry.set(None) try: self.verify_attestation(attestation_object, client_data_hash) return _last_entry.get() except UntrustedAttestation: return None finally: _last_entry.reset(token) def parse_blob(blob: bytes, trust_root: Optional[bytes]) -> MetadataBlobPayload: """Parse a FIDO MDS3 blob and verifies its signature. See https://fidoalliance.org/metadata/ for details on obtaining the blob, as well as the CA certificate used to sign it. The resulting MetadataBlobPayload can be used to lookup metadata entries for specific Authenticators, or used with the MdsAttestationVerifier to verify that the attestation from a WebAuthn registration is valid and included in the metadata blob. NOTE: If trust_root is None, the signature of the blob will NOT be verified! """ message, signature_b64 = blob.rsplit(b".", 1) signature = websafe_decode(signature_b64) header, payload = (json.loads(websafe_decode(x)) for x in message.split(b".")) if trust_root is not None: # Verify trust chain chain = [b64decode(c) for c in header.get("x5c", [])] chain += [trust_root] verify_x509_chain(chain) # Verify blob signature using leaf leaf = x509.load_der_x509_certificate(chain[0], default_backend()) public_key = CoseKey.for_name(header["alg"]).from_cryptography_key( leaf.public_key() ) public_key.verify(message, signature) else: logger.warn("Parsing MDS blob without trust anchor, CONTENT IS NOT VERIFIED!") return MetadataBlobPayload.from_dict(payload) fido2-1.2.0/fido2/attestation/0000775000175000017500000000000014741676716015450 5ustar winniewinniefido2-1.2.0/fido2/attestation/apple.py0000644000175000017500000000453714721556664017127 0ustar winniewinnie# Copyright (c) 2020 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations from .base import ( Attestation, AttestationType, AttestationResult, InvalidData, catch_builtins, ) from ..utils import sha256 from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.constant_time import bytes_eq OID_APPLE = x509.ObjectIdentifier("1.2.840.113635.100.8.2") class AppleAttestation(Attestation): FORMAT = "apple" @catch_builtins def verify(self, statement, auth_data, client_data_hash): x5c = statement["x5c"] expected_nonce = sha256(auth_data + client_data_hash) cert = x509.load_der_x509_certificate(x5c[0], default_backend()) ext = cert.extensions.get_extension_for_oid(OID_APPLE) ext_nonce = ext.value.value[6:] # Sequence of single element of octet string if not bytes_eq(expected_nonce, ext_nonce): raise InvalidData("Nonce does not match!") return AttestationResult(AttestationType.ANON_CA, x5c) fido2-1.2.0/fido2/attestation/android.py0000644000175000017500000000602514721556664017440 0ustar winniewinnie# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations from .base import ( Attestation, AttestationType, AttestationResult, InvalidData, catch_builtins, ) from ..cose import CoseKey from ..utils import sha256, websafe_decode from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.constant_time import bytes_eq import json class AndroidSafetynetAttestation(Attestation): FORMAT = "android-safetynet" def __init__(self, allow_rooted: bool = False): self.allow_rooted = allow_rooted @catch_builtins def verify(self, statement, auth_data, client_data_hash): jwt = statement["response"] header, payload, sig = (websafe_decode(x) for x in jwt.split(b".")) data = json.loads(payload.decode("utf8")) if not self.allow_rooted and data["ctsProfileMatch"] is not True: raise InvalidData("ctsProfileMatch must be true!") expected_nonce = sha256(auth_data + client_data_hash) if not bytes_eq(expected_nonce, websafe_decode(data["nonce"])): raise InvalidData("Nonce does not match!") data = json.loads(header.decode("utf8")) x5c = [websafe_decode(x) for x in data["x5c"]] cert = x509.load_der_x509_certificate(x5c[0], default_backend()) cn = cert.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME) if cn[0].value != "attest.android.com": raise InvalidData("Certificate not issued to attest.android.com!") CoseKey.for_name(data["alg"]).from_cryptography_key(cert.public_key()).verify( jwt.rsplit(b".", 1)[0], sig ) return AttestationResult(AttestationType.BASIC, x5c) fido2-1.2.0/fido2/attestation/tpm.py0000644000175000017500000004605314721556664016625 0ustar winniewinnie# -*- coding: utf-8 -*- # Copyright (c) 2019 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations from .base import ( Attestation, AttestationType, AttestationResult, InvalidData, InvalidSignature, catch_builtins, _validate_cert_common, ) from ..cose import CoseKey from ..utils import bytes2int, ByteBuffer from enum import IntEnum, unique from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import rsa, ec from cryptography.hazmat.primitives import hashes from cryptography import x509 from cryptography.exceptions import InvalidSignature as _InvalidSignature from dataclasses import dataclass from typing import Tuple, Union, cast import struct TPM_ALG_NULL = 0x0010 OID_AIK_CERTIFICATE = x509.ObjectIdentifier("2.23.133.8.3") @unique class TpmRsaScheme(IntEnum): RSASSA = 0x0014 RSAPSS = 0x0016 OAEP = 0x0017 RSAES = 0x0015 @unique class TpmAlgAsym(IntEnum): RSA = 0x0001 ECC = 0x0023 @unique class TpmAlgHash(IntEnum): SHA1 = 0x0004 SHA256 = 0x000B SHA384 = 0x000C SHA512 = 0x000D def _hash_alg(self) -> hashes.HashAlgorithm: if self == TpmAlgHash.SHA1: return hashes.SHA1() # nosec elif self == TpmAlgHash.SHA256: return hashes.SHA256() elif self == TpmAlgHash.SHA384: return hashes.SHA384() elif self == TpmAlgHash.SHA512: return hashes.SHA512() raise NotImplementedError(f"_hash_alg is not implemented for {self!r}") @dataclass class TpmsCertifyInfo: name: bytes qualified_name: bytes TPM_GENERATED_VALUE = b"\xffTCG" TPM_ST_ATTEST_CERTIFY = b"\x80\x17" @dataclass class TpmAttestationFormat: """the signature data is defined by [TPMv2-Part2] Section 10.12.8 (TPMS_ATTEST) as: TPM_GENERATED_VALUE (0xff544347 aka "\xffTCG") TPMI_ST_ATTEST - always TPM_ST_ATTEST_CERTIFY (0x8017) because signing procedure defines it should call TPM_Certify [TPMv2-Part3] Section 18.2 TPM2B_NAME size (uint16) name (size long) TPM2B_DATA size (uint16) name (size long) TPMS_CLOCK_INFO clock (uint64) resetCount (uint32) restartCount (uint32) safe (byte) 1 yes, 0 no firmwareVersion uint64 attested TPMS_CERTIFY_INFO (because TPM_ST_ATTEST_CERTIFY) name TPM2B_NAME qualified_name TPM2B_NAME See: https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-3-Commands-01.38.pdf """ name: bytes data: bytes clock_info: Tuple[int, int, int, bool] firmware_version: int attested: TpmsCertifyInfo @classmethod def parse(cls, data: bytes) -> TpmAttestationFormat: reader = ByteBuffer(data) generated_value = reader.read(4) # Verify that magic is set to TPM_GENERATED_VALUE. # see https://w3c.github.io/webauthn/#sctn-tpm-attestation # verification procedure if generated_value != TPM_GENERATED_VALUE: raise ValueError("generated value field is invalid") # Verify that type is set to TPM_ST_ATTEST_CERTIFY. # see https://w3c.github.io/webauthn/#sctn-tpm-attestation # verification procedure tpmi_st_attest = reader.read(2) if tpmi_st_attest != TPM_ST_ATTEST_CERTIFY: raise ValueError("tpmi_st_attest field is invalid") try: name = reader.read(reader.unpack("!H")) data = reader.read(reader.unpack("!H")) clock = reader.unpack("!Q") reset_count = reader.unpack("!L") restart_count = reader.unpack("!L") safe_value = reader.unpack("B") if safe_value not in (0, 1): raise ValueError(f"invalid value 0x{safe_value:x} for boolean") safe = safe_value == 1 firmware_version = reader.unpack("!Q") attested_name = reader.read(reader.unpack("!H")) attested_qualified_name = reader.read(reader.unpack("!H")) except struct.error as e: raise ValueError(e) return cls( name=name, data=data, clock_info=(clock, reset_count, restart_count, safe), firmware_version=firmware_version, attested=TpmsCertifyInfo( name=attested_name, qualified_name=attested_qualified_name ), ) @dataclass class TpmsRsaParms: """Parse TPMS_RSA_PARMS struct See: https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf section 12.2.3.5 """ symmetric: int scheme: int key_bits: int exponent: int @classmethod def parse(cls, reader, attributes): symmetric = reader.unpack("!H") restricted_decryption = attributes & ( ATTRIBUTES.RESTRICTED | ATTRIBUTES.DECRYPT ) is_restricted_decryption_key = restricted_decryption == ( ATTRIBUTES.DECRYPT | ATTRIBUTES.RESTRICTED ) if not is_restricted_decryption_key and symmetric != TPM_ALG_NULL: # if the key is not a restricted decryption key, this field # shall be set to TPM_ALG_NULL. raise ValueError("symmetric is expected to be NULL") # Otherwise should be set to a supported symmetric algorithm, keysize and mode # TODO(baloo): Should we have non-null value here, do we expect more data? scheme = reader.unpack("!H") restricted_sign = attributes & (ATTRIBUTES.RESTRICTED | ATTRIBUTES.SIGN_ENCRYPT) is_unrestricted_signing_key = restricted_sign == ATTRIBUTES.SIGN_ENCRYPT if is_unrestricted_signing_key and scheme not in ( TPM_ALG_NULL, TpmRsaScheme.RSASSA, TpmRsaScheme.RSAPSS, ): raise ValueError( "key is an unrestricted signing key, scheme is " "expected to be TPM_ALG_RSAPSS, TPM_ALG_RSASSA, " "or TPM_ALG_NULL" ) is_restricted_signing_key = restricted_sign == ( ATTRIBUTES.RESTRICTED | ATTRIBUTES.SIGN_ENCRYPT ) if is_restricted_signing_key and scheme not in ( TpmRsaScheme.RSASSA, TpmRsaScheme.RSAPSS, ): raise ValueError( "key is a restricted signing key, scheme is " "expected to be TPM_ALG_RSAPSS, or TPM_ALG_RSASSA" ) is_unrestricted_decryption_key = restricted_decryption == ATTRIBUTES.DECRYPT if is_unrestricted_decryption_key and scheme not in ( TpmRsaScheme.OAEP, TpmRsaScheme.RSAES, TPM_ALG_NULL, ): raise ValueError( "key is an unrestricted decryption key, scheme is " "expected to be TPM_ALG_RSAES, TPM_ALG_OAEP, or " "TPM_ALG_NULL" ) if is_restricted_decryption_key and scheme not in (TPM_ALG_NULL,): raise ValueError( "key is an restricted decryption key, scheme is " "expected to be TPM_ALG_NULL" ) key_bits = reader.unpack("!H") exponent = reader.unpack("!L") if exponent == 0: # When zero, indicates that the exponent is the default of 2^16 + 1 exponent = (2**16) + 1 return cls(symmetric, scheme, key_bits, exponent) class Tpm2bPublicKeyRsa(bytes): @classmethod def parse(cls, reader: ByteBuffer) -> Tpm2bPublicKeyRsa: return cls(reader.read(reader.unpack("!H"))) @unique class TpmEccCurve(IntEnum): """TPM_ECC_CURVE https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf section 6.4 """ NONE = 0x0000 NIST_P192 = 0x0001 NIST_P224 = 0x0002 NIST_P256 = 0x0003 NIST_P384 = 0x0004 NIST_P521 = 0x0005 BN_P256 = 0x0010 BN_P638 = 0x0011 SM2_P256 = 0x0020 def to_curve(self) -> ec.EllipticCurve: if self == TpmEccCurve.NONE: raise ValueError("No such curve") elif self == TpmEccCurve.NIST_P192: return ec.SECP192R1() elif self == TpmEccCurve.NIST_P224: return ec.SECP224R1() elif self == TpmEccCurve.NIST_P256: return ec.SECP256R1() elif self == TpmEccCurve.NIST_P384: return ec.SECP384R1() elif self == TpmEccCurve.NIST_P521: return ec.SECP521R1() raise ValueError("curve is not supported", self) @unique class TpmiAlgKdf(IntEnum): """TPMI_ALG_KDF https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf section 9.28 """ NULL = TPM_ALG_NULL KDF1_SP800_56A = 0x0020 KDF2 = 0x0021 KDF1_SP800_108 = 0x0022 @dataclass class TpmsEccParms: symmetric: int scheme: int curve_id: TpmEccCurve kdf: TpmiAlgKdf @classmethod def parse(cls, reader: ByteBuffer) -> TpmsEccParms: symmetric = reader.unpack("!H") scheme = reader.unpack("!H") if symmetric != TPM_ALG_NULL: raise ValueError("symmetric is expected to be NULL") if scheme != TPM_ALG_NULL: raise ValueError("scheme is expected to be NULL") curve_id = TpmEccCurve(reader.unpack("!H")) kdf_scheme = TpmiAlgKdf(reader.unpack("!H")) return cls(symmetric, scheme, curve_id, kdf_scheme) @dataclass class TpmsEccPoint: """TPMS_ECC_POINT https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf Section 11.2.5.2 """ x: bytes y: bytes @classmethod def parse(cls, reader: ByteBuffer) -> TpmsEccPoint: x = reader.read(reader.unpack("!H")) y = reader.read(reader.unpack("!H")) return cls(x, y) @unique class ATTRIBUTES(IntEnum): """Object attributes see section 8.3 https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf """ FIXED_TPM = 1 << 1 ST_CLEAR = 1 << 2 FIXED_PARENT = 1 << 4 SENSITIVE_DATA_ORIGIN = 1 << 5 USER_WITH_AUTH = 1 << 6 ADMIN_WITH_POLICY = 1 << 7 NO_DA = 1 << 10 ENCRYPTED_DUPLICATION = 1 << 11 RESTRICTED = 1 << 16 DECRYPT = 1 << 17 SIGN_ENCRYPT = 1 << 18 SHALL_BE_ZERO = ( (1 << 0) # 0 Reserved | (1 << 3) # 3 Reserved | (0x3 << 8) # 9:8 Reserved | (0xF << 12) # 15:12 Reserved | ((0xFFFFFFFF << 19) & (2**32 - 1)) # 31:19 Reserved ) _PublicKey = Union[rsa.RSAPublicKey, ec.EllipticCurvePublicKey] _Parameters = Union[TpmsRsaParms, TpmsEccParms] _Unique = Union[Tpm2bPublicKeyRsa, TpmsEccPoint] @dataclass class TpmPublicFormat: """the public area structure is defined by [TPMv2-Part2] Section 12.2.4 (TPMT_PUBLIC) as: TPMI_ALG_PUBLIC - type TPMI_ALG_HASH - nameAlg or + to indicate TPM_ALG_NULL TPMA_OBJECT - objectAttributes TPM2B_DIGEST - authPolicy TPMU_PUBLIC_PARMS - type parameters TPMU_PUBLIC_ID - uniq See: https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf """ sign_alg: TpmAlgAsym name_alg: TpmAlgHash attributes: int auth_policy: bytes parameters: _Parameters unique: _Unique data: bytes @classmethod def parse(cls, data: bytes) -> TpmPublicFormat: reader = ByteBuffer(data) sign_alg = TpmAlgAsym(reader.unpack("!H")) name_alg = TpmAlgHash(reader.unpack("!H")) attributes = reader.unpack("!L") if attributes & ATTRIBUTES.SHALL_BE_ZERO != 0: raise ValueError(f"attributes is not formated correctly: 0x{attributes:x}") auth_policy = reader.read(reader.unpack("!H")) if sign_alg == TpmAlgAsym.RSA: parameters: _Parameters = TpmsRsaParms.parse(reader, attributes) unique: _Unique = Tpm2bPublicKeyRsa.parse(reader) elif sign_alg == TpmAlgAsym.ECC: parameters = TpmsEccParms.parse(reader) unique = TpmsEccPoint.parse(reader) else: raise NotImplementedError(f"sign alg {sign_alg:x} is not supported") rest = reader.read() if len(rest) != 0: raise ValueError("there should not be any data left in buffer") return cls( sign_alg, name_alg, attributes, auth_policy, parameters, unique, data ) def public_key(self) -> _PublicKey: if self.sign_alg == TpmAlgAsym.RSA: exponent = cast(TpmsRsaParms, self.parameters).exponent modulus = bytes2int(cast(Tpm2bPublicKeyRsa, self.unique)) return rsa.RSAPublicNumbers(exponent, modulus).public_key(default_backend()) elif self.sign_alg == TpmAlgAsym.ECC: unique = cast(TpmsEccPoint, self.unique) return ec.EllipticCurvePublicNumbers( bytes2int(unique.x), bytes2int(unique.y), cast(TpmsEccParms, self.parameters).curve_id.to_curve(), ).public_key(default_backend()) raise NotImplementedError(f"public_key not implemented for {self.sign_alg!r}") def name(self) -> bytes: """ Computing Entity Names see: https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-1-Architecture-01.38.pdf section 16 Names Name ≔ nameAlg || HnameAlg (handle→nvPublicArea) where nameAlg algorithm used to compute Name HnameAlg hash using the nameAlg parameter in the NV Index location associated with handle nvPublicArea contents of the TPMS_NV_PUBLIC associated with handle """ output = struct.pack("!H", self.name_alg) digest = hashes.Hash(self.name_alg._hash_alg(), backend=default_backend()) digest.update(self.data) output += digest.finalize() return output def _validate_tpm_cert(cert): # https://www.w3.org/TR/webauthn/#tpm-cert-requirements _validate_cert_common(cert) s = cert.subject.get_attributes_for_oid(x509.NameOID) if s: raise InvalidData("Certificate should not have Subject") s = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName) if not s: raise InvalidData("Certificate should have SubjectAlternativeName") ext = cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage) has_aik = [x == OID_AIK_CERTIFICATE for x in ext.value] if True not in has_aik: raise InvalidData( 'Extended key usage MUST contain the "joint-iso-itu-t(2) ' "internationalorganizations(23) 133 tcg-kp(8) " 'tcg-kp-AIKCertificate(3)" OID.' ) class TpmAttestation(Attestation): FORMAT = "tpm" @catch_builtins def verify(self, statement, auth_data, client_data_hash): if "ecdaaKeyId" in statement: raise NotImplementedError("ECDAA not implemented") alg = statement["alg"] x5c = statement["x5c"] cert_info = statement["certInfo"] cert = x509.load_der_x509_certificate(x5c[0], default_backend()) _validate_tpm_cert(cert) pub_key = CoseKey.for_alg(alg).from_cryptography_key(cert.public_key()) try: pub_area = TpmPublicFormat.parse(statement["pubArea"]) except Exception as e: raise InvalidData("unable to parse pubArea", e) # Verify that the public key specified by the parameters and unique # fields of pubArea is identical to the credentialPublicKey in the # attestedCredentialData in authenticatorData. if ( auth_data.credential_data.public_key.from_cryptography_key( pub_area.public_key() ) != auth_data.credential_data.public_key ): raise InvalidSignature( "attestation pubArea does not match attestedCredentialData" ) try: # TpmAttestationFormat.parse is reponsible for: # Verify that magic is set to TPM_GENERATED_VALUE. # Verify that type is set to TPM_ST_ATTEST_CERTIFY. tpm = TpmAttestationFormat.parse(cert_info) # Verify that extraData is set to the hash of attToBeSigned # using the hash algorithm employed in "alg". att_to_be_signed = auth_data + client_data_hash hash_alg = pub_key._HASH_ALG # type: ignore digest = hashes.Hash(hash_alg, backend=default_backend()) digest.update(att_to_be_signed) data = digest.finalize() if tpm.data != data: raise InvalidSignature( "attestation does not sign for authData and ClientData" ) # Verify that attested contains a TPMS_CERTIFY_INFO structure as # specified in [TPMv2-Part2] section 10.12.3, whose name field # contains a valid Name for pubArea, as computed using the # algorithm in the nameAlg field of pubArea using the procedure # specified in [TPMv2-Part1] section 16. # [TPMv2-Part2]: # https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-2-Structures-01.38.pdf # [TPMv2-Part1]: # https://www.trustedcomputinggroup.org/wp-content/uploads/TPM-Rev-2.0-Part-1-Architecture-01.38.pdf if tpm.attested.name != pub_area.name(): raise InvalidData( "TPMS_CERTIFY_INFO does not include a valid name for pubArea" ) pub_key.verify(cert_info, statement["sig"]) return AttestationResult(AttestationType.ATT_CA, x5c) except _InvalidSignature: raise InvalidSignature("signature of certInfo does not match") fido2-1.2.0/fido2/attestation/u2f.py0000644000175000017500000000523614721556664016517 0ustar winniewinnie# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations from .base import ( Attestation, AttestationType, AttestationResult, InvalidSignature, catch_builtins, ) from ..cose import ES256 from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.exceptions import InvalidSignature as _InvalidSignature class FidoU2FAttestation(Attestation): FORMAT = "fido-u2f" @catch_builtins def verify(self, statement, auth_data, client_data_hash): cd = auth_data.credential_data pk = b"\x04" + cd.public_key[-2] + cd.public_key[-3] x5c = statement["x5c"] FidoU2FAttestation.verify_signature( auth_data.rp_id_hash, client_data_hash, cd.credential_id, pk, x5c[0], statement["sig"], ) return AttestationResult(AttestationType.BASIC, x5c) @staticmethod def verify_signature( app_param, client_param, key_handle, public_key, cert_bytes, signature ): m = b"\0" + app_param + client_param + key_handle + public_key cert = x509.load_der_x509_certificate(cert_bytes, default_backend()) try: ES256.from_cryptography_key(cert.public_key()).verify(m, signature) except _InvalidSignature: raise InvalidSignature() fido2-1.2.0/fido2/attestation/packed.py0000644000175000017500000001026314721556664017246 0ustar winniewinnie# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations from .base import ( Attestation, AttestationType, AttestationResult, InvalidData, InvalidSignature, catch_builtins, _validate_cert_common, ) from ..cose import CoseKey from cryptography import x509 from cryptography.exceptions import InvalidSignature as _InvalidSignature from cryptography.hazmat.backends import default_backend OID_AAGUID = x509.ObjectIdentifier("1.3.6.1.4.1.45724.1.1.4") def _validate_packed_cert(cert, aaguid): # https://www.w3.org/TR/webauthn/#packed-attestation-cert-requirements _validate_cert_common(cert) c = cert.subject.get_attributes_for_oid(x509.NameOID.COUNTRY_NAME) if not c: raise InvalidData("Subject must have C set!") o = cert.subject.get_attributes_for_oid(x509.NameOID.ORGANIZATION_NAME) if not o: raise InvalidData("Subject must have O set!") ous = cert.subject.get_attributes_for_oid(x509.NameOID.ORGANIZATIONAL_UNIT_NAME) if not ous: raise InvalidData('Subject must have OU = "Authenticator Attestation"!') ou = ous[0] if ou.value != "Authenticator Attestation": raise InvalidData('Subject must have OU = "Authenticator Attestation"!') cn = cert.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME) if not cn: raise InvalidData("Subject must have CN set!") try: ext = cert.extensions.get_extension_for_oid(OID_AAGUID) if ext.critical: raise InvalidData("AAGUID extension must not be marked as critical") ext_aaguid = ext.value.value[2:] if ext_aaguid != aaguid: raise InvalidData( "AAGUID in Authenticator data does not " "match attestation certificate!" ) except x509.ExtensionNotFound: pass # If missing, ignore class PackedAttestation(Attestation): FORMAT = "packed" @catch_builtins def verify(self, statement, auth_data, client_data_hash): if "ecdaaKeyId" in statement: raise NotImplementedError("ECDAA not implemented") alg = statement["alg"] x5c = statement.get("x5c") if x5c: cert = x509.load_der_x509_certificate(x5c[0], default_backend()) _validate_packed_cert(cert, auth_data.credential_data.aaguid) pub_key = CoseKey.for_alg(alg).from_cryptography_key(cert.public_key()) att_type = AttestationType.BASIC else: pub_key = CoseKey.parse(auth_data.credential_data.public_key) if pub_key.ALGORITHM != alg: raise InvalidData("Wrong algorithm of public key!") att_type = AttestationType.SELF try: pub_key.verify(auth_data + client_data_hash, statement["sig"]) return AttestationResult(att_type, x5c or []) except _InvalidSignature: raise InvalidSignature() fido2-1.2.0/fido2/attestation/base.py0000644000175000017500000002111714721556664016731 0ustar winniewinnie# Copyright (c) 2018 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from __future__ import annotations from ..webauthn import AuthenticatorData, AttestationObject from enum import IntEnum, unique from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import padding, ec, rsa from cryptography.exceptions import InvalidSignature as _InvalidSignature from dataclasses import dataclass from functools import wraps from typing import List, Type, Mapping, Sequence, Optional, Any import abc class InvalidAttestation(Exception): """Base exception for attestation-related errors.""" class InvalidData(InvalidAttestation): """Attestation contains invalid data.""" class InvalidSignature(InvalidAttestation): """The signature of the attestation could not be verified.""" class UntrustedAttestation(InvalidAttestation): """The CA of the attestation is not trusted.""" class UnsupportedType(InvalidAttestation): """The attestation format is not supported.""" def __init__(self, auth_data, fmt=None): super().__init__( f'Attestation format "{fmt}" is not supported' if fmt else "This attestation format is not supported!" ) self.auth_data = auth_data self.fmt = fmt @unique class AttestationType(IntEnum): """Supported attestation types.""" BASIC = 1 SELF = 2 ATT_CA = 3 ANON_CA = 4 NONE = 0 @dataclass class AttestationResult: """The result of verifying an attestation.""" attestation_type: AttestationType trust_path: List[bytes] def catch_builtins(f): """Utility decoractor to wrap common exceptions related to InvalidData.""" @wraps(f) def inner(*args, **kwargs): try: return f(*args, **kwargs) except (ValueError, KeyError, IndexError) as e: raise InvalidData(e) return inner @catch_builtins def verify_x509_chain(chain: List[bytes]) -> None: """Verifies a chain of certificates. Checks that the first item in the chain is signed by the next, and so on. The first item is the leaf, the last is the root. """ certs = [x509.load_der_x509_certificate(der, default_backend()) for der in chain] cert = certs.pop(0) while certs: child = cert cert = certs.pop(0) pub = cert.public_key() try: if isinstance(pub, rsa.RSAPublicKey): assert child.signature_hash_algorithm is not None # nosec pub.verify( child.signature, child.tbs_certificate_bytes, padding.PKCS1v15(), child.signature_hash_algorithm, ) elif isinstance(pub, ec.EllipticCurvePublicKey): assert child.signature_hash_algorithm is not None # nosec pub.verify( child.signature, child.tbs_certificate_bytes, ec.ECDSA(child.signature_hash_algorithm), ) else: raise ValueError("Unsupported signature key type") except _InvalidSignature: raise InvalidSignature() class Attestation(abc.ABC): """Implements verification of a specific attestation type.""" @abc.abstractmethod def verify( self, statement: Mapping[str, Any], auth_data: AuthenticatorData, client_data_hash: bytes, ) -> AttestationResult: """Verifies attestation statement. :return: An AttestationResult if successful. """ @staticmethod def for_type(fmt: str) -> Type[Attestation]: """Get an Attestation subclass type for the given format.""" for cls in Attestation.__subclasses__(): if getattr(cls, "FORMAT", None) == fmt: return cls class TypedUnsupportedAttestation(UnsupportedAttestation): def __init__(self): super().__init__(fmt) return TypedUnsupportedAttestation class UnsupportedAttestation(Attestation): def __init__(self, fmt=None): self.fmt = fmt def verify(self, statement, auth_data, client_data_hash): raise UnsupportedType(auth_data, self.fmt) class NoneAttestation(Attestation): FORMAT = "none" def verify(self, statement, auth_data, client_data_hash): if statement != {}: raise InvalidData("None Attestation requires empty statement.") return AttestationResult(AttestationType.NONE, []) def _validate_cert_common(cert): if cert.version != x509.Version.v3: raise InvalidData("Attestation certificate must use version 3!") try: bc = cert.extensions.get_extension_for_class(x509.BasicConstraints) if bc.value.ca: raise InvalidData("Attestation certificate must have CA=false!") except x509.ExtensionNotFound: raise InvalidData("Attestation certificate must have Basic Constraints!") def _default_attestations(): return [ cls() # type: ignore for cls in Attestation.__subclasses__() if getattr(cls, "FORMAT", "none") != "none" ] class AttestationVerifier(abc.ABC): """Base class for verifying attestation. Override the ca_lookup method to provide a trusted root certificate used to verify the trust path from the attestation. """ def __init__(self, attestation_types: Optional[Sequence[Attestation]] = None): self._attestation_types = attestation_types or _default_attestations() @abc.abstractmethod def ca_lookup( self, attestation_result: AttestationResult, auth_data: AuthenticatorData ) -> Optional[bytes]: """Lookup a CA certificate to be used to verify a trust path. :param attestation_result: The result of the attestation :param auth_data: The AuthenticatorData from the registration """ raise NotImplementedError() def verify_attestation( self, attestation_object: AttestationObject, client_data_hash: bytes ) -> None: """Verify attestation. :param attestation_object: dict containing attestation data. :param client_data_hash: SHA256 hash of the ClientData bytes. """ att_verifier: Attestation = UnsupportedAttestation(attestation_object.fmt) for at in self._attestation_types: if getattr(at, "FORMAT", None) == attestation_object.fmt: att_verifier = at break # An unsupported format causes an exception to be thrown, which # includes the auth_data. The caller may choose to handle this case # and allow the registration. result = att_verifier.verify( attestation_object.att_stmt, attestation_object.auth_data, client_data_hash, ) # Lookup CA to use for trust path verification ca = self.ca_lookup(result, attestation_object.auth_data) if not ca: raise UntrustedAttestation("No root found for Authenticator") # Validate the trust chain try: verify_x509_chain(result.trust_path + [ca]) except InvalidSignature as e: raise UntrustedAttestation(e) def __call__(self, *args): """Allows passing an instance to Fido2Server as verify_attestation""" self.verify_attestation(*args) fido2-1.2.0/fido2/attestation/__init__.py0000644000175000017500000000357114721556664017562 0ustar winniewinnie# Copyright (c) 2020 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. from .base import ( # noqa: F401 Attestation, NoneAttestation, AttestationType, AttestationResult, InvalidData, InvalidSignature, UnsupportedType, UnsupportedAttestation, UntrustedAttestation, verify_x509_chain, AttestationVerifier, ) from .apple import AppleAttestation # noqa: F401 from .android import AndroidSafetynetAttestation # noqa: F401 from .packed import PackedAttestation # noqa: F401 from .u2f import FidoU2FAttestation # noqa: F401 from .tpm import TpmAttestation # noqa: F401 fido2-1.2.0/fido2/utils.py0000644000175000017500000002416514721556664014626 0ustar winniewinnie# Copyright (c) 2013 Yubico AB # All rights reserved. # # Redistribution and use in source and binary forms, with or # without modification, are permitted provided that the following # conditions are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """Various utility functions. This module contains various functions used throughout the rest of the project. """ from __future__ import annotations from base64 import urlsafe_b64decode, urlsafe_b64encode from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hmac, hashes from io import BytesIO from dataclasses import fields, Field from abc import abstractmethod from typing import ( Union, Optional, Sequence, Mapping, Dict, Any, TypeVar, Hashable, get_type_hints, overload, Type, ) import struct import warnings __all__ = [ "websafe_encode", "websafe_decode", "sha256", "hmac_sha256", "bytes2int", "int2bytes", ] LOG_LEVEL_TRAFFIC = 5 def sha256(data: bytes) -> bytes: """Produces a SHA256 hash of the input. :param data: The input data to hash. :return: The resulting hash. """ h = hashes.Hash(hashes.SHA256(), default_backend()) h.update(data) return h.finalize() def hmac_sha256(key: bytes, data: bytes) -> bytes: """Performs an HMAC-SHA256 operation on the given data, using the given key. :param key: The key to use. :param data: The input data to hash. :return: The resulting hash. """ h = hmac.HMAC(key, hashes.SHA256(), default_backend()) h.update(data) return h.finalize() def bytes2int(value: bytes) -> int: """Parses an arbitrarily sized integer from a byte string. :param value: A byte string encoding a big endian unsigned integer. :return: The parsed int. """ return int.from_bytes(value, "big") def int2bytes(value: int, minlen: int = -1) -> bytes: """Encodes an int as a byte string. :param value: The integer value to encode. :param minlen: An optional minimum length for the resulting byte string. :return: The value encoded as a big endian byte string. """ ba = [] while value > 0xFF: ba.append(0xFF & value) value >>= 8 ba.append(value) ba.extend([0] * (minlen - len(ba))) return bytes(reversed(ba)) def websafe_decode(data: Union[str, bytes]) -> bytes: """Decodes a websafe-base64 encoded string. See: "Base 64 Encoding with URL and Filename Safe Alphabet" from Section 5 in RFC4648 without padding. :param data: The input to decode. :return: The decoded bytes. """ if isinstance(data, str): data = data.encode("ascii") else: warnings.warn( "Calling websafe_decode on a byte value is deprecated, " "and will no longer be allowed starting in python-fido2 2.0", DeprecationWarning, ) data += b"=" * (-len(data) % 4) return urlsafe_b64decode(data) def websafe_encode(data: bytes) -> str: """Encodes a byte string into websafe-base64 encoding. :param data: The input to encode. :return: The encoded string. """ return urlsafe_b64encode(data).replace(b"=", b"").decode("ascii") class ByteBuffer(BytesIO): """BytesIO-like object with the ability to unpack values.""" def unpack(self, fmt: str): """Reads and unpacks a value from the buffer. :param fmt: A struct format string yielding a single value. :return: The unpacked value. """ s = struct.Struct(fmt) return s.unpack(self.read(s.size))[0] def read(self, size: Optional[int] = -1) -> bytes: """Like BytesIO.read(), but checks the number of bytes read and raises an error if fewer bytes were read than expected. """ data = super().read(size) if size is not None and size > 0 and len(data) != size: raise ValueError( "Not enough data to read (need: %d, had: %d)." % (size, len(data)) ) return data _T = TypeVar("_T", bound=Hashable) _S = TypeVar("_S", bound="_DataClassMapping") class _DataClassMapping(Mapping[_T, Any]): """A data class with members also accessible as a Mapping.""" # TODO: This requires Python 3.9, and fixes the type errors we now ignore # __dataclass_fields__: ClassVar[Dict[str, Field[Any]]] def __post_init__(self): hints = get_type_hints(type(self)) self._field_keys: Dict[_T, Field[Any]] object.__setattr__(self, "_field_keys", {}) for f in fields(self): # type: ignore self._field_keys[self._get_field_key(f)] = f value = getattr(self, f.name) if value is not None: try: value = self._parse_value(hints[f.name], value) object.__setattr__(self, f.name, value) except (TypeError, KeyError, ValueError): raise ValueError( f"Error parsing field {f.name} for {self.__class__.__name__}" ) @classmethod @abstractmethod def _get_field_key(cls, field: Field) -> _T: raise NotImplementedError() def __iter__(self): return ( k for k, f in self._field_keys.items() if getattr(self, f.name) is not None ) def __len__(self): return len(list(iter(self))) def __getitem__(self, key): f = self._field_keys[key] value = getattr(self, f.name) if value is None: raise KeyError(key) serialize = f.metadata.get("serialize") if serialize: return serialize(value) if isinstance(value, Mapping) and not isinstance(value, dict): return dict(value) if isinstance(value, Sequence) and all(isinstance(v, Mapping) for v in value): return [v if isinstance(v, dict) else dict(v) for v in value] return value @classmethod def _parse_value(cls, t, value): if Optional[t] == t: # Optional, get the type t = t.__args__[0] # Check if type is already correct try: if t is Any or isinstance(value, t): return value except TypeError: pass # Handle list of values if issubclass(getattr(t, "__origin__", object), Sequence): t = getattr(t, "__args__")[0] return [cls._parse_value(t, v) for v in value] # Handle Mappings elif issubclass(getattr(t, "__origin__", object), Mapping) and isinstance( value, Mapping ): t_k, t_v = getattr(t, "__args__") return { cls._parse_value(t_k, k): cls._parse_value(t_v, v) for k, v in value.items() } # Check if type has from_dict from_dict = getattr(t, "from_dict", None) if from_dict: return from_dict(value) # Convert to enum values, other wrappers wrap = getattr(t, "__call__", None) if wrap: return wrap(value) raise ValueError(f"Unparseable value of type {type(value)} for {t}") @overload @classmethod def from_dict(cls: Type[_S], data: None) -> None: ... @overload @classmethod def from_dict(cls: Type[_S], data: Mapping[_T, Any]) -> _S: ... @classmethod def from_dict(cls, data): if data is None: return None if isinstance(data, cls): return data if not isinstance(data, Mapping): raise TypeError( f"{cls.__name__}.from_dict called with non-Mapping data of type" f"{type(data)}" ) kwargs = {} hints = get_type_hints(cls) for f in fields(cls): # type: ignore key = cls._get_field_key(f) value = data.get(key) if value is None: continue deserialize = f.metadata.get("deserialize") if deserialize: value = deserialize(value) else: t = hints[f.name] value = cls._parse_value(t, value) kwargs[f.name] = value return cls(**kwargs) class _JsonDataObject(_DataClassMapping[str]): """A data class with members also accessible as a JSON-serializable Mapping.""" @classmethod def _get_field_key(cls, field: Field) -> str: name = field.metadata.get("name") if name: return name parts = field.name.split("_") return parts[0] + "".join(p.title() for p in parts[1:]) def __getitem__(self, key): value = super().__getitem__(key) if isinstance(value, bytes): return websafe_encode(value) return value @classmethod def _parse_value(cls, t, value): if Optional[t] == t: # Optional, get the type t2 = t.__args__[0] else: t2 = t # bytes are encoded as websafe_b64 strings if isinstance(t2, type) and issubclass(t2, bytes) and isinstance(value, str): return websafe_decode(value) return super()._parse_value(t, value) fido2-1.2.0/COPYING.APLv20000644000175000017500000002613614721556664014027 0ustar winniewinnie 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.