diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 69ad7c2a..00000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,123 +0,0 @@ -{ - "name": "Build", - "on": { - "push": { - "branches-ignore": "gh-pages", - "tags-ignore": "*", - }, - "pull_request": null, - }, - - "jobs": { - "linux": { - "runs-on": "ubuntu-latest", - "strategy": { - "fail-fast": false, - "matrix": { - "name": ["debian-stable", "debian-heimdal", "centos-8", - "fedora-latest"], - "include": [ - { - "name": "debian-stable", - "distro": "debian:stable", - }, - { - "name": "debian-heimdal", - "distro": "debian:stable", - "krb5_ver": "heimdal", - }, - { - "name": "centos-8", - "distro": "centos:8", - }, - { - "name": "fedora-latest", - "distro": "fedora:latest", - "flake": "yes", - }, - ], - }, - }, - "steps": [ - { - "name": "Check out code", - "uses": "actions/checkout@v2", - }, - { - "name": "Build and test gssapi", - "run": "./ci/run-on-linux.sh ./ci/build.sh", - "env": { - "DISTRO": "${{ matrix.distro }}", - "KRB5_VER": "${{ matrix.krb5_ver }}", - "FLAKE": "${{ matrix.flake }}", - }, - }, - ], - }, - - "windows": { - "runs-on": "windows-latest", - "strategy": { - "fail-fast": false, - "matrix": { - "name": [ - "win-py-3.9", - "win-py-3.8", - "win-py-3.7", - "win-py-3.6", - ], - "include": [ - { - "name": "win-py-3.9", - "pyenv": "3.9", - }, - { - "name": "win-py-3.8", - "pyenv": "3.8", - }, - { - "name": "win-py-3.7", - "pyenv": "3.7", - }, - { - "name": "win-py-3.6", - "pyenv": "3.6", - }, - ], - }, - }, - "steps": [ - { - "name": "Check out code", - "uses": "actions/checkout@v2", - }, - { - "name": "Install the right python", - "uses": "actions/setup-python@v2", - "with": { "python-version": "${{ matrix.pyenv }}" }, - }, - { - "name": "Build and test gssapi", - "shell": "bash", - "run": "./ci/build.sh", - "env": { "OS_NAME": "windows" }, - }, - ], - }, - - "macos-heimdal": { - "runs-on": "macos-latest", - "steps": [ - { - "name": "Check out code", - "uses": "actions/checkout@v2", - }, - { - "name": "Build and test gssapi", - "run": "./ci/build.sh", - "env": { "KRB5_VER": "heimdal" }, - }, - ], - }, - }, -} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..ef63ae23 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,384 @@ +name: Build +on: + push: + branches-ignore: gh-pages + tags: v* + pull_request: null + +jobs: + build_sdist: + name: Build sdist + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Select python + uses: actions/setup-python@v5 + with: + python-version: 3.9 + + - name: Build sdist + run: | + python -m pip install build + python -m build --sdist + env: + GSSAPI_SUPPORT_DETECT: false + GSSAPI_LINKER_ARGS: '' + GSSAPI_COMPILER_ARGS: '' + + - name: Upload sdist + uses: actions/upload-artifact@v4 + with: + name: artifact-sdist + path: ./dist/*.tar.gz + + build_wheels: + name: Build wheels + needs: + - build_sdist + + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: macOS-13 + version: cp313-macosx_x86_64 + - os: macOS-15 + version: cp313-macosx_arm64 + - os: macOS-13 + version: cp312-macosx_x86_64 + - os: macOS-15 + version: cp312-macosx_arm64 + - os: macOS-13 + version: cp311-macosx_x86_64 + - os: macOS-15 + version: cp311-macosx_arm64 + - os: macOS-13 + version: cp310-macosx_x86_64 + - os: macOS-15 + version: cp310-macosx_arm64 + - os: macOS-13 + version: cp39-macosx_x86_64 + - os: macOS-15 + version: cp39-macosx_arm64 + - os: macOS-13 + version: cp38-macosx_x86_64 + - os: macOS-15 + version: cp38-macosx_arm64 + + - os: windows-2022 + version: cp313-win_amd64 + - os: windows-2022 + version: cp313-win32 + - os: windows-2022 + version: cp312-win_amd64 + - os: windows-2022 + version: cp312-win32 + - os: windows-2022 + version: cp311-win_amd64 + - os: windows-2022 + version: cp311-win32 + - os: windows-2022 + version: cp310-win_amd64 + - os: windows-2022 + version: cp310-win32 + - os: windows-2022 + version: cp39-win_amd64 + - os: windows-2022 + version: cp39-win32 + - os: windows-2022 + version: cp38-win_amd64 + - os: windows-2022 + version: cp38-win32 + + steps: + - name: Set up environment + if: startsWith(matrix.os, 'windows-') + shell: bash + run: | + choco.exe install \ + --no-progress \ + --yes \ + --ignore-detected-reboot \ + --allow-downgrade \ + --install-arguments "'ADDLOCAL=ALL'" \ + ${{ endsWith(matrix.version, '-win32') && '--x86' || '' }} mitkerberos || true + + echo "C:\Program Files${{ endsWith(matrix.version, '-win32') && ' (x86)' || '' }}\MIT\Kerberos\bin;$PATH" >> $GITHUB_PATH + + - name: Download gssapi sdist + uses: actions/download-artifact@v4 + with: + name: artifact-sdist + path: ./ + + - name: Extract sdist + shell: bash + run: | + tar xf gssapi-*.tar.gz + mv gssapi-*/* . + rm -r gssapi-*/ + rm gssapi-*.tar.gz + + - name: Build wheel + uses: pypa/cibuildwheel@v2.22.0 + env: + CIBW_BUILD: ${{ matrix.version }} + CIBW_BUILD_VERBOSITY: 1 + + - name: Upload wheel + uses: actions/upload-artifact@v4 + with: + path: ./wheelhouse/*.whl + name: artifact-wheel-${{ matrix.version }} + + # To catch issues like this https://github.com/pythongssapi/python-gssapi/issues/327 + assertion_build: + needs: + - build_sdist + + runs-on: ubuntu-latest + steps: + - name: Download gssapi sdist + uses: actions/download-artifact@v4 + with: + name: artifact-sdist + path: ./dist + + - name: Compile Python with assertions + shell: bash + run: | + PYTHON_VERSION="3.11.5" + wget --quiet "https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tar.xz" + tar xf "Python-${PYTHON_VERSION}.tar.xz" + cd "Python-${PYTHON_VERSION}/" + ./configure --with-assertions --prefix "${PWD}/../Python-${PYTHON_VERSION}-build" + make + make install + cd .. + + sudo apt-get update + DEBIAN_FRONTEND=noninteractive sudo apt-get -y install krb5-user libkrb5-dev + + GSSAPI_VER="$( find ./dist -type f -name 'gssapi-*.tar.gz' -printf "%f\n" | sed -n 's/gssapi-\(.*\)\.tar\.gz/\1/p' )" + + PATH="${PWD}/Python-${PYTHON_VERSION}-build/bin:${PATH}" + python3 -m pip install gssapi=="${GSSAPI_VER}" \ + --find-links "file://${PWD}/dist" \ + --verbose + + python3 -c "import gssapi" + + linux: + needs: + - build_sdist + - build_wheels + + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + name: + - debian-stable + - debian-heimdal + - centos-stream-9 + - fedora-latest + include: + - name: debian-stable + distro: debian:stable + - name: debian-heimdal + distro: debian:stable + krb5_ver: heimdal + - name: centos-stream-9 + distro: quay.io/centos/centos:stream9 + - name: fedora-latest + distro: fedora:latest + flake: 'yes' + + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Download built project + uses: actions/download-artifact@v4 + with: + pattern: artifact-* + merge-multiple: true + path: ./dist + + - name: Test gssapi + run: ./ci/run-on-linux.sh ./ci/test.sh + env: + DISTRO: ${{ matrix.distro }} + KRB5_VER: ${{ matrix.krb5_ver }} + FLAKE: ${{ matrix.flake }} + + windows: + needs: + - build_sdist + - build_wheels + + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + name: + - win-py-3.13 + - win-py-3.12 + - win-py-3.11 + - win-py-3.10 + - win-py-3.9 + - win-py-3.8 + arch: + - x64 + - x86 + include: + - name: win-py-3.13 + pyenv: '3.13.0-rc.3' + - name: win-py-3.12 + pyenv: '3.12' + - name: win-py-3.11 + pyenv: '3.11' + - name: win-py-3.10 + pyenv: '3.10' + - name: win-py-3.9 + pyenv: '3.9' + - name: win-py-3.8 + pyenv: '3.8' + + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Download built project + uses: actions/download-artifact@v4 + with: + pattern: artifact-* + merge-multiple: true + path: ./dist + + - name: Install the right python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.pyenv }} + architecture: ${{ matrix.arch }} + + - name: Test gssapi + shell: bash + run: ./ci/test.sh + env: + OS_NAME: windows + + macos: + needs: + - build_sdist + - build_wheels + + runs-on: macos-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Download built project + uses: actions/download-artifact@v4 + with: + pattern: artifact-* + merge-multiple: true + path: ./dist + + - name: Test gssapi + run: ./ci/test.sh + env: + KRB5_VER: heimdal + + publish: + name: publish + + needs: + - assertion_build + - linux + - macos + - windows + + runs-on: ubuntu-latest + permissions: + # Needed for OIDC publishing + id-token: write + # Needed for github-pages-deploy-action and other repo write tasks + contents: write + + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Download built project + uses: actions/download-artifact@v4 + with: + pattern: artifact-* + merge-multiple: true + path: ./dist + + - name: Create GitHub release artifact + run: ./ci/run-on-linux.sh ./ci/create-release-tar.sh + env: + DISTRO: fedora:latest + + - name: Get tarball path + id: tarball + run: echo "tarball=`ls tag_build/*.tar.gz | awk -F/ '{print $2}'`" >> $GITHUB_OUTPUT + + - name: Get release checksum path + id: checksum + run: echo "checksum=`ls tag_build/*.sha512sum | awk -F/ '{print $2}'`" >> $GITHUB_OUTPUT + + - name: Upload tagged build artifact + uses: actions/upload-artifact@v4 + with: + path: tag_build/${{ steps.tarball.outputs.tarball }} + name: release-asset + + - name: Deploy stable docs + if: startsWith(github.ref, 'refs/tags/v') + uses: JamesIves/github-pages-deploy-action@v4 + with: + branch: gh-pages + folder: ci_docs_build + target-folder: stable + + - name: Create release + if: startsWith(github.ref, 'refs/tags/v') + uses: actions/create-release@v1 + id: cr + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: ${{ github.ref }} + + - name: Upload release tarball + if: startsWith(github.ref, 'refs/tags/v') + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.cr.outputs.upload_url }} + asset_path: tag_build/${{ steps.tarball.outputs.tarball }} + asset_name: ${{ steps.tarball.outputs.tarball }} + asset_content_type: application/octet-stream + + - name: Upload release checksum + if: startsWith(github.ref, 'refs/tags/v') + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.cr.outputs.upload_url }} + asset_path: tag_build/${{ steps.checksum.outputs.checksum }} + asset_name: ${{ steps.checksum.outputs.checksum }} + asset_content_type: text/plain + + - name: Deploy to PyPI + if: startsWith(github.ref, 'refs/tags/v') + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index df17abac..3c6df505 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -1,30 +1,37 @@ -{ - "name": "Deploy docs", - "on": { "push": { "branches": "main" }}, - "jobs": { - "update-pages": { - "runs-on": "ubuntu-latest", - "steps": [ - { - "name": "Check out code", - "uses": "actions/checkout@v2", - }, - { - "name": "Build docs", - "env": { "DISTRO": "fedora:latest" }, - "run": "./ci/run-on-linux.sh ./ci/before-docs-deploy.sh", - }, - { - "name": "Deploy latest docs", - "uses": "JamesIves/github-pages-deploy-action@3.7.1", - "with": { - "GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}", - "BRANCH": "gh-pages", - "FOLDER": "ci_docs_build/html", - "TARGET_FOLDER": "latest", - }, - }, - ], - }, - }, -} +name: Deploy docs +on: + push: + branches: main + +jobs: + update-pages: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Select python + uses: actions/setup-python@v5 + with: + python-version: 3.9 + + - name: Build sdist + run: | + python -m pip install build + python -m build --sdist + env: + GSSAPI_SUPPORT_DETECT: false + GSSAPI_LINKER_ARGS: '' + GSSAPI_COMPILER_ARGS: '' + + - name: Build docs + run: ./ci/run-on-linux.sh ./ci/before-docs-deploy.sh + env: + DISTRO: fedora:latest + + - name: Deploy latest docs + uses: JamesIves/github-pages-deploy-action@v4 + with: + branch: gh-pages + folder: ci_docs_build + target-folder: latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 600a6657..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,131 +0,0 @@ -{ - "name": "Release", - "on": { "push": { "tags": "v*" }}, - "jobs": { - "release-linux": { - "runs-on": "ubuntu-latest", - "steps": [ - { - "name": "Check out code", - "uses": "actions/checkout@v2", - }, - { - "name": "Set things up", - "env": { "DISTRO": "fedora:latest" }, - "run": "./ci/run-on-linux.sh ./ci/before-deploy.sh", - }, - { - "name": "Deploy to PyPI", - "uses": "pypa/gh-action-pypi-publish@v1.1.0", - "with": { - "user": "rharwood", - "password": "${{ secrets.pypi_password }}", - }, - }, - { - "name": "Deploy stable docs", - "uses": "JamesIves/github-pages-deploy-action@3.7.1", - "with": { - "GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}", - "BRANCH": "gh-pages", - "FOLDER": "ci_docs_build/html", - "TARGET_FOLDER": "stable", - }, - }, - { - "name": "Create release", - "uses": "actions/create-release@v1", - "id": "cr", - "env": { "GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}" }, - "with": { - "tag_name": "${{ github.ref }}", - "release_name": "${{ github.ref }}", - }, - }, - { - "id": "tarball", - "run": "echo \"::set-output name=tarball::`ls tag_build/*.tar.gz | awk -F/ '{print $2}'`\"" - }, - { - "id": "checksum", - "run": "echo \"::set-output name=checksum::`ls tag_build/*.sha512sum | awk -F/ '{print $2}'`\"" - }, - { - "name": "Upload release tarball", - "uses": "actions/upload-release-asset@v1", - "env": { "GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}" }, - "with": { - "upload_url": "${{ steps.cr.outputs.upload_url }}", - "asset_path": "tag_build/${{ steps.tarball.outputs.tarball }}", - "asset_name": "${{ steps.tarball.outputs.tarball }}", - "asset_content_type": "application/octet-stream", - }, - }, - { - "name": "Upload release checksum", - "uses": "actions/upload-release-asset@v1", - "env": { "GITHUB_TOKEN": "${{ secrets.GITHUB_TOKEN }}" }, - "with": { - "upload_url": "${{ steps.cr.outputs.upload_url }}", - "asset_path": "tag_build/${{ steps.checksum.outputs.checksum }}", - "asset_name": "${{ steps.checksum.outputs.checksum }}", - "asset_content_type": "text/plain", - }, - }, - ], - }, - - "release-windows": { - "runs-on": "windows-latest", - "strategy": { - "matrix": { - "name": [ - "win-wheel-3.9", - "win-wheel-3.8", - "win-wheel-3.7", - "win-wheel-3.6", - ], - "include": [ - { - "name": "win-wheel-3.9", - "pyenv": "3.9", - }, - { - "name": "win-wheel-3.8", - "pyenv": "3.8", - }, - { - "name": "win-wheel-3.7", - "pyenv": "3.7", - }, - { - "name": "win-wheel-3.6", - "pyenv": "3.6", - }, - ], - }, - }, - "steps": [ - { - "name": "Check out code", - "uses": "actions/checkout@v2", - }, - { - "name": "Install the right python", - "uses": "actions/setup-python@v2", - "with": { "python-version": "${{ matrix.pyenv }}" }, - }, - { - "name": "Create and upload Windows wheel", - "shell": "bash", - "run": "./ci/release-win.sh", - "env": { - "OS_NAME": "windows", - "TWINE_USER": "rharwood", - "TWINE_PASSWORD": "${{ secrets.pypi_password }}", - }, - }, - ], - }, - }, -} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000..d55558d0 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,23 @@ +name: Stale pull request handler +on: + schedule: + - cron: 0 0 * * * + +permissions: + pull-requests: write + +jobs: + stale: + runs-on: ubuntu-latest + + steps: + - uses: actions/stale@v9.0.0 + id: stale + with: + days-before-stale: -1 + days-before-pr-stale: 28 + days-before-pr-close: 14 + stale-pr-label: stale + stale-pr-message: >- + This pull request is stale because it has been open for 4 weeks with no activity. + Remove stale label or comment or this will be closed in 2 weeks. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..4191974a --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,134 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +jborean93 AT gmail DOT com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available +at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations + diff --git a/MANIFEST.in b/MANIFEST.in index 67cc2bbc..c9ba29f4 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ include *.txt recursive-include docs *.txt recursive-include gssapi *.pxd -recursive-include gssapi *.c +recursive-include gssapi *.pyx recursive-include gssapi *.h +recursive-exclude gssapi *.c diff --git a/README.txt b/README.txt index 62554e97..f98ca7ec 100644 --- a/README.txt +++ b/README.txt @@ -18,12 +18,8 @@ Python-GSSAPI provides both low-level and high level wrappers around the GSSAPI C libraries. While it focuses on the Kerberos mechanism, it should also be useable with other GSSAPI mechanisms. -Documentation for the latest released version (including pre-release versions) -can be found at -`https://pythongssapi.github.io/python-gssapi/stable `_. - -Documentation for the latest commit on main can be found at -`https://pythongssapi.github.io/python-gssapi/latest `_. +Documentation can be found at +`https://pythongssapi.github.io/python-gssapi/ `_. Requirements ============ @@ -36,20 +32,19 @@ Basic * a C compiler (such as GCC) -* Python 3.6+ (older releases support older versions, but are unsupported) +* Python 3.8+ (older releases support older versions, but are unsupported) * the `decorator` python package Compiling from Scratch ---------------------- -To compile from scratch, you will need Cython >= 0.21.1. +To compile from scratch, you will need Cython ``>= 3.0.3, < 4.0.0`` which is automatically +installed by pip in an isolated build virtual environment. For Running the Tests --------------------- -* the `nose` package - * the `k5test` package To install test dependencies using pip: @@ -76,8 +71,7 @@ After being sure to install all the requirements, .. code-block:: bash $ git clone https://github.com/pythongssapi/python-gssapi.git - $ python setup.py build - $ python setup.py install + $ pip install . Tests ===== @@ -143,10 +137,14 @@ Extensions In addition to RFC 2743/2744, Python-GSSAPI also has support for: +* RFC 4178 (GSS-API Negotiation Mechanism) + * RFC 5587 (Extended GSS Mechanism Inquiry APIs) * RFC 5588 (GSS-API Extension for Storing Delegated Credentials) +* RFC 5801 (GSS-API SASL Extensions) + * (Additional) Credential Store Extension * Services4User @@ -161,13 +159,16 @@ In addition to RFC 2743/2744, Python-GSSAPI also has support for: * GGF Extensions +* Kerberos specific extensions + The Team ======== (GitHub usernames in parentheses) -* Robbie Harwood (@frozencemetery) - current maintainer and developer +* Jordan Borean (@jborean93) - current maintainer and developer * Simo Sorce (@simo5) - developer +* Robbie Harwood (@frozencemetery) - author emeritus * Solly Ross (@directxman12) - author emeritus * Hugh Cole-Baker (@sigmaris) - author emeritus @@ -176,5 +177,5 @@ Get Involved We welcome new contributions in the form of Issues and Pull Requests on Github. If you would like to join our discussions, you can find us on -`Freenode `_ IRC, channel `#python-gssapi -`_. +`libera.chat `_ IRC, channel `#python-gssapi +`_. diff --git a/ci/before-deploy.sh b/ci/before-deploy.sh deleted file mode 100755 index 1f86b7d1..00000000 --- a/ci/before-deploy.sh +++ /dev/null @@ -1,52 +0,0 @@ -#!/bin/bash -ex - -source ./ci/lib-setup.sh -source ./ci/lib-deploy.sh - -./ci/build.sh - -setup::activate - -yum -y install tar git - -# build the docs -deploy::build-docs - -# NB(directxman12): this is a *terrible* hack, but basically, -# `twine` gets called like this: -# - python setup.py $PYPI_DISTRIBUTIONS -# - twine upload -r pypi dist/* -# - [some other stuff] -# -# so if we set $PYPI_DISTRIBUTIONS to something harmless, like `check`, -# and then build the dist ourselves (and save it from the cleanup), -# dpl will upload that - -# build the sdist -python setup.py sdist -mv dist dist_saved - -# for the tarball upload -# clean up -git clean -Xdf - -# restore the saved "dist" directory -mv dist_saved dist - -# make the dir -rm -rf ./tag_build || true -mkdir ./tag_build - -# create and checksum the tarball - -tag=$(git describe --tags) -if [ x"${tag#v[0-9]}" = "x${tag}" ]; then - PYTHON_GSSAPI_VERSION=${tag} -else - PYTHON_GSSAPI_VERSION=${tag#v} -fi - -PKG_NAME_VER="python-gssapi-${PYTHON_GSSAPI_VERSION}" - -tar -czvf ./tag_build/${PKG_NAME_VER}.tar.gz --exclude='dist' --exclude='tag_build' --exclude='.git' --exclude='travis_docs_build' --exclude='.git' --transform "s,^\.,${PKG_NAME_VER}," . -sha512sum --binary ./tag_build/${PKG_NAME_VER}.tar.gz > ./tag_build/${PKG_NAME_VER}.sha512sum diff --git a/ci/before-docs-deploy.sh b/ci/before-docs-deploy.sh index a6bb1eb1..d7c719ed 100755 --- a/ci/before-docs-deploy.sh +++ b/ci/before-docs-deploy.sh @@ -1,12 +1,7 @@ #!/bin/bash -ex -source ./ci/lib-setup.sh -source ./ci/lib-deploy.sh +source ./ci/lib.sh -# GitHub Actions doesn't have a good concept of connected pipelines here, so -# just rebuild rather than trying to figure it out. -./ci/build.sh +lib::setup::install -setup::activate - -deploy::build-docs +lib::deploy::build_docs diff --git a/ci/build.sh b/ci/build.sh deleted file mode 100755 index 2db0b6c9..00000000 --- a/ci/build.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash -ex - -# set up dependencies, etc -source ./ci/lib-setup.sh -setup::install - -if [ x"$FLAKE" = "xyes" ]; then - flake8 setup.py - F8_SETUP=$? - - flake8 gssapi - F8_PY=$? - - # Cython requires special flags since it is not proper Python: - # - E225: missing whitespace around operator - # - E226: missing whitespace around arithmetic operator - # - E227: missing whitespace around bitwise or shift operator - # - E402: module level import not at top of file (needed for the `GSSAPI="blah" lines) - # - E901: SyntaxError or IndentationError - # - E999: Internal AST compilation error (flake8 specific) - flake8 gssapi --filename='*.pyx,*.pxd' --ignore=E225,E226,E227,E402,E901,E999 - F8_MAIN_CYTHON=$? - - if [ $F8_SETUP -ne 0 -o $F8_PY -ne 0 -o $F8_MAIN_CYTHON -ne 0 ]; then - exit 1 - fi -fi - -# always build in-place so that Sphinx can find the modules -python setup.py build_ext --inplace $EXTRA_BUILDEXT -BUILD_RES=$? - -if [ $BUILD_RES -ne 0 ]; then - # if the build failed, don't run the tests - exit $BUILD_RES -fi - -if [ x"$KRB5_VER" = "xheimdal" ] || [ "$OS_NAME" = "windows" ]; then - # heimdal/Windows can't run the tests yet, so just make sure it imports and exit - python -c "import gssapi" - exit $? -fi - -python setup.py nosetests --verbosity=3 -TEST_RES=$? - -exit $TEST_RES diff --git a/ci/create-release-tar.sh b/ci/create-release-tar.sh new file mode 100755 index 00000000..52410f04 --- /dev/null +++ b/ci/create-release-tar.sh @@ -0,0 +1,72 @@ +#!/bin/bash -ex + +source ./ci/lib.sh + +lib::setup::install + +yum -y install tar git + +# Git complains if this isn't owned by the user which is the case when running +# through the run-on-linux.sh +if [ -f /.dockerenv ]; then + git config --global --add safe.directory "${PWD}" +fi + +# build the docs +lib::deploy::build_docs + +# Save the sdist and venv dirs before the clean +mv dist dist_saved +mv .venv /tmp/.venv + +# for the tarball upload +# clean up +git clean -Xdf + +# restore the saved "dist"/".venv" directory +mv dist_saved dist +mv /tmp/.venv .venv + +# make the dir +rm -rf ./tag_build || true +mkdir ./tag_build + +# create and checksum the tarball + +set +e +tag=$(git describe --tags) +if [ "${?}" -ne 0 ]; then + tag=$(git rev-parse --short HEAD) +fi +set -e + +if [ x"${tag#v[0-9]}" = "x${tag}" ]; then + PYTHON_GSSAPI_VERSION=${tag} +else + PYTHON_GSSAPI_VERSION=${tag#v} +fi + +PKG_NAME_VER="python-gssapi-${PYTHON_GSSAPI_VERSION}" + +tar -cvf ./tag_build/${PKG_NAME_VER}.tar \ + --exclude='dist' \ + --exclude='tag_build' \ + --exclude='.git' \ + --exclude='ci_docs_build' \ + --exclude='.venv' \ + --exclude='README.rst' \ + --transform="s,^\.,${PKG_NAME_VER}," . + +# --transform clobbers symlink so add it last using Python +python - << EOF +import tarfile + +with tarfile.open("tag_build/${PKG_NAME_VER}.tar", mode="a:") as tf: + tf.add("README.rst", arcname="${PKG_NAME_VER}/README.rst") +EOF + +pushd ./tag_build +gzip ${PKG_NAME_VER}.tar + +sha512sum --binary ${PKG_NAME_VER}.tar.gz > ${PKG_NAME_VER}.sha512sum +popd diff --git a/ci/lib-deploy.sh b/ci/lib-deploy.sh deleted file mode 100755 index 25c11309..00000000 --- a/ci/lib-deploy.sh +++ /dev/null @@ -1,13 +0,0 @@ -deploy::build-docs() { - # the first run is for the docs build, so don't clean up - pip install -r docs-requirements.txt - - # install dependencies so that sphinx doesn't have issues - # (this actually just installs the whole package in dev mode) - pip install -e . - - # place in a non-standard location so that they don't get cleaned up - python setup.py build_sphinx --build-dir ci_docs_build - - echo "docs_build" -} diff --git a/ci/lib-setup.sh b/ci/lib-setup.sh deleted file mode 100755 index c90b530f..00000000 --- a/ci/lib-setup.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/bin/bash - -# We test Debian's cython. el7's cython is too old, and Rawhide's virtualenv -# doesn't work right (usrmerge bugs) so we can only test Debian's cython. - -setup::debian::install() { - export DEBIAN_FRONTEND=noninteractive - apt-get update - - if [ x"$KRB5_VER" = "xheimdal" ]; then - apt-get -y install heimdal-dev - else - apt-get -y install krb5-{user,kdc,admin-server,multidev} libkrb5-dev \ - gss-ntlmssp - fi - - apt-get -y install gcc virtualenv python3-{virtualenv,dev} cython3 - - virtualenv --system-site-packages -p $(which python3) .venv - source ./.venv/bin/activate -} - -setup::rh::yuminst() { - # yum has no update-only verb. Also: modularity just makes this slower. - yum -y --nogpgcheck --disablerepo=\*modul\* install $@ -} - -setup::centos::install() { - # Cython on el7 is too old - downstream patches - setup::rh::yuminst python3-{virtualenv,devel} - virtualenv -p $(which python3) .venv - source ./.venv/bin/activate - pip install --upgrade pip # el7 pip doesn't quite work right - pip install --install-option='--no-cython-compile' cython -} - -setup::fedora::install() { - # path to binary here in case Rawhide changes it - setup::rh::yuminst redhat-rpm-config \ - /usr/bin/virtualenv python3-{virtualenv,devel} - virtualenv -p $(which python3) .venv - source ./.venv/bin/activate - pip install --install-option='--no-cython-compile' cython -} - -setup::rh::install() { - setup::rh::yuminst krb5-{devel,libs,server,workstation} \ - which gcc findutils gssntlmssp - - if [ -f /etc/fedora-release ]; then - setup::fedora::install - else - setup::centos::install - fi -} - -setup::macos::install() { - sudo pip3 install virtualenv - python3 -m virtualenv -p $(which python3) .venv - source .venv/bin/activate - pip install --install-option='--no-cython-compile' cython -} - -setup::windows::install() { - CHINST="choco install --no-progress --yes --ignore-detected-reboot --allow-downgrade" - - # Install MIT Kerberos. choco will fail despite the installation working. - $CHINST mitkerberos --install-arguments "'ADDLOCAL=ALL'" || true - - # Update path to include it - export PATH="/c/Program Files/MIT/Kerberos/bin:$PATH" - - python -m pip install --upgrade pip -} - -setup::install() { - if [ -f /etc/debian_version ]; then - setup::debian::install - elif [ -f /etc/redhat-release ]; then - setup::rh::install - elif [ "$(uname)" == "Darwin" ]; then - setup::macos::install - elif [ "$OS_NAME" == "windows" ]; then - setup::windows::install - else - echo "Distro not found!" - false - fi - - pip install -r test-requirements.txt -} - -setup::activate() { - # remove (and restore) set -x to avoid log-spam the source - # script, which we don't care about - wastrace=${-//[^x]/} - set +x - source .venv/bin/activate - if [[ -n "$wastrace" ]]; then set -x; fi -} diff --git a/ci/lib.sh b/ci/lib.sh new file mode 100755 index 00000000..a8d29852 --- /dev/null +++ b/ci/lib.sh @@ -0,0 +1,162 @@ +#!/bin/bash + +lib::setup::debian_install() { + export DEBIAN_FRONTEND=noninteractive + apt-get update + + if [ x"$KRB5_VER" = "xheimdal" ]; then + apt-get -y install heimdal-{clients,dev,kdc} + + export GSSAPI_KRB5_MAIN_LIB="/usr/lib/x86_64-linux-gnu/libkrb5.so.26" + export PATH="/usr/lib/heimdal-servers:${PATH}" + else + apt-get -y install krb5-{user,kdc,admin-server,multidev} libkrb5-dev \ + gss-ntlmssp + + export GSSAPI_KRB5_MAIN_LIB="/usr/lib/x86_64-linux-gnu/libkrb5.so" + fi + + apt-get -y install gcc python3-{venv,dev} + + python3 -m venv .venv + source ./.venv/bin/activate +} + +lib::setup::rh_dnfinst() { + # dnf has no update-only verb. + dnf -y --nogpgcheck install $@ +} + +lib::setup::centos_install() { + lib::setup::rh_dnfinst python3-devel + python3 -m venv .venv + source ./.venv/bin/activate +} + +lib::setup::fedora_install() { + # path to binary here in case Rawhide changes it + lib::setup::rh_dnfinst redhat-rpm-config \ + python3-devel + python3 -m venv .venv + source ./.venv/bin/activate +} + +lib::setup::gssntlmssp_install() { + lib::setup::rh_dnfinst dnf-plugins-core + dnf config-manager --set-enabled crb + + lib::setup::rh_dnfinst autoconf automake gettext libtool \ + libunistring-devel openssl-devel zlib-devel + + curl -L -s https://github.com/gssapi/gss-ntlmssp/releases/download/v1.1.0/gssntlmssp-1.1.0.tar.gz --output /tmp/gssntlmssp.tar.gz + tar xf /tmp/gssntlmssp.tar.gz -C /tmp + + pushd /tmp/gssntlmssp-1.1.0 + + autoreconf -f -i + ./configure --with-wbclient=no --with-manpages=no + make + make install + + popd + + echo "gssntlmssp_v1 1.3.6.1.4.1.311.2.2.10 /usr/local/lib/gssntlmssp/gssntlmssp.so" > /etc/gss/mech.d/gssntlmssp.conf +} + +lib::setup::rh_install() { + lib::setup::rh_dnfinst krb5-{devel,libs,server,workstation} \ + which gcc findutils + + if grep -q 'release 9' /etc/redhat-release; then + # CentOS 9 Stream doesn't have a dnf package for gssntlmssp + lib::setup::gssntlmssp_install + else + lib::setup::rh_dnfinst gssntlmssp + fi + + export GSSAPI_KRB5_MAIN_LIB="/usr/lib64/libkrb5.so" + + if [ -f /etc/fedora-release ]; then + lib::setup::fedora_install + else + lib::setup::centos_install + fi +} + +lib::setup::macos_install() { + python3 -m venv .venv + source .venv/bin/activate + + export GSSAPI_KRB5_MAIN_LIB="/System/Library/PrivateFrameworks/Heimdal.framework/Heimdal" + + # macOS's Heimdal version is buggy, it will only use KRB5_KTNAME if the + # env var was set when GSSAPI creates the context. Setting it here to any + # value solves that problem for CI. + export KRB5_KTNAME=initial +} + +lib::setup::windows_install() { + CHINST="choco install --no-progress --yes --ignore-detected-reboot --allow-downgrade" + + # Install the 32bit version if Python is 32bit + if python -c "assert __import__('sys').maxsize <= 2**32"; then + CHINST="$CHINST --x86" + PF="Program Files (x86)" + else + PF="Program Files" + fi + + # Install MIT Kerberos. choco will fail despite the installation working. + $CHINST mitkerberos --install-arguments "'ADDLOCAL=ALL'" || true + + # Update path to include it + export PATH="/c/$PF/MIT/Kerberos/bin:$PATH" +} + +lib::setup::install() { + if [ -f /etc/debian_version ]; then + lib::setup::debian_install + elif [ -f /etc/redhat-release ]; then + lib::setup::rh_install + elif [ "$(uname)" == "Darwin" ]; then + lib::setup::macos_install + elif [ "$OS_NAME" == "windows" ]; then + lib::setup::windows_install + else + echo "Distro not found!" + false + fi + + # Get the explicit version to force pip to install from our local dir in + # case this is a pre-release and/or PyPi has a later version + echo "Installing gssapi" + GSSAPI_VER="$( grep 'version=' setup.py | cut -d "'" -f2 )" + + if [ "$(expr substr $(uname -s) 1 5)" == "MINGW" ]; then + DIST_LINK_PATH="$( echo "${PWD}/dist" | sed -e 's/^\///' -e 's/\//\\/g' -e 's/^./\0:/' )" + else + DIST_LINK_PATH="${PWD}/dist" + fi + + python -m pip install gssapi=="${GSSAPI_VER}" \ + --find-links "file://${DIST_LINK_PATH}" \ + --verbose + + echo "Installing dev dependencies" + python -m pip install -r test-requirements.txt +} + +lib::deploy::build_docs() { + # the first run is for the docs build, so don't clean up + pip install -r docs-requirements.txt + + # Don't run in root to make sure the local copies aren't imported + pushd docs + + # place in a non-standard location so that they don't get cleaned up + sphinx-build source ../ci_docs_build -a -W -n + + popd + + echo "docs_build" +} diff --git a/ci/release-win.sh b/ci/release-win.sh deleted file mode 100755 index 1e8aec4e..00000000 --- a/ci/release-win.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash -e - -source ./ci/lib-setup.sh -source ./ci/lib-deploy.sh - -./ci/build.sh - -# Sigh, go find paths again -export PATH="/c/Program Files/MIT/Kerberos/bin:$PATH" - -# build the wheel -python -m pip install wheel -python setup.py bdist_wheel - -cd dist - -tag=$(git describe --tags) - -# Rename and checksum the wheel -if [ x"${tag#v[0-9]}" = "x${tag}" ]; then - PYTHON_GSSAPI_VERSION=${tag} -else - PYTHON_GSSAPI_VERSION=${tag#v} -fi - -PKG_NAME_VER=$(ls *.whl | sed "s/gssapi-[^-]*-\(.*\)\.whl/python-gssapi-${PYTHON_GSSAPI_VERSION}-\1/") - -cp *.whl "${PKG_NAME_VER}.whl" - -sha512sum --binary ./${PKG_NAME_VER}.whl > ./${PKG_NAME_VER}.sha512sum - -cd .. - -# Hack around https://github.com/pypa/gh-action-pypi-publish/issues/32 - -echo 'Running: python -m pip install twine ...' -python -m pip install twine - -echo 'Running: set +x; python -m twine upload...' -# Please note this cannot be set -x or passwords will leak! -set +x - -python -m twine upload -u $TWINE_USER -p $TWINE_PASSWORD dist/gssapi* > out.log 2>&1 || true - -# and restore... -set -x -egrep -i 'fail|error' out.log && cat out.log && exit 1 - -exit 0 diff --git a/ci/run-on-linux.sh b/ci/run-on-linux.sh index 7e12d836..2025db8a 100755 --- a/ci/run-on-linux.sh +++ b/ci/run-on-linux.sh @@ -1,10 +1,10 @@ #!/bin/bash -ex -# If we try to use a normal Github Actions container with -# github-pages-deploy-action, it will fail due to inability to find git. - -docker run -h test.box \ - -v `pwd`:/tmp/build -w /tmp/build \ - -e KRB5_VER=${KRB5_VER:-mit} \ - -e FLAKE=${FLAKE:no} \ - $DISTRO /bin/bash -ex $@ +docker run \ + --rm \ + --hostname test.krbtest.com \ + --volume "$( pwd )":/tmp/build \ + --workdir /tmp/build \ + --env KRB5_VER=${KRB5_VER:-mit} \ + --env FLAKE=${FLAKE:no} \ + ${DISTRO} /bin/bash -ex $@ diff --git a/ci/test.sh b/ci/test.sh new file mode 100755 index 00000000..c953f207 --- /dev/null +++ b/ci/test.sh @@ -0,0 +1,66 @@ +#!/bin/bash -ex + +# set up dependencies, etc +source ./ci/lib.sh + +if [ x"${GITHUB_ACTIONS}" = "xtrue" ]; then + echo "::group::Installing Requirements" +fi + +lib::setup::install + +if [ x"${GITHUB_ACTIONS}" = "xtrue" ]; then + echo "::endgroup::" + echo "::group::Running Sanity Checks" +fi + +if [ x"$FLAKE" = "xyes" ]; then + flake8 setup.py + F8_SETUP=$? + + flake8 gssapi + F8_PY=$? + + # Cython requires special flags since it is not proper Python: + # - E225: missing whitespace around operator + # - E226: missing whitespace around arithmetic operator + # - E227: missing whitespace around bitwise or shift operator + # - E402: module level import not at top of file (needed for the `GSSAPI="blah" lines) + # - E901: SyntaxError or IndentationError + # - E999: Internal AST compilation error (flake8 specific) + flake8 gssapi --filename='*.pyx,*.pxd' --ignore=E225,E226,E227,E402,E901,E999 + F8_MAIN_CYTHON=$? + + if [ $F8_SETUP -ne 0 -o $F8_PY -ne 0 -o $F8_MAIN_CYTHON -ne 0 ]; then + exit 1 + fi +fi + +python -m mypy . +MYPY_RES=$? +if [ $MYPY_RES -ne 0 ]; then + exit $MYPY_RES +fi + +if [ x"${GITHUB_ACTIONS}" = "xtrue" ]; then + echo "::endgroup::" + echo "::group::Running Tests" +fi + +# Ensure we don't run in the normal dir so that unittest imports our installed +# package and not the source code +pushd gssapi/tests + +# Only call exit on failures so we can source this script +if [ "$OS_NAME" = "windows" ]; then + # Windows can't run the tests yet, so just make sure it imports and exit + python -c "import gssapi" || exit $? +else + python -m unittest -v || exit $? +fi + +popd + +if [ x"${GITHUB_ACTIONS}" = "xtrue" ]; then + echo "::endgroup::" +fi diff --git a/docs-requirements.txt b/docs-requirements.txt index 0e60f68f..8a0d1c8e 100644 --- a/docs-requirements.txt +++ b/docs-requirements.txt @@ -1,3 +1,4 @@ +myst-parser # For parsing markdown docs instead of rst Sphinx >= 1.3.1 +sphinx-autoapi sphinx-rtd-theme >= 0.2.5b1 -recommonmark >= 0.4.0 diff --git a/docs/custom_extensions/custom_recommonmark.py b/docs/custom_extensions/custom_recommonmark.py deleted file mode 100644 index f7e767b6..00000000 --- a/docs/custom_extensions/custom_recommonmark.py +++ /dev/null @@ -1,8 +0,0 @@ -from recommonmark.parser import CommonMarkParser -from docutils import nodes - -# treats "verbatim" (code without a language specified) as a code sample -class AllCodeCommonMarkParser(CommonMarkParser): - def verbatim(self, text): - node = nodes.literal_block(text, text) - self.current_node.append(node) diff --git a/docs/custom_extensions/gssapi_find_missing.py b/docs/custom_extensions/gssapi_find_missing.py index 9b8a7489..1f2ba0c3 100644 --- a/docs/custom_extensions/gssapi_find_missing.py +++ b/docs/custom_extensions/gssapi_find_missing.py @@ -28,8 +28,9 @@ def _missing_ref(app, env, node, contnode): raw_opts = [] non_raw_opts = [] for opt in options: - full_name, type_info = opt - lib_name, mod_name, _mod_type = type_info + type_info = opt[1] + mod_name = type_info.docname + if mod_name.startswith('gssapi.raw'): raw_opts.append(opt) else: @@ -52,8 +53,10 @@ def _missing_ref(app, env, node, contnode): else: choice = options[0] - choice_name, choice_info = choice - gssapi, choice_mod, choice_type = choice_info + choice_name = choice[0] + choice_info = choice[1] + choice_mod = choice_info.node_id + choice_type = choice_info.objtype if choice_type == 'module': return env.domains['py']._make_module_refnode( diff --git a/docs/source/_static/.keep b/docs/source/_static/.keep new file mode 100644 index 00000000..e69de29b diff --git a/docs/source/conf.py b/docs/source/conf.py index dd3933d5..8d99409a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -11,18 +11,15 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys, os +import sys, os, re # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) -sys.path.insert(0, os.path.abspath('../..')) sys.path.insert(0, os.path.abspath('../custom_extensions')) -from custom_recommonmark import AllCodeCommonMarkParser - # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. @@ -30,19 +27,30 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', "sphinx.ext.napoleon", 'gssapi_find_missing', 'requires_rfc'] +extensions = [ + 'myst_parser', + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.viewcode', + 'sphinx.ext.napoleon', + 'autoapi.extension', + 'gssapi_find_missing', + 'requires_rfc', +] + +autoapi_generate_api_docs = False +autoapi_type = 'python' +autoapi_dirs = ['../../gssapi'] +autoapi_file_patterns = ['*.pyi'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] -# Parsers for different suffixes -source_parsers = { - '.md': AllCodeCommonMarkParser -} - # The suffix of source filenames. source_suffix = ['.rst', '.md'] +myst_all_links_external = True + # The encoding of source files. #source_encoding = 'utf-8-sig' @@ -56,11 +64,55 @@ # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. -# +# https://www.python.org/dev/peps/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions +setup_py_path = os.path.abspath(os.path.join(__file__, '..', '..', '..', 'setup.py')) +version_pattern = re.compile(r""" +^\s*version=['|\"](?P + (?: + (?:(?P[0-9]+)!)? # epoch + (?P[0-9]+(?:\.[0-9]+)*) # release segment + (?P
                                          # pre-release
+            [-_\.]?
+            (?P(a|b|c|rc|alpha|beta|pre|preview))
+            [-_\.]?
+            (?P[0-9]+)?
+        )?
+        (?P                                         # post release
+            (?:-(?P[0-9]+))
+            |
+            (?:
+                [-_\.]?
+                (?Ppost|rev|r)
+                [-_\.]?
+                (?P[0-9]+)?
+            )
+        )?
+        (?P                                          # dev release
+            [-_\.]?
+            (?Pdev)
+            [-_\.]?
+            (?P[0-9]+)?
+        )?
+    )
+    (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
+)['|\"],?\s*$
+""", re.VERBOSE | re.IGNORECASE)
+
 # The short X.Y version.
-version = '1.6.11'
+version = ''
 # The full version, including alpha/beta/rc tags.
-release = '1.6.11'
+release = ''
+
+with open(setup_py_path, mode='r') as fd:
+    for line in fd:
+        version_match = version_pattern.match(line)
+        if version_match:
+            version = version_match.group('release')
+            release = version_match.group('full_version')
+            break
+
+if not version or not release:
+    raise Exception("Failed to find version in setup.py")
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
@@ -260,7 +312,7 @@
 
 
 # Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {'http://docs.python.org/': None}
+intersphinx_mapping = {'python': ('http://docs.python.org/', None)}
 
 # which docstring to use for the class
 # can be 'class', 'init', or 'both'
diff --git a/docs/source/gssapi.raw.rst b/docs/source/gssapi.raw.rst
index bebdeebd..912c5364 100644
--- a/docs/source/gssapi.raw.rst
+++ b/docs/source/gssapi.raw.rst
@@ -25,44 +25,44 @@ Names
     Instead, they are a special form of name specific to
     a given mechanism.
 
-.. automodule:: gssapi.raw.names
+.. autoapimodule:: gssapi.raw.names
     :members:
     :undoc-members:
 
 Credentials
 ~~~~~~~~~~~
 
-.. automodule:: gssapi.raw.creds
+.. autoapimodule:: gssapi.raw.creds
     :members:
     :undoc-members:
 
 Security Contexts
 ~~~~~~~~~~~~~~~~~
 
-.. automodule::  gssapi.raw.sec_contexts
+.. autoapimodule::  gssapi.raw.sec_contexts
     :members:
     :undoc-members:
 
-.. automodule:: gssapi.raw.message
+.. autoapimodule:: gssapi.raw.message
     :members:
     :undoc-members:
 
 Misc
 ~~~~
 
-.. automodule:: gssapi.raw.oids
+.. autoapimodule:: gssapi.raw.oids
     :members:
     :undoc-members:
 
-.. automodule:: gssapi.raw.misc
+.. autoapimodule:: gssapi.raw.misc
     :members:
     :undoc-members:
 
-.. automodule:: gssapi.raw.types
+.. autoapimodule:: gssapi.raw.types
     :members:
     :undoc-members:
 
-.. automodule:: gssapi.raw.chan_bindings
+.. autoapimodule:: gssapi.raw.chan_bindings
     :members:
     :undoc-members:
 
@@ -77,74 +77,127 @@ The following is a list of GSSAPI extensions supported by the low-level API.
     be compiled, and will simply not be available in the :mod:`gssapi.raw`
     namespace.
 
+:rfc:`4178` (GSS-API Negotiation Mechanism)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. autoapimodule:: gssapi.raw.ext_rfc4178
+    :members:
+    :undoc-members:
+
+:rfc:`5587` (GSS-API Extension for Mech Attributes)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. autoapimodule:: gssapi.raw.ext_rfc5587
+    :members:
+    :undoc-members:
+
 :rfc:`5588` (GSS-API Extension for Storing Delegated Credentials)
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-.. automodule:: gssapi.raw.ext_rfc5588
+.. autoapimodule:: gssapi.raw.ext_rfc5588
+    :members:
+    :undoc-members:
+
+:rfc:`5801` (GSS-API SASL Extensions)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. autoapimodule:: gssapi.raw.ext_rfc5801
     :members:
     :undoc-members:
 
 Credential Store Extensions
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-.. automodule:: gssapi.raw.ext_cred_store
+.. autoapimodule:: gssapi.raw.ext_cred_store
     :members:
     :undoc-members:
 
 :rfc:`6680` (GSS-API Naming Extensions)
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-.. automodule:: gssapi.raw.ext_rfc6680
-    :members:
-    :undoc-members:
-
-.. automodule:: gssapi.raw.ext_rfc6680_comp_oid
+.. autoapimodule:: gssapi.raw.ext_rfc6680
     :members:
     :undoc-members:
 
 Credentials Import-Export Extensions
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-.. automodule:: gssapi.raw.ext_cred_imp_exp
+.. autoapimodule:: gssapi.raw.ext_cred_imp_exp
     :members:
     :undoc-members:
 
 DCE (IOV/AEAD) Extensions
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 
-.. automodule:: gssapi.raw.ext_dce
+.. autoapimodule:: gssapi.raw.ext_dce
     :members:
     :undoc-members:
 
+..
+    gssapi.raw.ext_dce_aead is imported with ext_dce so no need to double up.
+
+
 IOV MIC Extensions
 ~~~~~~~~~~~~~~~~~~
 
-.. automodule:: gssapi.raw.ext_iov_mic
+.. autoapimodule:: gssapi.raw.ext_iov_mic
+    :members:
+    :undoc-members:
+
+Global Grid Forum (GGF) Extensions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. autoapimodule:: gssapi.raw.ext_ggf
     :members:
     :undoc-members:
 
 Services4User Extensions
 ~~~~~~~~~~~~~~~~~~~~~~~~
 
-.. automodule:: gssapi.raw.ext_s4u
+.. autoapimodule:: gssapi.raw.ext_s4u
     :members:
     :undoc-members:
 
 Acquiring Credentials With a Password Extensions
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-.. automodule:: gssapi.raw.ext_password
+.. autoapimodule:: gssapi.raw.ext_password
     :members:
     :undoc-members:
 
-.. automodule:: gssapi.raw.ext_password_add
+.. autoapimodule:: gssapi.raw.ext_password_add
+    :members:
+    :undoc-members:
+
+Kerberos Specific Extensions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. autoapimodule:: gssapi.raw.ext_krb5
+    :members:
+    :undoc-members:
+
+Other Extensions
+~~~~~~~~~~~~~~~~
+
+.. autoapimodule:: gssapi.raw.ext_set_cred_opt
+    :members:
+    :undoc-members:
+
+Results
+-------
+
+..
+    Use autoapimodule once
+    https://github.com/readthedocs/sphinx-autoapi/issues/323 is resolved.
+
+.. automodule:: gssapi.raw.named_tuples
     :members:
     :undoc-members:
 
 Exceptions
 ----------
 
-.. automodule:: gssapi.raw.exceptions
+.. autoapimodule:: gssapi.raw.exceptions
     :members:
     :undoc-members:
     :show-inheritance:
diff --git a/gssapi/__init__.py b/gssapi/__init__.py
index cb89f73f..73aa9dfb 100644
--- a/gssapi/__init__.py
+++ b/gssapi/__init__.py
@@ -38,3 +38,17 @@
 from gssapi.mechs import Mechanism  # noqa
 
 from gssapi._utils import set_encoding  # noqa
+
+__all__ = [
+    'AddressType',
+    'Credentials',
+    'IntEnumFlagSet',
+    'Mechanism',
+    'MechType',
+    'Name',
+    'NameType',
+    'OID',
+    'RequirementFlag',
+    'SecurityContext',
+    'set_encoding',
+]
diff --git a/gssapi/_utils.py b/gssapi/_utils.py
index d2da9e81..0a2d8132 100644
--- a/gssapi/_utils.py
+++ b/gssapi/_utils.py
@@ -1,14 +1,18 @@
 import sys
 import types
+import typing as t
 
 import decorator as deco
 
-from typing import Optional
-
 from gssapi.raw.misc import GSSError
 
+if t.TYPE_CHECKING:
+    from gssapi.sec_contexts import SecurityContext
+
 
-def import_gssapi_extension(name):
+def import_gssapi_extension(
+    name: str,
+) -> t.Optional[types.ModuleType]:
     """Import a GSSAPI extension module
 
     This method imports a GSSAPI extension module based
@@ -31,20 +35,10 @@ def import_gssapi_extension(name):
         return None
 
 
-def flag_property(flag):
-    def setter(self, val):
-        if val:
-            self.flags.add(flag)
-        else:
-            self.flags.discard(flag)
-
-    def getter(self):
-        return flag in self.flags
-
-    return property(getter, setter)
-
-
-def inquire_property(name: str, doc: Optional[str] = None):
+def inquire_property(
+    name: str,
+    doc: t.Optional[str] = None
+) -> property:
     """Creates a property based on an inquire result
 
     This method creates a property that calls the
@@ -58,7 +52,7 @@ def inquire_property(name: str, doc: Optional[str] = None):
         property: the created property
     """
 
-    def inquire_property(self):
+    def inquire_property(self: "SecurityContext") -> t.Any:
         if not self._started:
             msg = (f"Cannot read {name} from a security context whose "
                    "establishment has not yet been started.")
@@ -73,7 +67,7 @@ def inquire_property(self):
 _ENCODING = 'UTF-8'
 
 
-def _get_encoding():
+def _get_encoding() -> str:
     """Gets the current encoding used for strings.
 
     This value is used to encode and decode string
@@ -85,7 +79,9 @@ def _get_encoding():
     return _ENCODING
 
 
-def set_encoding(enc):
+def set_encoding(
+    enc: str,
+) -> None:
     """Sets the current encoding used for strings
 
     This value is used to encode and decode string
@@ -99,9 +95,11 @@ def set_encoding(enc):
     _ENCODING = enc
 
 
-def _encode_dict(d):
+def _encode_dict(
+    d: t.Dict[t.Union[bytes, str], t.Union[bytes, str]],
+) -> t.Dict[bytes, bytes]:
     """Encodes any relevant strings in a dict"""
-    def enc(x):
+    def enc(x: t.Union[bytes, str]) -> bytes:
         if isinstance(x, str):
             return x.encode(_ENCODING)
         else:
@@ -112,7 +110,12 @@ def enc(x):
 
 # in case of Python 3, just use exception chaining
 @deco.decorator
-def catch_and_return_token(func, self, *args, **kwargs):
+def catch_and_return_token(
+    func: t.Callable,
+    self: "SecurityContext",
+    *args: t.Any,
+    **kwargs: t.Any,
+) -> t.Optional[bytes]:
     """Optionally defer exceptions and return a token instead
 
     When `__DEFER_STEP_ERRORS__` is set on the implementing class
@@ -127,10 +130,12 @@ def catch_and_return_token(func, self, *args, **kwargs):
     try:
         return func(self, *args, **kwargs)
     except GSSError as e:
-        if e.token is not None and self.__DEFER_STEP_ERRORS__:
+        defer_step_errors = getattr(self, '__DEFER_STEP_ERRORS__', False)
+        if e.token is not None and defer_step_errors:
             self._last_err = e
             # skip the "return func" line above in the traceback
-            self._last_err.__traceback__ = e.__traceback__.tb_next
+            tb = e.__traceback__.tb_next  # type: ignore[union-attr]
+            self._last_err.__traceback__ = tb
 
             return e.token
         else:
@@ -138,7 +143,12 @@ def catch_and_return_token(func, self, *args, **kwargs):
 
 
 @deco.decorator
-def check_last_err(func, self, *args, **kwargs):
+def check_last_err(
+    func: t.Callable,
+    self: "SecurityContext",
+    *args: t.Any,
+    **kwargs: t.Any,
+) -> t.Any:
     """Check and raise deferred errors before running the function
 
     This method checks :python:`_last_err` before running the wrapped
@@ -154,16 +164,6 @@ def check_last_err(func, self, *args, **kwargs):
     else:
         return func(self, *args, **kwargs)
 
-    @deco.decorator
-    def check_last_err(func, self, *args, **kwargs):
-        if self._last_err is not None:
-            try:
-                raise self._last_err
-            finally:
-                self._last_err = None
-        else:
-            return func(self, *args, **kwargs)
-
 
 class CheckLastError(type):
     """Check for a deferred error on all methods
@@ -174,7 +174,12 @@ class CheckLastError(type):
     Additionally, it enabled `__DEFER_STEP_ERRORS__` by default.
     """
 
-    def __new__(cls, name, parents, attrs):
+    def __new__(
+        cls,
+        name: str,
+        parents: t.Tuple[t.Type],
+        attrs: t.Dict[str, t.Any],
+    ) -> "CheckLastError":
         attrs['__DEFER_STEP_ERRORS__'] = True
 
         for attr_name in attrs:
diff --git a/gssapi/_win_config.py b/gssapi/_win_config.py
index ccbdb259..1ef0a581 100644
--- a/gssapi/_win_config.py
+++ b/gssapi/_win_config.py
@@ -8,6 +8,7 @@
 
 import os
 import shutil
+import sys
 import ctypes
 
 #: Path to normal KfW installed bin folder
@@ -18,18 +19,32 @@
 #: Download location for KfW
 KFW_DL = "https://web.mit.edu/KERBEROS/dist"
 
+# Mypy needs to run on both Win and non-Win so the missing attribute will fire
+# on non-Win and Win will fire with unused ignore. Instead just cache the attr
+# by name and use it as needed.
+ADD_DLL_DIR = getattr(os, "add_dll_directory", None)
+CTYPES_WIN_DLL = getattr(ctypes, "WinDLL", ctypes.CDLL)
 
-def kfw_available():
+
+def _add_dll_directory(path: str) -> None:
+    if ADD_DLL_DIR:
+        ADD_DLL_DIR(path)
+
+
+def kfw_available() -> bool:
     """Return if the main GSSAPI DLL for KfW can be loaded"""
     try:  # to load the main GSSAPI DLL
-        ctypes.WinDLL('gssapi64.dll')
+        if sys.maxsize > 2**32:
+            CTYPES_WIN_DLL('gssapi64.dll')
+        else:
+            CTYPES_WIN_DLL('gssapi32.dll')
     except OSError:  # DLL is not in PATH
         return False
     else:  # DLL is in PATH, everything should work
         return True
 
 
-def error_not_found():
+def error_not_found() -> None:
     """Raise an OSError detailing that KfW is missing and how to get it"""
     raise OSError(
         "Could not find KfW installation. Please download and install "
@@ -39,7 +54,7 @@ def error_not_found():
     )
 
 
-def configure_windows():
+def configure_windows() -> None:
     """
     Validate that KfW appears to be installed correctly and add it to the
     DLL directories/PATH if necessary. In the case that it can't be located,
@@ -50,7 +65,7 @@ def configure_windows():
 
     if os.path.exists(KFW_BIN):  # In standard location
         try:  # to use Python 3.8's DLL handling
-            os.add_dll_directory(KFW_BIN)
+            _add_dll_directory(KFW_BIN)
         except AttributeError:  # <3.8, use PATH
             os.environ['PATH'] += os.pathsep + KFW_BIN
         if kfw_available():
@@ -60,7 +75,7 @@ def configure_windows():
     kinit_path = shutil.which('kinit')  # KfW provided binary
     if kinit_path:  # Non-standard install location
         try:  # Most likely >=3.8, otherwise it would have been found already
-            os.add_dll_directory(os.path.dirname(kinit_path))
+            _add_dll_directory(os.path.dirname(kinit_path))
         except AttributeError:  # <3.8, corrupted installation?
             pass
         else:
diff --git a/gssapi/creds.py b/gssapi/creds.py
index 2082a1dc..ea871a91 100644
--- a/gssapi/creds.py
+++ b/gssapi/creds.py
@@ -1,5 +1,9 @@
+import typing as t
+
 from gssapi.raw import creds as rcreds
 from gssapi.raw import named_tuples as tuples
+from gssapi.raw import names as rnames
+from gssapi.raw import oids as roids
 from gssapi._utils import import_gssapi_extension, _encode_dict
 
 from gssapi import names
@@ -26,7 +30,7 @@ class Credentials(rcreds.Creds):
     credentials.
 
     If the `base` argument is used, an existing
-    :class:`~gssapi.raw.creds.Cred` object from the low-level API is
+    :class:`~gssapi.raw.creds.Creds` object from the low-level API is
     converted into a high-level object.
 
     If the `token` argument is used, the credentials
@@ -37,17 +41,27 @@ class Credentials(rcreds.Creds):
     :meth:`acquire` method.
 
     Raises:
-        BadMechanismError
-        BadNameTypeError
-        BadNameError
-        ExpiredCredentialsError
-        MissingCredentialsError
+        ~gssapi.exceptions.BadMechanismError
+        ~gssapi.exceptions.BadNameTypeError
+        ~gssapi.exceptions.BadNameError
+        ~gssapi.exceptions.ExpiredCredentialsError
+        ~gssapi.exceptions.MissingCredentialsError
     """
 
     __slots__ = ()
 
-    def __new__(cls, base=None, token=None, name=None, lifetime=None,
-                mechs=None, usage='both', store=None):
+    def __new__(
+        cls,
+        base: t.Optional[rcreds.Creds] = None,
+        token: t.Optional[bytes] = None,
+        name: t.Optional[rnames.Name] = None,
+        lifetime: t.Optional[int] = None,
+        mechs: t.Optional[t.Iterable[roids.OID]] = None,
+        usage: str = 'both',
+        store: t.Optional[
+            t.Dict[t.Union[bytes, str], t.Union[bytes, str]]
+        ] = None,
+    ) -> "Credentials":
         # TODO(directxman12): this is missing support for password
         #                     (non-RFC method)
         if base is not None:
@@ -64,35 +78,48 @@ def __new__(cls, base=None, token=None, name=None, lifetime=None,
                               store=store)
             base_creds = res.creds
 
-        return super(Credentials, cls).__new__(cls, base_creds)
+        return t.cast("Credentials",
+                      super(Credentials, cls).__new__(cls, base_creds))
 
     @property
-    def name(self):
+    def name(self) -> names.Name:
         """Get the name associated with these credentials"""
-        return self.inquire(name=True, lifetime=False,
-                            usage=False, mechs=False).name
+        return t.cast(names.Name,
+                      self.inquire(name=True, lifetime=False, usage=False,
+                                   mechs=False).name)
 
     @property
-    def lifetime(self):
-        """Get the remaining lifetime of these credentials"""
-        return self.inquire(name=False, lifetime=True,
-                            usage=False, mechs=False).lifetime
+    def lifetime(self) -> int:
+        """Get the remaining lifetime of these credentials, in seconds"""
+        return t.cast(int,
+                      self.inquire(name=False, lifetime=True,
+                                   usage=False, mechs=False).lifetime)
 
     @property
-    def mechs(self):
+    def mechs(self) -> t.Set[roids.OID]:
         """Get the mechanisms for these credentials"""
-        return self.inquire(name=False, lifetime=False,
-                            usage=False, mechs=True).mechs
+        return t.cast(t.Set[roids.OID],
+                      self.inquire(name=False, lifetime=False,
+                                   usage=False, mechs=True).mechs)
 
     @property
-    def usage(self):
+    def usage(self) -> str:
         """Get the usage (initiate, accept, or both) of these credentials"""
-        return self.inquire(name=False, lifetime=False,
-                            usage=True, mechs=False).usage
+        return t.cast(str,
+                      self.inquire(name=False, lifetime=False,
+                                   usage=True, mechs=False).usage)
 
     @classmethod
-    def acquire(cls, name=None, lifetime=None, mechs=None, usage='both',
-                store=None):
+    def acquire(
+        cls,
+        name: t.Optional[rnames.Name] = None,
+        lifetime: t.Optional[int] = None,
+        mechs: t.Optional[t.Iterable[roids.OID]] = None,
+        usage: str = 'both',
+        store: t.Optional[
+            t.Dict[t.Union[bytes, str], t.Union[bytes, str]]
+        ] = None,
+    ) -> tuples.AcquireCredResult:
         """Acquire GSSAPI credentials
 
         This method acquires credentials.  If the `store` argument is
@@ -108,10 +135,10 @@ def acquire(cls, name=None, lifetime=None, mechs=None, usage='both',
         extension.
 
         Args:
-            name (Name): the name associated with the credentials,
-                or None for the default name
-            lifetime (int): the desired lifetime of the credentials, or None
-                for indefinite
+            name (~gssapi.names.Name): the name associated with the
+                credentials, or None for the default name
+            lifetime (int): the desired lifetime of the credentials in seconds,
+                or None for indefinite
             mechs (list): the desired :class:`MechType` OIDs to be used
                 with the credentials, or None for the default set
             usage (str): the usage for the credentials -- either 'both',
@@ -122,14 +149,14 @@ def acquire(cls, name=None, lifetime=None, mechs=None, usage='both',
 
         Returns:
             AcquireCredResult: the acquired credentials and information about
-                them
+            them
 
         Raises:
-            BadMechanismError
-            BadNameTypeError
-            BadNameError
-            ExpiredCredentialsError
-            MissingCredentialsError
+            ~gssapi.exceptions.BadMechanismError
+            ~gssapi.exceptions.BadNameTypeError
+            ~gssapi.exceptions.BadNameError
+            ~gssapi.exceptions.ExpiredCredentialsError
+            ~gssapi.exceptions.MissingCredentialsError
         """
 
         if store is None:
@@ -141,17 +168,25 @@ def acquire(cls, name=None, lifetime=None, mechs=None, usage='both',
                                           "not have support for manipulating "
                                           "credential stores")
 
-            store = _encode_dict(store)
+            b_store = _encode_dict(store)
 
-            res = rcred_cred_store.acquire_cred_from(store, name,
+            res = rcred_cred_store.acquire_cred_from(b_store, name,
                                                      lifetime, mechs,
                                                      usage)
 
         return tuples.AcquireCredResult(cls(base=res.creds), res.mechs,
                                         res.lifetime)
 
-    def store(self, store=None, usage='both', mech=None,
-              overwrite=False, set_default=False):
+    def store(
+        self,
+        store: t.Optional[
+            t.Dict[t.Union[bytes, str], t.Union[bytes, str]]
+        ] = None,
+        usage: str = 'both',
+        mech: t.Optional[roids.OID] = None,
+        overwrite: bool = False,
+        set_default: bool = False,
+    ) -> tuples.StoreCredResult:
         """Store these credentials into the given store
 
         This method stores the current credentials into the specified
@@ -166,7 +201,7 @@ def store(self, store=None, usage='both', mech=None,
                 or None for the default store.
             usage (str): the usage to store the credentials with -- either
                 'both', 'initiate', or 'accept'
-            mech (OID): the :class:`MechType` to associate with the
+            mech (~gssapi.OID): the :class:`MechType` to associate with the
                 stored credentials
             overwrite (bool): whether or not to overwrite existing credentials
                 stored with the same name, etc
@@ -177,11 +212,11 @@ def store(self, store=None, usage='both', mech=None,
             StoreCredResult: the results of the credential storing operation
 
         Raises:
-            GSSError
-            ExpiredCredentialsError
-            MissingCredentialsError
-            OperationUnavailableError
-            DuplicateCredentialsElementError
+            ~gssapi.exceptions.GSSError
+            ~gssapi.exceptions.ExpiredCredentialsError
+            ~gssapi.exceptions.MissingCredentialsError
+            ~gssapi.exceptions.OperationUnavailableError
+            ~gssapi.exceptions.DuplicateCredentialsElementError
         """
 
         if store is None:
@@ -197,13 +232,18 @@ def store(self, store=None, usage='both', mech=None,
                                           "not have support for manipulating "
                                           "credential stores directly")
 
-            store = _encode_dict(store)
+            b_store = _encode_dict(store)
 
-            return rcred_cred_store.store_cred_into(store, self, usage, mech,
+            return rcred_cred_store.store_cred_into(b_store, self, usage, mech,
                                                     overwrite, set_default)
 
-    def impersonate(self, name=None, lifetime=None, mechs=None,
-                    usage='initiate'):
+    def impersonate(
+        self,
+        name: t.Optional[rnames.Name] = None,
+        lifetime: t.Optional[int] = None,
+        mechs: t.Optional[t.Iterable[roids.OID]] = None,
+        usage: str = 'initiate',
+    ) -> "Credentials":
         """Impersonate a name using the current credentials
 
         This method acquires credentials by impersonating another
@@ -212,9 +252,9 @@ def impersonate(self, name=None, lifetime=None, mechs=None,
         :requires-ext:`s4u`
 
         Args:
-            name (Name): the name to impersonate
-            lifetime (int): the desired lifetime of the new credentials,
-                or None for indefinite
+            name (~gssapi.names.Name): the name to impersonate
+            lifetime (int): the desired lifetime of the new credentials in
+                seconds, or None for indefinite
             mechs (list): the desired :class:`MechType` OIDs for the new
                 credentials
             usage (str): the desired usage for the new credentials -- either
@@ -235,7 +275,13 @@ def impersonate(self, name=None, lifetime=None, mechs=None,
 
         return type(self)(base=res.creds)
 
-    def inquire(self, name=True, lifetime=True, usage=True, mechs=True):
+    def inquire(
+        self,
+        name: bool = True,
+        lifetime: bool = True,
+        usage: bool = True,
+        mechs: bool = True,
+    ) -> tuples.InquireCredResult:
         """Inspect these credentials for information
 
         This method inspects these credentials for information about them.
@@ -248,12 +294,12 @@ def inquire(self, name=True, lifetime=True, usage=True, mechs=True):
 
         Returns:
             InquireCredResult: the information about the credentials,
-                with None used when the corresponding argument was False
+            with None used when the corresponding argument was False
 
         Raises:
-            MissingCredentialsError
-            InvalidCredentialsError
-            ExpiredCredentialsError
+            ~gssapi.exceptions.MissingCredentialsError
+            ~gssapi.exceptions.InvalidCredentialsError
+            ~gssapi.exceptions.ExpiredCredentialsError
         """
 
         res = rcreds.inquire_cred(self, name, lifetime, usage, mechs)
@@ -266,25 +312,32 @@ def inquire(self, name=True, lifetime=True, usage=True, mechs=True):
         return tuples.InquireCredResult(res_name, res.lifetime,
                                         res.usage, res.mechs)
 
-    def inquire_by_mech(self, mech, name=True, init_lifetime=True,
-                        accept_lifetime=True, usage=True):
+    def inquire_by_mech(
+        self,
+        mech: roids.OID,
+        name: bool = True,
+        init_lifetime: bool = True,
+        accept_lifetime: bool = True,
+        usage: bool = True,
+    ) -> tuples.InquireCredByMechResult:
         """Inspect these credentials for per-mechanism information
 
         This method inspects these credentials for per-mechanism information
         about them.
 
         Args:
-            mech (OID): the mechanism for which to retrive the information
+            mech (~gssapi.OID): the mechanism for which to retrieve the
+                information
             name (bool): get the name associated with the credentials
             init_lifetime (bool): get the remaining initiate lifetime for
-                the credentials
+                the credentials in seconds
             accept_lifetime (bool): get the remaining accept lifetime for
-                the credentials
+                the credentials in seconds
             usage (bool): get the usage for the credentials
 
         Returns:
             InquireCredByMechResult: the information about the credentials,
-                with None used when the corresponding argument was False
+            with None used when the corresponding argument was False
         """
 
         res = rcreds.inquire_cred_by_mech(self, mech, name, init_lifetime,
@@ -300,9 +353,18 @@ def inquire_by_mech(self, mech, name=True, init_lifetime=True,
                                               res.accept_lifetime,
                                               res.usage)
 
-    def add(self, name, mech, usage='both',
-            init_lifetime=None, accept_lifetime=None, impersonator=None,
-            store=None):
+    def add(
+        self,
+        name: rnames.Name,
+        mech: roids.OID,
+        usage: str = 'both',
+        init_lifetime: t.Optional[int] = None,
+        accept_lifetime: t.Optional[int] = None,
+        impersonator: t.Optional[rcreds.Creds] = None,
+        store: t.Optional[
+            t.Dict[t.Union[bytes, str], t.Union[bytes, str]]
+        ] = None,
+    ) -> "Credentials":
         """Acquire more credentials to add to the current set
 
         This method works like :meth:`acquire`, except that it adds the
@@ -327,16 +389,16 @@ def add(self, name, mech, usage='both',
         `impersonator` argument.
 
         Args:
-            name (Name): the name associated with the
-                credentials
-            mech (OID): the desired :class:`MechType` to be used with the
+            name (~gssapi.names.Name): the name associated with the
                 credentials
+            mech (~gssapi.OID): the desired :class:`MechType` to be used with
+                the credentials
             usage (str): the usage for the credentials -- either 'both',
                 'initiate', or 'accept'
             init_lifetime (int): the desired initiate lifetime of the
-                credentials, or None for indefinite
+                credentials in seconds, or None for indefinite
             accept_lifetime (int): the desired accept lifetime of the
-                credentials, or None for indefinite
+                credentials in seconds, or None for indefinite
             impersonator (Credentials): the credentials to use to impersonate
                 the given name, or None to not acquire normally
                 (:requires-ext:`s4u`)
@@ -346,15 +408,15 @@ def add(self, name, mech, usage='both',
 
         Returns:
             Credentials: the credentials set containing the current credentials
-                and the newly acquired ones.
+            and the newly acquired ones.
 
         Raises:
-            BadMechanismError
-            BadNameTypeError
-            BadNameError
-            DuplicateCredentialsElementError
-            ExpiredCredentialsError
-            MissingCredentialsError
+            ~gssapi.exceptions.BadMechanismError
+            ~gssapi.exceptions.BadNameTypeError
+            ~gssapi.exceptions.BadNameError
+            ~gssapi.exceptions.DuplicateCredentialsElementError
+            ~gssapi.exceptions.ExpiredCredentialsError
+            ~gssapi.exceptions.MissingCredentialsError
         """
 
         if store is not None and impersonator is not None:
@@ -366,9 +428,9 @@ def add(self, name, mech, usage='both',
                 raise NotImplementedError("Your GSSAPI implementation does "
                                           "not have support for manipulating "
                                           "credential stores")
-            store = _encode_dict(store)
+            b_store = _encode_dict(store)
 
-            res = rcred_cred_store.add_cred_from(store, self, name, mech,
+            res = rcred_cred_store.add_cred_from(b_store, self, name, mech,
                                                  usage, init_lifetime,
                                                  accept_lifetime)
         elif impersonator is not None:
@@ -385,7 +447,7 @@ def add(self, name, mech, usage='both',
 
         return Credentials(res.creds)
 
-    def export(self):
+    def export(self) -> bytes:
         """Export these credentials into a token
 
         This method exports the current credentials to a token that can
@@ -407,6 +469,8 @@ def export(self):
         return rcred_imp_exp.export_cred(self)
 
     # pickle protocol support
-    def __reduce__(self):
+    def __reduce__(
+        self,
+    ) -> t.Tuple[t.Type["Credentials"], t.Tuple[None, bytes]]:
         # the unpickle arguments to new are (base=None, token=self.export())
         return (type(self), (None, self.export()))
diff --git a/gssapi/exceptions.py b/gssapi/exceptions.py
index 332a2bf3..4775bfe1 100644
--- a/gssapi/exceptions.py
+++ b/gssapi/exceptions.py
@@ -1,3 +1,5 @@
+import typing as t
+
 from gssapi.raw.exceptions import *  # noqa
 from gssapi.raw.misc import GSSError  # noqa
 
@@ -15,7 +17,11 @@ class GeneralError(Exception):
     MAJOR_MESSAGE = "General error"
     FMT_STR = "{maj}: {min}."
 
-    def __init__(self, minor_message, **kwargs):
+    def __init__(
+        self,
+        minor_message: str,
+        **kwargs: str,
+    ) -> None:
         maj_str = self.MAJOR_MESSAGE.format(**kwargs)
         err_str = self.FMT_STR.format(maj=maj_str, min=minor_message)
         super(GeneralError, self).__init__(err_str)
@@ -30,7 +36,12 @@ class EncryptionNotUsed(GeneralError):
     """An Error indicating that encryption was requested, but not used"""
     MAJOR_MESSAGE = "Confidentiality was requested, but not used"
 
-    def __init__(self, minor_message, unwrapped_message=None, **kwargs):
+    def __init__(
+        self,
+        minor_message: str,
+        unwrapped_message: t.Optional[bytes] = None,
+        **kwargs: str,
+    ) -> None:
         super(EncryptionNotUsed, self).__init__(minor_message, **kwargs)
 
         self.unwrapped_message = unwrapped_message
diff --git a/gssapi/mechs.py b/gssapi/mechs.py
index 5e6b6825..a6d7d18c 100644
--- a/gssapi/mechs.py
+++ b/gssapi/mechs.py
@@ -1,6 +1,10 @@
-from gssapi.raw import oids as roids
+import typing as t
+
 from gssapi._utils import import_gssapi_extension
+from gssapi.raw import oids as roids
 from gssapi.raw import misc as rmisc
+from gssapi.raw import named_tuples as tuples
+from gssapi.raw import names as rnames
 from gssapi import _utils
 
 rfc5587 = import_gssapi_extension('rfc5587')
@@ -17,39 +21,44 @@ class Mechanism(roids.OID):
     It inherits from the low-level GSSAPI :class:`~gssapi.raw.oids.OID` class,
     and thus can be used with both low-level and high-level API calls.
     """
-    def __new__(cls, cpy=None, elements=None):
-        return super(Mechanism, cls).__new__(cls, cpy, elements)
+    def __new__(
+        cls,
+        cpy: t.Optional[roids.OID] = None,
+        elements: t.Optional[bytes] = None,
+    ) -> "Mechanism":
+        return t.cast("Mechanism",
+                      super(Mechanism, cls).__new__(cls, cpy, elements))
 
     @property
-    def name_types(self):
+    def name_types(self) -> t.Set[roids.OID]:
         """
         Get the set of name types supported by this mechanism.
         """
         return rmisc.inquire_names_for_mech(self)
 
     @property
-    def _saslname(self):
+    def _saslname(self) -> tuples.InquireSASLNameResult:
         if rfc5801 is None:
             raise NotImplementedError("Your GSSAPI implementation does not "
                                       "have support for RFC 5801")
         return rfc5801.inquire_saslname_for_mech(self)
 
     @property
-    def _attrs(self):
+    def _attrs(self) -> tuples.InquireAttrsResult:
         if rfc5587 is None:
             raise NotImplementedError("Your GSSAPI implementation does not "
                                       "have support for RFC 5587")
 
         return rfc5587.inquire_attrs_for_mech(self)
 
-    def __str__(self):
+    def __str__(self) -> str:
         return self._bytes_desc().decode(_utils._get_encoding())
 
-    def __unicode__(self):
+    def __unicode__(self) -> str:
         return self._bytes_desc().decode(_utils._get_encoding())
 
-    def _bytes_desc(self):
-        base = self.dotted_form
+    def _bytes_desc(self) -> bytes:
+        base: t.Union[bytes, str] = self.dotted_form
         if rfc5801 is not None and self._saslname and self._saslname.mech_name:
             base = self._saslname.mech_name
 
@@ -58,7 +67,7 @@ def _bytes_desc(self):
 
         return base
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         """
         Get a name representing the mechanism; always safe to call
         """
@@ -72,7 +81,7 @@ def __repr__(self):
         return base
 
     @property
-    def sasl_name(self):
+    def sasl_name(self) -> str:
         """
         Get the SASL name for the mechanism
 
@@ -81,7 +90,7 @@ def sasl_name(self):
         return self._saslname.sasl_mech_name.decode('UTF-8')
 
     @property
-    def description(self):
+    def description(self) -> str:
         """
         Get the description of the mechanism
 
@@ -90,7 +99,7 @@ def description(self):
         return self._saslname.mech_description.decode('UTF-8')
 
     @property
-    def known_attrs(self):
+    def known_attrs(self) -> t.Set[roids.OID]:
         """
         Get the known attributes of the mechanism; returns a set of OIDs
         ([OID])
@@ -100,7 +109,7 @@ def known_attrs(self):
         return self._attrs.known_mech_attrs
 
     @property
-    def attrs(self):
+    def attrs(self) -> t.Set[roids.OID]:
         """
         Get the attributes of the mechanism; returns a set of OIDs ([OID])
 
@@ -109,30 +118,36 @@ def attrs(self):
         return self._attrs.mech_attrs
 
     @classmethod
-    def all_mechs(cls):
+    def all_mechs(cls) -> t.Iterator["Mechanism"]:
         """
         Get a generator of all mechanisms supported by GSSAPI
         """
         return (cls(mech) for mech in rmisc.indicate_mechs())
 
     @classmethod
-    def from_name(cls, name=None):
+    def from_name(
+        cls,
+        name: rnames.Name,
+    ) -> t.Iterator["Mechanism"]:
         """
         Get a generator of mechanisms that may be able to process the name
 
         Args:
-            name (Name): a name to inquire about
+            name (~gssapi.names.Name): a name to inquire about
 
         Returns:
             [Mechanism]: a set of mechanisms which support this name
 
         Raises:
-            GSSError
+            ~gssapi.exceptions.GSSError
         """
         return (cls(mech) for mech in rmisc.inquire_mechs_for_name(name))
 
     @classmethod
-    def from_sasl_name(cls, name=None):
+    def from_sasl_name(
+        cls,
+        name: t.Optional[t.Union[bytes, str]] = None,
+    ) -> "Mechanism":
         """
         Create a Mechanism from its SASL name
 
@@ -143,7 +158,7 @@ def from_sasl_name(cls, name=None):
             Mechanism: the desired mechanism
 
         Raises:
-            GSSError
+            ~gssapi.exceptions.GSSError
 
         :requires-ext:`rfc5801`
         """
@@ -158,8 +173,18 @@ def from_sasl_name(cls, name=None):
         return cls(m)
 
     @classmethod
-    def from_attrs(cls, desired_attrs=None, except_attrs=None,
-                   critical_attrs=None):
+    def from_attrs(
+        cls,
+        desired_attrs: t.Optional[
+            t.Union[roids.OID, t.Iterable[roids.OID]]
+        ] = None,
+        except_attrs: t.Optional[
+            t.Union[roids.OID, t.Iterable[roids.OID]]
+        ] = None,
+        critical_attrs: t.Optional[
+            t.Union[roids.OID, t.Iterable[roids.OID]]
+        ] = None,
+    ) -> t.Iterator["Mechanism"]:
         """
         Get a generator of mechanisms supporting the specified attributes. See
         RFC 5587's :func:`indicate_mechs_by_attrs` for more information.
@@ -173,7 +198,7 @@ def from_attrs(cls, desired_attrs=None, except_attrs=None,
             [Mechanism]: A set of mechanisms having the desired features.
 
         Raises:
-            GSSError
+            ~gssapi.exceptions.GSSError
 
         :requires-ext:`rfc5587`
         """
diff --git a/gssapi/names.py b/gssapi/names.py
index acd4b8bd..c6fd972a 100644
--- a/gssapi/names.py
+++ b/gssapi/names.py
@@ -1,11 +1,14 @@
+
+import typing as t
+
 from gssapi.raw import names as rname
 from gssapi.raw import NameType
 from gssapi.raw import named_tuples as tuples
+from gssapi.raw import oids as roids
 from gssapi import _utils
 
 from collections.abc import MutableMapping, Iterable
 
-
 rname_rfc6680 = _utils.import_gssapi_extension('rfc6680')
 rname_rfc6680_comp_oid = _utils.import_gssapi_extension('rfc6680_comp_oid')
 
@@ -21,7 +24,7 @@ class Name(rname.Name):
 
     This class may be pickled and unpickled, as well as copied.
 
-    The :func:`str` and :func:`bytes` methods may be used to retrieve the
+    The :class:`str` and :class:`bytes` methods may be used to retrieve the
     text of the name.
 
     Note:
@@ -35,8 +38,13 @@ class Name(rname.Name):
 
     __slots__ = ('_attr_obj')
 
-    def __new__(cls, base=None, name_type=None, token=None,
-                composite=False):
+    def __new__(
+        cls,
+        base: t.Optional[t.Union[rname.Name, bytes, str]] = None,
+        name_type: t.Optional[roids.OID] = None,
+        token: t.Optional[bytes] = None,
+        composite: bool = False,
+    ) -> "Name":
         if token is not None:
             if composite:
                 if rname_rfc6680 is None:
@@ -67,11 +75,19 @@ def __new__(cls, base=None, name_type=None, token=None,
             if isinstance(base, str):
                 base = base.encode(_utils._get_encoding())
 
-            base_name = rname.import_name(base, name_type)
+            base_name = rname.import_name(
+                base,  # type: ignore[arg-type]
+                name_type)
 
-        return super(Name, cls).__new__(cls, base_name)
+        return t.cast("Name", super(Name, cls).__new__(cls, base_name))
 
-    def __init__(self, base=None, name_type=None, token=None, composite=False):
+    def __init__(
+        self,
+        base: t.Optional[t.Union[rname.Name, bytes, str]] = None,
+        name_type: t.Optional[roids.OID] = None,
+        token: t.Optional[bytes] = None,
+        composite: bool = False,
+    ) -> None:
         """
         The constructor can be used to "import" a name from a human readable
         representation, or from a token, and can also be used to convert a
@@ -91,33 +107,39 @@ def __init__(self, base=None, name_type=None, token=None, composite=False):
         name type.
 
         Raises:
-            BadNameTypeError
-            BadNameError
-            BadMechanismError
+            ~gssapi.exceptions.BadNameTypeError
+            ~gssapi.exceptions.BadNameError
+            ~gssapi.exceptions.BadMechanismError
         """
 
+        self._attr_obj: t.Optional[_NameAttributeMapping]
+
         if rname_rfc6680 is not None:
             self._attr_obj = _NameAttributeMapping(self)
         else:
             self._attr_obj = None
 
-    def __str__(self):
+    def __str__(self) -> str:
         return bytes(self).decode(_utils._get_encoding())
 
-    def __unicode__(self):
+    def __unicode__(self) -> str:
         # Python 2 -- someone asked for unicode
         return self.__bytes__().decode(_utils._get_encoding())
 
-    def __bytes__(self):
+    def __bytes__(self) -> bytes:
         # Python 3 -- someone asked for bytes
         return rname.display_name(self, name_type=False).name
 
-    def display_as(self, name_type):
+    def display_as(
+        self,
+        name_type: roids.OID,
+    ) -> str:
         """
         Display this name as the given name type.
 
         This method attempts to display the current :class:`Name`
-        using the syntax of the given :class:`NameType`, if possible.
+        using the syntax of the given :class:`~gssapi.raw.types.NameType`, if
+        possible.
 
         Warning:
 
@@ -137,14 +159,14 @@ def display_as(self, name_type):
         :requires-ext:`rfc6680`
 
         Args:
-            name_type (OID): the :class:`NameType` to use to display the given
-                name
+            name_type (~gssapi.OID): the :class:`~gssapi.raw.types.NameType` to
+                use to display the given name
 
         Returns:
             str: the displayed name
 
         Raises:
-            OperationUnavailableError
+            ~gssapi.exceptions.OperationUnavailableError
         """
 
         if rname_rfc6680 is None:
@@ -155,11 +177,14 @@ def display_as(self, name_type):
             _utils._get_encoding())
 
     @property
-    def name_type(self):
-        """The :class:`NameType` of this name"""
+    def name_type(self) -> t.Optional[roids.OID]:
+        """The :class:`~gssapi.raw.types.NameType` of this name"""
         return rname.display_name(self, name_type=True).name_type
 
-    def __eq__(self, other):
+    def __eq__(
+        self,
+        other: object,
+    ) -> bool:
         if not isinstance(other, rname.Name):
             # maybe something else can compare this
             # to other classes, but we certainly can't
@@ -167,15 +192,21 @@ def __eq__(self, other):
         else:
             return rname.compare_name(self, other)
 
-    def __ne__(self, other):
+    def __ne__(
+        self,
+        other: object,
+    ) -> bool:
         return not self.__eq__(other)
 
-    def __repr__(self):
+    def __repr__(self) -> str:
         disp_res = rname.display_name(self, name_type=True)
-        return "Name({name}, {name_type})".format(name=disp_res.name,
-                                                  name_type=disp_res.name_type)
+        return "Name({name!r}, {name_type})".format(
+            name=disp_res.name, name_type=disp_res.name_type)
 
-    def export(self, composite=False):
+    def export(
+        self,
+        composite: bool = False,
+    ) -> bytes:
         """Export this name as a token.
 
         This method exports the name into a byte string which can then be
@@ -189,9 +220,9 @@ def export(self, composite=False):
             bytes: the exported name in token form
 
         Raises:
-            MechanismNameRequiredError
-            BadNameTypeError
-            BadNameError
+            ~gssapi.exceptions.MechanismNameRequiredError
+            ~gssapi.exceptions.BadNameTypeError
+            ~gssapi.exceptions.BadNameError
         """
 
         if composite:
@@ -204,33 +235,42 @@ def export(self, composite=False):
         else:
             return rname.export_name(self)
 
-    def canonicalize(self, mech):
+    def canonicalize(
+        self,
+        mech: roids.OID
+    ) -> "Name":
         """Canonicalize a name with respect to a mechanism.
 
         This method returns a new :class:`Name` that is canonicalized according
         to the given mechanism.
 
         Args:
-            mech (OID): the :class:`MechType` to use
+            mech (~gssapi.OID): the :class:`MechType` to use
 
         Returns:
             Name: the canonicalized name
 
         Raises:
-            BadMechanismError
-            BadNameTypeError
-            BadNameError
+            ~gssapi.exceptions.BadMechanismError
+            ~gssapi.exceptions.BadNameTypeError
+            ~gssapi.exceptions.BadNameError
         """
 
         return type(self)(rname.canonicalize_name(self, mech))
 
-    def __copy__(self):
+    def __copy__(self) -> "Name":
         return type(self)(rname.duplicate_name(self))
 
-    def __deepcopy__(self, memo):
+    def __deepcopy__(
+        self,
+        memo: t.Dict,
+    ) -> "Name":
         return type(self)(rname.duplicate_name(self))
 
-    def _inquire(self, **kwargs):
+    def _inquire(
+        self,
+        **kwargs: t.Any,
+    ) -> tuples.InquireNameResult:
         """Inspect this name for information.
 
         This method inspects the name for information.
@@ -249,7 +289,7 @@ def _inquire(self, **kwargs):
                 fields set to None
 
         Raises:
-            GSSError
+            ~gssapi.exceptions.GSSError
         """
 
         if rname_rfc6680 is None:
@@ -269,26 +309,26 @@ def _inquire(self, **kwargs):
                                           attrs=attrs)
 
     @property
-    def is_mech_name(self):
+    def is_mech_name(self) -> bool:
         """Whether or not this name is a mechanism name
         (:requires-ext:`rfc6680`)
         """
         return self._inquire(mech_name=True).is_mech_name
 
     @property
-    def mech(self):
+    def mech(self) -> roids.OID:
         """The mechanism associated with this name (:requires-ext:`rfc6680`)
         """
         return self._inquire(mech_name=True).mech
 
     @property
-    def attributes(self):
+    def attributes(self) -> t.Optional[MutableMapping]:
         """The attributes of this name (:requires-ext:`rfc6680`)
 
         The attributes are presenting in the form of a
-        :class:`~collections.MutableMapping` (a dict-like object).
+        :class:`~collections.abc.MutableMapping` (a dict-like object).
 
-        Retrieved values will always be in the form of :class:`frozensets`.
+        Retrieved values will always be in the form of :class:`frozenset`.
 
         When assigning values, if iterables are used, they be considered to be
         the set of values for the given attribute.  If a non-iterable is used,
@@ -310,31 +350,48 @@ def attributes(self):
 class _NameAttributeMapping(MutableMapping):
 
     """Provides dict-like access to RFC 6680 Name attributes."""
-    def __init__(self, name):
+    def __init__(
+        self,
+        name: Name,
+    ) -> None:
         self._name = name
 
-    def __getitem__(self, key):
+    def __getitem__(
+        self,
+        key: t.Union[bytes, str],
+    ) -> tuples.GetNameAttributeResult:
         if isinstance(key, str):
             key = key.encode(_utils._get_encoding())
 
-        res = rname_rfc6680.get_name_attribute(self._name, key)
-        return tuples.GetNameAttributeResult(frozenset(res.values),
-                                             frozenset(res.display_values),
+        res = rname_rfc6680.get_name_attribute(  # type: ignore[union-attr]
+            self._name, key)
+        res = t.cast(tuples.GetNameAttributeResult, res)
+
+        return tuples.GetNameAttributeResult(list(res.values),
+                                             list(res.display_values),
                                              res.authenticated,
                                              res.complete)
 
-    def __setitem__(self, key, value):
+    def __setitem__(
+        self,
+        key: t.Union[bytes, str],
+        value: t.Union[
+            tuples.GetNameAttributeResult, t.Tuple[bytes, bool], bytes
+        ],
+    ) -> None:
         if isinstance(key, str):
             key = key.encode(_utils._get_encoding())
 
-        rname_rfc6680.delete_name_attribute(self._name, key)
+        rname_rfc6680.delete_name_attribute(  # type: ignore[union-attr]
+            self._name, key)
 
+        attr_value: t.List[bytes]
         if isinstance(value, tuples.GetNameAttributeResult):
             complete = value.complete
-            value = value.values
+            attr_value = value.values
         elif isinstance(value, tuple) and len(value) == 2:
-            complete = value[1]
-            value = value[0]
+            complete = t.cast(bool, value[1])
+            attr_value = [t.cast(bytes, value[0])]
         else:
             complete = False
 
@@ -342,19 +399,23 @@ def __setitem__(self, key, value):
                 not isinstance(value, Iterable)):
             # NB(directxman12): this allows us to easily assign a single
             # value, since that's a common case
-            value = [value]
+            attr_value = [value]
 
-            rname_rfc6680.set_name_attribute(self._name, key, value,
-                                             complete=complete)
+        rname_rfc6680.set_name_attribute(  # type: ignore[union-attr]
+            self._name, key, attr_value, complete=complete)
 
-    def __delitem__(self, key):
+    def __delitem__(
+        self,
+        key: t.Union[bytes, str],
+    ) -> None:
         if isinstance(key, str):
             key = key.encode(_utils._get_encoding())
 
-        rname_rfc6680.delete_name_attribute(self._name, key)
+        rname_rfc6680.delete_name_attribute(  # type: ignore[union-attr]
+            self._name, key)
 
-    def __iter__(self):
+    def __iter__(self) -> t.Iterator[bytes]:
         return iter(self._name._inquire(attrs=True).attrs)
 
-    def __len__(self):
+    def __len__(self) -> int:
         return len(self._name._inquire(attrs=True).attrs)
diff --git a/gssapi/py.typed b/gssapi/py.typed
new file mode 100644
index 00000000..e69de29b
diff --git a/gssapi/raw/__init__.py b/gssapi/raw/__init__.py
index a74b74e9..0699c419 100644
--- a/gssapi/raw/__init__.py
+++ b/gssapi/raw/__init__.py
@@ -30,7 +30,7 @@
 are in modules of the form `gssapi.raw.ext_xyz`.
 
 All available functions and classes can be accessed directly from this
-module (`gssapi.raw`) -- it is unneccessary to directly import submodules.
+module (`gssapi.raw`) -- it is unnecessary to directly import submodules.
 """
 
 
@@ -111,7 +111,7 @@
 except ImportError:
     pass
 
-# optional DCE (IOV/AEAD) support
+# optional DCE (IOV) support
 try:
     from gssapi.raw.ext_dce import *  # noqa
     # optional IOV MIC support (requires DCE support)
@@ -119,6 +119,18 @@
 except ImportError:
     pass
 
+# optional DCE (AEAD) support
+try:
+    from gssapi.raw.ext_dce_aead import *  # noqa
+except ImportError:
+    pass
+
+# optional KRB5 specific extension support
+try:
+    from gssapi.raw.ext_krb5 import *  # noqa
+except ImportError:
+    pass
+
 # optional RFC 6680 support
 try:
     from gssapi.raw.ext_rfc6680 import *  # noqa
diff --git a/gssapi/raw/_enum_extensions/__init__.py b/gssapi/raw/_enum_extensions/__init__.py
index 8051c50c..96622e1f 100644
--- a/gssapi/raw/_enum_extensions/__init__.py
+++ b/gssapi/raw/_enum_extensions/__init__.py
@@ -1,16 +1,27 @@
+import typing as t
+
 from enum import EnumMeta
 
 
-_extra_values = {}
+_extra_values: t.Dict[str, t.Dict[str, t.Any]] = {}
 
 
-def register_value(cl_str, name, value):
+def register_value(
+    cl_str: str,
+    name: str,
+    value: t.Any,
+) -> None:
     _extra_values[cl_str] = _extra_values.get(cl_str, {})
     _extra_values[cl_str][name] = value
 
 
 class ExtendableEnum(EnumMeta):
-    def __new__(metacl, name, bases, classdict):
+    def __new__(
+        metacl,
+        name: str,
+        bases: t.Tuple[t.Type],
+        classdict: t.Dict[str, t.Any],
+    ) -> "ExtendableEnum":
         extra_vals = _extra_values.get(name)
 
         if extra_vals is not None:
@@ -22,5 +33,9 @@ def __new__(metacl, name, bases, classdict):
                 else:
                     classdict[extra_name] = extra_val
 
-        return super(ExtendableEnum, metacl).__new__(metacl, name,
-                                                     bases, classdict)
+        return super(ExtendableEnum, metacl).__new__(
+            metacl,
+            name,
+            bases,
+            classdict,  # type: ignore[arg-type] # Uses private explicit type
+        )
diff --git a/gssapi/raw/_enum_extensions/ext_dce.pyx b/gssapi/raw/_enum_extensions/ext_dce.pyx
index fd2989ae..190c8285 100644
--- a/gssapi/raw/_enum_extensions/ext_dce.pyx
+++ b/gssapi/raw/_enum_extensions/ext_dce.pyx
@@ -1,6 +1,6 @@
 from gssapi.raw.cython_types cimport OM_uint32
 
-import gssapi.raw._enum_extensions as ext_registry
+from gssapi.raw import _enum_extensions as ext_registry
 
 
 cdef extern from "python_gssapi_ext.h":
diff --git a/gssapi/raw/chan_bindings.pyi b/gssapi/raw/chan_bindings.pyi
new file mode 100644
index 00000000..bd1a2e1b
--- /dev/null
+++ b/gssapi/raw/chan_bindings.pyi
@@ -0,0 +1,30 @@
+import typing as t
+
+
+class ChannelBindings:
+    """GSSAPI Channel Bindings
+
+    This class represents a set of GSSAPI channel bindings.
+
+    Args:
+        initiator_address_type: the initiator address type
+        initiator_address: the initiator address
+        acceptor_address_type:  the acceptor address type
+        acceptor_address: the acceptor address
+        application_data: additional application-specific data
+    """
+
+    initiator_address_type: t.Optional[int]
+    initiator_address: t.Optional[bytes]
+    acceptor_address_type: t.Optional[int]
+    acceptor_address: t.Optional[bytes]
+    application_data: t.Optional[bytes]
+
+    def __init__(
+        self,
+        initiator_address_type: t.Optional[int] = None,
+        initiator_address: t.Optional[bytes] = None,
+        acceptor_address_type: t.Optional[int] = None,
+        acceptor_address: t.Optional[bytes] = None,
+        application_data: t.Optional[bytes] = None,
+    ) -> None: ...
diff --git a/gssapi/raw/chan_bindings.pyx b/gssapi/raw/chan_bindings.pyx
index 737b5b1a..cb9c6e38 100644
--- a/gssapi/raw/chan_bindings.pyx
+++ b/gssapi/raw/chan_bindings.pyx
@@ -3,11 +3,6 @@ from libc.stdlib cimport calloc, free
 from gssapi.raw.cython_types cimport *
 
 cdef class ChannelBindings:
-    """GSSAPI Channel Bindings
-
-    This class represents a set of GSSAPI channel bindings.
-    """
-
     # defined in pxd file
     # cdef public object initiator_address_type
     # cdef public bytes initiator_address
@@ -20,15 +15,6 @@ cdef class ChannelBindings:
     def __init__(ChannelBindings self, initiator_address_type=None,
                  initiator_address=None, acceptor_address_type=None,
                  acceptor_address=None, application_data=None):
-        """
-        Args:
-            initiator_address_type (AddressType): the initiator address type
-            initiator_address (bytes): the initiator address
-            acceptor_address_type (AddressType):  the acceptor address type
-            acceptor_address (bytes): the acceptor address
-            application_data (bytes): additional application-specific data
-        """
-
         self.initiator_address_type = initiator_address_type
         self.initiator_address = initiator_address
 
diff --git a/gssapi/raw/creds.pyi b/gssapi/raw/creds.pyi
new file mode 100644
index 00000000..723476cd
--- /dev/null
+++ b/gssapi/raw/creds.pyi
@@ -0,0 +1,188 @@
+import typing as t
+
+if t.TYPE_CHECKING:
+    from gssapi.raw.names import Name
+    from gssapi.raw.oids import OID
+    from gssapi.raw.named_tuples import (
+        AcquireCredResult,
+        AddCredResult,
+        InquireCredResult,
+        InquireCredByMechResult,
+    )
+
+
+class Creds:
+    """
+    GSSAPI Credentials
+    """
+
+    def __new__(
+        cls,
+        cpy: t.Optional["Creds"] = None,
+    ) -> "Creds": ...
+
+
+def acquire_cred(
+    name: t.Optional["Name"],
+    lifetime: t.Optional[int] = None,
+    mechs: t.Optional[t.Iterable["OID"]] = None,
+    usage: str = 'both',
+) -> "AcquireCredResult":
+    """Get GSSAPI credentials for the given name and mechanisms.
+
+    This method gets GSSAPI credentials corresponding to the given name
+    and mechanims.  The desired TTL and usage for the the credential may also
+    be specified.
+
+    Args:
+        name (~gssapi.raw.names.Name): the name for which to acquire the
+            credentials (or None for the "no name" functionality)
+        lifetime (int): the lifetime in seconds for the credentials (or None
+            for indefinite)
+        mechs (~gssapi.raw.types.MechType): the desired mechanisms for which
+            the credentials should work, or None for the default set
+        usage (str): the usage type for the credentials: may be
+            'initiate', 'accept', or 'both'
+
+    Returns:
+        AcquireCredResult: the resulting credentials, the actual mechanisms
+        with which they may be used, and their actual lifetime in seconds (or
+        None for indefinite or not supported)
+
+    Raises:
+        ~gssapi.exceptions.BadMechanismError
+        ~gssapi.exceptions.BadNameTypeError
+        ~gssapi.exceptions.BadNameError
+        ~gssapi.exceptions.ExpiredCredentialsError
+        ~gssapi.exceptions.MissingCredentialsError
+    """
+
+
+def release_cred(
+    creds: Creds,
+) -> None:
+    """
+    release_cred(creds)
+    Release GSSAPI Credentials.
+
+    This method releases GSSAPI credentials.
+
+    Warning:
+        This method is deprecated.  Credentials are
+        automatically freed by Python.
+
+    Args:
+        creds (Creds): the credentials in question
+
+    Raises:
+        ~gssapi.exceptions.MissingCredentialsError
+    """
+
+
+def add_cred(
+    input_cred: Creds,
+    name: "Name",
+    mech: "OID",
+    usage: str = 'initiate',
+    init_lifetime: t.Optional[int] = None,
+    accept_lifetime: t.Optional[int] = None,
+    mutate_input: bool = False,
+) -> "AddCredResult":
+    """Add a credential element to a credential.
+
+    This method can be used to either compose two credentials (i.e., original
+    and new credential), or to add a new element to an existing credential.
+
+    Args:
+        input_cred (Creds): the set of credentials to which to add the new
+            credentials
+        name (~gssapi.raw.names.Name): name of principal to acquire a
+            credential for
+        mech (~gssapi.raw.types.MechType): the desired security mechanism
+            (required).
+        usage (str): usage type for credentials.  Possible values:
+            'initiate' (default), 'accept', 'both' (failsafe).
+        init_lifetime (int): lifetime of credentials for use in initiating
+            security contexts in seconds (None for indefinite)
+        accept_lifetime (int): lifetime of credentials for use in accepting
+            security contexts in seconds (None for indefinite)
+        mutate_input (bool): whether to mutate the input credentials (True)
+            or produce a new set of credentials (False).  Defaults to False
+
+    Returns:
+        AddCredResult: the actual mechanisms with which the credentials may be
+        used, the actual initiator TTL, and the actual acceptor TTL (None for
+        either indefinite or not supported).  Note that the credentials may
+        be set to None if mutate_input is set to True.
+
+    Raises:
+        ~gssapi.exceptions.BadMechanismError
+        ~gssapi.exceptions.BadNameTypeError
+        ~gssapi.exceptions.BadNameError
+        ~gssapi.exceptions.DuplicateCredentialsElementError
+        ~gssapi.exceptions.ExpiredCredentialsError
+        ~gssapi.exceptions.MissingCredentialsError
+    """
+
+
+def inquire_cred(
+    creds: Creds,
+    name: bool = True,
+    lifetime: bool = True,
+    usage: bool = True,
+    mechs: bool = True,
+) -> "InquireCredResult":
+    """Inspect credentials for information.
+
+    This method inspects a :class:`Creds` object for information.
+
+    Args:
+        creds (Creds): the credentials to inspect
+        name (bool): get the Name associated with the credentials
+        lifetime (bool): get the TTL for the credentials
+        usage (bool): get the usage type of the credentials
+        mechs (bool): the mechanims used with the credentials
+
+    Returns:
+        InquireCredResult: the information about the credentials,
+        with unused fields set to None
+
+    Raises:
+        ~gssapi.exceptions.MissingCredentialsError
+        ~gssapi.exceptions.InvalidCredentialsError
+        ~gssapi.exceptions.ExpiredCredentialsError
+    """
+
+
+def inquire_cred_by_mech(
+    creds: Creds,
+    mech: "OID",
+    name: bool = True,
+    init_lifetime: bool = True,
+    accept_lifetime: bool = True,
+    usage: bool = True,
+) -> "InquireCredByMechResult":
+    """Inspect credentials for mechanism-specific information.
+
+    This method inspects a :class:`Creds` object for information
+    specific to a particular mechanism.  It functions similarly
+    to :func:`inquire_cred`.
+
+    Args:
+        creds (Creds): the credentials to inspect
+        mech (~gssapi.OID): the desired mechanism
+        name (bool): get the Name associated with the credentials
+        init_lifetime (bool): get the initiator TTL for the credentials (in
+            seconds)
+        accept_lifetime (bool): get the acceptor TTL for the credentials (in
+            seconds)
+        usage (bool): get the usage type of the credentials
+
+    Returns:
+        InquireCredByMechResult: the information about the credentials,
+        with unused fields set to None
+
+    Raises:
+        ~gssapi.exceptions.MissingCredentialsError
+        ~gssapi.exceptions.InvalidCredentialsError
+    """
diff --git a/gssapi/raw/creds.pyx b/gssapi/raw/creds.pyx
index d123857d..6a01cd02 100644
--- a/gssapi/raw/creds.pyx
+++ b/gssapi/raw/creds.pyx
@@ -56,9 +56,6 @@ cdef extern from "python_gssapi.h":
 
 
 cdef class Creds:
-    """
-    GSSAPI Credentials
-    """
     # defined in pxd
     # cdef gss_cred_id_t raw_creds
 
@@ -81,37 +78,6 @@ cdef class Creds:
 
 
 def acquire_cred(Name name=None, lifetime=None, mechs=None, usage='both'):
-    """
-    acquire_cred(name=None, lifetime=None, mechs=None, usage='both')
-    Get GSSAPI credentials for the given name and mechanisms.
-
-    This method gets GSSAPI credentials corresponding to the given name
-    and mechanims.  The desired TTL and usage for the the credential may also
-    be specified.
-
-    Args:
-        name (Name): the name for which to acquire the credentials (or None
-            for the "no name" functionality)
-        lifetime (int): the lifetime for the credentials (or None for
-            indefinite)
-        mechs ([MechType]): the desired mechanisms for which the credentials
-            should work, or None for the default set
-        usage (str): the usage type for the credentials: may be
-            'initiate', 'accept', or 'both'
-
-    Returns:
-        AcquireCredResult: the resulting credentials, the actual mechanisms
-        with which they may be used, and their actual lifetime (or None for
-        indefinite or not supported)
-
-    Raises:
-        BadMechanismError
-        BadNameTypeError
-        BadNameError
-        ExpiredCredentialsError
-        MissingCredentialsError
-    """
-
     cdef gss_OID_set desired_mechs
     if mechs is not None:
         desired_mechs = c_get_mech_oid_set(mechs)
@@ -162,23 +128,6 @@ def acquire_cred(Name name=None, lifetime=None, mechs=None, usage='both'):
 
 
 def release_cred(Creds creds not None):
-    """
-    release_cred(creds)
-    Release GSSAPI Credentials.
-
-    This method releases GSSAPI credentials.
-
-    Warning:
-        This method is deprecated.  Credentials are
-        automatically freed by Python.
-
-    Args:
-        creds (Creds): the credentials in question
-
-    Raises:
-        MissingCredentialsError
-    """
-
     cdef OM_uint32 maj_stat, min_stat
     maj_stat = gss_release_cred(&min_stat, &creds.raw_creds)
     if maj_stat != GSS_S_COMPLETE:
@@ -189,42 +138,6 @@ def release_cred(Creds creds not None):
 def add_cred(Creds input_cred, Name name not None, OID mech not None,
              usage='initiate', init_lifetime=None,
              accept_lifetime=None, mutate_input=False):
-    """
-    add_cred(input_cred, name, mech, usage='initiate', init_lifetime=None, \
-accept_lifetime=None, mutate_input=False)
-    Add a credential element to a credential.
-
-    This method can be used to either compose two credentials (i.e., original
-    and new credential), or to add a new element to an existing credential.
-
-    Args:
-        input_cred (Cred): the set of credentials to which to add the new
-            credentials
-        name (Name): name of principal to acquire a credential for
-        mech (MechType): the desired security mechanism (required).
-        usage (str): usage type for credentials.  Possible values:
-            'initiate' (default), 'accept', 'both' (failsafe).
-        init_lifetime (int): lifetime of credentials for use in initiating
-            security contexts (None for indefinite)
-        accept_lifetime (int): lifetime of credentials for use in accepting
-            security contexts (None for indefinite)
-        mutate_input (bool): whether to mutate the input credentials (True)
-            or produce a new set of credentials (False).  Defaults to False
-
-    Returns:
-        AddCredResult: the actual mechanisms with which the credentials may be
-        used, the actual initiator TTL, and the actual acceptor TTL (None for
-        either indefinite or not supported).  Note that the credentials may
-        be set to None if mutate_input is set to True.
-
-    Raises:
-        BadMechanismError
-        BadNameTypeError
-        BadNameError
-        DuplicateCredentialsElementError
-        ExpiredCredentialsError
-        MissingCredentialsError
-    """
     cdef gss_cred_usage_t c_usage
     if usage == 'initiate':
         c_usage = GSS_C_INITIATE
@@ -279,29 +192,6 @@ accept_lifetime=None, mutate_input=False)
 
 def inquire_cred(Creds creds not None, name=True, lifetime=True, usage=True,
                  mechs=True):
-    """
-    inquire_cred(creds, name=True, lifetime=True, usage=True, mechs=True)
-    Inspect credentials for information.
-
-    This method inspects a :class:`Creds` object for information.
-
-    Args:
-        creds (Creds): the credentials to inspect
-        name (bool): get the Name associated with the credentials
-        lifetime (bool): get the TTL for the credentials
-        usage (bool): get the usage type of the credentials
-        mechs (bool): the mechanims used with the credentials
-
-    Returns:
-        InquireCredResult: the information about the credentials,
-            with unused fields set to None
-
-    Raises:
-        MissingCredentialsError
-        InvalidCredentialsError
-        ExpiredCredentialsError
-    """
-
     # TODO(directxman12): add docs
     cdef gss_name_t res_name
     cdef gss_name_t *res_name_ptr = NULL
@@ -360,32 +250,6 @@ def inquire_cred(Creds creds not None, name=True, lifetime=True, usage=True,
 def inquire_cred_by_mech(Creds creds not None, OID mech not None,
                          name=True, init_lifetime=True,
                          accept_lifetime=True, usage=True):
-    """
-    inquire_cred_by_mech(creds, mech, name=True, init_lifetime=True, \
-accept_lifetime=True, usage=True)
-    Inspect credentials for mechanism-specific information.
-
-    This method inspects a :class:`Creds` object for information
-    specific to a particular mechanism.  It functions similarly
-    to :func:`inquire_cred`.
-
-    Args:
-        creds (Creds): the credentials to inspect
-        mech (OID): the desired mechanism
-        name (bool): get the Name associated with the credentials
-        init_lifetime (bool): get the initiator TTL for the credentials
-        accept_lifetime (bool): get the acceptor TTL for the credentials
-        usage (bool): get the usage type of the credentials
-
-    Returns:
-        InquireCredByMechResult: the information about the credentials,
-            with unused fields set to None
-
-    Raises:
-        MissingCredentialsError
-        InvalidCredentialsError
-    """
-
     # TODO(directxman12): add docs
     cdef gss_name_t res_name
     cdef gss_name_t *res_name_ptr = NULL
diff --git a/gssapi/raw/exceptions.pyi b/gssapi/raw/exceptions.pyi
new file mode 100644
index 00000000..4e2328f2
--- /dev/null
+++ b/gssapi/raw/exceptions.pyi
@@ -0,0 +1,67 @@
+from gssapi.raw.misc import GSSError
+
+class ParameterReadError(GSSError): ...
+
+class ParameterWriteError(GSSError): ...
+
+class MalformedParameterError(GSSError): ...
+
+class BadMechanismError(GSSError): ...
+
+class BadNameError(GSSError): ...
+
+class BadNameTypeError(GSSError): ...
+
+class BadChannelBindingsError(GSSError): ...
+
+class BadStatusError(GSSError): ...
+
+class BadMICError(GSSError): ...
+
+class MissingCredentialsError(GSSError): ...
+
+class MissingContextError(GSSError): ...
+
+class InvalidTokenError(GSSError): ...
+
+class InvalidCredentialsError(GSSError): ...
+
+class ExpiredCredentialsError(GSSError): ...
+
+class ExpiredContextError(GSSError): ...
+
+class BadQoPError(GSSError): ...
+
+class UnauthorizedError(GSSError): ...
+
+class OperationUnavailableError(GSSError): ...
+
+class DuplicateCredentialsElementError(GSSError): ...
+
+class MechanismNameRequiredError(GSSError): ...
+
+class NameReadError(ParameterReadError, BadNameError): ...
+
+class NameTypeReadError(ParameterReadError, BadNameTypeError): ...
+
+class TokenReadError(ParameterReadError, InvalidTokenError): ...
+
+class ContextReadError(ParameterReadError, MissingContextError): ...
+
+class CredentialsReadError(ParameterReadError, MissingCredentialsError): ...
+
+class ContextWriteError(ParameterWriteError, MissingContextError): ...
+
+class CredentialsWriteError(ParameterWriteError, MissingCredentialsError): ...
+
+class SupplementaryError(GSSError): ...
+
+class DuplicateTokenError(SupplementaryError): ...
+
+class ExpiredTokenError(SupplementaryError): ...
+
+class TokenOutOfSequenceError(SupplementaryError): ...
+
+class TokenTooLateError(TokenOutOfSequenceError): ...
+
+class TokenTooEarlyError(TokenOutOfSequenceError): ...
diff --git a/gssapi/raw/ext_cred_imp_exp.pyi b/gssapi/raw/ext_cred_imp_exp.pyi
new file mode 100644
index 00000000..2677e45c
--- /dev/null
+++ b/gssapi/raw/ext_cred_imp_exp.pyi
@@ -0,0 +1,44 @@
+"""Credentials Import/Export Extension"""
+
+import typing as t
+
+if t.TYPE_CHECKING:
+    from gssapi.raw.creds import Creds
+
+
+def export_cred(
+    creds: "Creds",
+) -> bytes:
+    """Export GSSAPI credentials.
+
+    This method exports GSSSAPI credentials into a token
+    which may be transmitted between different processes.
+
+    Args:
+        creds (Creds): the credentials object to be exported
+
+    Returns:
+        bytes: the exported token representing the given credentials object
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def import_cred(
+    token: bytes,
+) -> "Creds":
+    """Import GSSAPI credentials from a token.
+
+    This method imports a credentials object from a token
+    previously exported by :func:`export_cred`.
+
+    Args:
+        token (bytes): the token to import
+
+    Returns:
+        Creds: the imported credentials object
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
diff --git a/gssapi/raw/ext_cred_imp_exp.pyx b/gssapi/raw/ext_cred_imp_exp.pyx
index ce525245..d0561448 100644
--- a/gssapi/raw/ext_cred_imp_exp.pyx
+++ b/gssapi/raw/ext_cred_imp_exp.pyx
@@ -1,4 +1,3 @@
-"""Credentials Import/Export Extension"""
 GSSAPI="BASE"  # This ensures that a full module is generated by Cython
 
 from gssapi.raw.cython_types cimport *
@@ -22,23 +21,6 @@ cdef extern from "python_gssapi_ext.h":
 
 
 def export_cred(Creds creds not None):
-    """
-    export_cred(creds)
-    Export GSSAPI credentials.
-
-    This method exports GSSSAPI credentials into a token
-    which may be transmitted between different processes.
-
-    Args:
-        creds (Creds): the credentials object to be exported
-
-    Returns:
-        bytes: the exported token representing the given credentials object
-
-    Raises:
-        GSSError
-    """
-
     # GSS_C_EMPTY_BUFFER
     cdef gss_buffer_desc exported_creds = gss_buffer_desc(0, NULL)
 
@@ -56,23 +38,6 @@ def export_cred(Creds creds not None):
 
 
 def import_cred(token not None):
-    """
-    import_cred(token)
-    Import GSSAPI credentials from a token.
-
-    This method imports a credentials object from a token
-    previously exported by :func:`export_cred`.
-
-    Args:
-        token (bytes): the token to import
-
-    Returns:
-        Creds: the imported credentials object
-
-    Raises:
-        GSSError
-    """
-
     cdef gss_buffer_desc token_buffer = gss_buffer_desc(len(token), token)
 
     cdef gss_cred_id_t creds
diff --git a/gssapi/raw/ext_cred_store.pyi b/gssapi/raw/ext_cred_store.pyi
new file mode 100644
index 00000000..e3ae9b47
--- /dev/null
+++ b/gssapi/raw/ext_cred_store.pyi
@@ -0,0 +1,136 @@
+"""Credential Store Extension"""
+import typing as t
+
+if t.TYPE_CHECKING:
+    from gssapi.raw.creds import Creds
+    from gssapi.raw.named_tuples import AcquireCredResult, StoreCredResult
+    from gssapi.raw.names import Name
+    from gssapi.raw.oids import OID
+
+
+def acquire_cred_from(
+    dict_store: t.Optional[
+        t.Dict[t.Union[bytes, str], t.Union[bytes, str]]
+    ] = None,
+    name: t.Optional["Name"] = None,
+    lifetime: t.Optional[int] = None,
+    mechs: t.Optional[t.Iterable["OID"]] = None,
+    usage: str = 'both',
+) -> "AcquireCredResult":
+    """Acquire credentials from the given store.
+
+    This method acquires credentials from the store specified by the
+    given credential store information.
+
+    The credential store information is a dictionary containing
+    mechanisms-specific keys and values pointing to a credential store
+    or stores.
+
+    Args:
+        store (dict): the credential store information pointing to the
+            credential store from which to acquire the credentials.
+            See :doc:`credstore` for valid values
+        name (~gssapi.raw.names.Name): the name associated with the
+            credentials, or None for the default name
+        lifetime (int): the desired lifetime of the credentials in seconds, or
+            None for indefinite
+        mechs (list): the desired mechanisms to be used with these
+            credentials, or None for the default set
+        usage (str): the usage for these credentials -- either 'both',
+            'initiate', or 'accept'
+
+    Returns:
+        AcquireCredResult: the acquired credentials and information about
+        them
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def add_cred_from(
+    dict_store: t.Optional[
+        t.Dict[t.Union[bytes, str], t.Union[bytes, str]]
+    ],
+    input_creds: "Creds",
+    name: "Name",
+    mech: "OID",
+    usage: str = 'both',
+    init_lifetime: t.Optional[int] = None,
+    accept_lifetime: t.Optional[int] = None,
+) -> "AcquireCredResult":
+    """Acquire credentials to add to the current set from the given store.
+
+    This method works like :func:`acquire_cred_from`, except that it
+    adds the acquired credentials for a single mechanism to a copy of
+    the current set, instead of creating a new set for multiple mechanisms.
+    Unlike :func:`~gssapi.raw.creds.acquire_cred`, you cannot pass None for the
+    desired name or mechanism.
+
+    The credential store information is a dictionary containing
+    mechanisms-specific keys and values pointing to a credential store
+    or stores.
+
+    Args:
+        store (dict): the store into which to store the credentials,
+            or None for the default store.
+            See :doc:`credstore` for valid values
+        name (~gssapi.raw.names.Name): the name associated with the credentials
+        mech (~gssapi.OID): the desired mechanism to be used with these
+            credentials
+        usage (str): the usage for these credentials -- either 'both',
+            'initiate', or 'accept'
+        init_lifetime (int): the desired initiate lifetime of the credentials
+            in seconds, or None for indefinite
+        accept_lifetime (int): the desired accept lifetime of the credentials
+            in seconds, or None for indefinite
+
+    Returns:
+        AcquireCredResult: the new credentials set and information about
+        it
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def store_cred_into(
+    dict_store: t.Optional[
+        t.Dict[t.Union[bytes, str], t.Union[bytes, str]]
+    ],
+    creds: "Creds",
+    usage: str = 'both',
+    mech: t.Optional["OID"] = None,
+    overwrite: bool = False,
+    set_default: bool = False,
+) -> "StoreCredResult":
+    """Store credentials into the given store.
+
+    This method stores the given credentials into the store specified
+    by the given store information.  They may then be retrieved later using
+    :func:`acquire_cred_from` or :func:`add_cred_from`.
+
+    The credential store information is a dictionary containing
+    mechanisms-specific keys and values pointing to a credential store
+    or stores.
+
+    Args:
+        store (dict): the store into which to store the credentials,
+            or None for the default store.
+            See :doc:`credstore` for valid values
+        creds (Creds): the credentials to store
+        usage (str): the usage to store the credentials with -- either
+            'both', 'initiate', or 'accept'
+        mech (~gssapi.OID): the mechansim to associate with the stored
+            credentials
+        overwrite (bool): whether or not to overwrite existing credentials
+            stored with the same name, etc
+        set_default (bool): whether or not to set these credentials as
+            the default credentials for the given store.
+
+    Returns:
+        StoreCredResult: the results of the credential storing operation
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
diff --git a/gssapi/raw/ext_cred_store.pyx b/gssapi/raw/ext_cred_store.pyx
index ccfe0019..0b216204 100644
--- a/gssapi/raw/ext_cred_store.pyx
+++ b/gssapi/raw/ext_cred_store.pyx
@@ -1,4 +1,3 @@
-"""Credential Store Extension"""
 GSSAPI="BASE"  # This ensures that a full module is generated by Cython
 
 from libc.string cimport memcmp, memcpy, memset
@@ -18,6 +17,7 @@ from gssapi.raw.named_tuples import AddCredResult, AcquireCredResult
 from gssapi.raw.named_tuples import StoreCredResult
 from gssapi.raw.misc import GSSError
 
+from gssapi import _utils
 
 cdef extern from "python_gssapi_ext.h":
     ctypedef struct gss_key_value_element_desc:
@@ -82,8 +82,16 @@ cdef gss_key_value_set_desc* c_create_key_value_set(dict values) except NULL:
                           "key-value set elements")
 
     for (i, (k, v)) in enumerate(values.items()):
-        res.elements[i].key = k
-        res.elements[i].value = v
+        if isinstance(k, str):
+            k1 = k.encode(_utils._get_encoding())
+            res.elements[i].key = k1
+        else:
+            res.elements[i].key = k
+        if isinstance(v, str):
+            v1 = v.encode(_utils._get_encoding())
+            res.elements[i].value = v1
+        else:
+            res.elements[i].value = v
 
     return res
 
@@ -95,39 +103,6 @@ cdef void c_free_key_value_set(gss_key_value_set_desc *kvset):
 
 def acquire_cred_from(dict store=None, Name name=None, lifetime=None,
                       mechs=None, usage='both'):
-    """
-    acquire_cred_from(store=None, name=None, lifetime=None, mechs=None, \
-usage='both')
-    Acquire credentials from the given store.
-
-    This method acquires credentials from the store specified by the
-    given credential store information.
-
-    The credential store information is a dictionary containing
-    mechanisms-specific keys and values pointing to a credential store
-    or stores.
-
-    Args:
-        store (dict): the credential store information pointing to the
-            credential store from which to acquire the credentials.
-            See :doc:`credstore` for valid values
-        name (Name): the name associated with the credentials,
-            or None for the default name
-        lifetime (int): the desired lifetime of the credentials, or None
-            for indefinite
-        mechs (list): the desired mechanisms to be used with these
-            credentials, or None for the default set
-        usage (str): the usage for these credentials -- either 'both',
-            'initiate', or 'accept'
-
-    Returns:
-        AcquireCredResult: the acquired credentials and information about
-            them
-
-    Raises:
-        GSSError
-    """
-
     cdef gss_OID_set desired_mechs
     if mechs is not None:
         desired_mechs = c_get_mech_oid_set(mechs)
@@ -190,43 +165,6 @@ def add_cred_from(dict store, Creds input_creds,
                   Name name not None, OID mech not None,
                   usage='both', init_lifetime=None,
                   accept_lifetime=None):
-    """
-    add_cred_from(store, input_creds, name, mech, usage='both', \
-init_lifetime=None, accept_lifetime=None)
-    Acquire credentials to add to the current set from the given store.
-
-    This method works like :func:`acquire_cred_from`, except that it
-    adds the acquired credentials for a single mechanism to a copy of
-    the current set, instead of creating a new set for multiple mechanisms.
-    Unlike :func:`acquire_cred`, you cannot pass None for the desired name or
-    mechanism.
-
-    The credential store information is a dictionary containing
-    mechanisms-specific keys and values pointing to a credential store
-    or stores.
-
-    Args:
-        store (dict): the store into which to store the credentials,
-            or None for the default store.
-            See :doc:`credstore` for valid values
-        name (Name): the name associated with the credentials
-        mech (OID): the desired mechanism to be used with these
-            credentials
-        usage (str): the usage for these credentials -- either 'both',
-            'initiate', or 'accept'
-        init_lifetime (int): the desired initiate lifetime of the
-            credentials, or None for indefinite
-        accept_lifetime (int): the desired accept lifetime of the
-            credentials, or None for indefinite
-
-    Returns:
-        AcquireCredResult: the new credentials set and information about
-            it
-
-    Raises:
-        GSSError
-    """
-
     cdef OM_uint32 input_initiator_ttl = c_py_ttl_to_c(init_lifetime)
     cdef OM_uint32 input_acceptor_ttl = c_py_ttl_to_c(accept_lifetime)
 
@@ -287,39 +225,6 @@ init_lifetime=None, accept_lifetime=None)
 def store_cred_into(dict store, Creds creds not None,
                     usage='both', OID mech=None, bint overwrite=False,
                     bint set_default=False):
-    """
-    store_cred_into(store, creds, usage='both', mech=None, overwrite=False, \
-set_default=False)
-    Store credentials into the given store.
-
-    This method stores the given credentials into the store specified
-    by the given store information.  They may then be retrieved later using
-    :func:`acquire_cred_from` or :func:`add_cred_from`.
-
-    The credential store information is a dictionary containing
-    mechanisms-specific keys and values pointing to a credential store
-    or stores.
-
-    Args:
-        store (dict): the store into which to store the credentials,
-            or None for the default store.
-            See :doc:`credstore` for valid values
-        creds (Creds): the credentials to store
-        usage (str): the usage to store the credentials with -- either
-            'both', 'initiate', or 'accept'
-        mech (OID): the mechansim to associate with the stored credentials
-        overwrite (bool): whether or not to overwrite existing credentials
-            stored with the same name, etc
-        set_default (bool): whether or not to set these credentials as
-            the default credentials for the given store.
-
-    Returns:
-        StoreCredResult: the results of the credential storing operation
-
-    Raises:
-        GSSError
-    """
-
     cdef gss_OID desired_mech
     if mech is not None:
         desired_mech = &mech.raw_oid
diff --git a/gssapi/raw/ext_dce.pyi b/gssapi/raw/ext_dce.pyi
new file mode 100644
index 00000000..a535df9c
--- /dev/null
+++ b/gssapi/raw/ext_dce.pyi
@@ -0,0 +1,187 @@
+import typing as t
+
+from enum import IntEnum
+
+from gssapi.raw.ext_dce_aead import wrap_aead, unwrap_aead
+
+if t.TYPE_CHECKING:
+    from gssapi.raw.named_tuples import IOVUnwrapResult, WrapResult
+    from gssapi.raw.sec_contexts import SecurityContext
+
+
+class IOVBufferType(IntEnum):
+    """
+    IOV Buffer Types
+
+    This IntEnum represent GSSAPI IOV buffer
+    types to be used with the IOV methods.
+
+    The numbers behind the values correspond directly
+    to their C counterparts.
+    """
+
+    empty = 0 #: GSS_IOV_BUFFER_TYPE_EMPTY
+    data = 1 #: GSS_IOV_BUFFER_TYPE_DATA
+    header = 2 #: GSS_IOV_BUFFER_TYPE_HEADER
+    mech_params = 3 #: GSS_IOV_BUFFER_TYPE_MECH_PARAMS
+    trailer = 7 #: GSS_IOV_BUFFER_TYPE_TRAILER
+    padding = 9 #: GSS_IOV_BUFFER_TYPE_PADDING
+    stream = 10 #: GSS_IOV_BUFFER_TYPE_STREAM
+    sign_only = 11 #: GSS_IOV_BUFFER_TYPE_SIGN_ONLY
+    mic_token = 12 #: GSS_IOV_BUFFER_TYPE_MIC_TOKEN
+
+
+class IOVBuffer(t.NamedTuple):
+    type: IOVBufferType
+    allocate: t.Optional[bool]
+    value: t.Optional[bytes]
+
+
+class IOV:
+    """A GSSAPI IOV"""
+
+    def __init__(
+        self,
+        *args: t.Union[
+            IOVBuffer,
+            t.Tuple[
+                t.Union[IOVBufferType, int],
+                t.Optional[bool],
+                t.Optional[bytes]],
+            t.Tuple[
+                t.Union[IOVBufferType, int],
+                t.Optional[t.Union[bool, bytes]],
+            ],
+            bytes,
+            t.Union[IOVBufferType, int],
+        ],
+        std_layout: bool = True,
+        auto_alloc: bool = True,
+    ) -> None: ...
+
+    def __getitem__(
+        self,
+        ind: int,
+    ) -> IOVBuffer: ...
+
+    def __len__(self) -> int: ...
+
+    def __iter__(self) -> t.Iterator[IOVBuffer]: ...
+
+    def __contains__(
+        self,
+        item: IOVBuffer,
+    ) -> bool: ...
+
+    def __reversed__(self) -> t.Iterator[IOVBuffer]: ...
+
+    def index(
+        self,
+        value: t.Any,
+    ) -> int: ...
+
+    def count(
+        self,
+        value: t.Any,
+    ) -> int: ...
+
+
+def wrap_iov(
+    context: "SecurityContext",
+    message: IOV,
+    confidential: bool = True,
+    qop: t.Optional[int] = None,
+) -> bool:
+    """Wrap/Encrypt an IOV message.
+
+    This method wraps or encrypts an IOV message.  The allocate
+    parameter of the :class:`IOVBuffer` objects in the :class:`IOV`
+    indicates whether or not that particular buffer should be
+    automatically allocated (for use with padding, header, and
+    trailer buffers).
+
+    Warning:
+        This modifies the input :class:`IOV`.
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the current
+            security context
+        message (IOV): an :class:`IOV` containing the message
+        confidential (bool): whether or not to encrypt the miovessage (True),
+            or just wrap it with a MIC (False)
+        qop (int): the desired Quality of Protection
+            (or None for the default QoP)
+
+    Returns:
+        bool: whether or not confidentiality was actually used
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def unwrap_iov(
+    context: "SecurityContext",
+    message: IOV,
+) -> "IOVUnwrapResult":
+    """Unwrap/Decrypt an IOV message.
+
+    This method uwraps or decrypts an IOV message.  The allocate
+    parameter of the :class:`IOVBuffer` objects in the :class:`IOV`
+    indicates whether or not that particular buffer should be
+    automatically allocated (for use with padding, header, and
+    trailer buffers).
+
+    As a special case, you may pass an entire IOV message
+    as a single 'stream'.  In this case, pass a buffer type
+    of :attr:`IOVBufferType.stream` followed by a buffer type of
+    :attr:`IOVBufferType.data`.  The former should contain the
+    entire IOV message, while the latter should be empty.
+
+    Warning:
+        This modifies the input :class:`IOV`.
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the current
+            security context
+        message (IOV): an :class:`IOV` containing the message
+
+    Returns:
+        IOVUnwrapResult: whether or not confidentiality was used,
+        and the QoP used.
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def wrap_iov_length(
+    context: "SecurityContext",
+    message: IOV,
+    confidential: bool = True,
+    qop: t.Optional[int] = None,
+) -> "WrapResult":
+    """Appropriately size padding, trailer, and header IOV buffers.
+
+    This method sets the length values on the IOV buffers.  You
+    should already have data provided for the data (and sign-only)
+    buffer(s) so that padding lengths can be appropriately computed.
+
+    In Python terms, this will result in an appropriately sized
+    `bytes` object consisting of all zeros.
+
+    Warning:
+        This modifies the input :class:`IOV`.
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the current
+            security context
+        message (IOV): an :class:`IOV` containing the message
+
+    Returns:
+        WrapResult: a list of :class:IOVBuffer` objects, and whether or not
+        encryption was actually used
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
diff --git a/gssapi/raw/ext_dce.pyx b/gssapi/raw/ext_dce.pyx
index 52d87e5d..19476bb9 100644
--- a/gssapi/raw/ext_dce.pyx
+++ b/gssapi/raw/ext_dce.pyx
@@ -8,15 +8,30 @@ from gssapi.raw.sec_contexts cimport SecurityContext
 
 from gssapi.raw.misc import GSSError
 from gssapi.raw import types as gssapi_types
-from gssapi.raw.named_tuples import IOVUnwrapResult, WrapResult, UnwrapResult
+from gssapi.raw.named_tuples import IOVUnwrapResult
 from collections import namedtuple
 from collections.abc import Sequence
 
 from enum import IntEnum
 from gssapi.raw._enum_extensions import ExtendableEnum
 
+# Kept for backwards compatibility - functions used to be declared here
+try:
+    from gssapi.raw.ext_dce_aead import wrap_aead, unwrap_aead
+except ImportError:
+    pass
+
 
 cdef extern from "python_gssapi_ext.h":
+    """
+    #ifdef OSX_HAS_GSS_FRAMEWORK
+    #define gss_wrap_iov __ApplePrivate_gss_wrap_iov
+    #define gss_unwrap_iov __ApplePrivate_gss_unwrap_iov
+    #define gss_wrap_iov_length __ApplePrivate_gss_wrap_iov_length
+    #define gss_release_iov_buffer __ApplePrivate_gss_release_iov_buffer
+    #endif
+    """
+
     # NB(directxman12): this wiki page has a different argument order
     #                   than the header file, and uses size_t instead of int
     #                   (this file matches the header file)
@@ -37,18 +52,6 @@ cdef extern from "python_gssapi_ext.h":
                                      gss_iov_buffer_desc *iov,
                                      int iov_count) nogil
 
-    OM_uint32 gss_wrap_aead(OM_uint32 *min_stat, gss_ctx_id_t ctx_handle,
-                            int conf_req, gss_qop_t qop_req,
-                            gss_buffer_t input_assoc_buffer,
-                            gss_buffer_t input_payload_buffer, int *conf_ret,
-                            gss_buffer_t output_message_buffer) nogil
-
-    OM_uint32 gss_unwrap_aead(OM_uint32 *min_stat, gss_ctx_id_t ctx_handle,
-                              gss_buffer_t input_message_buffer,
-                              gss_buffer_t input_assoc_buffer,
-                              gss_buffer_t output_payload_buffer,
-                              int *conf_ret, gss_qop_t *qop_ret) nogil
-
     gss_iov_buffer_t GSS_C_NO_IOV_BUFFER
 
     OM_uint32 GSS_IOV_BUFFER_TYPE_EMPTY
@@ -68,16 +71,6 @@ cdef extern from "python_gssapi_ext.h":
 
 
 class IOVBufferType(IntEnum, metaclass=ExtendableEnum):
-    """
-    IOV Buffer Types
-
-    This IntEnum represent GSSAPI IOV buffer
-    types to be used with the IOV methods.
-
-    The numbers behind the values correspond directly
-    to their C counterparts.
-    """
-
     empty = GSS_IOV_BUFFER_TYPE_EMPTY
     data = GSS_IOV_BUFFER_TYPE_DATA
     header = GSS_IOV_BUFFER_TYPE_HEADER
@@ -92,7 +85,6 @@ IOVBuffer = namedtuple('IOVBuffer', ['type', 'allocate', 'value'])
 
 
 cdef class IOV:
-    """A GSSAPI IOV"""
     # defined in ext_dce.pxd
 
     # cdef int iov_len
@@ -305,34 +297,6 @@ cdef class IOV:
 
 def wrap_iov(SecurityContext context not None, IOV message not None,
              confidential=True, qop=None):
-    """
-    wrap_iov(context, message, confidential=True, qop=None)
-    Wrap/Encrypt an IOV message.
-
-    This method wraps or encrypts an IOV message.  The allocate
-    parameter of the :class:`IOVBuffer` objects in the :class:`IOV`
-    indicates whether or not that particular buffer should be
-    automatically allocated (for use with padding, header, and
-    trailer buffers).
-
-    Warning:
-        This modifies the input :class:`IOV`.
-
-    Args:
-        context (SecurityContext): the current security context
-        message (IOV): an :class:`IOV` containing the message
-        confidential (bool): whether or not to encrypt the message (True),
-            or just wrap it with a MIC (False)
-        qop (int): the desired Quality of Protection
-            (or None for the default QoP)
-
-    Returns:
-        bool: whether or not confidentiality was actually used
-
-    Raises:
-        GSSError
-    """
-
     cdef int conf_req = confidential
     cdef gss_qop_t qop_req = qop if qop is not None else GSS_C_QOP_DEFAULT
     cdef int conf_used
@@ -353,37 +317,6 @@ def wrap_iov(SecurityContext context not None, IOV message not None,
 
 
 def unwrap_iov(SecurityContext context not None, IOV message not None):
-    """
-    unwrap_iov(context, message)
-    Unwrap/Decrypt an IOV message.
-
-    This method uwraps or decrypts an IOV message.  The allocate
-    parameter of the :class:`IOVBuffer` objects in the :class:`IOV`
-    indicates whether or not that particular buffer should be
-    automatically allocated (for use with padding, header, and
-    trailer buffers).
-
-    As a special case, you may pass an entire IOV message
-    as a single 'stream'.  In this case, pass a buffer type
-    of :attr:`IOVBufferType.stream` followed by a buffer type of
-    :attr:`IOVBufferType.data`.  The former should contain the
-    entire IOV message, while the latter should be empty.
-
-    Warning:
-        This modifies the input :class:`IOV`.
-
-    Args:
-        context (SecurityContext): the current security context
-        message (IOV): an :class:`IOV` containing the message
-
-    Returns:
-        IOVUnwrapResult: whether or not confidentiality was used,
-            and the QoP used.
-
-    Raises:
-        GSSError
-    """
-
     cdef int conf_used
     cdef gss_qop_t qop_used
     cdef gss_iov_buffer_desc *res_arr = message.__cvalue__()
@@ -403,32 +336,6 @@ def unwrap_iov(SecurityContext context not None, IOV message not None):
 
 def wrap_iov_length(SecurityContext context not None, IOV message not None,
                     confidential=True, qop=None):
-    """
-    wrap_iov_length(context, message, confidential=True, qop=None)
-    Appropriately size padding, trailer, and header IOV buffers.
-
-    This method sets the length values on the IOV buffers.  You
-    should already have data provided for the data (and sign-only)
-    buffer(s) so that padding lengths can be appropriately computed.
-
-    In Python terms, this will result in an appropriately sized
-    `bytes` object consisting of all zeros.
-
-    Warning:
-        This modifies the input :class:`IOV`.
-
-    Args:
-        context (SecurityContext): the current security context
-        message (IOV): an :class:`IOV` containing the message
-
-    Returns:
-        WrapResult: a list of :class:IOVBuffer` objects, and whether or not
-            encryption was actually used
-
-    Raises:
-        GSSError
-    """
-
     cdef int conf_req = confidential
     cdef gss_qop_t qop_req = qop if qop is not None else GSS_C_QOP_DEFAULT
     cdef int conf_used
@@ -447,109 +354,3 @@ def wrap_iov_length(SecurityContext context not None, IOV message not None,
         return conf_used
     else:
         raise GSSError(maj_stat, min_stat)
-
-
-def wrap_aead(SecurityContext context not None, bytes message not None,
-              bytes associated=None, confidential=True, qop=None):
-    """
-    wrap_aead(context, message, associated=None, confidential=True, qop=None)
-    Wrap/Encrypt an AEAD message.
-
-    This method takes an input message and associated data,
-    and outputs and AEAD message.
-
-    Args:
-        context (SecurityContext): the current security context
-        message (bytes): the message to wrap or encrypt
-        associated (bytes): associated data to go with the message
-        confidential (bool): whether or not to encrypt the message (True),
-            or just wrap it with a MIC (False)
-        qop (int): the desired Quality of Protection
-            (or None for the default QoP)
-
-    Returns:
-        WrapResult: the wrapped/encrypted total message, and whether or not
-            encryption was actually used
-
-    Raises:
-        GSSError
-    """
-
-    cdef int conf_req = confidential
-    cdef gss_qop_t qop_req = qop if qop is not None else GSS_C_QOP_DEFAULT
-    cdef gss_buffer_desc message_buffer = gss_buffer_desc(len(message),
-                                                          message)
-
-    cdef gss_buffer_t assoc_buffer_ptr = GSS_C_NO_BUFFER
-    cdef gss_buffer_desc assoc_buffer
-    if associated is not None:
-        assoc_buffer = gss_buffer_desc(len(associated), associated)
-        assoc_buffer_ptr = &assoc_buffer
-
-    cdef int conf_used
-    # GSS_C_EMPTY_BUFFER
-    cdef gss_buffer_desc output_buffer = gss_buffer_desc(0, NULL)
-
-    cdef OM_uint32 maj_stat, min_stat
-
-    with nogil:
-        maj_stat = gss_wrap_aead(&min_stat, context.raw_ctx, conf_req, qop_req,
-                                 assoc_buffer_ptr, &message_buffer,
-                                 &conf_used, &output_buffer)
-
-    if maj_stat == GSS_S_COMPLETE:
-        output_message = (output_buffer.value)[:output_buffer.length]
-        gss_release_buffer(&min_stat, &output_buffer)
-        return WrapResult(output_message, conf_used)
-    else:
-        raise GSSError(maj_stat, min_stat)
-
-
-def unwrap_aead(SecurityContext context not None, bytes message not None,
-                bytes associated=None):
-    """
-    unwrap_aead(context, message, associated=None)
-    Unwrap/Decrypt an AEAD message.
-
-    This method takes an encrpyted/wrapped AEAD message and some associated
-    data, and returns an unwrapped/decrypted message.
-
-    Args:
-        context (SecurityContext): the current security context
-        message (bytes): the AEAD message to unwrap or decrypt
-        associated (bytes): associated data that goes with the message
-
-    Returns:
-        UnwrapResult: the unwrapped/decrypted message, whether or on
-            encryption was used, and the QoP used
-
-    Raises:
-        GSSError
-    """
-
-    cdef gss_buffer_desc input_buffer = gss_buffer_desc(len(message), message)
-
-    cdef gss_buffer_t assoc_buffer_ptr = GSS_C_NO_BUFFER
-    cdef gss_buffer_desc assoc_buffer
-    if associated is not None:
-        assoc_buffer = gss_buffer_desc(len(associated), associated)
-        assoc_buffer_ptr = &assoc_buffer
-
-    # GSS_C_EMPTY_BUFFER
-    cdef gss_buffer_desc output_buffer = gss_buffer_desc(0, NULL)
-    cdef int conf_state
-    cdef gss_qop_t qop_state
-
-    cdef OM_uint32 maj_stat, min_stat
-
-    with nogil:
-        maj_stat = gss_unwrap_aead(&min_stat, context.raw_ctx, &input_buffer,
-                                   assoc_buffer_ptr, &output_buffer,
-                                   &conf_state, &qop_state)
-
-    if maj_stat == GSS_S_COMPLETE:
-        output_message = (output_buffer.value)[:output_buffer.length]
-        gss_release_buffer(&min_stat, &output_buffer)
-        return UnwrapResult(output_message, conf_state, qop_state)
-    else:
-        raise GSSError(maj_stat, min_stat)
diff --git a/gssapi/raw/ext_dce_aead.pyi b/gssapi/raw/ext_dce_aead.pyi
new file mode 100644
index 00000000..55310f1e
--- /dev/null
+++ b/gssapi/raw/ext_dce_aead.pyi
@@ -0,0 +1,61 @@
+import typing as t
+
+if t.TYPE_CHECKING:
+    from gssapi.raw.named_tuples import WrapResult, UnwrapResult
+    from gssapi.raw.sec_contexts import SecurityContext
+
+
+def wrap_aead(
+    context: "SecurityContext",
+    message: bytes,
+    associated: t.Optional[bytes] = None,
+    confidential: bool = True,
+    qop: t.Optional[int] = None,
+) -> "WrapResult":
+    """Wrap/Encrypt an AEAD message.
+
+    This method takes an input message and associated data,
+    and outputs and AEAD message.
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the current
+            security context
+        message (bytes): the message to wrap or encrypt
+        associated (bytes): associated data to go with the message
+        confidential (bool): whether or not to encrypt the message (True),
+            or just wrap it with a MIC (False)
+        qop (int): the desired Quality of Protection
+            (or None for the default QoP)
+
+    Returns:
+        WrapResult: the wrapped/encrypted total message, and whether or not
+        encryption was actually used
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def unwrap_aead(
+    context: "SecurityContext",
+    message: bytes,
+    associated: t.Optional[bytes] = None,
+) -> "UnwrapResult":
+    """Unwrap/Decrypt an AEAD message.
+
+    This method takes an encrpyted/wrapped AEAD message and some associated
+    data, and returns an unwrapped/decrypted message.
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the current
+            security context
+        message (bytes): the AEAD message to unwrap or decrypt
+        associated (bytes): associated data that goes with the message
+
+    Returns:
+        UnwrapResult: the unwrapped/decrypted message, whether or on
+        encryption was used, and the QoP used
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
diff --git a/gssapi/raw/ext_dce_aead.pyx b/gssapi/raw/ext_dce_aead.pyx
new file mode 100644
index 00000000..2b31dbf4
--- /dev/null
+++ b/gssapi/raw/ext_dce_aead.pyx
@@ -0,0 +1,83 @@
+GSSAPI="BASE"  # This ensures that a full module is generated by Cython
+
+from gssapi.raw.cython_types cimport *
+from gssapi.raw.sec_contexts cimport SecurityContext
+
+from gssapi.raw.misc import GSSError
+from gssapi.raw.named_tuples import WrapResult, UnwrapResult
+
+
+cdef extern from "python_gssapi_ext.h":
+    OM_uint32 gss_wrap_aead(OM_uint32 *min_stat, gss_ctx_id_t ctx_handle,
+                            int conf_req, gss_qop_t qop_req,
+                            gss_buffer_t input_assoc_buffer,
+                            gss_buffer_t input_payload_buffer, int *conf_ret,
+                            gss_buffer_t output_message_buffer) nogil
+
+    OM_uint32 gss_unwrap_aead(OM_uint32 *min_stat, gss_ctx_id_t ctx_handle,
+                              gss_buffer_t input_message_buffer,
+                              gss_buffer_t input_assoc_buffer,
+                              gss_buffer_t output_payload_buffer,
+                              int *conf_ret, gss_qop_t *qop_ret) nogil
+
+
+def wrap_aead(SecurityContext context not None, bytes message not None,
+              bytes associated=None, confidential=True, qop=None):
+    cdef int conf_req = confidential
+    cdef gss_qop_t qop_req = qop if qop is not None else GSS_C_QOP_DEFAULT
+    cdef gss_buffer_desc message_buffer = gss_buffer_desc(len(message),
+                                                          message)
+
+    cdef gss_buffer_t assoc_buffer_ptr = GSS_C_NO_BUFFER
+    cdef gss_buffer_desc assoc_buffer
+    if associated is not None:
+        assoc_buffer = gss_buffer_desc(len(associated), associated)
+        assoc_buffer_ptr = &assoc_buffer
+
+    cdef int conf_used
+    # GSS_C_EMPTY_BUFFER
+    cdef gss_buffer_desc output_buffer = gss_buffer_desc(0, NULL)
+
+    cdef OM_uint32 maj_stat, min_stat
+
+    with nogil:
+        maj_stat = gss_wrap_aead(&min_stat, context.raw_ctx, conf_req, qop_req,
+                                 assoc_buffer_ptr, &message_buffer,
+                                 &conf_used, &output_buffer)
+
+    if maj_stat == GSS_S_COMPLETE:
+        output_message = (output_buffer.value)[:output_buffer.length]
+        gss_release_buffer(&min_stat, &output_buffer)
+        return WrapResult(output_message, conf_used)
+    else:
+        raise GSSError(maj_stat, min_stat)
+
+
+def unwrap_aead(SecurityContext context not None, bytes message not None,
+                bytes associated=None):
+    cdef gss_buffer_desc input_buffer = gss_buffer_desc(len(message), message)
+
+    cdef gss_buffer_t assoc_buffer_ptr = GSS_C_NO_BUFFER
+    cdef gss_buffer_desc assoc_buffer
+    if associated is not None:
+        assoc_buffer = gss_buffer_desc(len(associated), associated)
+        assoc_buffer_ptr = &assoc_buffer
+
+    # GSS_C_EMPTY_BUFFER
+    cdef gss_buffer_desc output_buffer = gss_buffer_desc(0, NULL)
+    cdef int conf_state
+    cdef gss_qop_t qop_state
+
+    cdef OM_uint32 maj_stat, min_stat
+
+    with nogil:
+        maj_stat = gss_unwrap_aead(&min_stat, context.raw_ctx, &input_buffer,
+                                   assoc_buffer_ptr, &output_buffer,
+                                   &conf_state, &qop_state)
+
+    if maj_stat == GSS_S_COMPLETE:
+        output_message = (output_buffer.value)[:output_buffer.length]
+        gss_release_buffer(&min_stat, &output_buffer)
+        return UnwrapResult(output_message, conf_state, qop_state)
+    else:
+        raise GSSError(maj_stat, min_stat)
diff --git a/gssapi/raw/ext_ggf.pyi b/gssapi/raw/ext_ggf.pyi
new file mode 100644
index 00000000..30436667
--- /dev/null
+++ b/gssapi/raw/ext_ggf.pyi
@@ -0,0 +1,97 @@
+"""
+GGF Extensions
+
+GGF provides extended credential and security context inquiry that allows
+application to retrieve more information about the client's credentials and
+security context. One common use case is to use
+:meth:`inquire_sec_context_by_oid` to retrieve the "session" key that is
+required by the SMB protocol for signing and encrypting a message.
+
+Draft IETF document for these extensions can be found at
+https://tools.ietf.org/html/draft-engert-ggf-gss-extensions-00
+"""
+import typing as t
+
+if t.TYPE_CHECKING:
+    from gssapi.raw.creds import Creds
+    from gssapi.raw.oids import OID
+    from gssapi.raw.sec_contexts import SecurityContext
+
+
+def inquire_cred_by_oid(
+    cred_handle: "Creds",
+    desired_aspect: "OID",
+) -> t.List[bytes]:
+    """
+    This method inspects a :class:`~gssapi.raw.creds.Creds` object for
+    information specific to a particular desired aspect as an OID.
+
+    Args:
+        cred_handle (Creds): the Credentials to query
+        desired_aspect (~gssapi.raw.oids.OID): the desired aspect of the
+            Credentials to inquire about.
+
+    Returns:
+        list: A list of zero or more pieces of data (as bytes objects)
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def inquire_sec_context_by_oid(
+    context: "SecurityContext",
+    desired_aspect: "OID",
+) -> t.List[bytes]:
+    """
+    This method inspects a :class:`~gssapi.raw.sec_contexts.SecurityContext`
+    object for information specific to a particular desired aspect as an OID.
+
+    This method can be used with the GSS_KRB5_INQ_SSPI_SESSION_KEY_OID OID to
+    retrieve the required key that is used to derive the SMB/SAMBA signing and
+    encryption keys.
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the Security
+            Context to query
+        desired_aspect (~gssapi.raw.oids.OID): the desired aspect of the
+            Security Context to inquire about.
+
+    Returns:
+        list: A list of zero or more pieces of data (as bytes objects)
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def set_sec_context_option(
+    desired_aspect: "OID",
+    context: "SecurityContext",
+    value: t.Optional[bytes] = None,
+) -> None:
+    """
+    This method is used to set a value for a specific OID of a
+    :class:`~gssapi.raw.sec_contexts.SecurityContext` object. The OID and value
+    to pass in depends on the mech the SecurityContext backs.
+
+    An example of how this can be used would be to reset the NTLM crypto engine
+    used in gss-ntlmssp. The OID that controls this value is
+    '1.3.6.1.4.1.7165.655.1.3' and it takes it a byte value that represents
+    an int32 where 1 resets the verifier handle and any other int resets the
+    sender handle.
+
+    Args:
+        desired_aspect (~gssapi.raw.oids.OID): the desired aspect of the
+            Security Context to set the value for.
+        context (~gssapi.raw.sec_contexts.SecurityContext): the Security
+            Context to set, or None to create a new context.
+        value (bytes): the value to set on the desired aspect of the Security
+            Context or None to send GSS_C_EMPTY_BUFFER.
+
+    Returns:
+        ~gssapi.raw.sec_contexts.SecurityContext: The output security context.
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
diff --git a/gssapi/raw/ext_ggf.pyx b/gssapi/raw/ext_ggf.pyx
index 7da82c93..ba689cf8 100644
--- a/gssapi/raw/ext_ggf.pyx
+++ b/gssapi/raw/ext_ggf.pyx
@@ -1,15 +1,3 @@
-"""
-GGF Extensions
-
-GGF provides extended credential and security context inquiry that allows
-application to retrieve more information about the client's credentials and
-security context. One common use case is to use
-:meth:`inquire_sec_context_by_oid` to retrieve the "session" key that is
-required by the SMB protocol for signing and encrypting a message.
-
-Draft IETF document for these extensions can be found at
-https://tools.ietf.org/html/draft-engert-ggf-gss-extensions-00
-"""
 GSSAPI="BASE"  # This ensures that a full module is generated by Cython
 
 from gssapi.raw.cython_types cimport *
@@ -39,24 +27,6 @@ cdef extern from "python_gssapi_ext.h":
 
 def inquire_cred_by_oid(Creds cred_handle not None,
                         OID desired_aspect not None):
-    """
-    inquire_cred_by_oid(cred_handle, desired_aspect)
-
-    This method inspects a :class:`Creds` object for information
-    specific to a particular desired aspect as an OID.
-
-    Args:
-        cred_handle (Creds): the Credentials to query
-        desired_aspect (OID): the desired aspect of the Credentials to inquire
-            about.
-
-    Returns:
-        list: A list of zero or more pieces of data (as bytes objects)
-
-    Raises:
-        GSSError
-    """
-
     cdef gss_buffer_set_t *data_set_ptr = NULL
     cdef gss_buffer_set_t data_set = GSS_C_NO_BUFFER_SET
     cdef OM_uint32 maj_stat, min_stat
@@ -85,28 +55,6 @@ def inquire_cred_by_oid(Creds cred_handle not None,
 
 def inquire_sec_context_by_oid(SecurityContext context not None,
                                OID desired_aspect not None):
-    """
-    inquire_sec_context_by_oid(context, desired_aspect)
-
-    This method inspects a :class:`SecurityContext` object for information
-    specific to a particular desired aspect as an OID.
-
-    This method can be used with the GSS_KRB5_INQ_SSPI_SESSION_KEY_OID OID to
-    retrieve the required key that is used to derive the SMB/SAMBA signing and
-    encryption keys.
-
-    Args:
-        context (SecurityContext): the Security Context to query
-        desired_aspect (OID): the desired aspect of the Security Context to
-            inquire about.
-
-    Returns:
-        list: A list of zero or more pieces of data (as bytes objects)
-
-    Raises:
-        GSSError
-    """
-
     cdef gss_buffer_set_t *data_set_ptr = NULL
     cdef gss_buffer_set_t data_set = GSS_C_NO_BUFFER_SET
     cdef OM_uint32 maj_stat, min_stat
@@ -136,34 +84,6 @@ def inquire_sec_context_by_oid(SecurityContext context not None,
 def set_sec_context_option(OID desired_aspect not None,
                            SecurityContext context=None,
                            value=None):
-    """
-    set_sec_context_option(desired_aspect, context=None, value=None)
-
-    This method is used to set a value for a specific OID of a
-    :class:`SecurityContext` object. The OID and value to pass in depends on
-    the mech the SecurityContext backs.
-
-    An example of how this can be used would be to reset the NTLM crypto engine
-    used in gss-ntlmssp. The OID that controls this value is
-    '1.3.6.1.4.1.7165.655.1.3' and it takes it a byte value that represents
-    an int32 where 1 resets the verifier handle and any other int resets the
-    sender handle.
-
-    Args:
-        desired_aspect (OID): the desired aspect of the Security Context to set
-            the value for.
-        context (SecurityContext): the Security Context to set, or None to
-            create a new context.
-        value (bytes): the value to set on the desired aspect of the Security
-            Context or None to send GSS_C_EMPTY_BUFFER.
-
-    Returns:
-        SecurityContext: The output security context.
-
-    Raises:
-        GSSError
-    """
-
     cdef gss_buffer_desc value_buffer
     if value is not None:
         value_buffer = gss_buffer_desc(len(value), value)
diff --git a/gssapi/raw/ext_iov_mic.pyi b/gssapi/raw/ext_iov_mic.pyi
new file mode 100644
index 00000000..377db63b
--- /dev/null
+++ b/gssapi/raw/ext_iov_mic.pyi
@@ -0,0 +1,90 @@
+import typing as t
+
+if t.TYPE_CHECKING:
+    from gssapi.raw.ext_dce import IOV
+    from gssapi.raw.sec_contexts import SecurityContext
+
+
+def get_mic_iov(
+    context: "SecurityContext",
+    message: "IOV",
+    qop: t.Optional[int] = None,
+) -> None:
+    """Generate MIC tokens for the given IOV message.
+
+    This method generates a MIC token for the given IOV message, and places it
+    in the :attr:`~gssapi.raw.ext_dce.IOVBufferType.mic_token` buffer in the
+    IOV. This method operates entirely in-place, and returns nothing.
+
+    Warning:
+        This modifies the input :class:`~gssapi.raw.ext_dce.IOV`.
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the current
+            security context
+        message (IOV): the :class:`~gssapi.raw.ext_dce.IOV` containing the
+            message
+        qop (int): the desired Quality of Protection
+            (or None for the default QoP)
+
+    Returns:
+        None
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def get_mic_iov_length(
+    context: "SecurityContext",
+    message: "IOV",
+    qop: t.Optional[int] = None,
+) -> None:
+    """Allocate space for the MIC buffer in the given IOV message.
+
+    This method allocates space for the MIC token buffer
+    (:attr:`~gssapi.raw.ext_dce.IOVBufferType.mic_token`) in the given IOV
+    message.
+
+    Warning:
+        This modifies the input :class:`~gssapi.raw.ext_dce.IOV`.
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the current
+            security context
+        message (IOV): the :class:`~gssapi.raw.ext_dce.IOV` containing the
+            message
+        qop (int): the desired Quality of Protection
+            (or None for the default QoP)
+
+    Returns:
+        None
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def verify_mic_iov(
+    context: "SecurityContext",
+    message: "IOV",
+    qop: t.Optional[int] = None,
+) -> int:
+    """Verify that the MIC matches the data in the given IOV message.
+
+    This method verifies that the MIC token in the MIC buffer
+    (:attr:`~gssapi.raw.ext_dce.IOVBufferType.mic_token`) match the data
+    buffer(s) in the given IOV method.
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the current
+            security context
+        message (IOV): the :class:`~gssapi.raw.ext_dce.IOV` containing the
+            message
+
+    Returns:
+        int: the QoP used to generate the MIC token
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
diff --git a/gssapi/raw/ext_iov_mic.pyx b/gssapi/raw/ext_iov_mic.pyx
index d18576f5..f6c5dda1 100644
--- a/gssapi/raw/ext_iov_mic.pyx
+++ b/gssapi/raw/ext_iov_mic.pyx
@@ -33,27 +33,6 @@ IOV.AUTO_ALLOC_BUFFERS.add(IOVBufferType.mic_token)
 
 def get_mic_iov(SecurityContext context not None, IOV message not None,
                 qop=None):
-    """
-    get_mic_iov(context, message, qop=None)
-    Generate MIC tokens for the given IOV message.
-
-    This method generates a MIC token for the given IOV message, and places it
-    in the :attr:`IOVBufferType.mic_token` buffer in the IOV.  This method
-    operates entirely in-place, and returns nothing.
-
-    Warning:
-        This modifies the input :class:`IOV`.
-
-    Args:
-        context (SecurityContext): the current security context
-        message (IOV): the :class:`IOV` containing the message
-        qop (int): the desired Quality of Protection
-            (or None for the default QoP)
-
-    Raises:
-        GSSError
-    """
-
     cdef gss_qop_t qop_req = qop if qop is not None else GSS_C_QOP_DEFAULT
 
     cdef gss_iov_buffer_desc *res_arr = message.__cvalue__()
@@ -73,26 +52,6 @@ def get_mic_iov(SecurityContext context not None, IOV message not None,
 
 def get_mic_iov_length(SecurityContext context not None, IOV message not None,
                        qop=None):
-    """
-    get_mic_iov_length(context, message, qop=None)
-    Allocate space for the MIC buffer in the given IOV message.
-
-    This method allocates space for the MIC token buffer
-    (:attr:`IOVBufferType.mic_token`) in the given IOV message.
-
-    Warning:
-        This modifies the input :class:`IOV`.
-
-    Args:
-        context (SecurityContext): the current security context
-        message (IOV): the :class:`IOV` containing the message
-        qop (int): the desired Quality of Protection
-            (or None for the default QoP)
-
-    Raises:
-        GSSError
-    """
-
     cdef gss_qop_t qop_req = qop if qop is not None else GSS_C_QOP_DEFAULT
 
     cdef gss_iov_buffer_desc *res_arr = message.__cvalue__()
@@ -112,25 +71,6 @@ def get_mic_iov_length(SecurityContext context not None, IOV message not None,
 
 def verify_mic_iov(SecurityContext context not None, IOV message not None,
                    qop=None):
-    """
-    verify_mic_iov(context, message, qop=None)
-    Verify that the MIC matches the data in the given IOV message.
-
-    This method verifies that the MIC token in the MIC buffer
-    (:attr:`IOVBufferType.mic_token`) match the data buffer(s)
-    in the given IOV method.
-
-    Args:
-        context (SecurityContext): the current security context
-        message (IOV): the :class:`IOV` containing the message
-
-    Returns:
-        int: the QoP used to generate the MIC token
-
-    Raises:
-        GSSError
-    """
-
     cdef gss_iov_buffer_desc *res_arr = message.__cvalue__()
 
     cdef gss_qop_t qop_state
diff --git a/gssapi/raw/ext_krb5.pxd b/gssapi/raw/ext_krb5.pxd
new file mode 100644
index 00000000..ae796c7a
--- /dev/null
+++ b/gssapi/raw/ext_krb5.pxd
@@ -0,0 +1,2 @@
+cdef class Krb5LucidContext:
+    cdef void *raw_ctx
diff --git a/gssapi/raw/ext_krb5.pyi b/gssapi/raw/ext_krb5.pyi
new file mode 100644
index 00000000..6d74b029
--- /dev/null
+++ b/gssapi/raw/ext_krb5.pyi
@@ -0,0 +1,280 @@
+import typing as t
+
+if t.TYPE_CHECKING:
+    from gssapi.raw.creds import Creds
+    from gssapi.raw.named_tuples import CfxKeyData, Rfc1964KeyData
+    from gssapi.raw.sec_contexts import SecurityContext
+
+
+class Krb5LucidContext:
+    """
+    The base container returned by :meth:`krb5_export_lucid_sec_context` when
+    an unknown version was requested.
+    """
+
+
+class Krb5LucidContextV1(Krb5LucidContext):
+    """
+    Kerberos context data returned by :meth:`krb5_export_lucid_sec_context`
+    when version 1 was requested.
+    """
+
+    @property
+    def version(self) -> t.Optional[int]:
+        """The structure version number
+
+        Returns:
+            Optional[int]: the structure version number
+        """
+
+    @property
+    def is_initiator(self) -> t.Optional[bool]:
+        """Whether the context was the initiator
+
+        Returns:
+            Optional[bool]: ``True`` when the exported context was the
+            initiator
+        """
+
+    @property
+    def endtime(self) -> t.Optional[int]:
+        """Expiration time of the context
+
+        Returns:
+            Optional[int]: the expiration time of the context
+        """
+
+    @property
+    def send_seq(self) -> t.Optional[int]:
+        """Sender sequence number
+
+        Returns:
+            Optional[int]: the sender sequence number
+        """
+
+    @property
+    def recv_seq(self) -> t.Optional[int]:
+        """Receiver sequence number
+
+        Returns:
+            Optional[int]: the receiver sequence number
+        """
+
+    @property
+    def protocol(self) -> t.Optional[int]:
+        """The protocol number
+
+        If the protocol number is 0 then :attr:`rfc1964_kd` is set and
+        :attr:`cfx_kd` is `None`. If the protocol number is 1 then the opposite
+        is true.
+
+        Protocol 0 refers to RFC1964 and 1 refers to RFC4121.
+
+        Returns:
+            Optional[int]: the protocol number
+        """
+
+    @property
+    def rfc1964_kd(self) -> t.Optional["Rfc1964KeyData"]:
+        """Keydata for protocol 0 (RFC1964)
+
+        This will be set when :attr:`protocol` is ``0``.
+
+        Returns:
+            Optional[Rfc1964KeyData]: the RFC1964 key data
+        """
+
+    @property
+    def cfx_kd(self) -> t.Optional["CfxKeyData"]:
+        """Key data for protocol 1 (RFC4121)
+
+        This will be set when :attr:`protocol` is ``1``.
+
+        Returns:
+            Optional[CfxKeyData]: the RFC4121 key data
+        """
+
+
+def krb5_ccache_name(
+    name: t.Optional[bytes],
+) -> bytes:
+    """Set the default Kerberos Protocol credentials cache name.
+
+    This method sets the default credentials cache name for use by he Kerberos
+    mechanism. The default credentials cache is used by
+    :meth:`~gssapi.raw.creds.acquire_cred` to create a GSS-API credential. It
+    is also used by :meth:`~gssapi.raw.sec_contexts.init_sec_context` when
+    `GSS_C_NO_CREDENTIAL` is specified.
+
+    Note:
+        Heimdal does not return the old name when called. It also does not
+        reset the ccache lookup behaviour when setting to ``None``.
+
+    Note:
+        The return value may not be thread safe.
+
+    Args:
+        name (Optional[bytes]): the name to set as the new thread specific
+            ccache name. Set to ``None`` to revert back to getting the ccache
+            from the config/environment settings.
+
+    Returns:
+        bytes: the old name that was previously set
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def krb5_export_lucid_sec_context(
+    context: "SecurityContext",
+    version: int,
+) -> Krb5LucidContext:
+    """Returns a non-opaque version of the internal context info.
+
+    Gets information about the Kerberos security context passed in. Currently
+    only version 1 is known and supported by this library.
+
+    Note:
+        The context handle must not be used again by the caller after this
+        call.
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the current
+            security context
+        version (int): the output structure version to export.  Currently
+            only 1 is supported.
+
+    Returns:
+        Krb5LucidContext: the non-opaque version context info
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def krb5_extract_authtime_from_sec_context(
+    context: "SecurityContext",
+) -> int:
+    """Get the auth time for the security context.
+
+    Gets the auth time for the established security context.
+
+    Note:
+        Heimdal can only get the authtime on the acceptor security context.
+        MIT is able to get the authtime on both initiators and acceptors.
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the current
+            security context
+
+    Returns:
+        int: the authtime
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def krb5_extract_authz_data_from_sec_context(
+    context: "SecurityContext",
+    ad_type: int,
+) -> bytes:
+    """Extracts Kerberos authorization data.
+
+    Extracts authorization data that may be stored within the context.
+
+    Note:
+        Only operates on acceptor contexts.
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the current
+            security context
+        ad_type (int): the type of data to extract
+
+    Returns:
+        bytes: the raw authz data from the sec context
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def krb5_import_cred(
+    cred_handle: "Creds",
+    cache: t.Optional[int] = None,
+    keytab_principal: t.Optional[int] = None,
+    keytab: t.Optional[int] = None,
+) -> None:
+    """Import Krb5 credentials into GSSAPI credential.
+
+    Imports the krb5 credentials (either or both of the keytab and cache) into
+    the GSSAPI credential so it can be used within GSSAPI. The ccache is
+    copied by reference and thus shared, so if the credential is destroyed,
+    all users of cred_handle will fail.
+
+    Args:
+        cred_handle (Creds): the credential handle to import into
+        cache (int): the krb5_ccache address pointer, as an int, to import
+            from
+        keytab_principal (int): the krb5_principal address pointer, as an int,
+            of the credential to import
+        keytab (int): the krb5_keytab address pointer, as an int, of the
+            keytab to import
+
+    Returns:
+        None
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def krb5_get_tkt_flags(
+    context: "SecurityContext",
+) -> int:
+    """Return ticket flags for the kerberos ticket.
+
+    Return the ticket flags for the kerberos ticket received when
+    authenticating the initiator.
+
+    Note:
+        Heimdal can only get the tkt flags on the acceptor security context.
+        MIT is able to get the tkt flags on initiators and acceptors.
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the security
+            context
+
+    Returns:
+        int: the ticket flags for the received kerberos ticket
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def krb5_set_allowable_enctypes(
+    cred_handle: "Creds",
+    ktypes: t.Iterable[int],
+) -> None:
+    """Limits the keys that can be exported.
+
+    Called by a context initiator after acquiring the creds but before calling
+    :meth:`~gssapi.raw.sec_contexts.init_sec_context` to restrict the set of
+    enctypes which will be negotiated during context establisment to those in
+    the provided list.
+
+    Warning:
+        The cred_handle should not be ``GSS_C_NO_CREDENTIAL``.
+
+    Args:
+        cred_hande (Creds): the credential handle
+        ktypes (List[int]): list of enctypes allowed
+
+    Returns:
+        None
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
diff --git a/gssapi/raw/ext_krb5.pyx b/gssapi/raw/ext_krb5.pyx
new file mode 100644
index 00000000..d6134a30
--- /dev/null
+++ b/gssapi/raw/ext_krb5.pyx
@@ -0,0 +1,360 @@
+GSSAPI="BASE"  # This ensures that a full module is generated by Cython
+
+import typing
+
+from libc.stdint cimport int32_t, int64_t, uint64_t, uintptr_t, UINT32_MAX
+from libc.stdlib cimport calloc, free
+from libc.time cimport time_t
+
+from gssapi.raw.creds cimport Creds
+from gssapi.raw.cython_converters cimport c_make_oid
+from gssapi.raw.cython_types cimport *
+from gssapi.raw.sec_contexts cimport SecurityContext
+
+from gssapi.raw import types as gsstypes
+from gssapi.raw.named_tuples import CfxKeyData, Rfc1964KeyData
+
+from gssapi.raw.misc import GSSError
+
+
+cdef extern from "python_gssapi_krb5.h":
+    # Heimdal on macOS hides these 3 functions behind a private symbol
+    """
+    #ifdef OSX_HAS_GSS_FRAMEWORK
+    #define gsskrb5_extract_authtime_from_sec_context \
+        __ApplePrivate_gsskrb5_extract_authtime_from_sec_context
+
+    #define gss_krb5_import_cred __ApplePrivate_gss_krb5_import_cred
+
+    #define gss_krb5_get_tkt_flags __ApplePrivate_gss_krb5_get_tkt_flags
+    #endif
+    """
+
+    cdef struct gss_krb5_lucid_key:
+        OM_uint32 type
+        OM_uint32 length
+        void *data
+    ctypedef gss_krb5_lucid_key gss_krb5_lucid_key_t
+
+    cdef struct gss_krb5_rfc1964_keydata:
+        OM_uint32 sign_alg
+        OM_uint32 seal_alg
+        gss_krb5_lucid_key_t ctx_key
+    ctypedef gss_krb5_rfc1964_keydata gss_krb5_rfc1964_keydata_t
+
+    cdef struct gss_krb5_cfx_keydata:
+        OM_uint32 have_acceptor_subkey
+        gss_krb5_lucid_key_t ctx_key
+        gss_krb5_lucid_key_t acceptor_subkey
+    ctypedef gss_krb5_cfx_keydata gss_krb5_cfx_keydata_t
+
+    cdef struct gss_krb5_lucid_context_v1:
+        OM_uint32 version
+        OM_uint32 initiate
+        OM_uint32 endtime
+        uint64_t send_seq
+        uint64_t recv_seq
+        OM_uint32 protocol
+        gss_krb5_rfc1964_keydata_t rfc1964_kd
+        gss_krb5_cfx_keydata_t cfx_kd
+    ctypedef gss_krb5_lucid_context_v1 gss_krb5_lucid_context_v1_t
+
+    gss_OID GSS_KRB5_NT_PRINCIPAL_NAME
+    int32_t _PY_GSSAPI_KRB5_TIMESTAMP
+
+    # The krb5 specific types are defined generically as the type names differ
+    # across GSSAPI implementations.
+
+    OM_uint32 gss_krb5_ccache_name(OM_uint32 *minor_status, const char *name,
+                                   const char **out_name) nogil
+
+    OM_uint32 gss_krb5_export_lucid_sec_context(OM_uint32 *minor_status,
+                                                gss_ctx_id_t *context_handle,
+                                                OM_uint32 version,
+                                                void **kctx) nogil
+
+    # The actual authtime size differs across implementations.  See individual
+    # methods for more information.
+    OM_uint32 gsskrb5_extract_authtime_from_sec_context(
+        OM_uint32 *minor_status, gss_ctx_id_t context_handle,
+        void *authtime) nogil
+
+    OM_uint32 gsskrb5_extract_authz_data_from_sec_context(
+        OM_uint32 *minor_status, const gss_ctx_id_t context_handle,
+        int ad_type, gss_buffer_t ad_data) nogil
+
+    OM_uint32 gss_krb5_free_lucid_sec_context(OM_uint32 *minor_status,
+                                              void *kctx) nogil
+
+    OM_uint32 gss_krb5_import_cred(OM_uint32 *minor_status,
+                                   void *id,  # krb5_ccache
+                                   void *keytab_principal,  # krb5_principal
+                                   void *keytab,  # krb5_keytab
+                                   gss_cred_id_t *cred) nogil
+
+    # MIT uses a int32_t whereas Heimdal uses uint32_t.  Use void * to satisfy
+    # the compiler.
+    OM_uint32 gss_krb5_get_tkt_flags(OM_uint32 *minor_status,
+                                     gss_ctx_id_t context_handle,
+                                     void *ticket_flags) nogil
+
+    OM_uint32 gss_krb5_set_allowable_enctypes(OM_uint32 *minor_status,
+                                              gss_cred_id_t cred,
+                                              OM_uint32 num_ktypes,
+                                              int32_t *ktypes) nogil
+
+
+cdef class Krb5LucidContext:
+    # defined in pxd
+    # cdef void *raw_ctx
+
+    def __cinit__(Krb5LucidContext self):
+        self.raw_ctx = NULL
+
+    def __dealloc__(Krb5LucidContext self):
+        cdef OM_uint32 min_stat = 0
+
+        if self.raw_ctx:
+            gss_krb5_free_lucid_sec_context(&min_stat, self.raw_ctx)
+            self.raw_ctx = NULL
+
+
+cdef class Krb5LucidContextV1(Krb5LucidContext):
+
+    @property
+    def version(self) -> typing.Optional[int]:
+        cdef gss_krb5_lucid_context_v1_t *ctx = NULL
+
+        if self.raw_ctx:
+            ctx = self.raw_ctx
+            return ctx.version
+
+    @property
+    def is_initiator(self) -> typing.Optional[bool]:
+        cdef gss_krb5_lucid_context_v1_t *ctx = NULL
+
+        if self.raw_ctx:
+            ctx = self.raw_ctx
+            return ctx.initiate != 0
+
+    @property
+    def endtime(self) -> typing.Optional[int]:
+        cdef gss_krb5_lucid_context_v1_t *ctx = NULL
+
+        if self.raw_ctx:
+            ctx = self.raw_ctx
+            return ctx.endtime
+
+    @property
+    def send_seq(self) -> typing.Optional[int]:
+        cdef gss_krb5_lucid_context_v1_t *ctx = NULL
+
+        if self.raw_ctx:
+            ctx = self.raw_ctx
+            return ctx.send_seq
+
+    @property
+    def recv_seq(self) -> typing.Optional[int]:
+        cdef gss_krb5_lucid_context_v1_t *ctx = NULL
+
+        if self.raw_ctx:
+            ctx = self.raw_ctx
+            return ctx.recv_seq
+
+    @property
+    def protocol(self) -> typing.Optional[int]:
+        cdef gss_krb5_lucid_context_v1_t *ctx = NULL
+
+        if self.raw_ctx:
+            ctx = self.raw_ctx
+            return ctx.protocol
+
+    @property
+    def rfc1964_kd(self) -> typing.Optional[Rfc1964KeyData]:
+        cdef gss_krb5_lucid_context_v1_t *ctx = NULL
+
+        if self.raw_ctx != NULL and self.protocol == 0:
+            ctx = self.raw_ctx
+            kd = ctx.rfc1964_kd
+            key = (kd.ctx_key.data)[:kd.ctx_key.length]
+
+            return Rfc1964KeyData(kd.sign_alg, kd.seal_alg, kd.ctx_key.type,
+                                  key)
+
+    @property
+    def cfx_kd(self) -> typing.Optional[CfxKeyData]:
+        cdef gss_krb5_lucid_context_v1_t *ctx = NULL
+
+        if self.raw_ctx != NULL and self.protocol == 1:
+            ctx = self.raw_ctx
+            kd = ctx.cfx_kd
+            ctx_type = ctx_key = acceptor_type = acceptor_key = None
+
+            ctx_type = kd.ctx_key.type
+            ctx_key = (kd.ctx_key.data)[:kd.ctx_key.length]
+
+            if kd.have_acceptor_subkey != 0:
+                acceptor_type = kd.acceptor_subkey.type
+                key = kd.acceptor_subkey
+                acceptor_key = (key.data)[:key.length]
+
+            return CfxKeyData(ctx_type, ctx_key, acceptor_type,
+                              acceptor_key)
+
+
+# Unfortunately MIT defines it as const - use the cast to silence warnings
+gsstypes.NameType.krb5_nt_principal_name = c_make_oid(
+    GSS_KRB5_NT_PRINCIPAL_NAME)
+
+
+def krb5_ccache_name(const unsigned char[:] name):
+    cdef const char *name_ptr = NULL
+    if name is not None and len(name):
+        name_ptr = &name[0]
+
+    cdef const char *old_name_ptr = NULL
+    cdef OM_uint32 maj_stat, min_stat
+    with nogil:
+        maj_stat = gss_krb5_ccache_name(&min_stat, name_ptr, &old_name_ptr)
+
+    if maj_stat == GSS_S_COMPLETE:
+        out_name = None
+        if old_name_ptr:
+            out_name = old_name_ptr
+
+        return out_name
+
+    else:
+        raise GSSError(maj_stat, min_stat)
+
+
+def krb5_export_lucid_sec_context(SecurityContext context not None,
+                                  OM_uint32 version):
+    info = {
+        1: Krb5LucidContextV1,
+    }.get(version, Krb5LucidContext)()
+    cdef void **raw_ctx = &(info).raw_ctx
+
+    cdef OM_uint32 maj_stat, min_stat
+    with nogil:
+        maj_stat = gss_krb5_export_lucid_sec_context(&min_stat,
+                                                     &context.raw_ctx,
+                                                     version, raw_ctx)
+
+    if maj_stat != GSS_S_COMPLETE:
+        raise GSSError(maj_stat, min_stat)
+
+    return info
+
+
+def krb5_extract_authtime_from_sec_context(SecurityContext context not None):
+    # In Heimdal, authtime is time_t which is either a 4 or 8 byte int.  By
+    # passing in a uint64_t reference, there should be enough space for GSSAPI
+    # to store the data in either situation. Coming back to Python it will be
+    # handled as a normal int without loosing data.
+    cdef uint64_t time = 0
+
+    cdef OM_uint32 maj_stat, min_stat
+    with nogil:
+        maj_stat = gsskrb5_extract_authtime_from_sec_context(&min_stat,
+                                                             context.raw_ctx,
+                                                             &time)
+
+    if maj_stat != GSS_S_COMPLETE:
+        raise GSSError(maj_stat, min_stat)
+
+    return time
+
+
+def krb5_extract_authz_data_from_sec_context(SecurityContext context not None,
+                                             ad_type):
+    # GSS_C_EMPTY_BUFFER
+    cdef gss_buffer_desc ad_data = gss_buffer_desc(0, NULL)
+    cdef int ad_type_val = ad_type
+
+    cdef OM_uint32 maj_stat, min_stat
+    with nogil:
+        maj_stat = gsskrb5_extract_authz_data_from_sec_context(&min_stat,
+                                                               context.raw_ctx,
+                                                               ad_type_val,
+                                                               &ad_data)
+
+    if maj_stat != GSS_S_COMPLETE:
+        raise GSSError(maj_stat, min_stat)
+
+    try:
+        return (ad_data.value)[:ad_data.length]
+
+    finally:
+        gss_release_buffer(&min_stat, &ad_data)
+
+
+def krb5_import_cred(Creds cred_handle not None, cache=None,
+                     keytab_principal=None, keytab=None):
+    cdef void *cache_ptr = NULL
+    if cache is not None and cache:
+        cache_ptr = (cache)
+
+    cdef void *keytab_princ = NULL
+    if keytab_principal is not None and keytab_principal:
+        keytab_princ = (keytab_principal)
+
+    cdef void *kt = NULL
+    if keytab is not None and keytab:
+        kt = (keytab)
+
+    if cache_ptr == NULL and kt == NULL:
+        raise ValueError("Either cache or keytab must be set")
+
+    cdef OM_uint32 maj_stat, min_stat
+    with nogil:
+        maj_stat = gss_krb5_import_cred(&min_stat, cache_ptr, keytab_princ,
+                                        kt, &cred_handle.raw_creds)
+
+    if maj_stat != GSS_S_COMPLETE:
+        raise GSSError(maj_stat, min_stat)
+
+
+def krb5_get_tkt_flags(SecurityContext context not None):
+    cdef OM_uint32 maj_stat, min_stat
+    cdef uint32_t ticket_flags = 0
+
+    with nogil:
+        maj_stat = gss_krb5_get_tkt_flags(&min_stat, context.raw_ctx,
+                                          &ticket_flags)
+
+    if maj_stat != GSS_S_COMPLETE:
+        raise GSSError(maj_stat, min_stat)
+
+    return ticket_flags
+
+
+def krb5_set_allowable_enctypes(Creds cred_handle not None,
+                                ktypes):
+    cdef OM_uint32 maj_stat, min_stat
+
+    # This shouldn't ever happen but it's here to satisfy compiler warnings
+    cdef size_t ktypes_count = len(ktypes)
+    if ktypes_count > UINT32_MAX:
+        raise ValueError("ktypes list size too large")
+
+    cdef uint32_t count = ktypes_count
+    cdef int32_t *enc_types = calloc(count, sizeof(int32_t))
+    if not enc_types:
+        raise MemoryError()
+
+    try:
+        for i, val in enumerate(ktypes):
+            enc_types[i] = val
+
+        with nogil:
+            maj_stat = gss_krb5_set_allowable_enctypes(&min_stat,
+                                                       cred_handle.raw_creds,
+                                                       count,
+                                                       enc_types)
+
+    finally:
+        free(enc_types)
+
+    if maj_stat != GSS_S_COMPLETE:
+        raise GSSError(maj_stat, min_stat)
diff --git a/gssapi/raw/ext_password.pyi b/gssapi/raw/ext_password.pyi
new file mode 100644
index 00000000..f926701f
--- /dev/null
+++ b/gssapi/raw/ext_password.pyi
@@ -0,0 +1,41 @@
+import typing as t
+
+if t.TYPE_CHECKING:
+    from gssapi.raw.named_tuples import AcquireCredResult
+    from gssapi.raw.names import Name
+    from gssapi.raw.oids import OID
+
+
+def acquire_cred_with_password(
+    name: "Name",
+    password: bytes,
+    lifetime: t.Optional[int] = None,
+    mechs: t.Optional[t.Iterable["OID"]] = None,
+    usage: str = 'initiate',
+) -> "AcquireCredResult":
+    """Acquire credentials through provided password.
+
+    This function is originally from Solaris and is not documented by either
+    MIT or Heimdal.
+
+    In general, it functions similarly to
+    :func:`~gssapi.raw.creds.acquire_cred`.
+
+    Args:
+        name (~gssapi.raw.names.Name): the name to acquire credentials for
+        password (bytes): the password used to acquire credentialss with
+        lifetime (int): the lifetime for the credentials in seconds (or None
+            for indefinite)
+        mechs (~gssapi.raw.types.MechType): the desired mechanisms for which
+            the credentials should work (or None for the default set)
+        usage (str): usage type for credentials.  Possible values:
+            'initiate' (default), 'accept', 'both' (failsafe).
+
+    Returns:
+        AcquireCredResult: the resulting credentials, the actual mechanisms
+        with which they may be used, and their actual lifetime in seconds (or
+        None for indefinite or not supported)
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
diff --git a/gssapi/raw/ext_password.pyx b/gssapi/raw/ext_password.pyx
index 93f9c757..33b2bae1 100644
--- a/gssapi/raw/ext_password.pyx
+++ b/gssapi/raw/ext_password.pyx
@@ -29,35 +29,6 @@ cdef extern from "python_gssapi_ext.h":
 
 def acquire_cred_with_password(Name name not None, password not None,
                                lifetime=None, mechs=None, usage="initiate"):
-    """
-    acquire_cred_with_password(name, password, lifetime=None, mechs=None, \
-usage="initiate")
-    Acquire credentials through provided password.
-
-    This function is originally from Solaris and is not documented by either
-    MIT or Heimdal.
-
-    In general, it functions similarly to :func:`acquire_cred`.
-
-    Args:
-        name (Name): the name to acquire credentials for
-        password (bytes): the password used to acquire credentialss with
-        lifetime (int): the lifetime for the credentials (or None for
-            indefinite)
-        mechs ([MechType]): the desired mechanisms for which the credentials
-            should work (or None for the default set)
-        usage (str): usage type for credentials.  Possible values:
-            'initiate' (default), 'accept', 'both' (failsafe).
-
-    Returns:
-        AcquireCredResult: the resulting credentials, the actual mechanisms
-        with which they may be used, and their actual lifetime (or None for
-        indefinite or not supported)
-
-    Raises:
-        GSSError
-    """
-
     cdef gss_buffer_desc password_buffer = gss_buffer_desc(len(password),
                                                            password)
 
diff --git a/gssapi/raw/ext_password_add.pyi b/gssapi/raw/ext_password_add.pyi
new file mode 100644
index 00000000..a09bb3d2
--- /dev/null
+++ b/gssapi/raw/ext_password_add.pyi
@@ -0,0 +1,48 @@
+import typing as t
+
+if t.TYPE_CHECKING:
+    from gssapi.raw.creds import Creds
+    from gssapi.raw.named_tuples import AddCredResult
+    from gssapi.raw.names import Name
+    from gssapi.raw.oids import OID
+
+
+def add_cred_with_password(
+    input_cred: "Creds",
+    name: "Name",
+    mech: "OID",
+    password: bytes,
+    usage: str = 'initiate',
+    init_lifetime: t.Optional[int] = None,
+    accept_lifetime: t.Optional[int] = None,
+) -> "AddCredResult":
+    """Add a credential-element to a credential using provided password.
+
+    This function is originally from Solaris and is not documented by either
+    MIT or Heimdal.
+
+    In general, it functions similarly to :func:`~gssapi.raw.creds.add_cred`.
+
+    Args:
+        input_cred (~gssapi.raw.creds.Creds): the credentials to add to
+        name (~gssapi.raw.names.Name): the name to acquire credentials for
+        mech (~gssapi.raw.types.MechType): the desired mechanism.  Note that
+            this is both singular and required
+        password (bytes): the password used to acquire credentialss with
+        usage (str): the usage type for the credentials: may be
+            'initiate', 'accept', or 'both'
+        init_lifetime (int): the lifetime, in seconds, for the credentials to
+            remain valid when using them to initiate security contexts (or None
+            for indefinite)
+        accept_lifetime (int): the lifetime, in seconds, for the credentials to
+            remain valid when using them to accept security contexts (or None
+            for indefinite)
+
+    Returns:
+        AddCredResult: the actual mechanisms with which the credentials may be
+        used, the actual initiator TTL in seconds, and the actual acceptor TTL
+        in seconds (the TTLs may be None for indefinite or not supported)
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
diff --git a/gssapi/raw/ext_password_add.pyx b/gssapi/raw/ext_password_add.pyx
index c020063f..9db07889 100644
--- a/gssapi/raw/ext_password_add.pyx
+++ b/gssapi/raw/ext_password_add.pyx
@@ -35,41 +35,6 @@ def add_cred_with_password(Creds input_cred not None, Name name not None,
                            OID mech not None, password not None,
                            usage="initiate", init_lifetime=None,
                            accept_lifetime=None):
-
-    """
-    add_cred_with_password(input_cred, name, mech, password, \
-usage='initiate', init_lifetime=None, accept_lifetime=None)
-    Add a credential-element to a credential using provided password.
-
-    This function is originally from Solaris and is not documented by either
-    MIT or Heimdal.
-
-    In general, it functions similarly to :func:`add_cred`.
-
-    Args:
-        input_cred (Creds): the credentials to add to
-        name (Name): the name to acquire credentials for
-        mech (MechType): the desired mechanism.  Note that this is both
-            singular and required
-        password (bytes): the password used to acquire credentialss with
-        usage (str): the usage type for the credentials: may be
-            'initiate', 'accept', or 'both'
-        init_lifetime (int): the lifetime for the credentials to remain valid
-            when using them to initiate security contexts (or None for
-            indefinite)
-        accept_lifetime (int): the lifetime for the credentials to remain
-            valid when using them to accept security contexts (or None for
-            indefinite)
-
-    Returns:
-        AddCredResult: the actual mechanisms with which the credentials may be
-        used, the actual initiator TTL, and the actual acceptor TTL (the TTLs
-        may be None for indefinite or not supported)
-
-    Raises:
-        GSSError
-    """
-
     cdef gss_buffer_desc password_buffer = gss_buffer_desc(len(password),
                                                            password)
 
diff --git a/gssapi/raw/ext_rfc4178.pyi b/gssapi/raw/ext_rfc4178.pyi
new file mode 100644
index 00000000..6826bc1e
--- /dev/null
+++ b/gssapi/raw/ext_rfc4178.pyi
@@ -0,0 +1,27 @@
+import typing as t
+
+if t.TYPE_CHECKING:
+    from gssapi.raw.creds import Creds
+    from gssapi.raw.oids import OID
+
+
+def set_neg_mechs(
+    cred_handle: "Creds",
+    mech_set: t.Iterable["OID"],
+) -> None:
+    """
+    Specify the set of security mechanisms that may be negotiated with
+    the credential identified by cred_handle.
+    If more than one mechanism is specified in mech_set, the order in
+    which those mechanisms are specified implies a relative preference.
+
+    Args:
+        cred_handle (Creds): credentials to set negotiable mechanisms for
+        mech_set (~gssapi.raw.types.MechType): negotiable mechanisms to be set
+
+    Returns:
+        None
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
diff --git a/gssapi/raw/ext_rfc4178.pyx b/gssapi/raw/ext_rfc4178.pyx
index 056608f7..44db2f8f 100644
--- a/gssapi/raw/ext_rfc4178.pyx
+++ b/gssapi/raw/ext_rfc4178.pyx
@@ -14,23 +14,6 @@ cdef extern from "python_gssapi_ext.h":
 
 
 def set_neg_mechs(Creds cred_handle not None, mech_set not None):
-    """
-    set_neg_mechs(cred_handle not None, mech_set not None)
-
-    Specify the set of security mechanisms that may be negotiated with
-    the credential identified by cred_handle.
-    If more than one mechanism is specified in mech_set, the order in
-    which those mechanisms are specified implies a relative preference.
-
-    Args:
-        cred_handle (Creds): credentials to set negotiable mechanisms for
-        mech_set ([MechType]): negotiable mechanisms to be set
-    Returns:
-        None
-    Raises:
-        GSSError
-    """
-
     cdef gss_OID_set negotiable_mechs = c_get_mech_oid_set(mech_set)
 
     cdef OM_uint32 maj_stat, min_stat
diff --git a/gssapi/raw/ext_rfc5587.pyi b/gssapi/raw/ext_rfc5587.pyi
new file mode 100644
index 00000000..332a4063
--- /dev/null
+++ b/gssapi/raw/ext_rfc5587.pyi
@@ -0,0 +1,63 @@
+import typing as t
+
+if t.TYPE_CHECKING:
+    from gssapi.raw.named_tuples import InquireAttrsResult, DisplayAttrResult
+    from gssapi.raw.oids import OID
+
+
+def indicate_mechs_by_attrs(
+    desired_mech_attrs: t.Optional[t.Iterable["OID"]] = None,
+    except_mech_attrs: t.Optional[t.Iterable["OID"]] = None,
+    critical_mech_attrs: t.Optional[t.Iterable["OID"]] = None,
+) -> t.Set["OID"]:
+    """Get a set of mechanisms that have the specified attributes.
+
+    Args:
+        desired_mech_attrs (~gssapi.OID): Attributes that the output mechs MUST
+            offer
+        except_mech_attrs (~gssapi.OID): Attributes that the output mechs MUST
+            NOT offer
+        critical_mech_attrs (~gssapi.OID): Attributes that the output mechs
+            MUST understand and offer
+
+    Returns:
+        ~gssapi.MechType: a set of mechs which satisfy the given criteria
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def inquire_attrs_for_mech(
+    mech: "OID",
+) -> "InquireAttrsResult":
+    """Gets the set of attrs supported and known by a mechanism.
+
+    Args:
+        mech (~gssapi.raw.types.MechType): Mechanism to inquire about
+
+    Returns:
+        InquireAttrsResult: the results of inquiry; a mech's attributes and
+        known attributes
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def display_mech_attr(
+    attr: "OID",
+) -> "DisplayAttrResult":
+    """Returns information about attributes in human readable form.
+
+    Args:
+        attr (~gssapi.OID): Mechanism attribute to retrieve names and
+            descriptions of
+
+    Returns:
+        DisplayAttrResult: the results of displaying the attribute; mech name,
+        short description, and long description.
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
diff --git a/gssapi/raw/ext_rfc5587.pyx b/gssapi/raw/ext_rfc5587.pyx
index a061690c..fbedf6ed 100644
--- a/gssapi/raw/ext_rfc5587.pyx
+++ b/gssapi/raw/ext_rfc5587.pyx
@@ -32,25 +32,6 @@ cdef extern from "python_gssapi_ext.h":
 
 def indicate_mechs_by_attrs(desired_mech_attrs=None, except_mech_attrs=None,
                             critical_mech_attrs=None):
-    """
-    indicate_mechs_by_attrs(desired_mech_attrs=None, except_mech_attrs=None,
-                            critical_mech_attrs=None)
-    Get a set of mechanisms that have the specified attributes.
-
-    Args:
-        desired_mech_attrs ([OID]): Attributes that the output mechs MUST
-            offer
-        except_mech_attrs ([OID]): Attributes that the output mechs MUST NOT
-            offer
-        critical_mech_attrs ([OID]): Attributes that the output mechs MUST
-            understand and offer
-
-    Returns:
-        [MechType]: a set of mechs which satisfy the given criteria
-
-    Raises:
-        GSSError
-    """
     cdef OM_uint32 maj_stat, min_stat
     cdef gss_OID_set desired_attrs = GSS_C_NO_OID_SET
     cdef gss_OID_set except_attrs = GSS_C_NO_OID_SET
@@ -78,20 +59,6 @@ def indicate_mechs_by_attrs(desired_mech_attrs=None, except_mech_attrs=None,
 
 
 def inquire_attrs_for_mech(OID mech):
-    """
-    inquire_attrs_for_mech(mech)
-    Gets the set of attrs supported and known by a mechanism.
-
-    Args:
-        mech (MechType): Mechanism to inquire about
-
-    Returns:
-        InquireAttrsResult: the results of inquiry; a mech's attributes and
-            known attributes
-
-    Raises:
-        GSSError
-    """
     cdef OM_uint32 maj_stat, min_stat
     cdef gss_OID m = GSS_C_NO_OID
     cdef gss_OID_set mech_attrs = GSS_C_NO_OID_SET
@@ -112,20 +79,6 @@ def inquire_attrs_for_mech(OID mech):
 
 
 def display_mech_attr(OID attr):
-    """
-    display_mech_attrs(attr)
-    Returns information about attributes in human readable form.
-
-    Args:
-        attr (OID): Mechanism attribute to retrive names and descriptions of
-
-    Returns:
-        DisplayAttrResult: the results of displaying the attribute; mech name,
-            short description, and long description.
-
-    Raises:
-        GSSError
-    """
     cdef OM_uint32 maj_stat, min_stat
     cdef gss_OID a = GSS_C_NO_OID
     cdef gss_buffer_desc name
diff --git a/gssapi/raw/ext_rfc5588.pyi b/gssapi/raw/ext_rfc5588.pyi
new file mode 100644
index 00000000..320b74aa
--- /dev/null
+++ b/gssapi/raw/ext_rfc5588.pyi
@@ -0,0 +1,42 @@
+import typing as t
+
+if t.TYPE_CHECKING:
+    from gssapi.raw.creds import Creds
+    from gssapi.raw.named_tuples import StoreCredResult
+    from gssapi.raw.oids import OID
+
+
+def store_cred(
+    creds: "Creds",
+    usage: str = 'both',
+    mech: t.Optional["OID"] = None,
+    overwrite: bool = False,
+    set_default: bool = False,
+) -> "StoreCredResult":
+    """Store credentials into the default store.
+
+    This method stores the given credentials into the default store.
+    They may then be retrieved later using
+    :func:`~gssapi.raw.creds.acquire_cred`.
+
+    Args:
+        creds (Creds): the credentials to store
+        usage (str): the usage to store the credentials with -- either
+            'both', 'initiate', or 'accept'
+        mech (~gssapi.OID): the mechansim to associate with the stored
+            credentials
+        overwrite (bool): whether or not to overwrite existing credentials
+            stored with the same name, etc
+        set_default (bool): whether or not to set these credentials as
+            the default credentials for the given store.
+
+    Returns:
+        StoreCredResult: the results of the credential storing operation
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+        ~gssapi.exceptions.ExpiredCredentialsError
+        ~gssapi.exceptions.MissingCredentialsError
+        ~gssapi.exceptions.OperationUnavailableError
+        ~gssapi.exceptions.DuplicateCredentialsElementError
+    """
diff --git a/gssapi/raw/ext_rfc5588.pyx b/gssapi/raw/ext_rfc5588.pyx
index 6244573b..95b924f5 100644
--- a/gssapi/raw/ext_rfc5588.pyx
+++ b/gssapi/raw/ext_rfc5588.pyx
@@ -24,34 +24,6 @@ cdef extern from "python_gssapi.h":
 
 def store_cred(Creds creds not None, usage='both', OID mech=None,
                bint overwrite=False, bint set_default=False):
-    """
-    store_cred(creds, usage='both', mech=None, overwrite=False, \
-set_default=False)
-    Store credentials into the default store.
-
-    This method stores the given credentials into the default store.
-    They may then be retrieved later using :func:`acquire_cred`.
-
-    Args:
-        creds (Creds): the credentials to store
-        usage (str): the usage to store the credentials with -- either
-            'both', 'initiate', or 'accept'
-        mech (OID): the mechansim to associate with the stored credentials
-        overwrite (bool): whether or not to overwrite existing credentials
-            stored with the same name, etc
-        set_default (bool): whether or not to set these credentials as
-            the default credentials for the given store.
-
-    Returns:
-        StoreCredResult: the results of the credential storing operation
-
-    Raises:
-        GSSError
-        ExpiredCredentialsError
-        MissingCredentialsError
-        OperationUnavailableError
-        DuplicateCredentialsElementError
-    """
     cdef gss_OID desired_mech
     if mech is not None:
         desired_mech = &mech.raw_oid
diff --git a/gssapi/raw/ext_rfc5801.pyi b/gssapi/raw/ext_rfc5801.pyi
new file mode 100644
index 00000000..55a7d06d
--- /dev/null
+++ b/gssapi/raw/ext_rfc5801.pyi
@@ -0,0 +1,39 @@
+import typing as t
+
+if t.TYPE_CHECKING:
+    from gssapi.raw.named_tuples import InquireSASLNameResult
+    from gssapi.raw.oids import OID
+
+
+def inquire_saslname_for_mech(
+    mech: "OID",
+) -> "InquireSASLNameResult":
+    """Gets information about a specified mech, including the SASL name,
+    the mech name, and the mech description.
+
+    Args:
+        mech (~gssapi.OID): Mechanism to inquire about
+
+    Returns:
+        InquireSASLNameResult: the results of inquiry; a mech's SASL name,
+        name, and description.
+
+    Raises:
+        ~gssapi.exceptions.GSSError: an unknown failure occurred
+    """
+
+
+def inquire_mech_for_saslname(
+    sasl_name: bytes,
+) -> "OID":
+    """Gets the OID for the mech specified by SASL name.
+
+    Args:
+        sasl_name (bytes): SASL name of the mechanism
+
+    Returns:
+        ~gssapi.OID: the mechanism with corresponding SASL name.
+
+    Raises:
+        ~gssapi.exceptions.GSSError: An unknown failure occurred
+    """
diff --git a/gssapi/raw/ext_rfc5801.pyx b/gssapi/raw/ext_rfc5801.pyx
index 772c8f55..4d9f5837 100644
--- a/gssapi/raw/ext_rfc5801.pyx
+++ b/gssapi/raw/ext_rfc5801.pyx
@@ -22,21 +22,6 @@ cdef extern from "python_gssapi_ext.h":
 
 
 def inquire_saslname_for_mech(OID mech not None):
-    """
-    inquire_saslname_for_mech(mech)
-    Gets information about a specified mech, including the SASL name,
-    the mech name, and the mech description.
-
-    Args:
-        mech (OID): Mechanism to inquire about
-
-    Returns:
-        InquireSASLNameResult: the results of inquiry; a mech's SASL name,
-            name, and description.
-
-    Raises:
-        GSSError: an unknown failure occurred
-    """
     cdef OM_uint32 maj_stat, min_stat
     cdef gss_buffer_desc sasl_mech_name
     cdef gss_buffer_desc mech_name
@@ -64,19 +49,6 @@ def inquire_saslname_for_mech(OID mech not None):
 
 
 def inquire_mech_for_saslname(bytes sasl_name not None):
-    """
-    inquire_mech_for_saslname(sasl_name)
-    Gets the OID for the mech specified by SASL name.
-
-    Args:
-        sasl_name (bytes): SASL name of the mechanism
-
-    Returns:
-        OID: the mechanism with corresponding SASL name.
-
-    Raises:
-        GSSError: An unknown failure occurred
-    """
     cdef OM_uint32 maj_stat, min_stat
     cdef gss_buffer_desc sn
     cdef gss_OID m
diff --git a/gssapi/raw/ext_rfc6680.pyi b/gssapi/raw/ext_rfc6680.pyi
new file mode 100644
index 00000000..e84efb07
--- /dev/null
+++ b/gssapi/raw/ext_rfc6680.pyi
@@ -0,0 +1,169 @@
+import typing as t
+
+if t.TYPE_CHECKING:
+    from gssapi.raw.named_tuples import (
+        GetNameAttributeResult,
+        InquireNameResult,
+    )
+    from gssapi.raw.names import Name
+    from gssapi.raw.oids import OID
+
+
+def display_name_ext(
+    name: "Name",
+    name_type: "OID",
+) -> bytes:
+    """Display the given Name using the given name type.
+
+    This method attempts to display the given Name using the syntax of
+    the given name type.  If this is not possible, an appropriate error
+    will be raised.
+
+    Args:
+        name (~gssapi.raw.names.Name): the name to display
+        name_type (~gssapi.OID): the name type (see NameType) to use to
+            display the given name
+
+    Returns:
+        bytes: the displayed name
+
+    Raises:
+        ~gssapi.exceptions.OperationUnavailableError: the given name could not
+            be displayed using the given name type
+    """
+
+
+def inquire_name(
+    name: "Name",
+    mech_name: bool = True,
+    attrs: bool = True,
+) -> "InquireNameResult":
+    """Get information about a Name.
+
+    This method retrieves information about the given name, including
+    the set of attribute names for the given name, as well as whether or
+    not the name is a mechanism name.  Additionally, if the given name is
+    a mechanism name, the associated mechansim is returned as well.
+
+    Args:
+        name (~gssapi.raw.names.Name): the name about which to inquire
+        mech_name (bool): whether or not to retrieve if this name
+            is a mech_name (and the associate mechanism)
+        attrs (bool): whether or not to retrieve the attribute name list
+
+    Returns:
+        InquireNameResult: the set of attribute names for the given name,
+        whether or not the name is a Mechanism Name, and potentially
+        the associated mechanism if it is a Mechanism Name
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def set_name_attribute(
+    name: "Name",
+    attr: bytes,
+    value: t.Iterable[bytes],
+    complete: bool = False,
+) -> None:
+    """Set the value(s) of a name attribute.
+
+    This method sets the value(s) of the given attribute on the given name.
+
+    Note that this functionality more closely matches the pseudo-API
+    presented in RFC 6680, not the C API (which uses multiple calls to
+    add multiple values).  However, multiple calls to this method will
+    continue adding values, so :func:`delete_name_attribute` must be
+    used in between calls to "clear" the values.
+
+    Args:
+        name (~gssapi.raw.names.Name): the Name on which to set the attribute
+        attr (bytes): the name of the attribute
+        value (list): a list of bytes objects to use as the value(s)
+        complete (bool): whether or not to mark this attribute's value
+            set as being "complete"
+
+    Raises:
+        ~gssapi.exceptions.OperationUnavailableError: the given attribute name
+            is unknown or could not be set
+    """
+
+
+def get_name_attribute(
+    name: "Name",
+    attr: bytes,
+    more: t.Optional[int] = None,
+) -> "GetNameAttributeResult":
+    """Get the value(s) of a name attribute.
+
+    This method retrieves the value(s) of the given attribute
+    for the given Name.
+
+    Note that this functionality matches pseudo-API presented
+    in RFC 6680, not the C API (which uses a state variable and
+    multiple calls to retrieve multiple values).
+
+    Args:
+        name (~gssapi.raw.names.Name): the Name from which to get the attribute
+        attr (bytes): the name of the attribute
+
+    Returns:
+        GetNameAttributeResult: the raw version of the value(s),
+        the human-readable version of the value(s), whether
+        or not the attribute was authenticated, and whether or
+        not the attribute's value set was marked as complete
+
+    Raises:
+        ~gssapi.exceptions.OperationUnavailableError: the given attribute is
+            unknown or unset
+    """
+
+
+def delete_name_attribute(
+    name: "Name",
+    attr: bytes,
+) -> None:
+    """Remove an attribute from a name.
+
+    This method removes an attribute from a Name.  This method may be
+    used before :func:`set_name_attribute` clear the values of an attribute
+    before setting a new value (making the latter method work like a 'set'
+    operation instead of an 'add' operation).
+
+    Note that the removal of certain attributes may not be allowed.
+
+    Args:
+        name (~gssapi.raw.names.Name): the name to remove the attribute from
+        attr (bytes): the name of the attribute
+
+    Raises:
+        ~gssapi.exceptions.OperationUnavailableError
+        ~gssapi.exceptions.UnauthorizedError
+    """
+
+
+def export_name_composite(
+    name: "Name",
+) -> bytes:
+    """Export a name, preserving attribute information.
+
+    This method functions similarly to :func:`~gssapi.raw.names.export_name`,
+    except that it preserves attribute information.  The resulting bytes may be
+    imported using :func:`~gssapi.raw.names.import_name` with the
+    :attr:`~gssapi.raw.types.NameType.composite_export` name type.
+
+    Note:
+        Some versions of MIT Kerberos require you to either canonicalize a name
+        once it has been imported with composite-export name type, or to import
+        using the normal export name type.
+
+    Args:
+        name (~gssapi.raw.names.Name): the name to export
+
+    Returns:
+        bytes: the exported composite name
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
diff --git a/gssapi/raw/ext_rfc6680.pyx b/gssapi/raw/ext_rfc6680.pyx
index bfd52319..70adcce3 100644
--- a/gssapi/raw/ext_rfc6680.pyx
+++ b/gssapi/raw/ext_rfc6680.pyx
@@ -37,27 +37,6 @@ cdef extern from "python_gssapi_ext.h":
 
 
 def display_name_ext(Name name not None, OID name_type not None):
-    """
-    display_name_ext(name, name_type)
-    Display the given Name using the given name type.
-
-    This method attempts to display the given Name using the syntax of
-    the given name type.  If this is not possible, an appropriate error
-    will be raised.
-
-    Args:
-        name (Name): the name to display
-        name_type (OID): the name type (see NameType) to use to
-            display the given name
-
-    Returns:
-        bytes: the displayed name
-
-    Raises:
-        OperationUnavailableError: the given name could not be displayed
-            using the given name type
-    """
-
     # GSS_C_EMPTY_BUFFER
     cdef gss_buffer_desc output_name = gss_buffer_desc(0, NULL)
 
@@ -75,30 +54,6 @@ def display_name_ext(Name name not None, OID name_type not None):
 
 
 def inquire_name(Name name not None, mech_name=True, attrs=True):
-    """
-    inquire_name(name, mech_name=True, attrs=True)
-    Get information about a Name.
-
-    This method retrives information about the given name, including
-    the set of attribute names for the given name, as well as whether or
-    not the name is a mechanism name.  Additionally, if the given name is
-    a mechanism name, the associated mechansim is returned as well.
-
-    Args:
-        name (Name): the name about which to inquire
-        mech_name (bool): whether or not to retrieve if this name
-            is a mech_name (and the associate mechanism)
-        attrs (bool): whether or not to retrieve the attribute name list
-
-    Returns:
-        InquireNameResult: the set of attribute names for the given name,
-            whether or not the name is a Mechanism Name, and potentially
-            the associated mechanism if it is a Mechanism Name
-
-    Raises:
-        GSSError
-    """
-
     cdef int *name_is_mn_ptr = NULL
     cdef gss_OID *mn_mech_ptr = NULL
     cdef gss_buffer_set_t *attr_names_ptr = NULL
@@ -143,30 +98,6 @@ def inquire_name(Name name not None, mech_name=True, attrs=True):
 
 def set_name_attribute(Name name not None, attr not None, value not None,
                        bint complete=False):
-    """
-    set_name_attribute(name, attr, value, complete=False)
-    Set the value(s) of a name attribute.
-
-    This method sets the value(s) of the given attribute on the given name.
-
-    Note that this functionality more closely matches the pseudo-API
-    presented in RFC 6680, not the C API (which uses multiple calls to
-    add multiple values).  However, multiple calls to this method will
-    continue adding values, so :func:`delete_name_attribute` must be
-    used in between calls to "clear" the values.
-
-    Args:
-        name (Name): the Name on which to set the attribute
-        attr (bytes): the name of the attribute
-        value (list): a list of bytes objects to use as the value(s)
-        complete (bool): whether or not to mark this attribute's value
-            set as being "complete"
-
-    Raises:
-        OperationUnavailableError: the given attribute name is unknown
-            or could not be set
-    """
-
     cdef gss_buffer_desc attr_buff = gss_buffer_desc(len(attr), attr)
     cdef gss_buffer_desc val_buff
 
@@ -189,31 +120,6 @@ def set_name_attribute(Name name not None, attr not None, value not None,
 
 
 def get_name_attribute(Name name not None, attr not None, more=None):
-    """
-    get_name_attribute(name, attr, more=None)
-    Get the value(s) of a name attribute.
-
-    This method retrieves the value(s) of the given attribute
-    for the given Name.
-
-    Note that this functionality matches pseudo-API presented
-    in RFC 6680, not the C API (which uses a state variable and
-    multiple calls to retrieve multiple values).
-
-    Args:
-        name (Name): the Name from which to get the attribute
-        attr (bytes): the name of the attribute
-
-    Returns:
-        GetNameAttributeResult: the raw version of the value(s),
-            the human-readable version of the value(s), whether
-            or not the attribute was authenticated, and whether or
-            not the attribute's value set was marked as complete
-
-    Raises:
-        OperationUnavailableError: the given attribute is unknown
-            or unset
-    """
     cdef gss_buffer_desc attr_buff = gss_buffer_desc(len(attr), attr)
 
     cdef gss_buffer_desc val_buff = gss_buffer_desc(0, NULL)
@@ -249,26 +155,6 @@ def get_name_attribute(Name name not None, attr not None, more=None):
 
 
 def delete_name_attribute(Name name not None, attr not None):
-    """
-    delete_name_attribute(name, attr)
-    Remove an attribute from a name.
-
-    This method removes an attribute from a Name.  This method may be
-    used before :func:`set_name_attribute` clear the values of an attribute
-    before setting a new value (making the latter method work like a 'set'
-    operation instead of an 'add' operation).
-
-    Note that the removal of certain attributes may not be allowed.
-
-    Args:
-        name (Name): the name to remove the attribute from
-        attr (bytes): the name of the attribute
-
-    Raises:
-        OperationUnavailableError
-        UnauthorizedError
-    """
-
     cdef gss_buffer_desc attr_buff = gss_buffer_desc(len(attr), attr)
 
     cdef OM_uint32 maj_stat, min_stat
@@ -281,30 +167,6 @@ def delete_name_attribute(Name name not None, attr not None):
 
 
 def export_name_composite(Name name not None):
-    """
-    export_name_composite(name)
-    Export a name, preserving attribute information.
-
-    This method functions similarly to :func:`export_name`, except that
-    it preserves attribute information.  The resulting bytes may be imported
-    using :func:`import_name` with the :attr:`NameType.composite_export`
-    name type.
-
-    Note:
-        Some versions of MIT Kerberos require you to either canonicalize a name
-        once it has been imported with composite-export name type, or to import
-        using the normal export name type.
-
-    Args:
-        name (Name): the name to export
-
-    Returns:
-        bytes: the exported composite name
-
-    Raises:
-        GSSError
-    """
-
     cdef gss_buffer_desc res = gss_buffer_desc(0, NULL)
 
     cdef OM_uint32 maj_stat, min_stat
diff --git a/gssapi/raw/ext_rfc6680_comp_oid.pyi b/gssapi/raw/ext_rfc6680_comp_oid.pyi
new file mode 100644
index 00000000..e69de29b
diff --git a/gssapi/raw/ext_s4u.pyi b/gssapi/raw/ext_s4u.pyi
new file mode 100644
index 00000000..87987fcf
--- /dev/null
+++ b/gssapi/raw/ext_s4u.pyi
@@ -0,0 +1,86 @@
+"""Service4User Extension"""
+import typing as t
+
+if t.TYPE_CHECKING:
+    from gssapi.raw.creds import Creds
+    from gssapi.raw.named_tuples import AcquireCredResult, AddCredResult
+    from gssapi.raw.names import Name
+    from gssapi.raw.oids import OID
+
+
+def acquire_cred_impersonate_name(
+    impersonator_cred: "Creds",
+    name: "Name",
+    lifetime: t.Optional[int] = None,
+    mechs: t.Optional[t.Iterable["OID"]] = None,
+    usage: str = 'initiate',
+) -> "AcquireCredResult":
+    """Acquire credentials by impersonating another name.
+
+    This method is one of the ways to use S4U2Self.  It acquires credentials
+    by impersonating another name using a set of proxy credentials.  The
+    impersonator credentials must have a usage of 'both' or 'initiate'.
+
+    Args:
+        impersonator_cred (~gssapi.raw.creds.Creds): the credentials with
+            permissions to impersonate the target name
+        name (~gssapi.raw.names.Name): the name to impersonate
+        lifetime (int): the lifetime for the credentials (or None for
+            indefinite) in seconds
+        mechs (~gssapi.raw.types.MechType): the desired mechanisms for which
+            the credentials should work (or None for the default set)
+        usage (str): the usage type for the credentials: may be
+            'initiate', 'accept', or 'both'
+
+    Returns:
+        AcquireCredResult: the resulting credentials, the actual mechanisms
+        with which they may be used, and their actual lifetime in seconds (or
+        None for indefinite or not support)
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def add_cred_impersonate_name(
+    input_cred: "Creds",
+    impersonator_cred: "Creds",
+    name: "Name",
+    mech: "OID",
+    usage: str = 'initiate',
+    init_lifetime: t.Optional[int] = None,
+    accept_lifetime: t.Optional[int] = None,
+) -> "AddCredResult":
+    """Add a credentials element to a credential by impersonating another name.
+
+    This method is one of the ways to use S4U2Self.  It adds credentials
+    to the input credentials by impersonating another name using a set of
+    proxy credentials.  The impersonator credentials must have a usage of
+    'both' or 'initiate'.
+
+    Args:
+        input_cred (~gssapi.raw.creds.Creds): the set of credentials to which
+            to add the new credentials
+        impersonator_cred (~gssapi.raw.creds.Creds): the credentials with
+            permissions to impersonate the target name
+        name (~gssapi.raw.names.Name): the name to impersonate
+        mech (~gssapi.raw.types.MechType): the desired mechanism. Note that
+            this is both
+            singular and required, unlike acquireCredImpersonateName
+        usage (str): the usage type for the credentials: may be
+            'initiate', 'accept', or 'both'
+        init_lifetime (int): the lifetime, in seconds, for the credentials to
+            remain valid when using them to initiate security contexts (or None
+            for indefinite)
+        accept_lifetime (int): the lifetime, in seconds, for the credentials to
+            remain valid when using them to accept security contexts (or None
+            for indefinite)
+
+    Returns:
+        AddCredResult: the actual mechanisms with which the credentials may be
+        used, the actual initiator TTL in seconds, and the actual acceptor TTL
+        in seconds (the TTLs may be None for indefinite or not supported)
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
diff --git a/gssapi/raw/ext_s4u.pyx b/gssapi/raw/ext_s4u.pyx
index d91986e4..b123e165 100644
--- a/gssapi/raw/ext_s4u.pyx
+++ b/gssapi/raw/ext_s4u.pyx
@@ -1,4 +1,3 @@
-"""Service4User Extension"""
 GSSAPI="BASE"  # This ensures that a full module is generated by Cython
 
 from gssapi.raw.cython_types cimport *
@@ -41,35 +40,6 @@ cdef extern from "python_gssapi_ext.h":
 def acquire_cred_impersonate_name(Creds impersonator_cred not None,
                                   Name name not None, lifetime=None,
                                   mechs=None, usage='initiate'):
-    """
-    acquire_cred_impersonate_name(impersonator_cred, name, lifetime=None, \
-mechs=None, usage='initiate')
-    Acquire credentials by impersonating another name.
-
-    This method is one of the ways to use S4U2Self.  It acquires credentials
-    by impersonating another name using a set of proxy credentials.  The
-    impersonator credentials must have a usage of 'both' or 'initiate'.
-
-    Args:
-        impersonator_cred (Cred): the credentials with permissions to
-            impersonate the target name
-        name (Name): the name to impersonate
-        lifetime (int): the lifetime for the credentials (or None for
-            indefinite)
-        mechs ([MechType]): the desired mechanisms for which the credentials
-            should work (or None for the default set)
-        usage (str): the usage type for the credentials: may be
-            'initiate', 'accept', or 'both'
-
-    Returns:
-        AcquireCredResult: the resulting credentials, the actual mechanisms
-        with which they may be used, and their actual lifetime (or None for
-        indefinite or not support)
-
-    Raises:
-        GSSError
-    """
-
     cdef gss_OID_set desired_mechs
     if mechs is not None:
         desired_mechs = c_get_mech_oid_set(mechs)
@@ -120,42 +90,6 @@ def add_cred_impersonate_name(Creds input_cred,
                               Name name not None, OID mech not None,
                               usage='initiate', init_lifetime=None,
                               accept_lifetime=None):
-    """
-    add_cred_impersonate_name(input_cred, impersonator_cred, name, mech, \
-usage='initiate', init_lifetime=None, accept_lifetime=None)
-    Add a credentials element to a credential by impersonating another name.
-
-    This method is one of the ways to use S4U2Self.  It adds credentials
-    to the input credentials by impersonating another name using a set of
-    proxy credentials.  The impersonator credentials must have a usage of
-    'both' or 'initiate'.
-
-    Args:
-        input_cred (Cred): the set of credentials to which to add the new
-            credentials
-        impersonator_cred (Cred): the credentials with permissions to
-            impersonate the target name
-        name (Name): the name to impersonate
-        mech (MechType): the desired mechanism.  Note that this is both
-            singular and required, unlike acquireCredImpersonateName
-        usage (str): the usage type for the credentials: may be
-            'initiate', 'accept', or 'both'
-        init_lifetime (int): the lifetime for the credentials to remain
-            valid when using them to initiate security contexts (or None for
-            indefinite)
-        accept_lifetime (int): the lifetime for the credentials to remain
-            valid when using them to accept security contexts (or None for
-            indefinite)
-
-    Returns:
-        AddCredResult: the actual mechanisms with which the credentials may be
-        used, the actual initiator TTL, and the actual acceptor TTL (the TTLs
-        may be None for indefinite or not supported)
-
-    Raises:
-        GSSError
-    """
-
     cdef OM_uint32 input_initiator_ttl = c_py_ttl_to_c(init_lifetime)
     cdef OM_uint32 input_acceptor_ttl = c_py_ttl_to_c(accept_lifetime)
     cdef gss_name_t c_name = name.raw_name
diff --git a/gssapi/raw/ext_set_cred_opt.pyi b/gssapi/raw/ext_set_cred_opt.pyi
new file mode 100644
index 00000000..eeb0e123
--- /dev/null
+++ b/gssapi/raw/ext_set_cred_opt.pyi
@@ -0,0 +1,50 @@
+"""
+gss_set_cred_option
+
+Provides a way to set options on a credential based on the OID specified. A
+common use case is to set the GSS_KRB5_CRED_NO_CI_FLAGS_X on a Kerberos
+credential. This is used for interoperability with Microsoft's SSPI.
+
+Note this function is commonly lumped with the GGF extensions but they are not
+part of the GGF IETF draft so it's separated into it's own file.
+
+Closest draft IETF document for the gss_set_cred_option can be found at
+https://tools.ietf.org/html/draft-williams-kitten-channel-bound-flag-01
+"""
+import typing as t
+
+if t.TYPE_CHECKING:
+    from gssapi.raw.creds import Creds
+    from gssapi.raw.oids import OID
+
+
+def set_cred_option(
+    desired_aspect: "OID",
+    creds: t.Optional["Creds"] = None,
+    value: t.Optional[bytes] = None,
+) -> "Creds":
+    """
+    This method is used to set options of a :class:`~gssapi.raw.creds.Creds`
+    object based on an OID key. The options that can be set depends on the mech
+    the credentials were created with.
+
+    An example of how this can be used would be to set the
+    GSS_KRB5_CRED_NO_CI_FLAGS_X on a Kerberos credential. The OID string for
+    this flag is '1.2.752.43.13.29' and it requires no value to be set. This
+    must be set before the SecurityContext was initialised with the
+    credentials.
+
+    Args:
+        desired_aspect (~gssapi.raw.oids.OID): the desired aspect of the
+            Credential to set.
+        cred_handle (~gssapi.raw.creds.Creds): the Credentials to set, or None
+            to create a new credential.
+        value (bytes): the value to set on the desired aspect of the Credential
+            or None to send GSS_C_EMPTY_BUFFER.
+
+    Returns:
+        Creds: The output credential.
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
diff --git a/gssapi/raw/ext_set_cred_opt.pyx b/gssapi/raw/ext_set_cred_opt.pyx
index a72f7487..b59d18d1 100644
--- a/gssapi/raw/ext_set_cred_opt.pyx
+++ b/gssapi/raw/ext_set_cred_opt.pyx
@@ -1,16 +1,3 @@
-"""
-gss_set_cred_option
-
-Provides a way to set options on a credential based on the OID specified. A
-common use case is to set the GSS_KRB5_CRED_NO_CI_FLAGS_X on a Kerberos
-credential. This is used for interoperability with Microsoft's SSPI.
-
-Note this function is commonly lumped with the GGF extensions but they are not
-part of the GGF IETF draft so it's separated into it's own file.
-
-Closest draft IETF document for the gss_set_cred_option can be found at
-https://tools.ietf.org/html/draft-williams-kitten-channel-bound-flag-01
-"""
 GSSAPI="BASE"  # This ensures that a full module is generated by Cython
 
 from gssapi.raw.cython_types cimport *
@@ -28,33 +15,6 @@ cdef extern from "python_gssapi_ext.h":
 
 
 def set_cred_option(OID desired_aspect not None, Creds creds=None, value=None):
-    """
-    set_cred_option(desired_aspect, creds=None, value=None)
-
-    This method is used to set options of a :class:`Creds` object based on
-    an OID key. The options that can be set depends on the mech the credentials
-    were created with.
-
-    An example of how this can be used would be to set the
-    GSS_KRB5_CRED_NO_CI_FLAGS_X on a Kerberos credential. The OID string for
-    this flag is '1.2.752.43.13.29' and it requires no value to be set. This
-    must be set before the SecurityContext was initialised with the
-    credentials.
-
-    Args:
-        desired_aspect (OID): the desired aspect of the Credential to set.
-        cred_handle (Creds): the Credentials to set, or None to create a new
-            credential.
-        value (bytes): the value to set on the desired aspect of the Credential
-            or None to send GSS_C_EMPTY_BUFFER.
-
-    Returns:
-        Creds: The output credential.
-
-    Raises:
-        GSSError
-    """
-
     cdef gss_buffer_desc value_buffer
     if value is not None:
         value_buffer = gss_buffer_desc(len(value), value)
diff --git a/gssapi/raw/mech_krb5.pyi b/gssapi/raw/mech_krb5.pyi
new file mode 100644
index 00000000..e69de29b
diff --git a/gssapi/raw/message.pyi b/gssapi/raw/message.pyi
new file mode 100644
index 00000000..04c6df5e
--- /dev/null
+++ b/gssapi/raw/message.pyi
@@ -0,0 +1,156 @@
+import typing as t
+
+if t.TYPE_CHECKING:
+    from gssapi.raw.named_tuples import WrapResult, UnwrapResult
+    from gssapi.sec_contexts import SecurityContext
+
+
+def get_mic(
+    context: "SecurityContext",
+    message: bytes,
+    qop: t.Optional[int] = None,
+) -> bytes:
+    """Generate a MIC for a message.
+
+    This method generates a Message Integrity Check token for the
+    given message.  This can be separately trasmitted to the other
+    entity, unlike wrap, which bundles the MIC and the message
+    together.
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the current
+            security context
+        message (bytes): the message for which to generate the MIC
+        qop (int): the requested Quality of Protection
+            (or None to use the default)
+
+    Returns:
+        bytes: the generated MIC token
+
+    Raises:
+        ~gssapi.exceptions.ExpiredContextError
+        ~gssapi.exceptions.MissingContextError
+        ~gssapi.exceptions.BadQoPError
+    """
+
+
+def verify_mic(
+    context: "SecurityContext",
+    message: bytes,
+    token: bytes,
+) -> int:
+    """Verify that a MIC matches a message.
+
+    This method verifies that the given MIC matches the given message.
+    If the MIC does not match the given message, an exception will
+    be raised.
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the current
+            security context
+        message (bytes): the message in question
+        token (bytes): the MIC token in question
+
+    Returns:
+        int: the QoP used.
+
+    Raises:
+        ~gssapi.exceptions.InvalidTokenError
+        ~gssapi.exceptions.BadMICError
+        ~gssapi.exceptions.DuplicateTokenError
+        ~gssapi.exceptions.ExpiredTokenError
+        ~gssapi.exceptions.TokenTooLateError
+        ~gssapi.exceptions.TokenTooEarlyError
+        ~gssapi.exceptions.ExpiredContextError
+        ~gssapi.exceptions.MissingContextError
+    """
+
+
+def wrap_size_limit(
+    context: "SecurityContext",
+    output_size: int,
+    confidential: bool = True,
+    qop: t.Optional[int] = None,
+) -> int:
+    """Calculate the max message size.
+
+    This method calculates the unwrapped/unencrypted message size for
+    the given maximum wrapped/encrypted message size.
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the current
+            security context
+        output_size (int): the maximum desired wrapped/encrypted message size
+        confidential (bool): whether or not confidentiality is being used
+        qop (int): the QoP that will be when you actually call wrap
+            (or None for the default QoP)
+
+    Returns:
+        int: the maximum unencrypted/unwrapped message size
+
+    Raises:
+        ~gssapi.exceptions.MissingContextError
+        ~gssapi.exceptions.ExpiredContextError
+        ~gssapi.exceptions.BadQoPError
+    """
+
+
+def wrap(
+    context: "SecurityContext",
+    message: bytes,
+    confidential: bool = True,
+    qop: t.Optional[int] = None,
+) -> "WrapResult":
+    """Wrap/Encrypt a message.
+
+    This method wraps or encrypts a message (depending on the value
+    of confidential) with the given Quality of Protection.
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the current
+            security context
+        message (bytes): the message to wrap or encrypt
+        confidential (bool): whether or not to encrypt the message (True),
+            or just wrap it with a MIC (False)
+        qop (int): the desired Quality of Protection
+            (or None for the default QoP)
+
+    Returns:
+        WrapResult: the wrapped/encrypted message, and whether or not
+            encryption was actually used
+
+    Raises:
+        ~gssapi.exceptions.ExpiredContextError
+        ~gssapi.exceptions.MissingContextError
+        ~gssapi.exceptions.BadQoPError
+    """
+
+
+def unwrap(
+    context: "SecurityContext",
+    message: bytes,
+) -> "UnwrapResult":
+    """Unwrap/Decrypt a message.
+
+    This method unwraps or decrypts a message, depending
+    on whether the sender used confidentiality.
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the current
+            security context
+        message (bytes): the message to unwrap/decrypt
+
+    Returns:
+        UnwrapResult: the unwrapped/decrypted message, whether or on
+            encryption was used, and the QoP used
+
+    Raises:
+        ~gssapi.exceptions.InvalidTokenError
+        ~gssapi.exceptions.BadMICError
+        ~gssapi.exceptions.DuplicateTokenError
+        ~gssapi.exceptions.ExpiredTokenError
+        ~gssapi.exceptions.TokenTooLateError
+        ~gssapi.exceptions.TokenTooEarlyError
+        ~gssapi.exceptions.ExpiredContextError
+        ~gssapi.exceptions.MissingContextError
+    """
diff --git a/gssapi/raw/message.pyx b/gssapi/raw/message.pyx
index d45e430c..314ebf4d 100644
--- a/gssapi/raw/message.pyx
+++ b/gssapi/raw/message.pyx
@@ -54,7 +54,8 @@ def get_mic(SecurityContext context not None, message, qop=None):
     together.
 
     Args:
-        context (SecurityContext): the current security context
+        context (~gssapi.raw.sec_contexts.SecurityContext): the current
+            security context
         message (bytes): the message for which to generate the MIC
         qop (int): the requested Quality of Protection
             (or None to use the default)
@@ -63,9 +64,9 @@ def get_mic(SecurityContext context not None, message, qop=None):
         bytes: the generated MIC token
 
     Raises:
-        ExpiredContextError
-        MissingContextError
-        BadQoPError
+        ~gssapi.exceptions.ExpiredContextError
+        ~gssapi.exceptions.MissingContextError
+        ~gssapi.exceptions.BadQoPError
     """
 
     cdef gss_buffer_desc message_buffer = gss_buffer_desc(len(message),
@@ -99,7 +100,8 @@ def verify_mic(SecurityContext context not None, message, token):
     be raised.
 
     Args:
-        context (SecurityContext): the current security context
+        context (~gssapi.raw.sec_contexts.SecurityContext): the current
+            security context
         message (bytes): the message in question
         token (bytes): the MIC token in question
 
@@ -107,14 +109,14 @@ def verify_mic(SecurityContext context not None, message, token):
         int: the QoP used.
 
     Raises:
-        InvalidTokenError
-        BadMICError
-        DuplicateTokenError
-        ExpiredTokenError
-        TokenTooLateError
-        TokenTooEarlyError
-        ExpiredContextError
-        MissingContextError
+        ~gssapi.exceptions.InvalidTokenError
+        ~gssapi.exceptions.BadMICError
+        ~gssapi.exceptions.DuplicateTokenError
+        ~gssapi.exceptions.ExpiredTokenError
+        ~gssapi.exceptions.TokenTooLateError
+        ~gssapi.exceptions.TokenTooEarlyError
+        ~gssapi.exceptions.ExpiredContextError
+        ~gssapi.exceptions.MissingContextError
     """
 
     cdef gss_buffer_desc message_buffer = gss_buffer_desc(len(message),
@@ -145,7 +147,8 @@ def wrap_size_limit(SecurityContext context not None, OM_uint32 output_size,
     the given maximum wrapped/encrypted message size.
 
     Args:
-        context (SecurityContext): the current security context
+        context (~gssapi.raw.sec_contexts.SecurityContext): the current
+            security context
         output_size (int): the maximum desired wrapped/encrypted message size
         confidential (bool): whether or not confidentiality is being used
         qop (int): the QoP that will be when you actually call wrap
@@ -155,9 +158,9 @@ def wrap_size_limit(SecurityContext context not None, OM_uint32 output_size,
         int: the maximum unencrypted/unwrapped message size
 
     Raises:
-        MissingContextError
-        ExpiredContextError
-        BadQoPError
+        ~gssapi.exceptions.MissingContextError
+        ~gssapi.exceptions.ExpiredContextError
+        ~gssapi.exceptions.BadQoPError
     """
 
     cdef int conf_req = confidential
@@ -187,7 +190,8 @@ def wrap(SecurityContext context not None, message, confidential=True,
     of confidential) with the given Quality of Protection.
 
     Args:
-        context (SecurityContext): the current security context
+        context (~gssapi.raw.sec_contexts.SecurityContext): the current
+            security context
         message (bytes): the message to wrap or encrypt
         confidential (bool): whether or not to encrypt the message (True),
             or just wrap it with a MIC (False)
@@ -199,9 +203,9 @@ def wrap(SecurityContext context not None, message, confidential=True,
             encryption was actually used
 
     Raises:
-        ExpiredContextError
-        MissingContextError
-        BadQoPError
+        ~gssapi.exceptions.ExpiredContextError
+        ~gssapi.exceptions.MissingContextError
+        ~gssapi.exceptions.BadQoPError
     """
 
     cdef int conf_req = confidential
@@ -236,7 +240,8 @@ def unwrap(SecurityContext context not None, message):
     on whether the sender used confidentiality.
 
     Args:
-        context (SecurityContext): the current security context
+        context (~gssapi.raw.sec_contexts.SecurityContext): the current
+            security context
         message (bytes): the message to unwrap/decrypt
 
     Returns:
@@ -244,14 +249,14 @@ def unwrap(SecurityContext context not None, message):
             encryption was used, and the QoP used
 
     Raises:
-        InvalidTokenError
-        BadMICError
-        DuplicateTokenError
-        ExpiredTokenError
-        TokenTooLateError
-        TokenTooEarlyError
-        ExpiredContextError
-        MissingContextError
+        ~gssapi.exceptions.InvalidTokenError
+        ~gssapi.exceptions.BadMICError
+        ~gssapi.exceptions.DuplicateTokenError
+        ~gssapi.exceptions.ExpiredTokenError
+        ~gssapi.exceptions.TokenTooLateError
+        ~gssapi.exceptions.TokenTooEarlyError
+        ~gssapi.exceptions.ExpiredContextError
+        ~gssapi.exceptions.MissingContextError
     """
 
     cdef gss_buffer_desc input_buffer = gss_buffer_desc(len(message), message)
diff --git a/gssapi/raw/misc.pyi b/gssapi/raw/misc.pyi
new file mode 100644
index 00000000..574b178f
--- /dev/null
+++ b/gssapi/raw/misc.pyi
@@ -0,0 +1,166 @@
+import typing as t
+
+from gssapi.raw.names import Name
+from gssapi.raw.oids import OID
+from gssapi.raw.types import MechType
+
+
+def indicate_mechs() -> t.Set[OID]:
+    """Get the currently supported mechanisms.
+
+    This method retrieves the currently supported GSSAPI mechanisms.
+    Note that if unknown mechanims are found, those will be skipped.
+    """
+
+
+def inquire_names_for_mech(
+    mech: OID,
+) -> t.Set[OID]:
+    """
+    inquire_names_for_mech(mech)
+    Get the name types supported by a mechanism.
+
+    This method retrieves the different name types supported by
+    the given mechanism.
+
+    Args:
+        mech (~gssapi.OID): the mechanism in question
+
+    Returns:
+        list: the name type OIDs supported by the given mechanism
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def inquire_mechs_for_name(
+    name: Name,
+) -> t.Set[OID]:
+    """
+    inquire_mechs_for_name(name)
+    List the mechanisms which can process a name.
+
+    This method lists the mechanisms which may be able to
+    process the given name.
+
+    Args:
+        name (~gssapi.raw.names.Name): the name in question
+
+    Returns:
+        The mechanism OIDs able to process the given name
+
+    Raises:
+        ~gssapi.exceptions.GSSError
+    """
+
+
+def _display_status(
+    error_code: int,
+    is_major_code: bool,
+    mech: t.Optional[MechType] = None,
+    message_context: int = 0,
+) -> t.Tuple[bytes, int, bool]:
+    """
+    Display a string message for a GSSAPI error code.
+
+    This method displays a message for a corresponding GSSAPI error code.
+    Since some error codes might have multiple messages, a context parameter
+    may be passed to indicate where in the series of messages we currently are
+    (this is the second item in the return value tuple).  Additionally, the
+    third item in the return value tuple indicates whether or not more
+    messages are available.
+
+    Args:
+        error_code (int): The error code in question
+        is_major_code (bool): is this a major code (True) or a
+            minor code (False)
+        mech (~gssapi.raw.types.MechType): The mechanism type that returned
+            this error code (defaults to None, for the default mechanism)
+        message_context (int): The context for this call -- this is used when
+            multiple messages are available (defaults to 0)
+
+    Returns:
+        (bytes, int, bool): the message, the new message context, and
+            whether or not to call again for further messages
+
+    Raises:
+       ValueError
+    """
+
+
+class GSSError(Exception):
+    """
+    A GSSAPI Error
+
+    This Exception represents an error returned from the GSSAPI
+    C bindings.  It contains the major and minor status codes
+    returned by the method which caused the error, and can
+    generate human-readable string messages from the error
+    codes
+    """
+
+    maj_code: int
+    min_code: int
+    token: t.Optional[bytes]
+    calling_code: int
+    routine_code: int
+    supplementary_code: int
+
+    @classmethod
+    def _parse_major_code(
+        cls,
+        maj_code: int
+    ) -> t.Tuple[int, int, int]: ...
+
+    def __init__(
+        self,
+        maj_code: int,
+        min_code: int,
+        token: t.Optional[bytes] = None,
+    ) -> None:
+        """
+        Create a new GSSError.
+
+        This method creates a new GSSError,
+        retrieves the related human-readable
+        string messages, and uses the results to construct an
+        exception message
+
+        Args:
+            maj_code: the major code associated with this error
+            min_code: the minor code associated with this error
+            token: an error token associated with the error
+        """
+
+    def get_all_statuses(
+        self,
+        code: int,
+        is_maj: bool,
+    ) -> t.List[str]:
+        """
+        Retrieve all messages for a status code.
+
+        This method retrieves all human-readable messages
+        available for the given status code.
+
+        Args:
+            code: the status code in question
+            is_maj: whether this is a major status code (True)
+                or minor status code (False)
+
+        Returns:
+            [str]: A list of string messages associated with the
+                given code
+        """
+
+    def gen_message(self) -> str:
+        """
+        Retrieves all messages for this error's status codes
+
+        This method retrieves all messages for this error's status codes,
+        and forms them into a string for use as an exception message
+
+        Returns:
+            str: a string for use as this error's message
+        """
diff --git a/gssapi/raw/misc.pyx b/gssapi/raw/misc.pyx
index 622bfb7e..4179f642 100644
--- a/gssapi/raw/misc.pyx
+++ b/gssapi/raw/misc.pyx
@@ -31,14 +31,6 @@ cdef extern from "python_gssapi.h":
 
 
 def indicate_mechs():
-    """
-    indicate_mechs()
-    Get the currently supported mechanisms.
-
-    This method retrieves the currently supported GSSAPI mechanisms.
-    Note that if unknown mechanims are found, those will be skipped.
-    """
-
     cdef gss_OID_set mech_set
 
     cdef OM_uint32 maj_stat, min_stat
@@ -52,23 +44,6 @@ def indicate_mechs():
 
 
 def inquire_names_for_mech(OID mech not None):
-    """
-    inquire_names_for_mech(mech)
-    Get the name types supported by a mechanism.
-
-    This method retrives the different name types supported by
-    the given mechanism.
-
-    Args:
-        mech (OID): the mechanism in question
-
-    Returns:
-        list: the name type OIDs supported by the given mechanism
-
-    Raises:
-        GSSError
-    """
-
     cdef gss_OID_set name_types
 
     cdef OM_uint32 maj_stat, min_stat
@@ -83,23 +58,6 @@ def inquire_names_for_mech(OID mech not None):
 
 
 def inquire_mechs_for_name(Name name not None):
-    """
-    inquire_mechs_for_name(name)
-    List the mechanisms which can process a name.
-
-    This method lists the mechanisms which may be able to
-    process the given name.
-
-    Args:
-        name (Name): the name in question
-
-    Returns:
-        list: the mechanism OIDs able to process the given name
-
-    Raises:
-        GSSError
-    """
-
     cdef gss_OID_set mech_types
 
     cdef OM_uint32 maj_stat, min_stat
@@ -115,33 +73,6 @@ def inquire_mechs_for_name(Name name not None):
 
 def _display_status(unsigned int error_code, bint is_major_code,
                     OID mech=None, unsigned int message_context=0):
-    """
-    Display a string message for a GSSAPI error code.
-
-    This method displays a message for a corresponding GSSAPI error code.
-    Since some error codes might have multiple messages, a context parameter
-    may be passed to indicate where in the series of messages we currently are
-    (this is the second item in the return value tuple).  Additionally, the
-    third item in the return value tuple indicates whether or not more
-    messages are available.
-
-    Args:
-        error_code (int): The error code in question
-        is_major_code (bool): is this a major code (True) or a
-            minor code (False)
-        mech (MechType): The mechanism type that returned this error code
-            (defaults to None, for the default mechanism)
-        message_context (int): The context for this call -- this is used when
-            multiple messages are available (defaults to 0)
-
-    Returns:
-        (bytes, int, bool): the message, the new message context, and
-            whether or not to call again for further messages
-
-    Raises:
-       ValueError
-    """
-
     cdef int status_type
     cdef gss_OID c_mech_type
 
@@ -223,15 +154,6 @@ class GSSErrorRegistry(type):
 # NB(directxman12): this needs to be here (and not in another file)
 #                   so that display_status can use it
 class GSSError(Exception, metaclass=GSSErrorRegistry):
-    """
-    A GSSAPI Error
-
-    This Exception represents an error returned from the GSSAPI
-    C bindings.  It contains the major and minor status codes
-    returned by the method which caused the error, and can
-    generate human-readable string messages from the error
-    codes
-    """
 
     MESSAGE = u"Major ({maj_stat}): {maj_str}, Minor ({min_stat}): {min_str}"
 
@@ -248,20 +170,6 @@ class GSSError(Exception, metaclass=GSSErrorRegistry):
         return (calling_code, routine_code, supplementary_code)
 
     def __init__(self, maj_code, min_code, token=None):
-        """
-        Create a new GSSError.
-
-        This method creates a new GSSError,
-        retrieves the releated human-readable
-        string messages, and uses the results to construct an
-        exception message
-
-        Args:
-            maj_code (int): the major code associated with this error
-            min_code (int): the minor code associated with this error
-            token (bytes): an error token associated with the error
-        """
-
         self.maj_code = maj_code
         self.min_code = min_code
 
@@ -275,22 +183,6 @@ class GSSError(Exception, metaclass=GSSErrorRegistry):
         super(GSSError, self).__init__(self.gen_message())
 
     def get_all_statuses(self, code, is_maj):
-        """
-        Retrieve all messages for a status code.
-
-        This method retrieves all human-readable messages
-        available for the given status code.
-
-        Args:
-            code (int): the status code in question
-            is_maj (bool): whether this is a major status code (True)
-                or minor status code (False)
-
-        Returns:
-            [bytes]: A list of string messages associated with the
-                given code
-        """
-
         try:
             msg_encoding = locale.getlocale(locale.LC_MESSAGES)[1] or 'UTF-8'
         except AttributeError:  # Windows doesn't have LC_MESSAGES
@@ -309,23 +201,13 @@ class GSSError(Exception, metaclass=GSSErrorRegistry):
                 msg, ctx, cont = _display_status(code, is_maj,
                                                  message_context=ctx)
                 res.append(msg.decode(msg_encoding))
-            except ValueError:
+            except ValueError as e:
                 res.append(u'{0}  Decoding code: {1}'.format(e, code))
                 cont = False
 
         return res
 
     def gen_message(self):
-        """
-        Retrieves all messages for this error's status codes
-
-        This method retrieves all messages for this error's status codes,
-        and forms them into a string for use as an exception message
-
-        Returns:
-            bytes: a string for use as this error's message
-        """
-
         maj_statuses = self.get_all_statuses(self.maj_code, True)
         min_statuses = self.get_all_statuses(self.min_code, False)
 
diff --git a/gssapi/raw/named_tuples.py b/gssapi/raw/named_tuples.py
index 3ce494ea..4eda145c 100644
--- a/gssapi/raw/named_tuples.py
+++ b/gssapi/raw/named_tuples.py
@@ -1,76 +1,223 @@
-from collections import namedtuple
-
-
-AcquireCredResult = namedtuple('AcquireCredResult',
-                               ['creds', 'mechs', 'lifetime'])
-
-
-InquireCredResult = namedtuple('InquireCredResult',
-                               ['name', 'lifetime', 'usage',
-                                'mechs'])
-
-
-InquireCredByMechResult = namedtuple('InquireCredByMechResult',
-                                     ['name', 'init_lifetime',
-                                      'accept_lifetime', 'usage'])
-
-
-AddCredResult = namedtuple('AddCredResult',
-                           ['creds', 'mechs', 'init_lifetime',
-                            'accept_lifetime'])
-
-
-DisplayNameResult = namedtuple('DisplayNameResult',
-                               ['name', 'name_type'])
-
-
-WrapResult = namedtuple('WrapResult',
-                        ['message', 'encrypted'])
-
-
-UnwrapResult = namedtuple('UnwrapResult',
-                          ['message', 'encrypted', 'qop'])
-
-
-AcceptSecContextResult = namedtuple('AcceptSecContextResult',
-                                    ['context', 'initiator_name',
-                                     'mech', 'token', 'flags', 'lifetime',
-                                     'delegated_creds', 'more_steps'])
-
-
-InitSecContextResult = namedtuple('InitSecContextResult',
-                                  ['context', 'mech', 'flags', 'token',
-                                   'lifetime', 'more_steps'])
-
-
-InquireContextResult = namedtuple('InquireContextResult',
-                                  ['initiator_name', 'target_name',
-                                   'lifetime', 'mech', 'flags',
-                                   'locally_init', 'complete'])
-
-
-StoreCredResult = namedtuple('StoreCredResult',
-                             ['mechs', 'usage'])
-
-
-IOVUnwrapResult = namedtuple('IOVUnwrapResult',
-                             ['encrypted', 'qop'])
-
-
-InquireNameResult = namedtuple('InquireNameResult',
-                               ['attrs', 'is_mech_name', 'mech'])
-
-
-GetNameAttributeResult = namedtuple('GetNamedAttributeResult',
-                                    ['values', 'display_values',
-                                     'authenticated', 'complete'])
-
-InquireAttrsResult = namedtuple('InquireAttrsResult',
-                                ['mech_attrs', 'known_mech_attrs'])
-
-DisplayAttrResult = namedtuple('DisplayAttrResult', ['name', 'short_desc',
-                                                     'long_desc'])
-
-InquireSASLNameResult = namedtuple('InquireSASLNameResult',
-                                   ['sasl_mech_name', 'mech_name',
-                                    'mech_description'])
+from typing import List, NamedTuple, Optional, Set, TYPE_CHECKING
+
+from gssapi.raw.oids import OID
+from gssapi.raw.types import RequirementFlag
+
+if TYPE_CHECKING:
+    import gssapi
+
+
+class AcquireCredResult(NamedTuple):
+    """Credential result when acquiring a GSSAPI credential."""
+    #: GSSAPI credentials that were acquired
+    creds: "gssapi.raw.creds.Creds"
+    #: Set of mechs the cred is for
+    mechs: Set[OID]
+    #: Number of seconds for which the cred will remain valid
+    lifetime: int
+
+
+class InquireCredResult(NamedTuple):
+    """Information about the credential."""
+    #: The principal associated with the credential
+    name: Optional["gssapi.raw.names.Name"]
+    #: Number of seconds which the cred is valid for
+    lifetime: Optional[int]
+    #: How the credential can be used
+    usage: Optional[str]
+    #: Set of mechs the cred is for
+    mechs: Optional[Set[OID]]
+
+
+class InquireCredByMechResult(NamedTuple):
+    """Information about the credential for a specific mechanism."""
+    #: The principal associated with the credential
+    name: Optional["gssapi.raw.names.Name"]
+    #: Time valid for initiation, in seconds
+    init_lifetime: Optional[int]
+    #: Time valid for accepting, in seconds
+    accept_lifetime: Optional[int]
+    #: How the credential can be used
+    usage: Optional[str]
+
+
+class AddCredResult(NamedTuple):
+    """Result of adding to a GSSAPI credential."""
+    #: The credential that was generated
+    creds: Optional["gssapi.raw.creds.Creds"]
+    #: Set of mechs the cred is for
+    mechs: Set[OID]
+    #: Time valid for initiation, in seconds
+    init_lifetime: int
+    #: Time valid for accepting, in seconds
+    accept_lifetime: int
+
+
+class DisplayNameResult(NamedTuple):
+    """Textual representation of a GSSAPI name."""
+    #: The representation of the GSSAPI name
+    name: bytes
+    #: The type of GSSAPI name
+    name_type: Optional[OID]
+
+
+class WrapResult(NamedTuple):
+    """Wrapped message result."""
+    #: The wrapped message
+    message: bytes
+    #: Whether the message is encrypted and not just signed
+    encrypted: bool
+
+
+class UnwrapResult(NamedTuple):
+    """Unwrapped message result."""
+    #: The unwrapped message
+    message: bytes
+    #: Whether the message was encrypted and not just signed
+    encrypted: bool
+    #: The quality of protection applied to the message
+    qop: int
+
+
+class AcceptSecContextResult(NamedTuple):
+    """Result when accepting a security context by an initiator."""
+    #: The acceptor security context
+    context: "gssapi.raw.sec_contexts.SecurityContext"
+    #: The authenticated name of the initiator
+    initiator_name: "gssapi.raw.names.Name"
+    #: Mechanism with which the context was established
+    mech: OID
+    #: Token to be returned to the initiator
+    token: Optional[bytes]
+    #: Services requested by the initiator
+    flags: RequirementFlag
+    #: Seconds for which the context is valid for
+    lifetime: int
+    #: Delegated credentials
+    delegated_creds: Optional["gssapi.raw.creds.Creds"]
+    #: More input is required to complete the exchange
+    more_steps: bool
+
+
+class InitSecContextResult(NamedTuple):
+    """Result when initiating a security context"""
+    #: The initiator security context
+    context: "gssapi.raw.sec_contexts.SecurityContext"
+    #: Mechanism used in the security context
+    mech: OID
+    #: Services available for the context
+    flags: RequirementFlag
+    #: Token to be sent to the acceptor
+    token: Optional[bytes]
+    #: Seconds for which the context is valid for
+    lifetime: int
+    #: More input is required to complete the exchange
+    more_steps: bool
+
+
+class InquireContextResult(NamedTuple):
+    """Information about the security context."""
+    #: Name of the initiator
+    initiator_name: Optional["gssapi.raw.names.Name"]
+    #: Name of the acceptor
+    target_name: Optional["gssapi.raw.names.Name"]
+    #: Time valid for the security context, in seconds
+    lifetime: Optional[int]
+    #: Mech used to create the security context
+    mech: Optional[OID]
+    #: Services available for the context
+    flags: Optional[RequirementFlag]
+    #: Context was initiated locally
+    locally_init: Optional[bool]
+    #: Context has been established and ready to use
+    complete: Optional[bool]
+
+
+class StoreCredResult(NamedTuple):
+    """Result of the credential storing operation."""
+    #: Mechs that were stored in the credential store
+    mechs: List[OID]
+    #: How the credential can be used
+    usage: str
+
+
+class IOVUnwrapResult(NamedTuple):
+    """Unwrapped IOV message result."""
+    #: Whether the message was encrypted and not just signed
+    encrypted: bool
+    #: The quality of protection applied to the message
+    qop: int
+
+
+class InquireNameResult(NamedTuple):
+    """Information about a GSSAPI Name."""
+    #: Set of attribute names
+    attrs: List[bytes]
+    #: Name is a mechanism name
+    is_mech_name: bool
+    #: The mechanism if is_name_mech is True
+    mech: OID
+
+
+class GetNameAttributeResult(NamedTuple):
+    """GSSAPI Name attribute values."""
+    #: Raw values
+    values: List[bytes]
+    #: Human-readable values
+    display_values: List[bytes]
+    #: Attribute has been authenticated
+    authenticated: bool
+    #: Attribute value is marked as complete
+    complete: bool
+
+
+class InquireAttrsResult(NamedTuple):
+    """Set of attributes supported and known by a mechanism."""
+    #: The mechanisms attributes
+    mech_attrs: Set[OID]
+    #: Known attributes of the mechanism
+    known_mech_attrs: Set[OID]
+
+
+class DisplayAttrResult(NamedTuple):
+    """Information about an attribute."""
+    #: The mechanism name
+    name: bytes
+    #: Short description of the mechanism
+    short_desc: bytes
+    #: Long description of the mechanism
+    long_desc: bytes
+
+
+class InquireSASLNameResult(NamedTuple):
+    """SASL informmation about a GSSAPI Name."""
+    #: The SASL name
+    sasl_mech_name: bytes
+    #: The mechanism name
+    mech_name: bytes
+    #: The mechanism description
+    mech_description: bytes
+
+
+class Rfc1964KeyData(NamedTuple):
+    """Security context key data based on RFC1964."""
+    #: Signing algorithm identifier
+    sign_alg: int
+    #: Sealing algorithm identifier
+    seal_alg: int
+    #: Key encryption type identifier
+    key_type: int
+    #: Encryption key data
+    key: bytes
+
+
+class CfxKeyData(NamedTuple):
+    """Securty context key data."""
+    #: Context key encryption type identifier
+    ctx_key_type: int
+    #: Context key data - session or sub-session key
+    ctx_key: bytes
+    #: Acceptor key enc type identifier
+    acceptor_subkey_type: Optional[int]
+    #: Acceptor key data
+    acceptor_subkey: Optional[bytes]
diff --git a/gssapi/raw/names.pyi b/gssapi/raw/names.pyi
new file mode 100644
index 00000000..85d91611
--- /dev/null
+++ b/gssapi/raw/names.pyi
@@ -0,0 +1,170 @@
+import typing as t
+
+if t.TYPE_CHECKING:
+    from gssapi.raw.named_tuples import DisplayNameResult
+    from gssapi.raw.oids import OID
+
+class Name:
+    """
+    A GSSAPI Name
+    """
+
+    def __new__(
+        cls,
+        cpy: t.Optional["Name"] = None,
+    ) -> "Name": ...
+
+
+def import_name(
+    name: bytes,
+    name_type: t.Optional["OID"] = None,
+) -> Name:
+    """Convert a string and a name type into a GSSAPI name.
+
+    This method takes a string name and a name type and converts
+    them into a GSSAPI :class:`Name`.
+
+    Args:
+        name (~gssapi.raw.names.Name): the string version of the name
+        name_type (~gssapi.raw.types.MechType): the type of this name
+
+    Returns:
+        Name: the GSSAPI version of the name
+
+    Raises:
+        ~gssapi.exceptions.BadNameTypeError
+        ~gssapi.exceptions.BadNameError
+        ~gssapi.exceptions.BadMechanismError
+    """
+
+
+def display_name(
+    name: Name,
+    name_type: bool = True,
+) -> "DisplayNameResult":
+    """Convert a GSSAPI name into its components.
+
+    This method converts a GSSAPI :class:`Name` back into its
+    text form.  If ``name_type`` is True, it also attempts to
+    retrieve the :class:`~gssapi.raw.types.NameType` of the name (otherwise the
+    returned name type will be ``None``).
+
+    Args:
+        name (~gssapi.raw.names.Name): the name in question
+        name_type (~gssapi.raw.types.MechType): whether or not to retrieve the
+            name type
+
+    Returns:
+        DisplayNameResult: the text part of the name and its type
+
+    Raises:
+        ~gssapi.exceptions.BadNameError
+    """
+
+
+def compare_name(
+    name1: Name,
+    name2: Name,
+) -> bool:
+    """Check two GSSAPI names to see if they are the same.
+
+    This method compares two GSSAPI names, checking to
+    see if they are equivalent.
+
+    Args:
+        name1 (~gssapi.raw.names.Name): the first name to compare
+        name2 (~gssapi.raw.names.Name): the second name to compare
+
+    Returns:
+        bool: whether or not the names are equal
+
+    Raises:
+        ~gssapi.exceptions.BadNameTypeError
+        ~gssapi.exceptions.BadNameError
+    """
+
+
+def export_name(
+    name: Name,
+) -> bytes:
+    """Export a GSSAPI name.
+
+    This method "produces a canonical contigous string representation
+    of a mechanism name, suitable for direct comparison for use in
+    authorization functions".
+
+    The input name must be a valid GSSAPI mechanism name, as generated by
+    :func:`canonicalize_name` or
+    :func:`~gssapi.raw.sec_contexts.accept_sec_context`.
+
+    Args:
+        name (~gssapi.raw.names.Name): the name to export
+
+    Returns:
+        bytes: the exported name
+
+    Raises:
+        ~gssapi.exceptions.MechanismNameRequiredError
+        ~gssapi.exceptions.BadNameTypeError
+        ~gssapi.exceptions.BadNameError
+    """
+
+
+def canonicalize_name(
+    name: Name,
+    mech: "OID",
+) -> Name:
+    """Canonicalize an arbitrary GSSAPI Name into a Mechanism Name
+
+    This method turns any GSSAPI name into a "mechanism name" --
+    a full form name specific to a mechanism.
+
+    Args:
+        name (~gssapi.raw.names.Name): the name to canonicalize
+        mech (~gssapi.raw.types.MechType): the mechanism type to use to
+            canonicalize the name
+
+    Returns:
+        Name: a canonicalized version of the input name
+
+    Raises:
+        ~gssapi.exceptions.BadMechanismError
+        ~gssapi.exceptions.BadNameTypeError
+        ~gssapi.exceptions.BadNameError
+    """
+
+
+def duplicate_name(
+    name: Name,
+) -> Name:
+    """Duplicate a GSSAPI name.
+
+    Args:
+        name (~gssapi.raw.names.Name): the name to duplicate
+
+    Returns:
+        Name: a duplicate of the input name
+
+    Raises:
+        ~gssapi.exceptions.BadNameError
+    """
+
+
+def release_name(
+    name: Name,
+) -> None:
+    """Release a GSSAPI name.
+
+    This method frees a GSSAPI :class:`Name`.
+    You probably won't have to do this.
+
+    Warning:
+        This method is deprecated.  Names are
+        automatically freed by Python.
+
+    Args:
+        name (~gssapi.raw.names.Name): the name in question
+
+    Raises:
+        ~gssapi.exceptions.BadNameError
+    """
diff --git a/gssapi/raw/names.pyx b/gssapi/raw/names.pyx
index cfeaef77..f4d22bef 100644
--- a/gssapi/raw/names.pyx
+++ b/gssapi/raw/names.pyx
@@ -41,9 +41,6 @@ cdef extern from "python_gssapi.h":
 
 
 cdef class Name:
-    """
-    A GSSAPI Name
-    """
     # defined in pxd
     # cdef gss_name_t raw_name
 
@@ -66,26 +63,6 @@ cdef class Name:
 
 
 def import_name(name not None, OID name_type=None):
-    """
-    import_name(name, name_type=None)
-    Convert a string and a name type into a GSSAPI name.
-
-    This method takes a string name and a name type and converts
-    them into a GSSAPI :class:`Name`.
-
-    Args:
-        name (bytes): the string version of the name
-        name_type (NameType): the type of this name
-
-    Returns:
-        Name: the GSSAPI version of the name
-
-    Raises:
-        BadNameTypeError
-        BadNameError
-        BadMechanismError
-    """
-
     cdef gss_OID nt
     if name_type is None:
         nt = GSS_C_NO_OID
@@ -114,26 +91,6 @@ def import_name(name not None, OID name_type=None):
 
 
 def display_name(Name name not None, name_type=True):
-    """
-    display_name(name, name_type=True)
-    Convert a GSSAPI name into its components.
-
-    This method converts a GSSAPI :class:`Name` back into its
-    text form.  If ``name_type`` is True, it also attempts to
-    retrieve the :class:`NameType` of the name (otherwise the
-    returned name type will be ``None``).
-
-    Args:
-        name (Name): the name in question
-        name_type (bool): whether or not to retrieve the name type
-
-    Returns:
-        DisplayNameResult: the text part of the name and its type
-
-    Raises:
-        BadNameError
-    """
-
     # GSS_C_EMPTY_BUFFER
     cdef gss_buffer_desc output_buffer = gss_buffer_desc(0, NULL)
 
@@ -169,25 +126,6 @@ def display_name(Name name not None, name_type=True):
 
 
 def compare_name(Name name1=None, Name name2=None):
-    """
-    compare_name(name1, name2)
-    Check two GSSAPI names to see if they are the same.
-
-    This method compares two GSSAPI names, checking to
-    see if they are equivalent.
-
-    Args:
-        name1 (Name): the first name to compare
-        name2 (Name): the second name to compare
-
-    Returns:
-        bool: whether or not the names are equal
-
-    Raises:
-        BadNameTypeError
-        BadNameError
-    """
-
     # check for either value being None
     if name1 is None and name2 is None:
         return True
@@ -208,28 +146,6 @@ def compare_name(Name name1=None, Name name2=None):
 
 
 def export_name(Name name not None):
-    """
-    Export a GSSAPI name.
-
-    This method "produces a canonical contigous string representation
-    of a mechanism name, suitable for direct comparison for use in
-    authorization functions".
-
-    The input name must be a valid GSSAPI mechanism name, as generated
-    by :func:`canonicalize_name` or :func:`accept_sec_context`.
-
-    Args:
-        name (Name): the name to export
-
-    Returns:
-        bytes: the exported name
-
-    Raises:
-        MechanismNameRequiredError
-        BadNameTypeError
-        BadNameError
-    """
-
     # GSS_C_EMPTY_BUFFER
     cdef gss_buffer_desc exported_name = gss_buffer_desc(0, NULL)
 
@@ -248,27 +164,6 @@ def export_name(Name name not None):
 
 
 def canonicalize_name(Name name not None, OID mech not None):
-    """
-    canonicalize_name(name, mech)
-    Canonicalize an arbitrary GSSAPI Name into a Mechanism Name
-
-    This method turns any GSSAPI name into a "mechanism name" --
-    a full form name specific to a mechanism.
-
-    Args:
-        name (Name): the name to canonicalize
-        mech (MechType): the mechanism type to use to
-            canonicalize the name
-
-    Returns:
-        Name: a canonicalized version of the input name
-
-    Raises:
-        BadMechanismError
-        BadNameTypeError
-        BadNameError
-    """
-
     cdef gss_name_t canonicalized_name
 
     cdef OM_uint32 maj_stat, min_stat
@@ -287,20 +182,6 @@ def canonicalize_name(Name name not None, OID mech not None):
 
 
 def duplicate_name(Name name not None):
-    """
-    duplicate_name(name)
-    Duplicate a GSSAPI name.
-
-    Args:
-        name (Name): the name to duplicate
-
-    Returns:
-        Name: a duplicate of the input name
-
-    Raises:
-        BadNameError
-    """
-
     cdef gss_name_t new_name
 
     cdef OM_uint32 maj_stat, min_stat
@@ -316,24 +197,6 @@ def duplicate_name(Name name not None):
 
 
 def release_name(Name name not None):
-    """
-    release_name(name)
-    Release a GSSAPI name.
-
-    This method frees a GSSAPI :class:`Name`.
-    You probably won't have to do this.
-
-    Warning:
-        This method is deprecated.  Names are
-        automatically freed by Python.
-
-    Args:
-        name (Name): the name in question
-
-    Raises:
-        BadNameError
-    """
-
     cdef OM_uint32 maj_stat, min_stat
     maj_stat = gss_release_name(&min_stat, &name.raw_name)
     if maj_stat != GSS_S_COMPLETE:
diff --git a/gssapi/raw/oids.pyi b/gssapi/raw/oids.pyi
new file mode 100644
index 00000000..7815601b
--- /dev/null
+++ b/gssapi/raw/oids.pyi
@@ -0,0 +1,50 @@
+import typing as t
+
+class OID:
+    """
+    A GSSAPI OID
+
+    A new OID may be created by passing the `elements` argument
+    to the constructor.  The `elements` argument should be a
+    :class:`bytes` consisting of the BER-encoded values in the OID.
+
+    To retrieve the underlying bytes, use the :class:`bytes`
+    function in Python 3.
+
+    This object is hashable, and may be compared using equality
+    operators.
+    """
+
+    def __new__(
+        cls,
+        cpy: t.Optional["OID"] = None,
+        elements: t.Optional[bytes] = None,
+    ) -> "OID": ...
+
+    @classmethod
+    def from_int_seq(
+        cls,
+        integer_sequence: t.Union[str, t.Iterable[int]],
+    ) -> "OID":
+        """Create a OID from a sequence of integers.
+
+        This method creates an OID from a sequence of integers.
+        The sequence can either be in dotted form as a string,
+        or in list form.
+
+        This method is not for BER-encoded byte strings, which
+        can be passed directly to the OID constructor.
+
+        Args:
+            integer_sequence: either a list of integers or
+                a string in dotted form
+
+        Returns:
+            OID: the OID represented by the given integer sequence
+
+        Raises:
+            ValueError: the sequence is less than two elements long
+        """
+
+    @property
+    def dotted_form(self) -> str: ...
diff --git a/gssapi/raw/oids.pyx b/gssapi/raw/oids.pyx
index b0bd5546..aa0e17af 100644
--- a/gssapi/raw/oids.pyx
+++ b/gssapi/raw/oids.pyx
@@ -11,20 +11,6 @@ cdef inline bint c_compare_oids(gss_OID a, gss_OID b):
 
 
 cdef class OID:
-    """
-    A GSSAPI OID
-
-    A new OID may be created by passing the `elements` argument
-    to the constructor.  The `elements` argument should be a
-    `bytes` consisting of the BER-encoded values in the OID.
-
-    To retrive the underlying bytes, use the :func:`bytes`
-    function in Python 3 or the :meth:`__bytes__` method directly
-    in Python 2.
-
-    This object is hashable, and may be compared using equality
-    operators.
-    """
     # defined in pxd
     # cdef gss_OID_desc raw_oid = NULL
     # cdef bint _free_on_dealloc = NULL
@@ -73,28 +59,6 @@ cdef class OID:
 
     @classmethod
     def from_int_seq(cls, integer_sequence):
-        """
-        from_int_seq(integer_sequence)
-        Create a OID from a sequence of integers.
-
-        This method creates an OID from a sequence of integers.
-        The sequence can either be in dotted form as a string,
-        or in list form.
-
-        This method is not for BER-encoded byte strings, which
-        can be passed directly to the OID constructor.
-
-        Args:
-            integer_sequence: either a list of integers or
-                a string in dotted form
-
-        Returns:
-            OID: the OID represented by the given integer sequence
-
-        Raises:
-            ValueError: the sequence is less than two elements long
-        """
-
         if isinstance(integer_sequence, str):
             integer_sequence = integer_sequence.split('.')
 
diff --git a/gssapi/raw/python_gssapi_ext.h b/gssapi/raw/python_gssapi_ext.h
index 3d32a4b8..11376d9a 100644
--- a/gssapi/raw/python_gssapi_ext.h
+++ b/gssapi/raw/python_gssapi_ext.h
@@ -1,6 +1,40 @@
 #ifdef OSX_HAS_GSS_FRAMEWORK
 #include 
-#else
+
+/*
+ * Starting in macOS 10.7, Apple's GSS defines these in
+ * gssapi_private.h. However, that header isn't present on the host, so we
+ * need to explicitly define them.  The originals can be found at:
+ * https://opensource.apple.com/source/Heimdal/Heimdal-172.18/lib/gssapi/gssapi/gssapi_spi.h.auto.html
+ */
+
+OM_uint32 __ApplePrivate_gss_unwrap_iov(OM_uint32 *minor_status,
+                                        gss_ctx_id_t context_handle,
+                                        int *conf_state, gss_qop_t *qop_state,
+                                        gss_iov_buffer_desc *iov,
+                                        int iov_count);
+
+OM_uint32 __ApplePrivate_gss_wrap_iov(OM_uint32 *minor_status,
+                                      gss_ctx_id_t context_handle,
+                                      int conf_req_flag, gss_qop_t qop_req,
+                                      int *conf_state,
+                                      gss_iov_buffer_desc *iov,
+                                      int iov_count);
+
+OM_uint32 __ApplePrivate_gss_wrap_iov_length(OM_uint32 *minor_status,
+                                             gss_ctx_id_t context_handle,
+                                             int conf_req_flag,
+                                             gss_qop_t qop_req,
+                                             int *conf_state,
+                                             gss_iov_buffer_desc *iov,
+                                             int iov_count);
+
+OM_uint32 __ApplePrivate_gss_release_iov_buffer(OM_uint32 *minor_status,
+                                                gss_iov_buffer_desc *iov,
+                                                int iov_count);
+
+#else /* !OSX_HAS_GSS_FRAMEWORK */
+
 #if defined(__MINGW32__) && defined(__MSYS__)
 #include 
 #else
@@ -10,4 +44,5 @@
 #include 
 #endif
 #endif
-#endif
+
+#endif /* !OSX_HAS_GSS_FRAMEWORK */
diff --git a/gssapi/raw/python_gssapi_krb5.h b/gssapi/raw/python_gssapi_krb5.h
index c67af3b9..1ca45e01 100644
--- a/gssapi/raw/python_gssapi_krb5.h
+++ b/gssapi/raw/python_gssapi_krb5.h
@@ -1,5 +1,23 @@
 #ifdef OSX_HAS_GSS_FRAMEWORK
 #include 
+
+/* These functions are "private" in macOS GSS. They need to be redeclared so
+ * Cython can see them. */
+OM_uint32
+__ApplePrivate_gsskrb5_extract_authtime_from_sec_context(OM_uint32 *minor,
+                                                         gss_ctx_id_t context,
+                                                         void *authtime);
+
+OM_uint32 __ApplePrivate_gss_krb5_import_cred(OM_uint32 *minor_status,
+                                              void *id,
+                                              void *keytab_principal,
+                                              void *keytab,
+                                              gss_cred_id_t *cred);
+
+OM_uint32 __ApplePrivate_gss_krb5_get_tkt_flags(OM_uint32 *minor_status,
+                                                gss_ctx_id_t context_handle,
+                                                void *tkt_flags);
+
 #elif defined(__MINGW32__) && defined(__MSYS__)
 #include 
 #else
diff --git a/gssapi/raw/sec_contexts.pyi b/gssapi/raw/sec_contexts.pyi
new file mode 100644
index 00000000..40022053
--- /dev/null
+++ b/gssapi/raw/sec_contexts.pyi
@@ -0,0 +1,294 @@
+import typing as t
+
+if t.TYPE_CHECKING:
+    from gssapi.raw.chan_bindings import ChannelBindings
+    from gssapi.raw.creds import Creds
+    from gssapi.raw.named_tuples import (
+        AcceptSecContextResult,
+        InitSecContextResult,
+        InquireContextResult,
+    )
+    from gssapi.raw.names import Name
+    from gssapi.raw.oids import OID
+    from gssapi.raw.types import RequirementFlag
+
+class SecurityContext:
+    """
+    A GSSAPI Security Context
+    """
+
+    def __new__(
+        cls,
+        cpy: t.Optional["SecurityContext"] = None,
+    ) -> "SecurityContext": ...
+
+    @property
+    def _started(self) -> bool: ...
+
+
+def init_sec_context(
+    name: "Name",
+    creds: t.Optional["Creds"] = None,
+    context: t.Optional[SecurityContext] = None,
+    mech: t.Optional["OID"] = None,
+    flags: t.Optional[t.Union[
+        int, "RequirementFlag",
+        t.Iterable[int], t.Iterable["RequirementFlag"]
+    ]] = None,
+    lifetime: t.Optional[int] = None,
+    channel_bindings: t.Optional["ChannelBindings"] = None,
+    input_token: t.Optional[bytes] = None,
+) -> "InitSecContextResult":
+    """Initiate a GSSAPI security context.
+
+    This method initiates a GSSAPI security context, targeting the given
+    target name.  To create a basic context, just provide the target name.
+    Further calls used to update the context should pass in the output context
+    of the last call, as well as the input token received from the acceptor.
+
+    Warning:
+        This changes the input context!
+
+    Args:
+        target_name (~gssapi.raw.names.Name): the target for the security
+            context
+        creds (Creds): the credentials to use to initiate the context,
+            or None to use the default credentials
+        context (~gssapi.raw.sec_contexts.SecurityContext): the security
+            context to update, or None to create a new context
+        mech (~gssapi.raw.types.MechType): the mechanism type for this security
+            context, or None for the default mechanism type
+        flags (list): the flags to request for the security context, or
+            None to use the default set: mutual_authentication and
+            out_of_sequence_detection.  This may also be an
+            :class:`IntEnumFlagSet`
+        lifetime (int): the request lifetime of the security context in seconds
+            (a value of 0 or None means indefinite)
+        channel_bindings (ChannelBindings): The channel bindings (or None for
+            no channel bindings)
+        input_token (bytes): the token to use to update the security context,
+            or None if you are creating a new context
+
+    Returns:
+        InitSecContextResult: the output security context, the actual mech
+        type, the actual flags used, the output token to send to the acceptor,
+        the actual lifetime of the context in seconds (or None if not supported
+        or indefinite), and whether or not more calls are needed to finish the
+        initiation.
+
+    Raises:
+        ~gssapi.exceptions.InvalidTokenError
+        ~gssapi.exceptions.InvalidCredentialsError
+        ~gssapi.exceptions.MissingCredentialsError
+        ~gssapi.exceptions.ExpiredCredentialsError
+        ~gssapi.exceptions.BadChannelBindingsError
+        ~gssapi.exceptions.BadMICError
+        ~gssapi.exceptions.ExpiredTokenError
+        ~gssapi.exceptions.DuplicateTokenError
+        ~gssapi.exceptions.MissingContextError
+        ~gssapi.exceptions.BadNameTypeError
+        ~gssapi.exceptions.BadNameError
+        ~gssapi.exceptions.BadMechanismError
+    """
+
+
+def accept_sec_context(
+    input_token: bytes,
+    acceptor_creds: t.Optional["Creds"] = None,
+    context: t.Optional[SecurityContext] = None,
+    channel_bindings: t.Optional["ChannelBindings"] = None,
+) -> "AcceptSecContextResult":
+    """Accept a GSSAPI security context.
+
+    This method accepts a GSSAPI security context using a token sent by the
+    initiator, using the given credentials.  It can either be used to accept a
+    security context and create a new security context object, or to update an
+    existing security context object.
+
+    Warning:
+        This changes the input context!
+
+    Args:
+        input_token (bytes): the token sent by the context initiator
+        acceptor_creds (Creds): the credentials to be used to accept the
+            context (or None to use the default credentials)
+        context (~gssapi.raw.sec_contexts.SecurityContext): the security
+            context to update (or None to create a new security context object)
+        channel_bindings (ChannelBindings): The channel bindings (or None for
+            no channel bindings)
+
+    Returns:
+        AcceptSecContextResult: the resulting security context, the initiator
+        name, the mechanism being used, the output token, the flags in use,
+        the lifetime of the context in seconds (or None for indefinite or not
+        supported), the delegated credentials (valid only if the
+        delegate_to_peer flag is set), and whether or not further token
+        exchanges are needed to finalize the security context.
+
+    Raises:
+        ~gssapi.exceptions.InvalidTokenError
+        ~gssapi.exceptions.InvalidCredentialsError
+        ~gssapi.exceptions.MissingCredentialsError
+        ~gssapi.exceptions.ExpiredCredentialsError
+        ~gssapi.exceptions.BadChannelBindingsError
+        ~gssapi.exceptions.MissingContextError
+        ~gssapi.exceptions.BadMICError
+        ~gssapi.exceptions.ExpiredTokenError
+        ~gssapi.exceptions.DuplicateTokenError
+        ~gssapi.exceptions.BadMechanismError
+    """
+
+
+def inquire_context(
+    context: SecurityContext,
+    initiator_name: bool = True,
+    target_name: bool = True,
+    lifetime: bool = True,
+    mech: bool = True,
+    flags: bool = True,
+    locally_init: bool = True,
+    complete: bool = True,
+) -> "InquireContextResult":
+    """Get information about a security context.
+
+    This method obtains information about a security context, including
+    the initiator and target names, as well as the TTL, mech,
+    flags, and its current state (open vs closed).
+
+    Note:
+        the target name may be ``None`` if it would have been ``GSS_C_NO_NAME``
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the context in
+            question
+
+    Returns:
+        InquireContextResult: the initiator name, the target name, the TTL
+        (can be None for indefinite or not supported), the mech type, the
+        flags, whether or not the context was locally initiated,
+        and whether or not the context is currently fully established
+
+    Raises:
+        ~gssapi.exceptions.MissingContextError
+    """
+
+
+def context_time(
+    context: SecurityContext,
+) -> int:
+    """Get the amount of time for which the given context will remain valid.
+
+    This method determines the amount of time for which the given
+    security context will remain valid.  An expired context will
+    give a result of 0.
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the security
+            context in question
+
+    Returns:
+        int: the number of seconds for which the context will be valid
+
+    Raises:
+        ~gssapi.exceptions.ExpiredContextError
+        ~gssapi.exceptions.MissingContextError
+    """
+
+
+def process_context_token(
+    context: SecurityContext,
+    token: bytes,
+) -> None:
+    """Process a token asynchronously.
+
+    This method provides a way to process a token, even if the
+    given security context is not expecting one.  For example,
+    if the initiator has the initSecContext return that the context
+    is complete, but the acceptor is unable to accept the context,
+    and wishes to send a token to the initiator, letting the
+    initiator know of the error.
+
+    Warning:
+        This method has been essentially deprecated by :rfc:`2744`.
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the security
+            context against which to process the token
+        token (bytes): the token to process
+
+    Raises:
+        ~gssapi.exceptions.InvalidTokenError
+        ~gssapi.exceptions.MissingContextError
+    """
+
+
+def import_sec_context(
+    token: bytes,
+) -> SecurityContext:
+    """Import a context from another process.
+
+    This method imports a security context established in another process
+    by reading the specified token which was output by
+    :func:`export_sec_context`.
+
+    Raises:
+        ~gssapi.exceptions.MissingContextError
+        ~gssapi.exceptions.InvalidTokenError
+        ~gssapi.exceptions.OperationUnavailableError
+        ~gssapi.exceptions.UnauthorizedError
+    """
+
+
+def export_sec_context(
+    context: SecurityContext,
+) -> bytes:
+    """Export a context for use in another process.
+
+    This method exports a security context, deactivating in the current process
+    and creating a token which can then be imported into another process
+    with :func:`import_sec_context`.
+
+    Warning: this modifies the input context
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the context to send
+            to another process
+
+    Returns:
+        bytes: the output token to be imported
+
+    Raises:
+        ~gssapi.exceptions.ExpiredContextError
+        ~gssapi.exceptions.MissingContextError
+        ~gssapi.exceptions.OperationUnavailableError
+    """
+
+
+def delete_sec_context(
+    context: SecurityContext,
+    local_only: bool = True,
+) -> bytes:
+    """Delete a GSSAPI security context.
+
+    This method deletes a GSSAPI security context,
+    returning an output token to send to the other
+    holder of the security context to notify them
+    of the deletion.
+
+    Note:
+        This method generally should not be used.  :class:`SecurityContext`
+        objects will automatically be freed by Python.
+
+    Args:
+        context (~gssapi.raw.sec_contexts.SecurityContext): the security
+            context in question
+        local_only (bool): should we request local deletion (True), or also
+            remote deletion (False), in which case a token is also returned
+
+    Returns:
+        bytes: the output token (if remote deletion is requested).  Generally
+            this is None, but bytes for compatibility.
+
+    Raises:
+        ~gssapi.exceptions.MissingContextError
+    """
diff --git a/gssapi/raw/sec_contexts.pyx b/gssapi/raw/sec_contexts.pyx
index c18da3fa..d2c92051 100644
--- a/gssapi/raw/sec_contexts.pyx
+++ b/gssapi/raw/sec_contexts.pyx
@@ -75,9 +75,6 @@ cdef extern from "python_gssapi.h":
 
 
 cdef class SecurityContext:
-    """
-    A GSSAPI Security Context
-    """
     # defined in pxd
     # cdef gss_ctx_id_t raw_ctx
 
@@ -115,60 +112,6 @@ def init_sec_context(Name target_name not None, Creds creds=None,
                      flags=None, lifetime=None,
                      ChannelBindings channel_bindings=None,
                      input_token=None):
-    """
-    init_sec_context(target_name, creds=None, context=None, mech=None, \
-flags=None, lifetime=None, channel_bindings=None, input_token=None)
-    Initiate a GSSAPI security context.
-
-    This method initiates a GSSAPI security context, targeting the given
-    target name.  To create a basic context, just provide the target name.
-    Further calls used to update the context should pass in the output context
-    of the last call, as well as the input token received from the acceptor.
-
-    Warning:
-        This changes the input context!
-
-    Args:
-        target_name (Name): the target for the security context
-        creds (Creds): the credentials to use to initiate the context,
-            or None to use the default credentials
-        context (SecurityContext): the security context to update, or
-            None to create a new context
-        mech (MechType): the mechanism type for this security context,
-            or None for the default mechanism type
-        flags (list): the flags to request for the security context, or
-            None to use the default set: mutual_authentication and
-            out_of_sequence_detection.  This may also be an
-            :class:`IntEnumFlagSet`
-        lifetime (int): the request lifetime of the security context (a value
-            of 0 or None means indefinite)
-        channel_bindings (ChannelBindings): The channel bindings (or None for
-            no channel bindings)
-        input_token (bytes): the token to use to update the security context,
-            or None if you are creating a new context
-
-    Returns:
-        InitSecContextResult: the output security context, the actual mech
-        type, the actual flags used, the output token to send to the acceptor,
-        the actual lifetime of the context (or None if not supported or
-        indefinite), and whether or not more calls are needed to finish the
-        initiation.
-
-    Raises:
-        InvalidTokenError
-        InvalidCredentialsError
-        MissingCredentialsError
-        ExpiredCredentialsError
-        BadChannelBindingsError
-        BadMICError
-        ExpiredTokenError
-        DuplicateTokenError
-        MissingContextError
-        BadNameTypeError
-        BadNameError
-        BadMechanismError
-    """
-
     cdef gss_OID mech_oid
     if mech is not None:
         mech_oid = &mech.raw_oid
@@ -248,49 +191,6 @@ flags=None, lifetime=None, channel_bindings=None, input_token=None)
 def accept_sec_context(input_token not None, Creds acceptor_creds=None,
                        SecurityContext context=None,
                        ChannelBindings channel_bindings=None):
-    """
-    accept_sec_context(input_token, acceptor_creds=None, context=None, \
-channel_bindings=None)
-    Accept a GSSAPI security context.
-
-    This method accepts a GSSAPI security context using a token sent by the
-    initiator, using the given credentials.  It can either be used to accept a
-    security context and create a new security context object, or to update an
-    existing security context object.
-
-    Warning:
-        This changes the input context!
-
-    Args:
-        input_token (bytes): the token sent by the context initiator
-        acceptor_creds (Creds): the credentials to be used to accept the
-            context (or None to use the default credentials)
-        context (SecurityContext): the security context to update
-            (or None to create a new security context object)
-        channel_bindings (ChannelBindings): The channel bindings (or None for
-            no channel bindings)
-
-    Returns:
-        AcceptSecContextResult: the resulting security context, the initiator
-            name, the mechanism being used, the output token, the flags in use,
-            the lifetime of the context (or None for indefinite or not
-            supported), the delegated credentials (valid only if the
-            delegate_to_peer flag is set), and whether or not further token
-            exchanges are needed to finalize the security context.
-
-    Raises:
-        InvalidTokenError
-        InvalidCredentialsError
-        MissingCredentialsError
-        ExpiredCredentialsError
-        BadChannelBindingsError
-        MissingContextError
-        BadMICError
-        ExpiredTokenError
-        DuplicateTokenError
-        BadMechanismError
-    """
-
     cdef gss_channel_bindings_t bdng
     if channel_bindings is not None:
         bdng = channel_bindings.__cvalue__()
@@ -373,31 +273,6 @@ channel_bindings=None)
 def inquire_context(SecurityContext context not None, initiator_name=True,
                     target_name=True, lifetime=True, mech=True,
                     flags=True, locally_init=True, complete=True):
-    """
-    inquire_context(context, initiator_name=True, target_name=True, \
-lifetime=True, mech=True, flags=True, locally_init=True, complete=True)
-    Get information about a security context.
-
-    This method obtains information about a security context, including
-    the initiator and target names, as well as the TTL, mech,
-    flags, and its current state (open vs closed).
-
-    Note:
-        the target name may be ``None`` if it would have been ``GSS_C_NO_NAME``
-
-    Args:
-        context (SecurityContext): the context in question
-
-    Returns:
-        InquireContextResult: the initiator name, the target name, the TTL
-            (can be None for indefinite or not supported), the mech type, the
-            flags, whether or not the context was locally initiated,
-            and whether or not the context is currently fully established
-
-    Raises:
-        MissingContextError
-    """
-
     cdef gss_name_t output_init_name
     cdef gss_name_t *init_name_ptr = NULL
     if initiator_name:
@@ -489,25 +364,6 @@ lifetime=True, mech=True, flags=True, locally_init=True, complete=True)
 
 
 def context_time(SecurityContext context not None):
-    """
-    context_time(context)
-    Get the amount of time for which the given context will remain valid.
-
-    This method determines the amount of time for which the given
-    security context will remain valid.  An expired context will
-    give a result of 0.
-
-    Args:
-        context (SecurityContext): the security context in question
-
-    Returns:
-        int: the number of seconds for which the context will be valid
-
-    Raises:
-        ExpiredContextError
-        MissingContextError
-    """
-
     cdef OM_uint32 ttl
 
     cdef OM_uint32 maj_stat, min_stat
@@ -521,30 +377,6 @@ def context_time(SecurityContext context not None):
 
 
 def process_context_token(SecurityContext context not None, token):
-    """
-    process_context_token(context, token)
-    Process a token asynchronously.
-
-    This method provides a way to process a token, even if the
-    given security context is not expecting one.  For example,
-    if the initiator has the initSecContext return that the context
-    is complete, but the acceptor is unable to accept the context,
-    and wishes to send a token to the initiator, letting the
-    initiator know of the error.
-
-    Warning:
-        This method has been essentially deprecated by :rfc:`2744`.
-
-    Args:
-        context (SecurityContext): the security context against which
-            to process the token
-        token (bytes): the token to process
-
-    Raises:
-        InvalidTokenError
-        MissingContextError
-    """
-
     cdef gss_buffer_desc token_buffer = gss_buffer_desc(len(token), token)
 
     cdef OM_uint32 maj_stat, min_stat
@@ -558,21 +390,6 @@ def process_context_token(SecurityContext context not None, token):
 
 
 def import_sec_context(token not None):
-    """
-    import_sec_context(token)
-    Import a context from another process.
-
-    This method imports a security context established in another process
-    by reading the specified token which was output by
-    :func:`export_sec_context`.
-
-    Raises:
-        MissingContextError
-        InvalidTokenError
-        OperationUnavailableError
-        UnauthorizedError
-    """
-
     cdef gss_buffer_desc token_buffer = gss_buffer_desc(len(token), token)
 
     cdef gss_ctx_id_t ctx
@@ -591,28 +408,6 @@ def import_sec_context(token not None):
 
 
 def export_sec_context(SecurityContext context not None):
-    """
-    export_sec_context(context)
-    Export a context for use in another process.
-
-    This method exports a security context, deactivating in the current process
-    and creating a token which can then be imported into another process
-    with :func:`import_sec_context`.
-
-    Warning: this modifies the input context
-
-    Args:
-        context (SecurityContext): the context to send to another process
-
-    Returns:
-        bytes: the output token to be imported
-
-    Raises:
-        ExpiredContextError
-        MissingContextError
-        OperationUnavailableError
-    """
-
     cdef gss_buffer_desc output_token = gss_buffer_desc(0, NULL)
 
     cdef OM_uint32 maj_stat, min_stat
@@ -630,32 +425,6 @@ def export_sec_context(SecurityContext context not None):
 
 
 def delete_sec_context(SecurityContext context not None, local_only=True):
-    """
-    delete_sec_context(context, local_only=True)
-    Delete a GSSAPI security context.
-
-    This method deletes a GSSAPI security context,
-    returning an output token to send to the other
-    holder of the security context to notify them
-    of the deletion.
-
-    Note:
-        This method generally should not be used.  :class:`SecurityContext`
-        objects will automatically be freed by Python.
-
-    Args:
-        context (SecurityContext): the security context in question
-        local_only (bool): should we request local deletion (True), or also
-            remote deletion (False), in which case a token is also returned
-
-    Returns:
-        bytes: the output token (if remote deletion is requested).  Generally
-            this is None, but bytes for compatibility.
-
-    Raises:
-        MissingContextError
-    """
-
     cdef OM_uint32 maj_stat, min_stat
     # GSS_C_EMPTY_BUFFER
     cdef gss_buffer_desc output_token = gss_buffer_desc(0, NULL)
diff --git a/gssapi/raw/types.pyi b/gssapi/raw/types.pyi
new file mode 100644
index 00000000..8d928664
--- /dev/null
+++ b/gssapi/raw/types.pyi
@@ -0,0 +1,174 @@
+import numbers
+import typing as t
+
+from collections.abc import MutableSet
+from enum import IntEnum
+
+if t.TYPE_CHECKING:
+    from gssapi.raw.oids import OID
+
+
+class NameType:
+    """
+    GSSAPI Name Types
+
+    This enum-like object represents GSSAPI name
+    types (to be used with :func:`~gssapi.raw.names.import_name`, etc)
+    """
+    #: GSS_C_NT_ANONYMOUS 1.3.6.1.5.6.3
+    anonymous: "OID" = ...
+    #: GSS_C_NT_EXPORT_NAME 1.3.6.1.5.6.4
+    export: "OID" = ...
+    #: GSS_C_NT_HOSTBASED_SERVICE 1.2.840.113554.1.2.1.4
+    hostbased_service: "OID" = ...
+    #: GSS_C_NT_MACHINE_UID_NAME 1.2.840.113554.1.2.1.2
+    machine_uid: "OID" = ...
+    #: GSS_C_NT_STRING_UID_NAME 1.2.840.113554.1.2.1.3
+    string_uid: "OID" = ...
+    #: GSS_C_NT_USER_NAME 1.2.840.113554.1.2.1.1
+    user: "OID" = ...
+
+    # Provided through optional extensions
+    #: GSS_C_NT_COMPOSITE_EXPORT 1.3.6.1.5.6.6
+    composite_export: "OID" = ...
+    #: GSS_KRB5_NT_PRINCIPAL_NAME 1.2.840.113554.1.2.2.1
+    kerberos_principal: "OID" = ...
+    #: GSS_KRB5_NT_PRINCIPAL_NAME 1.2.840.113554.1.2.2.1
+    krb5_nt_principal_name: "OID" = ...
+
+
+class RequirementFlag(IntEnum):
+    """
+    GSSAPI Requirement Flags
+
+    This :class:`~enum.IntEnum` represents flags used with the
+    :class:`~gssapi.raw.sec_contexts.SecurityContext`-related methods (e.g.
+    :func:`~gssapi.raw.sec_contexts.init_sec_context`)
+
+    The numbers behind the values correspond directly
+    to their C counterparts.
+    """
+    # Note the values are only set here for documentation and type hints
+    delegate_to_peer = 1 #: GSS_C_DELEG_FLAG
+    mutual_authentication = 2 #: GSS_C_MUTUAL_FLAG
+    replay_detection = 4 #: GSS_C_REPLAY_FLAG
+    out_of_sequence_detection = 8 #: GSS_C_SEQUENCE_FLAG
+    confidentiality = 16 #: GSS_C_CONF_FLAG
+    integrity = 32 #: GSS_C_INTEG_FLAG
+    anonymity = 64 #: GSS_C_ANON_FLAG
+    protection_ready = 128 #: GSS_C_PROT_READY_FLAG
+    transferable = 256 #: GSS_C_TRANS_FLAG
+    channel_bound = 2048 #: GSS_C_CHANNEL_BOUND_FLAG
+    dce_style = 4096 #: GSS_C_DCE_STYLE
+    identify = 8192 #: GSS_C_IDENTIFY_FLAG
+    extended_error = 16384 #: GSS_C_EXTENDED_ERROR_FLAG
+    ok_as_delegate = 32768 #: GSS_C_DELEG_POLICY_FLAG
+
+
+class AddressType(IntEnum):
+    """
+    GSSAPI Channel Bindings Address Types
+
+    This :class:`~enum.IntEnum` represents the various address
+    types used with the :class:`~gssapi.raw.chan_bindings.ChannelBindings`
+    structure.
+
+    The numbers behind the values correspond directly
+    to their C counterparts.  There is no value for
+    ``GSS_C_AF_UNSPEC``, since this is represented
+    by ``None``.
+    """
+    # Note the values are only set here for documentation and type hints
+    local = 1 #: GSS_C_AF_LOCAL
+    ip = 2 #: GSS_C_AF_INET
+    arpanet = 3 #: GSS_C_AF_IMPLINK
+    pup = 4 #: GSS_C_AF_PUP
+    chaos = 5 #: GSS_C_AF_CHAOS
+    xerox_ns = 6 #: GSS_C_AF_NS
+    nbs = 7 #: GSS_C_AF_NBS
+    ecma = 8 #: GSS_C_AF_ECMA
+    datakit = 9 #: GSS_C_AF_DATAKIT
+    ccitt = 10 #: GSS_C_AF_CCITT
+    ibm_sna = 11 #: GSS_C_AF_SNA
+    decnet = 12 #: GSS_C_AF_DECnet
+    dli = 13 #: GSS_C_AF_DLI
+    lat = 14 #: GSS_C_AF_LAT
+    hyperchannel = 15 #: GSS_C_AF_HYLINK
+    appletalk = 16 #: GSS_C_AF_APPLETALK
+    bisync = 17 #: GSS_C_AF_BSC
+    dss = 18 #: GSS_C_AF_DSS
+    osi_tp4 = 19 #: GSS_C_AF_OSI
+    x25 = 21 #: GSS_C_AF_X25
+    null = 255 #: GSS_C_AF_NULLADDR
+
+
+class MechType:
+    """
+    GSSAPI Mechanism Types
+
+    This enum-like object contains any mechanism :class:`~gssapi.raw.oids.OID`
+    values registered by imported mechanisms.
+    """
+    kerberos: "OID" #: gss_mech_krb5 1.2.840.113554.1.2.2
+
+
+class GenericFlagSet(MutableSet):
+    """A set backed by a 32-bit integer
+
+    This is a set backed by a 32 bit integer.
+    the members are integers where only one
+    bit is set.
+
+    The class supports normal set operations,
+    as well as traditional "flag set" operations,
+    such as bitwise AND, OR, and XOR.
+    """
+
+    MAX_VAL: int
+
+    def __init__(
+        self,
+        flags: t.Optional[
+            t.Union[GenericFlagSet, numbers.Integral, int]
+        ] = None,
+    ) -> None: ...
+
+    def __contains__(
+        self,
+        flag: object,
+    ) -> bool: ...
+
+    def __iter__(self) -> t.Iterator[int]: ...
+
+    def __len__(self) -> int: ...
+
+    def add(
+        self,
+        flag: int,
+    ) -> None: ...
+
+    def discard(
+        self,
+        flag: int,
+    ) -> None: ...
+
+
+class IntEnumFlagSet(GenericFlagSet):
+    """A set backed by a 32-bit integer with enum members
+
+    This class is a :class:`GenericFlagSet` where the returned
+    members are values in an :class:`~enum.IntEnum`.
+
+    It functions exactly like a `GenericFlagSet`, except that
+    it also supports bitwise operations with the enum values.
+    """
+
+    def __init__(
+        self,
+        enum: t.Type[IntEnum],
+        flags: t.Optional[
+            t.Union[GenericFlagSet, numbers.Integral, int]
+        ] = None,
+    ) -> None: ...
+
+    def __iter__(self) -> t.Iterator[IntEnum]: ...
diff --git a/gssapi/raw/types.pyx b/gssapi/raw/types.pyx
index d99f74a5..37697a13 100644
--- a/gssapi/raw/types.pyx
+++ b/gssapi/raw/types.pyx
@@ -16,13 +16,6 @@ from collections.abc import MutableSet
 
 
 class NameType(object):
-    """
-    GSSAPI Name Types
-
-    This enum-like object represents GSSAPI name
-    types (to be used with :func:`import_name`, etc)
-    """
-
     # mech-agnostic name types
     hostbased_service = c_make_oid(GSS_C_NT_HOSTBASED_SERVICE)
     # NB(directxman12): skip GSS_C_NT_HOSTBASED_SERVICE_X since it's deprecated
@@ -36,17 +29,6 @@ class NameType(object):
 
 
 class RequirementFlag(IntEnum, metaclass=ExtendableEnum):
-    """
-    GSSAPI Requirement Flags
-
-    This :class:`~enum.IntEnum` represents flags used with the
-    :class:`SecurityContext`-related methods (e.g.
-    :func:`init_sec_context`)
-
-    The numbers behind the values correspond directly
-    to their C counterparts.
-    """
-
     delegate_to_peer = GSS_C_DELEG_FLAG
     mutual_authentication = GSS_C_MUTUAL_FLAG
     replay_detection = GSS_C_REPLAY_FLAG
@@ -62,20 +44,12 @@ class RequirementFlag(IntEnum, metaclass=ExtendableEnum):
     # support it will ignore it.
     ok_as_delegate = 32768
 
+    # GSS_C_CHANNEL_BOUND_FLAG, implemented in MIT krb5-1.19
+    # See draft-ietf-kitten-channel-bound-flag-04
+    channel_bound = 2048
 
-class AddressType(IntEnum, metaclass=ExtendableEnum):
-    """
-    GSSAPI Channel Bindings Address Types
-
-    This :class:`~enum.IntEnum` represents the various address
-    types used with the :class:`ChannelBindings` structure.
-
-    The numbers behind the values correspond directly
-    to their C counterparts.  There is no value for
-    ``GSS_C_AF_UNSPEC``, since this is represented
-    by ``None``.
-    """
 
+class AddressType(IntEnum, metaclass=ExtendableEnum):
     # unspecified = GSS_C_AF_UNSPEC  # None --> GSS_C_AF_UNSPEC
     local = GSS_C_AF_LOCAL
     ip = GSS_C_AF_INET
@@ -101,28 +75,12 @@ class AddressType(IntEnum, metaclass=ExtendableEnum):
 
 
 class MechType(object):
-    """
-    GSSAPI Mechanism Types
-
-    This enum-like object contains any mechanism :class:`OID`
-    values registered by imported mechanisms.
-    """
     pass
 
     # these are added in by the individual mechanism files on import
 
 
 class GenericFlagSet(MutableSet):
-    """A set backed by a 32-bit integer
-
-    This is a set backed by a 32 bit integer.
-    the members are integers where only one
-    bit is set.
-
-    The class supports normal set operations,
-    as well as traditional "flag set" operations,
-    such as bitwise AND, OR, and XOR.
-    """
 
     __slots__ = '_val'
     MAX_VAL = 1 << 31
@@ -217,14 +175,6 @@ class GenericFlagSet(MutableSet):
 
 
 class IntEnumFlagSet(GenericFlagSet):
-    """A set backed by a 32-bit integer with enum members
-
-    This class is a :class:`GenericFlagSet` where the returned
-    members are values in an :class:`~enum.IntEnum`.
-
-    It functions exactly like a `GenericFlagSet`, except that
-    it also supports bitwise operations with the enum values.
-    """
 
     __slots__ = ('_val', '_enum')
 
diff --git a/gssapi/sec_contexts.py b/gssapi/sec_contexts.py
index a1893d98..adbbf301 100644
--- a/gssapi/sec_contexts.py
+++ b/gssapi/sec_contexts.py
@@ -1,6 +1,11 @@
+import typing as t
+
+from gssapi.raw import chan_bindings as rchan_bindings
 from gssapi.raw import sec_contexts as rsec_contexts
 from gssapi.raw import message as rmessage
 from gssapi.raw import named_tuples as tuples
+from gssapi.raw import names as rnames
+from gssapi.raw import oids as roids
 from gssapi.raw.types import RequirementFlag, IntEnumFlagSet
 
 import gssapi.exceptions as excs
@@ -24,18 +29,37 @@ class SecurityContext(rsec_contexts.SecurityContext,
     credentials object will not be preserved, however).
     """
 
-    def __new__(cls, base=None, token=None,
-                name=None, creds=None, lifetime=None, flags=None,
-                mech=None, channel_bindings=None, usage=None):
+    def __new__(
+        cls,
+        base: t.Optional[rsec_contexts.SecurityContext] = None,
+        token: t.Optional[bytes] = None,
+        name: t.Optional[rnames.Name] = None,
+        creds: t.Optional[Credentials] = None,
+        lifetime: t.Optional[int] = None,
+        flags: t.Optional[int] = None,
+        mech: t.Optional[roids.OID] = None,
+        channel_bindings: t.Optional[rchan_bindings.ChannelBindings] = None,
+        usage: t.Optional[str] = None,
+    ) -> "SecurityContext":
 
         if token is not None:
             base = rsec_contexts.import_sec_context(token)
 
-        return super(SecurityContext, cls).__new__(cls, base)
-
-    def __init__(self, base=None, token=None,
-                 name=None, creds=None, lifetime=None, flags=None,
-                 mech=None, channel_bindings=None, usage=None):
+        return t.cast("SecurityContext",
+                      super(SecurityContext, cls).__new__(cls, base))
+
+    def __init__(
+        self,
+        base: t.Optional[rsec_contexts.SecurityContext] = None,
+        token: t.Optional[bytes] = None,
+        name: t.Optional[rnames.Name] = None,
+        creds: t.Optional[Credentials] = None,
+        lifetime: t.Optional[int] = None,
+        flags: t.Optional[int] = None,
+        mech: t.Optional[roids.OID] = None,
+        channel_bindings: t.Optional[rchan_bindings.ChannelBindings] = None,
+        usage: t.Optional[str] = None,
+    ) -> None:
         """
         The constructor creates a new security context, but does not begin
         the initiate or accept process.
@@ -123,14 +147,17 @@ def __init__(self, base=None, token=None,
                 raise excs.UnknownUsageError(msg, obj="security context")
 
         # This is to work around an MIT krb5 bug (see the `complete` property)
-        self._complete = None
+        self._complete: t.Optional[bool] = None
 
     # NB(directxman12): DO NOT ADD AN __del__ TO THIS CLASS -- it screws up
     #                   the garbage collector if _last_tb is still defined
 
     # TODO(directxman12): implement flag properties
 
-    def get_signature(self, message):
+    def get_signature(
+        self,
+        message: bytes,
+    ) -> bytes:
         """Calculate the signature for a message.
 
         This method calculates the signature (called a MIC) for
@@ -146,15 +173,19 @@ def get_signature(self, message):
             bytes: the message signature
 
         Raises:
-            ExpiredContextError
-            MissingContextError
-            BadQoPError
+            ~gssapi.exceptions.ExpiredContextError
+            ~gssapi.exceptions.MissingContextError
+            ~gssapi.exceptions.BadQoPError
         """
 
         # TODO(directxman12): check flags?
         return rmessage.get_mic(self, message)
 
-    def verify_signature(self, message, mic):
+    def verify_signature(
+        self,
+        message: bytes,
+        mic: bytes,
+    ) -> int:
         """Verify the signature for a message.
 
         This method verifies that a signature (generated by
@@ -167,20 +198,27 @@ def verify_signature(self, message, mic):
             message (bytes): the message
             mic (bytes): the signature to verify
 
+        Returns:
+            int: the QoP used.
+
         Raises:
-            BadMICError: the signature was not valid
-            InvalidTokenError
-            DuplicateTokenError
-            ExpiredTokenError
-            TokenTooLateError
-            TokenTooEarlyError
-            ExpiredContextError
-            MissingContextError
+            ~gssapi.exceptions.BadMICError: the signature was not valid
+            ~gssapi.exceptions.InvalidTokenError
+            ~gssapi.exceptions.DuplicateTokenError
+            ~gssapi.exceptions.ExpiredTokenError
+            ~gssapi.exceptions.TokenTooLateError
+            ~gssapi.exceptions.TokenTooEarlyError
+            ~gssapi.exceptions.ExpiredContextError
+            ~gssapi.exceptions.MissingContextError
         """
 
         return rmessage.verify_mic(self, message, mic)
 
-    def wrap(self, message, encrypt):
+    def wrap(
+        self,
+        message: bytes,
+        encrypt: bool,
+    ) -> tuples.WrapResult:
         """Wrap a message, optionally with encryption
 
         This wraps a message, signing it and optionally
@@ -192,17 +230,20 @@ def wrap(self, message, encrypt):
 
         Returns:
             WrapResult: the wrapped message and details about it
-                (e.g. whether encryption was used succesfully)
+            (e.g. whether encryption was used succesfully)
 
         Raises:
-            ExpiredContextError
-            MissingContextError
-            BadQoPError
+            ~gssapi.exceptions.ExpiredContextError
+            ~gssapi.exceptions.MissingContextError
+            ~gssapi.exceptions.BadQoPError
         """
 
         return rmessage.wrap(self, message, encrypt)
 
-    def unwrap(self, message):
+    def unwrap(
+        self,
+        message: bytes,
+    ) -> tuples.UnwrapResult:
         """Unwrap a wrapped message.
 
         This method unwraps/unencrypts a wrapped message,
@@ -213,22 +254,25 @@ def unwrap(self, message):
 
         Returns:
             UnwrapResult: the unwrapped message and details about it
-                (e.g. wheter encryption was used)
+            (e.g. wheter encryption was used)
 
         Raises:
-            InvalidTokenError
-            BadMICError
-            DuplicateTokenError
-            ExpiredTokenError
-            TokenTooLateError
-            TokenTooEarlyError
-            ExpiredContextError
-            MissingContextError
+            ~gssapi.exceptions.InvalidTokenError
+            ~gssapi.exceptions.BadMICError
+            ~gssapi.exceptions.DuplicateTokenError
+            ~gssapi.exceptions.ExpiredTokenError
+            ~gssapi.exceptions.TokenTooLateError
+            ~gssapi.exceptions.TokenTooEarlyError
+            ~gssapi.exceptions.ExpiredContextError
+            ~gssapi.exceptions.MissingContextError
         """
 
         return rmessage.unwrap(self, message)
 
-    def encrypt(self, message):
+    def encrypt(
+        self,
+        message: bytes,
+    ) -> bytes:
         """Encrypt a message.
 
         This method wraps and encrypts a message, similarly to
@@ -244,10 +288,11 @@ def encrypt(self, message):
             bytes: the encrypted message
 
         Raises:
-            EncryptionNotUsed: the encryption could not be used
-            ExpiredContextError
-            MissingContextError
-            BadQoPError
+            ~gssapi.exceptions.EncryptionNotUsed: the encryption could not be
+                used
+            ~gssapi.exceptions.ExpiredContextError
+            ~gssapi.exceptions.MissingContextError
+            ~gssapi.exceptions.BadQoPError
         """
 
         res = self.wrap(message, encrypt=True)
@@ -257,7 +302,10 @@ def encrypt(self, message):
 
         return res.message
 
-    def decrypt(self, message):
+    def decrypt(
+        self,
+        message: bytes,
+    ) -> bytes:
         """Decrypt a message.
 
         This method decrypts and unwraps a message, verifying the signature
@@ -273,15 +321,16 @@ def decrypt(self, message):
             bytes: the decrypted message
 
         Raises:
-            EncryptionNotUsed: encryption was expected, but not used
-            InvalidTokenError
-            BadMICError
-            DuplicateTokenError
-            ExpiredTokenError
-            TokenTooLateError
-            TokenTooEarlyError
-            ExpiredContextError
-            MissingContextError
+            ~gssapi.exceptions.EncryptionNotUsed: encryption was expected, but
+                not used
+            ~gssapi.exceptions.InvalidTokenError
+            ~gssapi.exceptions.BadMICError
+            ~gssapi.exceptions.DuplicateTokenError
+            ~gssapi.exceptions.ExpiredTokenError
+            ~gssapi.exceptions.TokenTooLateError
+            ~gssapi.exceptions.TokenTooEarlyError
+            ~gssapi.exceptions.ExpiredContextError
+            ~gssapi.exceptions.MissingContextError
         """
 
         res = self.unwrap(message)
@@ -295,8 +344,11 @@ def decrypt(self, message):
 
         return res.message
 
-    def get_wrap_size_limit(self, desired_output_size,
-                            encrypted=True):
+    def get_wrap_size_limit(
+        self,
+        desired_output_size: int,
+        encrypted: bool = True,
+    ) -> int:
         """Calculate the maximum message size for a given wrapped message size.
 
         This method calculates the maximum input message size for a given
@@ -311,15 +363,18 @@ def get_wrap_size_limit(self, desired_output_size,
             int: the maximum input message size
 
         Raises:
-            MissingContextError
-            ExpiredContextError
-            BadQoPError
+            ~gssapi.exceptions.MissingContextError
+            ~gssapi.exceptions.ExpiredContextError
+            ~gssapi.exceptions.BadQoPError
         """
 
         return rmessage.wrap_size_limit(self, desired_output_size,
                                         encrypted)
 
-    def process_token(self, token):
+    def process_token(
+        self,
+        token: bytes,
+    ) -> None:
         """Process an output token asynchronously.
 
         This method processes an output token even when the security context
@@ -332,13 +387,13 @@ def process_token(self, token):
             token (bytes): the token to process
 
         Raises:
-            InvalidTokenError
-            MissingContextError
+            ~gssapi.exceptions.InvalidTokenError
+            ~gssapi.exceptions.MissingContextError
         """
 
         rsec_contexts.process_context_token(self, token)
 
-    def export(self):
+    def export(self) -> bytes:
         """Export a security context.
 
         This method exports a security context, allowing it to be passed
@@ -348,9 +403,9 @@ def export(self):
             bytes: the exported security context
 
         Raises:
-            ExpiredContextError
-            MissingContextError
-            OperationUnavailableError
+            ~gssapi.exceptions.ExpiredContextError
+            ~gssapi.exceptions.MissingContextError
+            ~gssapi.exceptions.OperationUnavailableError
         """
 
         return rsec_contexts.export_sec_context(self)
@@ -359,7 +414,10 @@ def export(self):
                      'mech', 'flags', 'locally_init', 'complete')
 
     @_utils.check_last_err
-    def _inquire(self, **kwargs):
+    def _inquire(
+        self,
+        **kwargs: bool,
+    ) -> tuples.InquireContextResult:
         """Inspect the security context for information
 
         This method inspects the security context for information.
@@ -371,7 +429,8 @@ def _inquire(self, **kwargs):
         Args:
             initiator_name (bool): get the initiator name for this context
             target_name (bool): get the target name for this context
-            lifetime (bool): get the remaining lifetime for this context
+            lifetime (bool): get the remaining lifetime, in seconds, for this
+                context
             mech (bool): get the :class:`MechType` used by this context
             flags (bool): get the flags set on this context
             locally_init (bool): get whether this context was locally initiated
@@ -380,10 +439,10 @@ def _inquire(self, **kwargs):
 
         Returns:
             InquireContextResult: the results of the inquiry, with unused
-                fields set to None
+            fields set to None
 
         Raises:
-            MissingContextError
+            ~gssapi.exceptions.MissingContextError
         """
         if not kwargs:
             default_val = True
@@ -413,18 +472,18 @@ def _inquire(self, **kwargs):
                                            res.complete)
 
     @property
-    def lifetime(self):
+    def lifetime(self) -> int:
         """The amount of time for which this context remains valid"""
         return rsec_contexts.context_time(self)
 
     @property
-    def delegated_creds(self):
+    def delegated_creds(self) -> t.Optional[Credentials]:
         """The credentials delegated from the initiator to the acceptor
 
         .. warning::
 
             This value will not be preserved across picklings.  These should
-            be separately exported and transfered.
+            be separately exported and transferred.
 
         """
         return self._delegated_creds
@@ -440,28 +499,32 @@ def delegated_creds(self):
     locally_initiated = _utils.inquire_property(
         'locally_init', 'Whether this context was locally intiated')
 
-    @property
+    @property  # type: ignore # https://github.com/python/mypy/issues/1362
     @_utils.check_last_err
-    def complete(self):
+    def complete(self) -> bool:
         """Whether negotiation for this context has been completed"""
         # NB(directxman12): MIT krb5 has a bug where it refuses to
         #                   inquire about partially completed contexts,
         #                   so we can't just use `self._inquire` generally
         if self._started:
-            if self._complete is None:
+            complete = self._complete
+            if complete is None:
                 try:
-                    res = self._inquire(complete=True).complete
+                    complete = self._inquire(complete=True).complete
                 except excs.MissingContextError:
                     return False
                 else:
-                    self._complete = res
+                    self._complete = complete
 
-            return self._complete
+            return complete
         else:
             return False
 
     @_utils.catch_and_return_token
-    def step(self, token=None):
+    def step(
+        self,
+        token: t.Optional[bytes] = None,
+    ) -> t.Optional[bytes]:
         """Perform a negotation step.
 
         This method performs a negotiation step based on the usage type
@@ -479,6 +542,8 @@ def step(self, token=None):
            try:
                while not ctx.complete:
                    output_token = ctx.step(input_token)
+                   if not output_token:
+                       break
                    input_token = send_and_receive(output_token)
            except GSSError as e:
                 handle_the_issue()
@@ -499,26 +564,29 @@ def step(self, token=None):
             bytes: the output token to send to the other participant
 
         Raises:
-            InvalidTokenError
-            InvalidCredentialsError
-            MissingCredentialsError
-            ExpiredCredentialsError
-            BadChannelBindingsError
-            BadMICError
-            ExpiredTokenError: (initiate only)
-            DuplicateTokenError
-            MissingContextError
-            BadNameTypeError: (initiate only)
-            BadNameError: (initiate only)
-            BadMechanismError
+            ~gssapi.exceptions.InvalidTokenError
+            ~gssapi.exceptions.InvalidCredentialsError
+            ~gssapi.exceptions.MissingCredentialsError
+            ~gssapi.exceptions.ExpiredCredentialsError
+            ~gssapi.exceptions.BadChannelBindingsError
+            ~gssapi.exceptions.BadMICError
+            ~gssapi.exceptions.ExpiredTokenError: (initiate only)
+            ~gssapi.exceptions.DuplicateTokenError
+            ~gssapi.exceptions.MissingContextError
+            ~gssapi.exceptions.BadNameTypeError: (initiate only)
+            ~gssapi.exceptions.BadNameError: (initiate only)
+            ~gssapi.exceptions.BadMechanismError
         """
 
         if self.usage == 'accept':
-            return self._acceptor_step(token=token)
+            return self._acceptor_step(token=token or b"")
         else:
             return self._initiator_step(token=token)
 
-    def _acceptor_step(self, token):
+    def _acceptor_step(
+        self,
+        token: bytes,
+    ) -> t.Optional[bytes]:
         res = rsec_contexts.accept_sec_context(token, self._creds,
                                                self, self._channel_bindings)
 
@@ -531,7 +599,10 @@ def _acceptor_step(self, token):
 
         return res.token
 
-    def _initiator_step(self, token=None):
+    def _initiator_step(
+        self,
+        token: t.Optional[bytes] = None,
+    ) -> t.Optional[bytes]:
         res = rsec_contexts.init_sec_context(self._target_name, self._creds,
                                              self, self._mech,
                                              self._desired_flags,
@@ -544,6 +615,8 @@ def _initiator_step(self, token=None):
         return res.token
 
     # pickle protocol support
-    def __reduce__(self):
+    def __reduce__(
+        self,
+    ) -> t.Tuple[t.Type["SecurityContext"], t.Tuple[None, bytes]]:
         # the unpickle arguments to new are (base=None, token=self.export())
         return (type(self), (None, self.export()))
diff --git a/gssapi/tests/test_high_level.py b/gssapi/tests/test_high_level.py
index 09cf05b0..ed6a4a0f 100644
--- a/gssapi/tests/test_high_level.py
+++ b/gssapi/tests/test_high_level.py
@@ -18,11 +18,13 @@
 
 
 TARGET_SERVICE_NAME = b'host'
-FQDN = socket.getfqdn().encode('utf-8')
+FQDN = (
+    'localhost' if sys.platform == 'darwin' else socket.getfqdn()
+).encode('utf-8')
 SERVICE_PRINCIPAL = TARGET_SERVICE_NAME + b'/' + FQDN
 
 # disable error deferring to catch errors immediately
-gssctx.SecurityContext.__DEFER_STEP_ERRORS__ = False
+gssctx.SecurityContext.__DEFER_STEP_ERRORS__ = False  # type: ignore
 
 
 class _GSSAPIKerberosTestCase(kt.KerberosTestCase):
@@ -124,7 +126,8 @@ def setUp(self):
                  usage='both')
     def test_acquire_by_init(self, str_name, kwargs):
         creds = gsscreds.Credentials(name=self.name, **kwargs)
-        self.assertIsInstance(creds.lifetime, int)
+        if sys.platform != 'darwin':
+            self.assertIsInstance(creds.lifetime, int)
         del creds
 
     @exist_perms(lifetime=30, mechs=[gb.MechType.kerberos],
@@ -137,7 +140,8 @@ def test_acquire_by_method(self, str_name, kwargs):
         creds, actual_mechs, ttl = cred_resp
         self.assertIsInstance(creds, gsscreds.Credentials)
         self.assertIn(gb.MechType.kerberos, actual_mechs)
-        self.assertIsInstance(ttl, int)
+        if sys.platform != 'darwin':
+            self.assertIsInstance(ttl, int)
 
         del creds
 
@@ -165,9 +169,12 @@ def test_store_acquire(self):
         self.assertIsNotNone(deleg_creds)
 
         store_res = deleg_creds.store(usage='initiate', set_default=True,
+                                      mech=gb.MechType.kerberos,
                                       overwrite=True)
-        self.assertEqual(store_res.usage, "initiate")
-        self.assertIn(gb.MechType.kerberos, store_res.mechs)
+        # While Heimdal doesn't fail it doesn't set the return values as exp.
+        if self.realm.provider.lower() != 'heimdal':
+            self.assertEqual(store_res.usage, "initiate")
+            self.assertIn(gb.MechType.kerberos, store_res.mechs)
 
         reacquired_creds = gsscreds.Credentials(name=deleg_creds.name,
                                                 usage='initiate')
@@ -187,10 +194,18 @@ def test_store_into_acquire_from(self):
         initial_creds = gsscreds.Credentials(name=None,
                                              usage='initiate')
 
-        store_res = initial_creds.store(store, overwrite=True)
+        acquire_kwargs = {}
+        expected_usage = 'initiate'
+        if self.realm.provider.lower() == 'heimdal':
+            acquire_kwargs['usage'] = 'initiate'
+            acquire_kwargs['mech'] = gb.MechType.kerberos
+            expected_usage = 'both'
+
+        store_res = initial_creds.store(store, overwrite=True,
+                                        **acquire_kwargs)
         self.assertIsNotNone(store_res.mechs)
         self.assertGreater(len(store_res.mechs), 0)
-        self.assertEqual(store_res.usage, "initiate")
+        self.assertEqual(store_res.usage, expected_usage)
 
         name = gssnames.Name(princ_name)
         retrieved_creds = gsscreds.Credentials(name=name, store=store)
@@ -209,16 +224,18 @@ def test_inquire(self, str_name, kwargs):
 
         if kwargs['name']:
             self.assertEqual(resp.name, self.name)
+            self.assertIsInstance(resp.name, gssnames.Name)
         else:
             self.assertIsNone(resp.name)
 
-        if kwargs['lifetime']:
+        if kwargs['lifetime'] and sys.platform != 'darwin':
             self.assertIsInstance(resp.lifetime, int)
         else:
             self.assertIsNone(resp.lifetime)
 
         if kwargs['usage']:
-            self.assertEqual(resp.usage, "both")
+            expected = "accept" if sys.platform == "darwin" else "both"
+            self.assertEqual(resp.usage, expected)
         else:
             self.assertIsNone(resp.usage)
 
@@ -234,6 +251,7 @@ def test_inquire_by_mech(self, str_name, kwargs):
 
         if kwargs['name']:
             self.assertEqual(resp.name, self.name)
+            self.assertIsInstance(resp.name, gssnames.Name)
         else:
             self.assertIsNone(resp.name)
 
@@ -242,17 +260,21 @@ def test_inquire_by_mech(self, str_name, kwargs):
         else:
             self.assertIsNone(resp.init_lifetime)
 
-        if kwargs['accept_lifetime']:
+        if kwargs['accept_lifetime'] and sys.platform != "darwin":
             self.assertIsInstance(resp.accept_lifetime, int)
         else:
             self.assertIsNone(resp.accept_lifetime)
 
         if kwargs['usage']:
-            self.assertEqual(resp.usage, "both")
+            expected = "accept" if sys.platform == "darwin" else "both"
+            self.assertEqual(resp.usage, expected)
         else:
             self.assertIsNone(resp.usage)
 
     def test_add(self):
+        if sys.platform == 'darwin':
+            self.skipTest("macOS Heimdal broken")
+
         input_creds = gsscreds.Credentials(gb.Creds())
         name = gssnames.Name(SERVICE_PRINCIPAL)
         new_creds = input_creds.add(name, gb.MechType.kerberos,
@@ -265,7 +287,7 @@ def test_store_into_add_from(self):
         KT = '{tmpdir}/other_keytab'.format(tmpdir=self.realm.tmpdir)
         store = {'ccache': CCACHE, 'keytab': KT}
 
-        princ_name = 'service/cs@' + self.realm.realm
+        princ_name = 'service_add_from/cs@' + self.realm.realm
         self.realm.addprinc(princ_name)
         self.realm.extract_keytab(princ_name, KT)
         self.realm.kinit(princ_name, None, ['-k', '-t', KT])
@@ -273,10 +295,17 @@ def test_store_into_add_from(self):
         initial_creds = gsscreds.Credentials(name=None,
                                              usage='initiate')
 
-        store_res = initial_creds.store(store, overwrite=True)
+        store_kwargs = {}
+        expected_usage = 'initiate'
+        if self.realm.provider.lower() == 'heimdal':
+            store_kwargs['usage'] = 'initiate'
+            store_kwargs['mech'] = gb.MechType.kerberos
+            expected_usage = 'both'
+
+        store_res = initial_creds.store(store, overwrite=True, **store_kwargs)
         self.assertIsNotNone(store_res.mechs)
         self.assertGreater(len(store_res.mechs), 0)
-        self.assertEqual(store_res.usage, "initiate")
+        self.assertEqual(store_res.usage, expected_usage)
 
         name = gssnames.Name(princ_name)
         input_creds = gsscreds.Credentials(gb.Creds())
@@ -286,26 +315,34 @@ def test_store_into_add_from(self):
 
     @ktu.gssapi_extension_test('cred_imp_exp', 'credentials import-export')
     def test_export(self):
-        creds = gsscreds.Credentials(name=self.name)
+        creds = gsscreds.Credentials(name=self.name,
+                                     mechs=[gb.MechType.kerberos])
         token = creds.export()
         self.assertIsInstance(token, bytes)
 
     @ktu.gssapi_extension_test('cred_imp_exp', 'credentials import-export')
     def test_import_by_init(self):
-        creds = gsscreds.Credentials(name=self.name)
+        creds = gsscreds.Credentials(name=self.name,
+                                     mechs=[gb.MechType.kerberos])
         token = creds.export()
         imported_creds = gsscreds.Credentials(token=token)
 
-        self.assertEqual(imported_creds.lifetime, creds.lifetime)
+        # lifetime seems to be None in Heimdal
+        if self.realm.provider.lower() != 'heimdal':
+            self.assertEqual(imported_creds.lifetime, creds.lifetime)
+
         self.assertEqual(imported_creds.name, creds.name)
 
     @ktu.gssapi_extension_test('cred_imp_exp', 'credentials import-export')
     def test_pickle_unpickle(self):
-        creds = gsscreds.Credentials(name=self.name)
+        creds = gsscreds.Credentials(name=self.name,
+                                     mechs=[gb.MechType.kerberos])
         pickled_creds = pickle.dumps(creds)
         unpickled_creds = pickle.loads(pickled_creds)
 
-        self.assertEqual(unpickled_creds.lifetime, creds.lifetime)
+        # lifetime seems to be None in Heimdal
+        if self.realm.provider.lower() != 'heimdal':
+            self.assertEqual(unpickled_creds.lifetime, creds.lifetime)
         self.assertEqual(unpickled_creds.name, creds.name)
 
     @exist_perms(lifetime=30, mechs=[gb.MechType.kerberos],
@@ -381,8 +418,15 @@ def test_sasl_properties(self):
             if mech.description:
                 self.assertIsInstance(mech.description, str)
 
-            cmp_mech = gssmechs.Mechanism.from_sasl_name(mech.sasl_name)
-            self.assertEqual(str(cmp_mech), str(mech))
+            # Heimdal fails with Unknown mech-code on sanon
+            if not (self.realm.provider.lower() == "heimdal" and
+                    s == '1.3.6.1.4.1.5322.26.1.110'):
+                cmp_mech = gssmechs.Mechanism.from_sasl_name(mech.sasl_name)
+
+                # For some reason macOS sometimes returns this for mechs
+                if not (sys.platform == 'darwin' and
+                        str(cmp_mech) == '1.2.752.43.14.2'):
+                    self.assertEqual(str(cmp_mech), str(mech))
 
     @ktu.gssapi_extension_test('rfc5587', 'RFC 5587: Mech Inquiry')
     def test_mech_inquiry(self):
@@ -441,6 +485,8 @@ def test_create_from_token(self):
         self.assertEqual(name2.name_type, gb.NameType.kerberos_principal)
 
     @ktu.gssapi_extension_test('rfc6680', 'RFC 6680')
+    @ktu.krb_provider_test(['mit'], 'gss_display_name_ext as it is not '
+                           'implemented for krb5')
     def test_display_as(self):
         name = gssnames.Name(TARGET_SERVICE_NAME,
                              gb.NameType.hostbased_service)
@@ -457,6 +503,8 @@ def test_display_as(self):
         self.assertEqual(krb_name, princ_str)
 
     @ktu.gssapi_extension_test('rfc6680', 'RFC 6680')
+    @ktu.krb_provider_test(['mit'], 'gss_canonicalize_name as it is not '
+                           'implemented for krb5')
     def test_create_from_composite_token_no_attrs(self):
         name1 = gssnames.Name(TARGET_SERVICE_NAME,
                               gb.NameType.hostbased_service)
@@ -539,7 +587,16 @@ def test_canonicalize(self):
 
         canonicalized_name = name.canonicalize(gb.MechType.kerberos)
         self.assertIsInstance(canonicalized_name, gssnames.Name)
-        self.assertEqual(bytes(canonicalized_name), SERVICE_PRINCIPAL + b"@")
+
+        expected = SERVICE_PRINCIPAL + b"@"
+        if sys.platform == 'darwin':
+            # No idea - just go with it
+            expected = b"host/wellknown:org.h5l.hostbased-service@" \
+                b"H5L.HOSTBASED-SERVICE"
+        elif self.realm.provider.lower() == 'heimdal':
+            expected += self.realm.realm.encode('utf-8')
+
+        self.assertEqual(bytes(canonicalized_name), expected)
 
     def test_copy(self):
         name1 = gssnames.Name(SERVICE_PRINCIPAL)
@@ -551,6 +608,7 @@ def test_copy(self):
     # doesn't actually implement it
 
     @ktu.gssapi_extension_test('rfc6680', 'RFC 6680')
+    @ktu.krb_provider_test(['mit'], 'Heimdal does not implemented for krb5')
     def test_is_mech_name(self):
         name = gssnames.Name(TARGET_SERVICE_NAME,
                              gb.NameType.hostbased_service)
@@ -562,6 +620,7 @@ def test_is_mech_name(self):
         self.assertEqual(canon_name.mech, gb.MechType.kerberos)
 
     @ktu.gssapi_extension_test('rfc6680', 'RFC 6680')
+    @ktu.krb_provider_test(['mit'], 'Heimdal does not implemented for krb5')
     def test_export_name_composite_no_attrs(self):
         name = gssnames.Name(TARGET_SERVICE_NAME,
                              gb.NameType.hostbased_service)
@@ -611,8 +670,13 @@ def setUp(self):
         self.client_creds = gsscreds.Credentials(name=None,
                                                  usage='initiate')
 
-        self.target_name = gssnames.Name(TARGET_SERVICE_NAME,
-                                         gb.NameType.hostbased_service)
+        if sys.platform == "darwin":
+            spn = TARGET_SERVICE_NAME + b"@" + FQDN
+            self.target_name = gssnames.Name(spn,
+                                             gb.NameType.hostbased_service)
+        else:
+            self.target_name = gssnames.Name(TARGET_SERVICE_NAME,
+                                             gb.NameType.hostbased_service)
 
         self.server_name = gssnames.Name(SERVICE_PRINCIPAL)
         self.server_creds = gsscreds.Credentials(name=self.server_name,
@@ -628,7 +692,12 @@ def _create_client_ctx(self, **kwargs):
     def test_create_from_other(self):
         raw_client_ctx, raw_server_ctx = self._create_completed_contexts()
         high_level_ctx = gssctx.SecurityContext(raw_client_ctx)
-        self.assertEqual(high_level_ctx.target_name, self.target_name)
+
+        expected = self.target_name
+        if self.realm.provider.lower() == "heimdal":
+            expected = gssnames.Name(self.realm.host_princ.encode('utf-8'),
+                                     name_type=gb.NameType.kerberos_principal)
+        self.assertEqual(high_level_ctx.target_name, expected)
 
     @exist_perms(lifetime=30, flags=[],
                  mech=gb.MechType.kerberos,
@@ -688,7 +757,13 @@ def test_initiate_accept_steps(self):
         self.assertTrue(server_ctx.complete)
 
         self.assertLessEqual(client_ctx.lifetime, 400)
-        self.assertEqual(client_ctx.target_name, self.target_name)
+
+        expected = self.target_name
+        if self.realm.provider.lower() == "heimdal":
+            expected = gssnames.Name(self.realm.host_princ.encode('utf-8'),
+                                     name_type=gb.NameType.kerberos_principal)
+        self.assertEqual(client_ctx.target_name, expected)
+
         self.assertIsInstance(client_ctx.mech, gb.OID)
         self.assertIsInstance(client_ctx.actual_flags, gb.IntEnumFlagSet)
         self.assertTrue(client_ctx.locally_initiated)
@@ -714,6 +789,9 @@ def test_channel_bindings(self):
         client_ctx.step(server_token)
 
     def test_bad_channel_bindings_raises_error(self):
+        if sys.platform == "darwin":
+            self.skipTest("macOS Heimdal doesn't fail as expected")
+
         bdgs = gb.ChannelBindings(application_data=b'abcxyz',
                                   initiator_address_type=gb.AddressType.ip,
                                   initiator_address=b'127.0.0.1',
@@ -738,7 +816,13 @@ def test_export_create_from_token(self):
 
         imported_ctx = gssctx.SecurityContext(token=token)
         self.assertEqual(imported_ctx.usage, "initiate")
-        self.assertEqual(imported_ctx.target_name, self.target_name)
+
+        expected = self.target_name
+        if self.realm.provider.lower() == "heimdal":
+            expected = gssnames.Name(self.realm.host_princ.encode('utf-8'),
+                                     name_type=gb.NameType.kerberos_principal)
+
+        self.assertEqual(imported_ctx.target_name, expected)
 
     def test_pickle_unpickle(self):
         client_ctx, server_ctx = self._create_completed_contexts()
@@ -747,7 +831,12 @@ def test_pickle_unpickle(self):
         unpickled_ctx = pickle.loads(pickled_ctx)
         self.assertIsInstance(unpickled_ctx, gssctx.SecurityContext)
         self.assertEqual(unpickled_ctx.usage, "initiate")
-        self.assertEqual(unpickled_ctx.target_name, self.target_name)
+
+        expected = self.target_name
+        if self.realm.provider.lower() == "heimdal":
+            expected = gssnames.Name(self.realm.host_princ.encode('utf-8'),
+                                     name_type=gb.NameType.kerberos_principal)
+        self.assertEqual(unpickled_ctx.target_name, expected)
 
     def test_encrypt_decrypt(self):
         client_ctx, server_ctx = self._create_completed_contexts()
@@ -810,7 +899,8 @@ def test_verify_signature_raise(self):
         self.assertRaises(gb.GSSError, server_ctx.verify_signature,
                           b"other message", mic_token)
 
-    @ktu.krb_minversion_test("1.11", "returning tokens")
+    @ktu.krb_minversion_test("1.11", "returning tokens", provider="mit")
+    @ktu.krb_provider_test(["mit"], "returning tokens")
     def test_defer_step_error_on_method(self):
         gssctx.SecurityContext.__DEFER_STEP_ERRORS__ = True
         bdgs = gb.ChannelBindings(application_data=b'abcxyz')
@@ -827,7 +917,8 @@ def test_defer_step_error_on_method(self):
         self.assertRaises(gb.BadChannelBindingsError, server_ctx.encrypt,
                           b"test")
 
-    @ktu.krb_minversion_test("1.11", "returning tokens")
+    @ktu.krb_minversion_test("1.11", "returning tokens", provider="mit")
+    @ktu.krb_provider_test(["mit"], "returning tokens")
     def test_defer_step_error_on_complete_property_access(self):
         gssctx.SecurityContext.__DEFER_STEP_ERRORS__ = True
         bdgs = gb.ChannelBindings(application_data=b'abcxyz')
diff --git a/gssapi/tests/test_raw.py b/gssapi/tests/test_raw.py
index 3742b279..1ab7ab3a 100644
--- a/gssapi/tests/test_raw.py
+++ b/gssapi/tests/test_raw.py
@@ -1,6 +1,9 @@
 import copy
+import ctypes
+import ctypes.util
 import os
 import socket
+import sys
 import unittest
 
 import gssapi.raw as gb
@@ -12,9 +15,14 @@
 
 
 TARGET_SERVICE_NAME = b'host'
-FQDN = socket.getfqdn().encode('utf-8')
+FQDN = (
+    'localhost' if sys.platform == 'darwin' else socket.getfqdn()
+).encode('utf-8')
 SERVICE_PRINCIPAL = TARGET_SERVICE_NAME + b'/' + FQDN
 
+if sys.platform == 'darwin':
+    TARGET_SERVICE_NAME += b"@" + FQDN
+
 
 class _GSSAPIKerberosTestCase(kt.KerberosTestCase):
     @classmethod
@@ -28,6 +36,7 @@ def setUpClass(cls):
 
         cls.USER_PRINC = cls.realm.user_princ.split('@')[0].encode("UTF-8")
         cls.ADMIN_PRINC = cls.realm.admin_princ.split('@')[0].encode("UTF-8")
+        cls.KRB5_LIB_PATH = os.environ.get("GSSAPI_KRB5_MAIN_LIB", None)
 
     @classmethod
     def _init_env(cls):
@@ -99,6 +108,7 @@ def test_display_name(self):
     # doesn't actually implement it
 
     @ktu.gssapi_extension_test('rfc6680', 'RFC 6680')
+    @ktu.krb_provider_test(['mit'], 'Heimdal does not implemented for krb5')
     def test_inquire_name_not_mech_name(self):
         base_name = gb.import_name(TARGET_SERVICE_NAME,
                                    gb.NameType.hostbased_service)
@@ -109,6 +119,7 @@ def test_inquire_name_not_mech_name(self):
         self.assertIsNone(inquire_res.mech)
 
     @ktu.gssapi_extension_test('rfc6680', 'RFC 6680')
+    @ktu.krb_provider_test(['mit'], 'Heimdal does not implemented for krb5')
     def test_inquire_name_mech_name(self):
         base_name = gb.import_name(TARGET_SERVICE_NAME,
                                    gb.NameType.hostbased_service)
@@ -241,7 +252,8 @@ def test_acquire_creds(self):
         creds, actual_mechs, ttl = cred_resp
         self.assertIsInstance(creds, gb.Creds)
         self.assertIn(gb.MechType.kerberos, actual_mechs)
-        self.assertIsInstance(ttl, int)
+        if sys.platform != 'darwin':
+            self.assertIsInstance(ttl, int)
 
         gb.release_name(name)
         gb.release_cred(creds)
@@ -379,7 +391,8 @@ def test_acquire_creds_impersonate_name(self):
 
     @ktu.gssapi_extension_test('s4u', 'S4U')
     @ktu.krb_minversion_test('1.11',
-                             'returning delegated S4U2Proxy credentials')
+                             'returning delegated S4U2Proxy credentials',
+                             provider='mit')
     def test_always_get_delegated_creds(self):
         svc_princ = SERVICE_PRINCIPAL.decode("UTF-8")
         self.realm.kinit(svc_princ, flags=['-k', '-f'])
@@ -421,10 +434,14 @@ def test_store_cred_acquire_cred(self):
         self.assertIsNotNone(deleg_creds)
 
         store_res = gb.store_cred(deleg_creds, usage='initiate',
+                                  mech=gb.MechType.kerberos,
                                   set_default=True, overwrite=True)
         self.assertIsNotNone(store_res)
-        self.assertEqual(store_res.usage, "initiate")
-        self.assertIn(gb.MechType.kerberos, store_res.mechs)
+
+        if self.realm.provider.lower() != 'heimdal':
+            # Heimdal does not return this info as expected
+            self.assertEqual(store_res.usage, "initiate")
+            self.assertIn(gb.MechType.kerberos, store_res.mechs)
 
         deleg_name = gb.inquire_cred(deleg_creds).name
         acq_resp = gb.acquire_cred(deleg_name, usage='initiate')
@@ -445,9 +462,17 @@ def test_store_cred_into_acquire_cred(self):
         initial_creds = gb.acquire_cred(None, usage='initiate').creds
 
         # NB(sross): overwrite because the ccache doesn't exist yet
-        store_res = gb.store_cred_into(store, initial_creds, overwrite=True)
+        expected_usage = 'initiate'
+        store_kwargs = {}
+        if self.realm.provider.lower() == 'heimdal':
+            expected_usage = 'both'
+            store_kwargs['mech'] = gb.MechType.kerberos
+            store_kwargs['usage'] = 'initiate'
+
+        store_res = gb.store_cred_into(store, initial_creds, overwrite=True,
+                                       **store_kwargs)
         self.assertIsNotNone(store_res.mechs)
-        self.assertEqual(store_res.usage, "initiate")
+        self.assertEqual(store_res.usage, expected_usage)
 
         name = gb.import_name(princ_name.encode('UTF-8'))
         retrieve_res = gb.acquire_cred_from(store, name)
@@ -459,6 +484,9 @@ def test_store_cred_into_acquire_cred(self):
         self.assertIsInstance(retrieve_res.lifetime, int)
 
     def test_add_cred(self):
+        if sys.platform == 'darwin':
+            self.skipTest('macOS fails to find the credential')
+
         target_name = gb.import_name(TARGET_SERVICE_NAME,
                                      gb.NameType.hostbased_service)
         client_ctx_resp = gb.init_sec_context(target_name)
@@ -494,9 +522,19 @@ def test_inquire_creds(self):
         inq_resp = gb.inquire_cred(cred)
         self.assertIsNotNone(inq_resp)
         self.assertIsInstance(inq_resp.name, gb.Name)
+
+        if self.realm.provider.lower() == 'heimdal':
+            name = gb.import_name(self.realm.host_princ.encode('utf-8'),
+                                  gb.NameType.kerberos_principal)
+
         self.assertTrue(gb.compare_name(name, inq_resp.name))
-        self.assertIsInstance(inq_resp.lifetime, int)
-        self.assertEqual(inq_resp.usage, "both")
+
+        if sys.platform == 'darwin':
+            self.assertEqual(inq_resp.usage, "accept")
+        else:
+            self.assertIsInstance(inq_resp.lifetime, int)
+            self.assertEqual(inq_resp.usage, "both")
+
         self.assertIn(gb.MechType.kerberos, inq_resp.mechs)
 
     def test_create_oid_from_bytes(self):
@@ -542,8 +580,11 @@ def test_acquire_cred_with_password(self):
         imp_creds, actual_mechs, output_ttl = imp_resp
         self.assertIsNotNone(imp_creds)
         self.assertIsInstance(imp_creds, gb.Creds)
-        self.assertIn(gb.MechType.kerberos, actual_mechs)
-        self.assertIsInstance(output_ttl, int)
+        if sys.platform == 'darwin':
+            self.assertIn(gb.OID.from_int_seq('1.3.6.1.5.2.5'), actual_mechs)
+        else:
+            self.assertIn(gb.MechType.kerberos, actual_mechs)
+            self.assertIsInstance(output_ttl, int)
 
     @ktu.gssapi_extension_test('password_add', 'Password (add)')
     def test_add_cred_with_password(self):
@@ -566,6 +607,9 @@ def test_add_cred_with_password(self):
 
     @ktu.gssapi_extension_test('rfc5587', 'RFC 5587')
     def test_rfc5587(self):
+        if sys.platform == "darwin":
+            self.skipTest("too many edge cases on macOS")
+
         mechs = gb.indicate_mechs_by_attrs(None, None, None)
         self.assertIsInstance(mechs, set)
         self.assertGreater(len(mechs), 0)
@@ -626,26 +670,34 @@ def test_rfc5587(self):
             for expected_mech in expected_mechs:
                 self.assertNotIn(expected_mech, mechs)
 
-        for attr, expected_mechs in known_attrs_dict.items():
-            attrs = set([attr])
+        if self.realm.provider.lower() != 'heimdal':
+            # Heimdal doesn't fully implement gss_indicate_mechs_by_attrs
+            for attr, expected_mechs in known_attrs_dict.items():
+                attrs = set([attr])
 
-            mechs = gb.indicate_mechs_by_attrs(None, None, attrs)
-            self.assertGreater(len(mechs), 0)
-            self.assertEqual(mechs, expected_mechs)
+                mechs = gb.indicate_mechs_by_attrs(None, None, attrs)
+                self.assertGreater(len(mechs), 0)
+                self.assertEqual(mechs, expected_mechs)
 
     @ktu.gssapi_extension_test('rfc5587', 'RFC 5587')
     def test_display_mech_attr(self):
         test_attrs = [
             # oid, name, short_desc, long_desc
             # Taken from krb5/src/tests/gssapi/t_saslname
-            [gb.OID.from_int_seq("1.3.6.1.5.5.13.24"), b"GSS_C_MA_CBINDINGS",
-             b"channel-bindings", b"Mechanism supports channel bindings."],
+            [gb.OID.from_int_seq("1.3.6.1.5.5.13.24"),
+                b"GSS_C_MA_CBINDINGS", b"channel-bindings",
+                b"Mechanism supports channel bindings."],
             [gb.OID.from_int_seq("1.3.6.1.5.5.13.1"),
-             b"GSS_C_MA_MECH_CONCRETE", b"concrete-mech",
-             b"Mechanism is neither a pseudo-mechanism nor a composite "
-             b"mechanism."]
+                b"GSS_C_MA_MECH_CONCRETE", b"concrete-mech",
+                b"Mechanism is neither a pseudo-mechanism nor a composite "
+                b"mechanism."]
         ]
 
+        if self.realm.provider.lower() == 'heimdal':
+            test_attrs[0][3] = b""
+            test_attrs[1][3] = b"Indicates that a mech is neither a " \
+                b"pseudo-mechanism nor a composite mechanism"
+
         for attr in test_attrs:
             display_out = gb.display_mech_attr(attr[0])
             self.assertEqual(display_out.name, attr[1])
@@ -660,8 +712,9 @@ def test_sasl_names(self):
             out = gb.inquire_saslname_for_mech(mech)
 
             out_smn = out.sasl_mech_name
-            self.assertIsInstance(out_smn, bytes)
-            self.assertGreater(len(out_smn), 0)
+            if out_smn:
+                self.assertIsInstance(out_smn, bytes)
+                self.assertGreater(len(out_smn), 0)
 
             out_mn = out.mech_name
             self.assertIsInstance(out_mn, bytes)
@@ -669,9 +722,16 @@ def test_sasl_names(self):
             out_md = out.mech_description
             self.assertIsInstance(out_md, bytes)
 
-            cmp_mech = gb.inquire_mech_for_saslname(out_smn)
-            self.assertIsNotNone(cmp_mech)
-            self.assertEqual(cmp_mech, mech)
+            # Heimdal fails with Unknown mech-code on sanon
+            if not (self.realm.provider.lower() == 'heimdal' and
+                    mech.dotted_form == '1.3.6.1.4.1.5322.26.1.110'):
+                cmp_mech = gb.inquire_mech_for_saslname(out_smn)
+                self.assertIsNotNone(cmp_mech)
+
+                # For some reason macOS sometimes returns this for mechs
+                if not (sys.platform == 'darwin' and
+                        cmp_mech.dotted_form == '1.2.752.43.14.2'):
+                    self.assertEqual(cmp_mech, mech)
 
     @ktu.gssapi_extension_test('rfc4178', 'Negotiation Mechanism')
     def test_set_neg_mechs(self):
@@ -745,7 +805,8 @@ def test_set_neg_mechs(self):
     @ktu.krb_minversion_test('1.16',
                              'querying impersonator name of krb5 GSS '
                              'Credential using the '
-                             'GSS_KRB5_GET_CRED_IMPERSONATOR OID')
+                             'GSS_KRB5_GET_CRED_IMPERSONATOR OID',
+                             provider='mit')
     def test_inquire_cred_by_oid_impersonator(self):
         svc_princ = SERVICE_PRINCIPAL.decode("UTF-8")
         self.realm.kinit(svc_princ, flags=['-k', '-f'])
@@ -830,6 +891,9 @@ def test_inquire_sec_context_by_oid_should_raise_error(self):
     @ktu.gssapi_extension_test('ggf', 'Global Grid Forum')
     @ktu.gssapi_extension_test('password', 'Add Credential with Password')
     def test_set_sec_context_option(self):
+        if sys.platform == 'darwin':
+            self.skipTest("macOS NTLM does not implement this OID")
+
         ntlm_mech = gb.OID.from_int_seq("1.3.6.1.4.1.311.2.2.10")
         username = gb.import_name(name=b"user",
                                   name_type=gb.NameType.user)
@@ -882,7 +946,7 @@ def test_set_sec_context_option_fail(self):
     @ktu.gssapi_extension_test('set_cred_opt', 'Kitten Set Credential Option')
     @ktu.krb_minversion_test('1.14',
                              'GSS_KRB5_CRED_NO_CI_FLAGS_X was added in MIT '
-                             'krb5 1.14')
+                             'krb5 1.14', provider='mit')
     def test_set_cred_option(self):
         name = gb.import_name(SERVICE_PRINCIPAL,
                               gb.NameType.kerberos_principal)
@@ -906,6 +970,345 @@ def test_set_cred_option_should_raise_error(self):
         self.assertRaises(gb.GSSError, gb.set_cred_option, invalid_oid,
                           orig_cred, b"\x00")
 
+    @ktu.gssapi_extension_test('krb5', 'Kerberos Extensions')
+    @ktu.krb_provider_test(['mit'], 'Cannot revert ccache on Heimdal')
+    # https://github.com/heimdal/heimdal/issues/803
+    def test_krb5_ccache_name(self):
+        provider = self.realm.provider.lower()
+
+        new_ccache = os.path.join(self.realm.tmpdir, 'ccache-new')
+        new_env = self.realm.env.copy()
+        new_env['KRB5CCNAME'] = new_ccache
+        self.realm.kinit(self.realm.user_princ,
+                         password=self.realm.password('user'),
+                         env=new_env)
+
+        old_ccache = gb.krb5_ccache_name(new_ccache.encode('utf-8'))
+        try:
+            if provider == 'heimdal':
+                # Heimdal never returns the old name - see above link
+                self.assertTrue(old_ccache is None)
+            else:
+                self.assertEqual(old_ccache.decode('utf-8'), self.realm.ccache)
+
+            cred_resp = gb.acquire_cred(usage='initiate').creds
+
+            princ_name = gb.inquire_cred(cred_resp, name=True).name
+            name = gb.display_name(princ_name, name_type=False).name
+            self.assertEqual(name, self.realm.user_princ.encode('utf-8'))
+
+            if provider != 'heimdal':
+                changed_ccache = gb.krb5_ccache_name(old_ccache)
+                self.assertEqual(changed_ccache.decode('utf-8'), new_ccache)
+
+        finally:
+            # Ensure original behaviour is back for other tests
+            gb.krb5_ccache_name(None)
+
+        target_name = gb.import_name(TARGET_SERVICE_NAME,
+                                     gb.NameType.hostbased_service)
+        client_resp = gb.init_sec_context(target_name, creds=cred_resp)
+        client_ctx = client_resp[0]
+        client_token = client_resp[3]
+
+        server_name = gb.import_name(SERVICE_PRINCIPAL,
+                                     gb.NameType.kerberos_principal)
+        server_creds = gb.acquire_cred(server_name)[0]
+        server_resp = gb.accept_sec_context(client_token,
+                                            acceptor_creds=server_creds)
+        server_ctx = server_resp[0]
+        server_token = server_resp[3]
+
+        gb.init_sec_context(target_name, context=client_ctx,
+                            input_token=server_token)
+        initiator = gb.inquire_context(server_ctx,
+                                       initiator_name=True).initiator_name
+        initiator_name = gb.display_name(initiator, name_type=False).name
+
+        self.assertEqual(initiator_name, self.realm.user_princ.encode('utf-8'))
+
+    @ktu.gssapi_extension_test('krb5', 'Kerberos Extensions')
+    def test_krb5_export_lucid_sec_context(self):
+        target_name = gb.import_name(TARGET_SERVICE_NAME,
+                                     gb.NameType.hostbased_service)
+        ctx_resp = gb.init_sec_context(target_name)
+
+        client_token1 = ctx_resp[3]
+        client_ctx = ctx_resp[0]
+        server_name = gb.import_name(SERVICE_PRINCIPAL,
+                                     gb.NameType.kerberos_principal)
+        server_creds = gb.acquire_cred(server_name)[0]
+        server_resp = gb.accept_sec_context(client_token1,
+                                            acceptor_creds=server_creds)
+        server_ctx = server_resp[0]
+        server_tok = server_resp[3]
+
+        client_resp2 = gb.init_sec_context(target_name,
+                                           context=client_ctx,
+                                           input_token=server_tok)
+        ctx = client_resp2[0]
+
+        self.assertRaises(gb.GSSError, gb.krb5_export_lucid_sec_context,
+                          ctx, 0)
+
+        initiator_info = gb.krb5_export_lucid_sec_context(ctx, 1)
+        self.assertTrue(isinstance(initiator_info, gb.Krb5LucidContextV1))
+        self.assertEqual(initiator_info.version, 1)
+        self.assertTrue(initiator_info.is_initiator)
+        self.assertTrue(isinstance(initiator_info.endtime, int))
+        self.assertTrue(isinstance(initiator_info.send_seq, int))
+        self.assertTrue(isinstance(initiator_info.recv_seq, int))
+        self.assertEqual(initiator_info.protocol, 1)
+        self.assertEqual(initiator_info.rfc1964_kd, None)
+        self.assertTrue(isinstance(initiator_info.cfx_kd, gb.CfxKeyData))
+        self.assertTrue(isinstance(initiator_info.cfx_kd.ctx_key_type, int))
+        self.assertTrue(isinstance(initiator_info.cfx_kd.ctx_key, bytes))
+        self.assertTrue(isinstance(initiator_info.cfx_kd.acceptor_subkey_type,
+                                   int))
+        self.assertTrue(isinstance(initiator_info.cfx_kd.acceptor_subkey,
+                                   bytes))
+
+        acceptor_info = gb.krb5_export_lucid_sec_context(server_ctx, 1)
+        self.assertTrue(isinstance(acceptor_info, gb.Krb5LucidContextV1))
+        self.assertEqual(acceptor_info.version, 1)
+        self.assertFalse(acceptor_info.is_initiator)
+        self.assertTrue(isinstance(acceptor_info.endtime, int))
+        self.assertTrue(isinstance(acceptor_info.send_seq, int))
+        self.assertTrue(isinstance(acceptor_info.recv_seq, int))
+        self.assertEqual(acceptor_info.protocol, 1)
+        self.assertEqual(acceptor_info.rfc1964_kd, None)
+        self.assertTrue(isinstance(acceptor_info.cfx_kd, gb.CfxKeyData))
+        self.assertTrue(isinstance(acceptor_info.cfx_kd.ctx_key_type, int))
+        self.assertTrue(isinstance(acceptor_info.cfx_kd.ctx_key, bytes))
+        self.assertTrue(isinstance(acceptor_info.cfx_kd.acceptor_subkey_type,
+                                   int))
+        self.assertTrue(isinstance(acceptor_info.cfx_kd.acceptor_subkey,
+                                   bytes))
+
+        self.assertEqual(initiator_info.endtime, acceptor_info.endtime)
+        self.assertEqual(initiator_info.send_seq, acceptor_info.recv_seq)
+        self.assertEqual(initiator_info.recv_seq, acceptor_info.send_seq)
+        self.assertEqual(initiator_info.cfx_kd.ctx_key_type,
+                         acceptor_info.cfx_kd.ctx_key_type)
+        self.assertEqual(initiator_info.cfx_kd.ctx_key,
+                         acceptor_info.cfx_kd.ctx_key)
+        self.assertEqual(initiator_info.cfx_kd.acceptor_subkey_type,
+                         acceptor_info.cfx_kd.acceptor_subkey_type)
+        self.assertEqual(initiator_info.cfx_kd.acceptor_subkey,
+                         acceptor_info.cfx_kd.acceptor_subkey)
+
+    @ktu.gssapi_extension_test('krb5', 'Kerberos Extensions')
+    def test_krb5_extract_authtime_from_sec_context(self):
+        target_name = gb.import_name(TARGET_SERVICE_NAME,
+                                     gb.NameType.hostbased_service)
+        ctx_resp = gb.init_sec_context(target_name)
+
+        client_token1 = ctx_resp[3]
+        client_ctx = ctx_resp[0]
+        server_name = gb.import_name(SERVICE_PRINCIPAL,
+                                     gb.NameType.kerberos_principal)
+        server_creds = gb.acquire_cred(server_name)[0]
+        server_resp = gb.accept_sec_context(client_token1,
+                                            acceptor_creds=server_creds)
+        server_ctx = server_resp[0]
+        server_tok = server_resp[3]
+
+        client_resp2 = gb.init_sec_context(target_name,
+                                           context=client_ctx,
+                                           input_token=server_tok)
+        ctx = client_resp2[0]
+
+        if self.realm.provider.lower() == 'heimdal':
+            # Heimdal doesn't store the ticket info on the initiator
+            client_authtime = server_authtime = \
+                gb.krb5_extract_authtime_from_sec_context(server_ctx)
+            self.assertRaises(gb.GSSError,
+                              gb.krb5_extract_authtime_from_sec_context,
+                              client_ctx)
+        else:
+            client_authtime = gb.krb5_extract_authtime_from_sec_context(ctx)
+            server_authtime = gb.krb5_extract_authtime_from_sec_context(
+                server_ctx)
+
+        self.assertTrue(isinstance(client_authtime, int))
+        self.assertTrue(isinstance(server_authtime, int))
+        self.assertEqual(client_authtime, server_authtime)
+
+    @ktu.gssapi_extension_test('krb5', 'Kerberos Extensions')
+    def test_krb5_extract_authz_data_from_sec_context(self):
+        target_name = gb.import_name(TARGET_SERVICE_NAME,
+                                     gb.NameType.hostbased_service)
+        client_token = gb.init_sec_context(target_name)[3]
+
+        server_name = gb.import_name(SERVICE_PRINCIPAL,
+                                     gb.NameType.kerberos_principal)
+        server_creds = gb.acquire_cred(server_name)[0]
+        server_ctx = gb.accept_sec_context(client_token,
+                                           acceptor_creds=server_creds)[0]
+
+        # KRB5_AUTHDATA_IF_RELEVANT = 1
+        authz_data = gb.krb5_extract_authz_data_from_sec_context(server_ctx, 1)
+        self.assertTrue(isinstance(authz_data, bytes))
+
+    @ktu.gssapi_extension_test('krb5', 'Kerberos Extensions')
+    def test_krb5_import_cred(self):
+        # Ensuring we match the krb5 library to the GSSAPI library is a thorny
+        # problem.  Avoid it by requiring test suite users to explicitly
+        # enable this test.
+        if not self.KRB5_LIB_PATH:
+            self.skipTest("Env var GSSAPI_KRB5_MAIN_LIB not defined")
+
+        creds = gb.Creds()
+
+        # Should fail if only creds are specified
+        self.assertRaises(ValueError, gb.krb5_import_cred, creds)
+
+        new_ccache = os.path.join(self.realm.tmpdir, 'ccache-new')
+        new_env = self.realm.env.copy()
+        new_env['KRB5CCNAME'] = new_ccache
+        self.realm.kinit(self.realm.user_princ,
+                         password=self.realm.password('user'),
+                         env=new_env)
+
+        krb5 = ctypes.CDLL(self.KRB5_LIB_PATH)
+        krb5_ctx = ctypes.c_void_p()
+        krb5.krb5_init_context(ctypes.byref(krb5_ctx))
+        try:
+            ccache_ptr = ctypes.c_void_p()
+            err = krb5.krb5_cc_resolve(krb5_ctx, new_ccache.encode('utf-8'),
+                                       ctypes.byref(ccache_ptr))
+            self.assertEqual(err, 0)
+
+            try:
+                gb.krb5_import_cred(creds, cache=ccache_ptr.value)
+
+                # Creds will be invalid once the cc is closed so do this now
+                target_name = gb.import_name(TARGET_SERVICE_NAME,
+                                             gb.NameType.hostbased_service)
+                client_resp = gb.init_sec_context(target_name, creds=creds)
+
+            finally:
+                krb5.krb5_cc_close(krb5_ctx, ccache_ptr)
+        finally:
+            krb5.krb5_free_context(krb5_ctx)
+
+        client_ctx = client_resp[0]
+        client_token = client_resp[3]
+
+        server_name = gb.import_name(SERVICE_PRINCIPAL,
+                                     gb.NameType.kerberos_principal)
+        server_creds = gb.acquire_cred(server_name)[0]
+        server_resp = gb.accept_sec_context(client_token,
+                                            acceptor_creds=server_creds)
+        server_ctx = server_resp[0]
+        server_token = server_resp[3]
+
+        gb.init_sec_context(target_name, context=client_ctx,
+                            input_token=server_token)
+        initiator = gb.inquire_context(server_ctx,
+                                       initiator_name=True).initiator_name
+        initiator_name = gb.display_name(initiator, name_type=False).name
+
+        self.assertEqual(initiator_name, self.realm.user_princ.encode('utf-8'))
+
+    @ktu.gssapi_extension_test('krb5', 'Kerberos Extensions')
+    def test_krb5_get_tkt_flags(self):
+        target_name = gb.import_name(TARGET_SERVICE_NAME,
+                                     gb.NameType.hostbased_service)
+        ctx_resp = gb.init_sec_context(target_name)
+
+        client_token1 = ctx_resp[3]
+        client_ctx = ctx_resp[0]
+        server_name = gb.import_name(SERVICE_PRINCIPAL,
+                                     gb.NameType.kerberos_principal)
+        server_creds = gb.acquire_cred(server_name)[0]
+        server_resp = gb.accept_sec_context(client_token1,
+                                            acceptor_creds=server_creds)
+        server_ctx = server_resp[0]
+        server_tok = server_resp[3]
+
+        client_resp2 = gb.init_sec_context(target_name,
+                                           context=client_ctx,
+                                           input_token=server_tok)
+        client_ctx = client_resp2[0]
+
+        if self.realm.provider.lower() == 'heimdal':
+            # Heimdal doesn't store the ticket info on the initiator
+            client_flags = server_flags = gb.krb5_get_tkt_flags(server_ctx)
+            self.assertRaises(gb.GSSError, gb.krb5_get_tkt_flags, client_ctx)
+        else:
+            client_flags = gb.krb5_get_tkt_flags(client_ctx)
+            server_flags = gb.krb5_get_tkt_flags(server_ctx)
+
+        self.assertTrue(isinstance(client_flags, int))
+        self.assertTrue(isinstance(server_flags, int))
+        self.assertEqual(client_flags, server_flags)
+
+    @ktu.gssapi_extension_test('krb5', 'Kerberos Extensions')
+    @ktu.krb_provider_test(['mit'], 'Cannot revert ccache on Heimdal')
+    # https://github.com/heimdal/heimdal/issues/803
+    def test_krb5_set_allowable_enctypes(self):
+        krb5_mech = gb.OID.from_int_seq("1.2.840.113554.1.2.2")
+        AES_128 = 0x11
+        AES_256 = 0x12
+
+        new_ccache = os.path.join(self.realm.tmpdir, 'ccache-new')
+        new_env = self.realm.env.copy()
+        new_env['KRB5CCNAME'] = new_ccache
+        self.realm.kinit(self.realm.user_princ,
+                         password=self.realm.password('user'),
+                         env=new_env)
+
+        gb.krb5_ccache_name(new_ccache.encode('utf-8'))
+        try:
+            creds = gb.acquire_cred(usage='initiate',
+                                    mechs=[krb5_mech]).creds
+        finally:
+            gb.krb5_ccache_name(None)
+
+        gb.krb5_set_allowable_enctypes(creds, [AES_128])
+
+        target_name = gb.import_name(TARGET_SERVICE_NAME,
+                                     gb.NameType.hostbased_service)
+        server_name = gb.import_name(SERVICE_PRINCIPAL,
+                                     gb.NameType.kerberos_principal)
+        server_creds = gb.acquire_cred(server_name, usage='accept',
+                                       mechs=[krb5_mech])[0]
+
+        if self.realm.provider.lower() != 'heimdal':
+            # Will fail because the client only offers AES128
+            # Only seems to work on MIT and not Heimdal
+            ctx_resp = gb.init_sec_context(target_name, creds=creds)
+            client_token1 = ctx_resp[3]
+            client_ctx = ctx_resp[0]
+            gb.krb5_set_allowable_enctypes(server_creds, [AES_256])
+            self.assertRaises(gb.GSSError, gb.accept_sec_context,
+                              client_token1, acceptor_creds=server_creds)
+
+            gb.krb5_set_allowable_enctypes(server_creds, [AES_128, AES_256])
+
+        ctx_resp = gb.init_sec_context(target_name, creds=creds)
+        client_token1 = ctx_resp[3]
+        client_ctx = ctx_resp[0]
+
+        server_resp = gb.accept_sec_context(client_token1,
+                                            acceptor_creds=server_creds)
+        server_ctx = server_resp[0]
+        server_tok = server_resp[3]
+
+        client_resp2 = gb.init_sec_context(target_name,
+                                           context=client_ctx,
+                                           input_token=server_tok)
+        ctx = client_resp2[0]
+
+        initiator_info = gb.krb5_export_lucid_sec_context(ctx, 1)
+        acceptor_info = gb.krb5_export_lucid_sec_context(server_ctx, 1)
+        self.assertEqual(AES_128, initiator_info.cfx_kd.ctx_key_type)
+        self.assertEqual(initiator_info.cfx_kd.ctx_key_type,
+                         initiator_info.cfx_kd.acceptor_subkey_type)
+        self.assertEqual(acceptor_info.cfx_kd.ctx_key_type,
+                         acceptor_info.cfx_kd.acceptor_subkey_type)
+
 
 class TestIntEnumFlagSet(unittest.TestCase):
     def test_create_from_int(self):
@@ -1064,7 +1467,8 @@ def test_basic_init_default_ctx(self):
         self.assertIsInstance(ctx, gb.SecurityContext)
         self.assertEqual(out_mech_type, gb.MechType.kerberos)
         self.assertIsInstance(out_req_flags, Set)
-        self.assertGreaterEqual(len(out_req_flags), 2)
+        if sys.platform != 'darwin':
+            self.assertGreaterEqual(len(out_req_flags), 2)
         self.assertGreater(len(out_token), 0)
         self.assertGreater(out_ttl, 0)
         self.assertIsInstance(cont_needed, bool)
@@ -1161,6 +1565,9 @@ def test_channel_bindings(self):
         self.server_ctx = server_resp.context
 
     def test_bad_channel_binding_raises_error(self):
+        if sys.platform == 'darwin':
+            self.skipTest('macOS does not raise error with validation')
+
         bdgs = gb.ChannelBindings(application_data=b'abcxyz',
                                   initiator_address_type=gb.AddressType.ip,
                                   initiator_address=b'127.0.0.1',
@@ -1304,7 +1711,7 @@ def test_basic_iov_wrap_unwrap_prealloc(self):
         self.assertEqual(init_message[2].value, init_data)
         self.assertEqual(init_message[3].value, init_other_data)
 
-    @ktu.gssapi_extension_test('dce', 'DCE (IOV/AEAD)')
+    @ktu.gssapi_extension_test('dce', 'DCE (IOV)')
     def test_basic_iov_wrap_unwrap_autoalloc(self):
         init_data = b'some encrypted data'
         init_other_data = b'some other encrypted data'
@@ -1334,7 +1741,8 @@ def test_basic_iov_wrap_unwrap_autoalloc(self):
         self.assertEqual(init_message[2].value, init_data)
         self.assertEqual(init_message[3].value, init_other_data)
 
-    @ktu.gssapi_extension_test('dce', 'DCE (IOV/AEAD)')
+    @ktu.gssapi_extension_test('dce_aead', 'DCE (AEAD)')
+    @ktu.krb_provider_test(['mit'], 'unwrapping AEAD stream')
     def test_basic_aead_wrap_unwrap(self):
         assoc_data = b'some sig data'
         wrapped_message, conf = gb.wrap_aead(self.client_ctx, b"test message",
@@ -1353,7 +1761,8 @@ def test_basic_aead_wrap_unwrap(self):
         self.assertIsInstance(qop, int)
         self.assertGreaterEqual(qop, 0)
 
-    @ktu.gssapi_extension_test('dce', 'DCE (IOV/AEAD)')
+    @ktu.gssapi_extension_test('dce_aead', 'DCE (AEAD)')
+    @ktu.krb_provider_test(['mit'], 'unwrapping AEAD stream')
     def test_basic_aead_wrap_unwrap_no_assoc(self):
         wrapped_message, conf = gb.wrap_aead(self.client_ctx, b"test message")
         self.assertIsInstance(wrapped_message, bytes)
@@ -1370,7 +1779,8 @@ def test_basic_aead_wrap_unwrap_no_assoc(self):
         self.assertIsInstance(qop, int)
         self.assertGreaterEqual(qop, 0)
 
-    @ktu.gssapi_extension_test('dce', 'DCE (IOV/AEAD)')
+    @ktu.gssapi_extension_test('dce_aead', 'DCE (AEAD)')
+    @ktu.krb_provider_test(['mit'], 'unwrapping AEAD stream')
     def test_basic_aead_wrap_unwrap_bad_assoc_raises_error(self):
         assoc_data = b'some sig data'
         wrapped_message, conf = gb.wrap_aead(self.client_ctx, b"test message",
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..41c0bcf8
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,58 @@
+[build-system]
+requires = [
+    "Cython >= 3.0.3, < 4.0.0",
+    "setuptools >= 40.6.0",  # Start of PEP 517 support for setuptools
+]
+build-backend = "setuptools.build_meta"
+
+[tool.mypy]
+exclude = """
+(?x)(
+  setup.py
+  | docs/
+  | build/
+)
+"""
+show_error_codes = true
+show_column_numbers = true
+disallow_any_unimported = true
+disallow_untyped_calls = true
+disallow_untyped_defs = true
+disallow_incomplete_defs = true
+check_untyped_defs = true
+disallow_untyped_decorators = true
+warn_redundant_casts = true
+warn_unused_ignores = true
+
+[[tool.mypy.overrides]]
+module = "gssapi.tests.*"
+disallow_any_unimported = false
+disallow_untyped_calls = false
+disallow_untyped_defs = false
+check_untyped_defs = false
+
+[[tool.mypy.overrides]]
+module = "k5test"
+ignore_missing_imports = true
+
+[[tool.mypy.overrides]]
+module = "k5test.unit"
+ignore_missing_imports = true
+
+[[tool.mypy.overrides]]
+module = "parameterized"
+ignore_missing_imports = true
+
+[tool.tox]
+legacy_tox_ini = """
+[tox]
+envlist = py36,py37,py38
+
+[testenv]
+whitelist_externals=bash
+commands =
+    bash -c "source ./.travis/lib-verify.sh && verify::flake8"
+    python -m unittest
+
+deps = -r{toxinidir}/test-requirements.txt
+"""
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index d799dbc2..00000000
--- a/setup.cfg
+++ /dev/null
@@ -1,7 +0,0 @@
-[build_sphinx]
-source-dir=docs/source
-build-dir=docs/build
-all_files=1
-
-[upload_sphinx]
-upload-dir = docs/build/html
diff --git a/setup.py b/setup.py
index 1d1646fa..e636c506 100755
--- a/setup.py
+++ b/setup.py
@@ -1,10 +1,4 @@
 #!/usr/bin/env python
-from __future__ import print_function
-
-from setuptools import setup
-from setuptools import Distribution
-from setuptools.command.sdist import sdist
-from setuptools.extension import Extension
 import subprocess
 import platform
 import re
@@ -13,21 +7,15 @@
 import shutil
 import shlex
 
+# Enables the vendored distutils in setuptools over the stdlib one to avoid
+# the deprecation warning. Must be done before importing setuptools,
+# setuptools also must be imported before distutils.
+# https://github.com/pypa/setuptools/blob/main/docs/deprecated/distutils-legacy.rst
+os.environ['SETUPTOOLS_USE_DISTUTILS'] = 'local'
 
-SKIP_CYTHON_FILE = '__dont_use_cython__.txt'
-
-if os.path.exists(SKIP_CYTHON_FILE):
-    print("In distributed package, building from C files...", file=sys.stderr)
-    SOURCE_EXT = 'c'
-else:
-    try:
-        from Cython.Build import cythonize
-        print("Building from Cython files...", file=sys.stderr)
-        SOURCE_EXT = 'pyx'
-    except ImportError:
-        print("Cython not found, building from C files...",
-              file=sys.stderr)
-        SOURCE_EXT = 'c'
+from setuptools import setup  # noqa: E402
+from setuptools.extension import Extension  # noqa: E402
+from Cython.Build import cythonize  # noqa: E402
 
 
 def get_output(*args, **kwargs):
@@ -38,7 +26,19 @@ def get_output(*args, **kwargs):
 
 # get the compile and link args
 kc = "krb5-config"
+autodetect_kc = True
 posix = os.name != 'nt'
+
+# Per https://docs.python.org/3/library/platform.html#platform.architecture
+# this is the preferred way of determining "64-bitness".
+is64bit = sys.maxsize > 2**32
+
+kc_env = 'GSSAPI_KRB5CONFIG'
+if kc_env in os.environ:
+    kc = os.environ[kc_env]
+    autodetect_kc = False
+    print(f"Using {kc} from env")
+
 link_args, compile_args = [
     shlex.split(os.environ[e], posix=posix) if e in os.environ else None
     for e in ['GSSAPI_LINKER_ARGS', 'GSSAPI_COMPILER_ARGS']
@@ -72,7 +72,7 @@ def get_output(*args, **kwargs):
     except ValueError:
         cygwinccompiler.get_msvcr = lambda *a, **kw: []
 
-if sys.platform.startswith("freebsd"):
+if sys.platform.startswith("freebsd") and autodetect_kc:
     # FreeBSD does $PATH backward, for our purposes.  That is, the package
     # manager's version of the software is in /usr/local, which is in PATH
     # *after* the version in /usr.  We prefer the package manager's version
@@ -97,7 +97,7 @@ def get_output(*args, **kwargs):
         link_args = ['-framework', 'GSS']
     elif winkrb_path:
         _libs = os.path.join(
-            winkrb_path, 'lib', 'amd64' if sys.maxsize > 2 ** 32 else 'i386'
+            winkrb_path, 'lib', 'amd64' if is64bit else 'i386'
         )
         link_args = (
             ['-L%s' % _libs]
@@ -110,12 +110,13 @@ def get_output(*args, **kwargs):
 
 if compile_args is None:
     if osx_has_gss_framework:
-        compile_args = ['-framework', 'GSS', '-DOSX_HAS_GSS_FRAMEWORK']
+        compile_args = ['-DOSX_HAS_GSS_FRAMEWORK']
     elif winkrb_path:
         compile_args = [
             '-I%s' % os.path.join(winkrb_path, 'include'),
-            '-DMS_WIN64'
         ]
+        if is64bit:
+            compile_args.append('-DMS_WIN64')
     elif os.environ.get('MINGW_PREFIX'):
         compile_args = ['-fPIC']
     else:
@@ -155,6 +156,7 @@ def get_output(*args, **kwargs):
 ENABLE_SUPPORT_DETECTION = \
     (os.environ.get('GSSAPI_SUPPORT_DETECT', 'true').lower() == 'true')
 
+wrap_iov_symbol_name = 'gss_wrap_iov'
 if ENABLE_SUPPORT_DETECTION:
     import ctypes.util
 
@@ -162,14 +164,19 @@ def get_output(*args, **kwargs):
     main_path = ""
     if main_lib is None and osx_has_gss_framework:
         main_lib = ctypes.util.find_library('GSS')
+        if not main_lib:
+            # https://github.com/pythongssapi/python-gssapi/issues/235
+            # CPython has a bug on Big Sur where find_library will fail to
+            # find the library path of shared frameworks.  This has been fixed
+            # in newer versions but we have this fallback in case an older
+            # version is still in use.  This fix is expected to be included in
+            # 3.8.8 and 3.9.2.
+            main_lib = '/System/Library/Frameworks/GSS.framework/GSS'
     elif os.environ.get('MINGW_PREFIX'):
         main_lib = os.environ.get('MINGW_PREFIX')+'/bin/libgss-3.dll'
     elif sys.platform == 'msys':
         # Plain msys, not running in MINGW_PREFIX. Try to get the lib from one
-        _main_lib = (
-            '/mingw%d/bin/libgss-3.dll'
-            % (64 if sys.maxsize > 2 ** 32 else 32)
-        )
+        _main_lib = f'/mingw{64 if is64bit else 32}/bin/libgss-3.dll'
         if os.path.exists(_main_lib):
             main_lib = _main_lib
             os.environ['PATH'] += os.pathsep + os.path.dirname(main_lib)
@@ -199,61 +206,13 @@ def get_output(*args, **kwargs):
 
     GSSAPI_LIB = ctypes.CDLL(os.path.join(main_path, main_lib))
 
-
-# add in the flag that causes us not to compile from Cython when
-# installing from an sdist
-class sdist_gssapi(sdist):
-    def run(self):
-        if not self.dry_run:
-            with open(SKIP_CYTHON_FILE, 'w') as flag_file:
-                flag_file.write('COMPILE_FROM_C_ONLY')
-
-            sdist.run(self)
-
-            os.remove(SKIP_CYTHON_FILE)
-
-
-DONT_CYTHONIZE_FOR = ('clean',)
-
-
-class GSSAPIDistribution(Distribution, object):
-    def run_command(self, command):
-        self._last_run_command = command
-        Distribution.run_command(self, command)
-
-    @property
-    def ext_modules(self):
-        if SOURCE_EXT != 'pyx':
-            return getattr(self, '_ext_modules', None)
-
-        if getattr(self, '_ext_modules', None) is None:
-            return None
-
-        if getattr(self, '_last_run_command', None) in DONT_CYTHONIZE_FOR:
-            return self._ext_modules
-
-        if getattr(self, '_cythonized_ext_modules', None) is None:
-            self._cythonized_ext_modules = cythonize(
-                self._ext_modules,
-                language_level=2,
-            )
-
-        return self._cythonized_ext_modules
-
-    @ext_modules.setter
-    def ext_modules(self, mods):
-        self._cythonized_ext_modules = None
-        self._ext_modules = mods
-
-    @ext_modules.deleter
-    def ext_modules(self):
-        del self._ext_modules
-        del self._cythonized_ext_modules
+    if hasattr(GSSAPI_LIB, '__ApplePrivate_gss_wrap_iov'):
+        wrap_iov_symbol_name = '__ApplePrivate_gss_wrap_iov'
 
 
 def make_extension(name_fmt, module, **kwargs):
     """Helper method to remove the repetition in extension declarations."""
-    source = name_fmt.replace('.', '/') % module + '.' + SOURCE_EXT
+    source = name_fmt.replace('.', '/') % module + '.pyx'
     if not os.path.exists(source):
         raise OSError(source)
     return Extension(
@@ -305,7 +264,7 @@ def gssapi_modules(lst):
     # add in any present enum extension files
     res.extend(ENUM_EXTS)
 
-    return res
+    return cythonize(res, language_level=2)
 
 
 long_desc = re.sub(r'\.\. role:: \w+\(code\)\s*\n\s*.+', '',
@@ -319,33 +278,36 @@ def gssapi_modules(lst):
 
 setup(
     name='gssapi',
-    version='1.6.11',
+    version='1.9.0',
     author='The Python GSSAPI Team',
-    author_email='rharwood@redhat.com',
+    author_email='jborean93@gmail.com',
     packages=['gssapi', 'gssapi.raw', 'gssapi.raw._enum_extensions',
               'gssapi.tests'],
+    package_data={
+        "gssapi": ["py.typed"],
+        "gssapi.raw": ["*.pyi"],
+    },
     description='Python GSSAPI Wrapper',
     long_description=long_desc,
     license='LICENSE.txt',
     url="https://github.com/pythongssapi/python-gssapi",
-    python_requires=">=3.6.*",
+    python_requires=">=3.8",
     classifiers=[
         'Development Status :: 5 - Production/Stable',
         'Programming Language :: Python',
         'Programming Language :: Python :: 3',
-        'Programming Language :: Python :: 3.6',
-        'Programming Language :: Python :: 3.7',
         'Programming Language :: Python :: 3.8',
         'Programming Language :: Python :: 3.9',
+        'Programming Language :: Python :: 3.10',
+        'Programming Language :: Python :: 3.11',
+        'Programming Language :: Python :: 3.12',
+        'Programming Language :: Python :: 3.13',
         'Intended Audience :: Developers',
-        'License :: OSI Approved :: ISC License (ISCL)',
         'Programming Language :: Python :: Implementation :: CPython',
         'Programming Language :: Cython',
         'Topic :: Security',
         'Topic :: Software Development :: Libraries :: Python Modules'
     ],
-    distclass=GSSAPIDistribution,
-    cmdclass={'sdist': sdist_gssapi},
     ext_modules=gssapi_modules([
         main_file('misc'),
         main_file('exceptions'),
@@ -364,7 +326,8 @@ def gssapi_modules(lst):
         extension_file('rfc5588', 'gss_store_cred'),
         extension_file('rfc5801', 'gss_inquire_saslname_for_mech'),
         extension_file('cred_imp_exp', 'gss_import_cred'),
-        extension_file('dce', 'gss_wrap_iov'),
+        extension_file('dce', wrap_iov_symbol_name),
+        extension_file('dce_aead', 'gss_wrap_aead'),
         extension_file('iov_mic', 'gss_get_mic_iov'),
         extension_file('ggf', 'gss_inquire_sec_context_by_oid'),
         extension_file('set_cred_opt', 'gss_set_cred_option'),
@@ -376,6 +339,8 @@ def gssapi_modules(lst):
         # see ext_password{,_add}.pyx for more information on this split
         extension_file('password', 'gss_acquire_cred_with_password'),
         extension_file('password_add', 'gss_add_cred_with_password'),
+
+        extension_file('krb5', 'gss_krb5_ccache_name'),
     ]),
     keywords=['gssapi', 'security'],
     install_requires=install_requires
diff --git a/test-requirements.txt b/test-requirements.txt
index 7d800c70..f612f153 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,6 +1,7 @@
+build
 flake8
-nose
 parameterized
-Cython
 k5test
 decorator
+mypy==0.971
+types-decorator
\ No newline at end of file
diff --git a/tox.ini b/tox.ini
deleted file mode 100644
index 30da1a0a..00000000
--- a/tox.ini
+++ /dev/null
@@ -1,15 +0,0 @@
-# Tox (http://tox.testrun.org/) is a tool for running tests
-# in multiple virtualenvs. This configuration file will run the
-# test suite on all supported python versions. To use it, "pip install tox"
-# and then run "tox" from this directory.
-
-[tox]
-envlist = py36,py37,py38
-
-[testenv]
-whitelist_externals=bash
-commands =
-    bash -c "source ./.travis/lib-verify.sh && verify::flake8"
-    python setup.py nosetests []
-
-deps = -r{toxinidir}/test-requirements.txt