{"id":13585326,"url":"https://github.com/inickt/pam_wtid","last_synced_at":"2026-01-18T14:24:57.013Z","repository":{"id":37210963,"uuid":"442856912","full_name":"inickt/pam_wtid","owner":"inickt","description":"Patch for Apple's pam_tid PAM Touch ID module to add sudo watch authentication","archived":false,"fork":false,"pushed_at":"2024-05-26T19:17:58.000Z","size":725,"stargazers_count":63,"open_issues_count":0,"forks_count":3,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-04-07T06:35:45.663Z","etag":null,"topics":["authentication","macos","objdump","pam","pam-module","patch","reverse-engineering","sudo","touchid","watch"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/inickt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-12-29T18:22:44.000Z","updated_at":"2025-04-04T15:56:41.000Z","dependencies_parsed_at":"2024-11-06T03:03:48.949Z","dependency_job_id":"43ab9368-798c-4be5-91cf-f7c3248d28f8","html_url":"https://github.com/inickt/pam_wtid","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/inickt/pam_wtid","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inickt%2Fpam_wtid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inickt%2Fpam_wtid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inickt%2Fpam_wtid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inickt%2Fpam_wtid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/inickt","download_url":"https://codeload.github.com/inickt/pam_wtid/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/inickt%2Fpam_wtid/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28537554,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-18T13:04:05.990Z","status":"ssl_error","status_checked_at":"2026-01-18T13:01:44.092Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","macos","objdump","pam","pam-module","patch","reverse-engineering","sudo","touchid","watch"],"created_at":"2024-08-01T15:04:52.563Z","updated_at":"2026-01-18T14:24:57.006Z","avatar_url":"https://github.com/inickt.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"# pam_wtid\n\n`pam_wtid` is a patching utility used to add support to Apple's `pam_tid` PAM module for watch authentication in addition to Touch ID. If you have a watch paired to your Mac or fingerprints enrolled for unlocking, you can use this module to authenticate sudo.\n\n## Screenshot\n\n![Preview Image](preview.png)\n\n## Install\n\n`make enable` will patch the exisitng `pam_tid` binary on your system (found in `/usr/lib/pam/`), and place a modified version of `pam_wtid` in `/usr/local/lib/pam`. It will also modify `/etc/pam.d/sudo` to enable the PAM module.\n\n`make disable` will remove the patched binary and revert the changes in `/etc/pam.d/sudo`.\n\n## Why\n\nThere are already a lot of really cool, open source PAM modules/utilities that add Tocuh ID and watch authentication to `sudo`. To name a few:\n\n- [mattrajca/sudo-touchid](https://github.com/mattrajca/sudo-touchid) – A `sudo` fork that adds Touch ID support\n- [artginzburg/sudo-touchid](https://github.com/artginzburg/sudo-touchid) – Utility to automatically install Apple's native `pam_tid` PAM module\n- [Reflejo/pam-touchID](https://github.com/Reflejo/pam-touchID) – A Swift PAM module for adding Touch ID support\n- [biscuitehh/pam-watchid](https://github.com/biscuitehh/pam-watchid) - A Swift PAM module for adding watch unlocking support (forked from the above)\n- Apple's own [pam_tid](https://github.com/apple-oss-distributions/pam_modules/tree/main/modules/pam_tid), available in `/usr/lib/pam/pam_tid.so.2` for Touch ID authentication\n\nWhen my computer is docked in clamshell mode, I can only use watch unlocking. But if I am on the go, I want to use Touch ID over watch unlocking since it is usually quicker. I used to use `pam-watchid`, but on my new 2021 16\" MacBook Pro only watch support works. Doing a little debugging, it seems that the context/sandbox in which the PAM module is called does not correctly link back to the current user, and I was seeing error messages from the `LAPolicy` framework that no fingerprints are enrolled.\n\n## How\n\nApple's [Local Authentication framework](https://developer.apple.com/documentation/localauthentication) gives an API to authenticate users through different `LAPolicy`s. This is how all of programs above work. \n\nFor our purposes, there are only 2 important policies used to determine how a user authenticated:\n\n[`LAPolicy.deviceOwnerAuthenticationWithBiometrics = 1`](https://developer.apple.com/documentation/localauthentication/lapolicy/deviceownerauthenticationwithbiometrics)\n\u003e User authentication with biometry\n\n[`LAPolicy.deviceOwnerAuthenticationWithBiometricsOrWatch = 4`](https://developer.apple.com/documentation/localauthentication/lapolicy/deviceownerauthenticationwithbiometricsorwatch)\n\u003e User authentication with either biometry or Apple Watch.\n\n`pam-watchid` forks `pam-touchID` to switch the policy from `.deviceOwnerAuthenticationWithBiometrics` to `.deviceOwnerAuthenticationWithBiometricsOrWatch`. I am unsure why the biometrics and watch policy fails to make Touch ID work on my new machine, because when the policy is only biometrics, Touch ID works as intended. I was able to reporduce this in a sample project when running the executable as `root`. I able to use Touch ID on its own, but after adding the watch option to the policy it failed to work. I will be updating this repo with the sample project and a link to a Radar since this seems like an OS bug.\n\n## Patching\n\nSince the current Swift implementations had issues, I decided to see if Apple's own `pam_tid` had a similar problem. When reading through the [source](https://github.com/apple-oss-distributions/pam_modules/blob/main/modules/pam_tid/pam_tid.c), I noticed some interesting code that attempts to determine the user authenticating and whether or not they are in an Aqua session. I figured this is enough of a reason to try and recompile the PAM module with watch support added.\n\nI unfortunately was unable to compile the project, due to it depending on some internal headers I was not able to patch out easily. I also noticed that the call to `LAEvaluatePolicy` has an extra `options` dictionary argument, which is not available in the [public API](https://developer.apple.com/documentation/localauthentication/lacontext/1514176-evaluatepolicy).\n\n```c\noptions = CFDictionaryCreateMutable(kCFAllocatorDefault, 2, \u0026kCFTypeDictionaryKeyCallBacks, \u0026kCFTypeDictionaryValueCallBacks);\nCFDictionarySetValue(options, key, value);\nCFDictionarySetValue(options, key2, value2);\n\ncontext = LACreateNewContextWithACMContext(NULL, \u0026error);\nif (!context) {\n    os_log_error(PAM_LOG, \"unable to create context.\");\n    retval = PAM_AUTH_ERR;\n    goto cleanup;\n}\n\n/* evaluate policy */\nif (!LAEvaluatePolicy(context, kLAPolicyDeviceOwnerAuthenticationWithBiometrics, options, \u0026error)) {\n    // error is intended as failure means Touch ID is not usable which is in fact not an error but the state we need to handle\n    if (CFErrorGetCode(error) != kLAErrorNotInteractive) {\n        os_log_debug(PAM_LOG, \"policy evaluation failed: %ld\", CFErrorGetCode(error));\n        retval = PAM_AUTH_ERR;\n        goto cleanup;\n    }\n}\n```\n\nThe `options` dictionary is filled with a few keys that are not publically documented, but it does have a refrence to the user's ID. If this fixed the issue I saw above, then watch support should be simple enough to add.\n\nSince I couldn't compile this code easily and didn't particularly want to require it in Objective-C/Swift and attempt to call into the private API, I decided to try and figure out a binary patch. I only need to change the 2nd argument in the call from a 1 to a 4 to match the enumeration above, and thought that should be simple enoguh. \n\nIn the code, `LAEvaluatePolicy` is only called once. Using `objtool` we can dissassemble the `pam_tid` binary and search for this call:\n\n```\n❯❯❯ objdump --macho -d /usr/lib/pam/pam_tid.so.2 | grep \"_LAEvaluatePolicy$\" -B 8       \n    4e3d:    e8 a5 0f 00 00     callq   _LACreateNewContextWithACMContext\n    4e42:    48 85 c0           testq   %rax, %rax\n    4e45:    0f 84 34 01 00 00  je      0x4f7f\n    4e4b:    48 8d 4d c8        leaq    -56(%rbp), %rcx\n    4e4f:    48 89 45 98        movq    %rax, -104(%rbp)\n    4e53:    48 89 c7           movq    %rax, %rdi\n    4e56:    be 01 00 00 00     movl    $1, %esi\n    4e5b:    4c 89 e2           movq    %r12, %rdx\n    4e5e:    e8 50 0b 00 00     callq   _LAEvaluatePolicy\n--\n    489c:    a1 83 01 d1        sub     x1, x29, #96\n    48a0:    00 00 80 d2        mov     x0, #0\n    48a4:    2e 04 00 94        bl      _LACreateNewContextWithACMContext\n    48a8:    60 08 00 b4        cbz     x0, 0x49b4\n    48ac:    f9 03 00 aa        mov     x25, x0\n    48b0:    a3 83 01 d1        sub     x3, x29, #96\n    48b4:    21 00 80 52        mov     w1, #1\n    48b8:    e2 03 18 aa        mov     x2, x24\n    48bc:    f3 02 00 94        bl      _LAEvaluatePolicy\n```\n\nSure enough we get 2 results, one for the `x86_64` slice and the other for the `arm64e` slice in the fat binary (used for universal support on Appple Silicon). We can see the registers `esi` and `w1` (the second arguments in the `x86_64`/`arm64e` calling conventions) are set to `0x1`.\n\nAt this point we just need to replace the corresponding bytes for the argument instruction with the updated `LAPolicy` value. \n\nFor `x86_64`:\n\n```\nbe 01 00 00 00      ;; movl    $1, %esi\n;; should be replaced with\nbe 04 00 00 00      ;; movl    $4, %esi\n```\n\nFor `arm64e`:\n\n```\n21 00 80 52         ;; mov    w1, #1\n;; should be replaced with\n81 00 80 52         ;; mov    w1, #4\n```\n\nThese can be verified using an online assembler. \n\nAfter we know what to replace, it was trivial to write a Python script to find the bytes of the call to `_LAEvaluatePolicy` for both `x86_64` and `arm64e`, and then backtrack to find the most recent instruction that set the 2nd argument to `0x1` and replace it with one that sets it to `0x4`. This has the benefit of being slightly flexible if Apple recompiles the binary with a different compiler/small code changes. I found the binaries differed slightly between my machione on Big Sur versus my new one on Monterey so this method was better than simply doing a full find/replace on this code snippit.\n\nThe only other thing worth noting is we have to re-sign the library with an adhoc signature using `codesign --force -s - pam_wtid.so`, otherwise the system kills our module since its code signature was modified. Makes sense, but I thought this wasn't going to work the first time I tested out my patch!\n\n## Thoughts\n\nThis was a really fun project to hack on. Compilers/systems were some of my favorite classes in undergrad, so it was fun to try and use some of that knowledge to work backwards and write a pretty straightforward patch. Is this better than theoretically fixing the open source versions? Maybe? We are using something Apple's engineers wrote, but it is in a memory unsafe language instead of Swift ;). That being said, the harm of using this is pretty low and I know I will have it running on my machines for the mild convenience it adds, even if this project took more time than me typing in my password into `sudo` for 50 years.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finickt%2Fpam_wtid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Finickt%2Fpam_wtid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Finickt%2Fpam_wtid/lists"}