├── .dockerignore ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── PULL_REQUEST_TEMPLATE.md ├── SECURITY.md ├── dependabot.yml └── workflows │ ├── build.yml │ └── fixtures │ ├── 7D851EB72D73BDA0.key │ ├── 7D851EB72D73BDA0.pass │ ├── generate.sh │ ├── gpg-agent.conf │ └── gpg.conf ├── .gitignore ├── .golangci.yml ├── Dockerfile ├── LICENSE ├── MAINTAINERS ├── Makefile ├── README.md ├── client ├── client.go ├── client_test.go └── command.go ├── credentials ├── credentials.go ├── credentials_test.go ├── error.go ├── helper.go └── version.go ├── deb ├── Dockerfile ├── build-deb └── debian │ ├── compat │ ├── control │ ├── docker-credential-pass.install │ ├── docker-credential-secretservice.install │ └── rules ├── docker-bake.hcl ├── go.mod ├── go.sum ├── hack ├── git-meta └── release ├── osxkeychain ├── cmd │ └── main.go ├── osxkeychain.go └── osxkeychain_test.go ├── pass ├── cmd │ └── main.go ├── pass.go └── pass_test.go ├── registryurl ├── parse.go └── parse_test.go ├── secretservice ├── cmd │ └── main.go ├── secretservice.c ├── secretservice.go ├── secretservice.h └── secretservice_test.go ├── vendor ├── github.com │ ├── danieljoos │ │ └── wincred │ │ │ ├── .gitattributes │ │ │ ├── .gitignore │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── conversion.go │ │ │ ├── conversion_unsupported.go │ │ │ ├── sys.go │ │ │ ├── sys_unsupported.go │ │ │ ├── types.go │ │ │ └── wincred.go │ └── keybase │ │ └── go-keychain │ │ ├── .gitignore │ │ ├── .golangci.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── corefoundation.go │ │ ├── datetime.go │ │ ├── ios.go │ │ ├── keychain.go │ │ ├── macos.go │ │ └── util.go ├── golang.org │ └── x │ │ └── sys │ │ ├── LICENSE │ │ ├── PATENTS │ │ └── windows │ │ ├── aliases.go │ │ ├── dll_windows.go │ │ ├── env_windows.go │ │ ├── eventlog.go │ │ ├── exec_windows.go │ │ ├── memory_windows.go │ │ ├── mkerrors.bash │ │ ├── mkknownfolderids.bash │ │ ├── mksyscall.go │ │ ├── race.go │ │ ├── race0.go │ │ ├── security_windows.go │ │ ├── service.go │ │ ├── setupapi_windows.go │ │ ├── str.go │ │ ├── syscall.go │ │ ├── syscall_windows.go │ │ ├── types_windows.go │ │ ├── types_windows_386.go │ │ ├── types_windows_amd64.go │ │ ├── types_windows_arm.go │ │ ├── types_windows_arm64.go │ │ ├── zerrors_windows.go │ │ ├── zknownfolderids_windows.go │ │ └── zsyscall_windows.go └── modules.txt └── wincred ├── cmd └── main.go ├── wincred.go └── wincred_test.go /.dockerignore: -------------------------------------------------------------------------------- 1 | /bin 2 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of conduct 2 | 3 | - [Moby community guidelines](https://github.com/moby/moby/blob/master/CONTRIBUTING.md#moby-community-guidelines) 4 | - [Docker Code of Conduct](https://github.com/docker/code-of-conduct) 5 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 15 | 16 | **- What I did** 17 | 18 | **- How I did it** 19 | 20 | **- How to verify it** 21 | 22 | **- Description for the changelog** 23 | 27 | 28 | 29 | **- A picture of a cute animal (not mandatory but encouraged)** 30 | 31 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting security issues 2 | 3 | The project maintainers take security seriously. If you discover a security 4 | issue, please bring it to their attention right away! 5 | 6 | **Please _DO NOT_ file a public issue**, instead send your report privately to 7 | [security@docker.com](mailto:security@docker.com). 8 | 9 | Security reports are greatly appreciated, and we will publicly thank you for it. 10 | We also like to send gifts—if you're into schwag, make sure to let 11 | us know. We currently do not offer a paid security bounty program, but are not 12 | ruling it out in the future. 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | open-pull-requests-limit: 10 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | labels: 9 | - "dependencies" 10 | - "bot" 11 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | workflow_dispatch: 9 | push: 10 | branches: 11 | - 'master' 12 | tags: 13 | - 'v*' 14 | pull_request: 15 | 16 | env: 17 | DESTDIR: ./bin 18 | GO_VERSION: 1.23.6 19 | 20 | jobs: 21 | validate: 22 | runs-on: ubuntu-22.04 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | target: 27 | - lint 28 | - validate-vendor 29 | steps: 30 | - 31 | name: Checkout 32 | uses: actions/checkout@v4 33 | - 34 | name: Set up Docker Buildx 35 | uses: docker/setup-buildx-action@v3 36 | - 37 | name: Run 38 | run: | 39 | make ${{ matrix.target }} 40 | 41 | test: 42 | runs-on: ${{ matrix.os }} 43 | strategy: 44 | fail-fast: false 45 | matrix: 46 | os: 47 | - ubuntu-24.04 48 | - ubuntu-22.04 49 | - macOS-15 50 | - macOS-14 51 | - macOS-13 52 | - windows-2022 53 | steps: 54 | - 55 | name: Checkout 56 | uses: actions/checkout@v4 57 | - 58 | name: Set up Go 59 | uses: actions/setup-go@v5 60 | with: 61 | go-version: ${{ env.GO_VERSION }} 62 | - 63 | name: Install deps (ubuntu) 64 | if: startsWith(matrix.os, 'ubuntu-') 65 | run: | 66 | sudo apt-get update 67 | sudo apt-get install -y dbus-x11 gnome-keyring libsecret-1-dev pass 68 | - 69 | name: Install deps (macOS) 70 | if: startsWith(matrix.os, 'macOS-') 71 | run: | 72 | brew install pass 73 | - 74 | name: GPG conf 75 | if: ${{ !startsWith(matrix.os, 'windows-') }} 76 | uses: actions/github-script@v7 77 | id: gpg 78 | with: 79 | script: | 80 | const fs = require('fs'); 81 | const gnupgfolder = `${require('os').homedir()}/.gnupg`; 82 | if (!fs.existsSync(gnupgfolder)){ 83 | fs.mkdirSync(gnupgfolder); 84 | } 85 | fs.copyFile('.github/workflows/fixtures/gpg.conf', `${gnupgfolder}/gpg.conf`, (err) => { 86 | if (err) throw err; 87 | }); 88 | core.setOutput('key', fs.readFileSync('.github/workflows/fixtures/7D851EB72D73BDA0.key', {encoding: 'utf8'})); 89 | core.setOutput('passphrase', fs.readFileSync('.github/workflows/fixtures/7D851EB72D73BDA0.pass', {encoding: 'utf8'})); 90 | - 91 | name: Import GPG key 92 | if: ${{ !startsWith(matrix.os, 'windows-') }} 93 | uses: crazy-max/ghaction-import-gpg@v6 94 | with: 95 | gpg_private_key: ${{ steps.gpg.outputs.key }} 96 | passphrase: ${{ steps.gpg.outputs.passphrase }} 97 | trust_level: 5 98 | - 99 | name: Init pass 100 | if: ${{ !startsWith(matrix.os, 'windows-') }} 101 | run: | 102 | pass init 7D851EB72D73BDA0 103 | shell: bash 104 | - 105 | name: Test 106 | run: | 107 | make test COVERAGEDIR=${{ env.DESTDIR }} 108 | shell: bash 109 | - 110 | name: Upload coverage 111 | uses: codecov/codecov-action@v5 112 | with: 113 | files: ${{ env.DESTDIR }}/coverage.txt 114 | token: ${{ secrets.CODECOV_TOKEN }} 115 | 116 | test-sandboxed: 117 | runs-on: ubuntu-22.04 118 | steps: 119 | - 120 | name: Set up Docker Buildx 121 | uses: docker/setup-buildx-action@v3 122 | - 123 | name: Test 124 | uses: docker/bake-action@v6 125 | with: 126 | targets: test 127 | set: | 128 | *.cache-from=type=gha,scope=test 129 | *.cache-to=type=gha,scope=test,mode=max 130 | - 131 | name: Upload coverage 132 | uses: codecov/codecov-action@v5 133 | with: 134 | files: ${{ env.DESTDIR }}//coverage.txt 135 | token: ${{ secrets.CODECOV_TOKEN }} 136 | 137 | build: 138 | runs-on: ubuntu-22.04 139 | steps: 140 | - 141 | name: Checkout 142 | uses: actions/checkout@v4 143 | with: 144 | fetch-depth: 0 145 | - 146 | name: Set up QEMU 147 | uses: docker/setup-qemu-action@v3 148 | - 149 | name: Set up Docker Buildx 150 | uses: docker/setup-buildx-action@v3 151 | - 152 | name: Build 153 | run: | 154 | make release 155 | env: 156 | CACHE_FROM: type=gha,scope=build 157 | CACHE_TO: type=gha,scope=build,mode=max 158 | - 159 | name: List artifacts 160 | run: | 161 | tree -nh ${{ env.DESTDIR }} 162 | - 163 | name: Check artifacts 164 | run: | 165 | find ${{ env.DESTDIR }} -type f -exec file -e ascii -e text -- {} + 166 | - 167 | name: Upload artifacts 168 | uses: actions/upload-artifact@v4 169 | with: 170 | name: docker-credential-helpers 171 | path: ${{ env.DESTDIR }}/* 172 | if-no-files-found: error 173 | - 174 | name: GitHub Release 175 | if: startsWith(github.ref, 'refs/tags/v') 176 | uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1 177 | env: 178 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 179 | with: 180 | draft: true 181 | files: ${{ env.DESTDIR }}/* 182 | 183 | build-deb: 184 | runs-on: ubuntu-22.04 185 | steps: 186 | - 187 | name: Checkout 188 | uses: actions/checkout@v4 189 | with: 190 | fetch-depth: 0 191 | - 192 | name: Build 193 | run: | 194 | make deb 195 | -------------------------------------------------------------------------------- /.github/workflows/fixtures/7D851EB72D73BDA0.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PRIVATE KEY BLOCK----- 2 | 3 | lQdGBF6tzaABEACjFbX7PFEG6vDPN2MPyxYW7/3o/sonORj4HXUFjFxxJxktJ3x3 4 | N1ayHPJ1lqIeoiY7jVbq0ZdEVGkd3YsKG9ZMdZkzGzY6PQPC/+M8OnzOiOPwUdWc 5 | +Tdhh115LvVz0MMKYiab6Sn9cgxj9On3LCQKpjvMDpPo9Ttf6v2GQIw8h2ACvdzQ 6 | 71LtIELS/I+dLbfZiwpUu2fhQT13EJkEnYMOYwM5jNUd66P9itUc7MrOWjkicrKP 7 | oF1dQaCM+tuKuxvD8WLdiwU5x60NoGkJHHUehKQXl2dVzjpqEqHKEBJt9tfJ9lpE 8 | YIisgwB8o3pes0fgCehjW2zI95/o9+ayJ6nl4g5+mSvWRXEu66h71nwM0Yuvquk8 9 | 3me7qhYfDrDdCwcxS5BS1hwakTgUQLD99FZjbx1j8sq96I65O0GRdyU2PR8KIjwu 10 | JrkTH4ZlKxK3FQghUhFoA5GkiDb+eClmRMSni5qg+81T4XChmUkEprA3eWCHL+Ma 11 | xRNNxLS+r6hH9HG5JBxpV3iaTI9HHpnQKhEeaLXqsUTDZliN9hP7Ywo8bpUB8j2d 12 | oWYwDV4dPyMKr6Fb8RDCh2q5gJGbVp8w/NmmBTeL+IP2fFggJkRfyumv3Ul7x66L 13 | tBFQ4rYo4JUUrGweSTneG6REIgxH66hIrNl6Vo/D1ZyknTe1dMOu/BTkkQARAQAB 14 | /gcDAqra8KO+h3bfyu90vxTL1ro4x/x9il7VBcWlIR4cBP7Imgxv+T4hwPIu8P1x 15 | lOlxLNWegFOV0idoTy1o3VLLBev/F+IlspX4A+2XEIddR6nZnKFi0Lv2L4TKgE9E 16 | VJJTszmviDIRLMLN9dWzDfA8hj5tR5Inot92CHRF414AS22JHvlhbFSLQnjqsN+C 17 | n1cQpNOJhkxsSfZsxjnFa/70y/u8v0o8mzyLZmk9HpzRHGzoz8IfpLp8OTqBR9u6 18 | zzoKLy16zZO55OKbj7h8uVZvDUq9l8iDICpqWMdZqBJIl56MBexYKgYxh3YO/8v2 19 | oXli+8Xuaq5QLiCN3yT7IbKoYzplnFfaJwFiMh7R1iPLXaYAZ0qdRijlbtseTK1m 20 | oHNkwUbxVzjkh4LfE8UpmMwZn5ZjWni3230SoiXuKy0OHkGvwGvWWAL1mEuoYuUI 21 | mFMcH5MnixP8oQYZKDj2IR/yEeOpdU6B/tr3Tk1NidLf7pUMqG7Ff1NU6dAUeBpa 22 | 9xahITMjHvrhgMISY4IYZep5cEnVw8lQTpUJtW/ePMzrFhu3sA7oNdj9joW/VMfz 23 | H7MHwwavtICsYqoqV3lnjX4EC9dW6o8PTUg2u956dmtK7KAyUK/+w2aLNGT28ChN 24 | jhRYHvHzB9Kw5asqI/lTM49eqslBqYQMTTjdBphkYuSZQzNMf291j/ZmoLhD1A1a 25 | S8tUnNygKV4D1cJYgSXfzhFoU8ib/0SPo+KqQ+CzGS+wxXg6WNBA6wepTjpnVVx3 26 | 4JADP8IJcDC3P0iwAreWjSy15F1cvemFFB0SLNUkyZGzsxtKzbM1+8khl68+eazC 27 | LzRj0rxfIF5znWjX1QFhKxCk6eF0IWDY0+b3DBkmChME9YDXJ3TthcqA7JgcX4JI 28 | M4/wdqhgerJYOmj+i2Q0M+Bu02icOJYMwTMMsDVl7XGHkaCuRgZ54eZAUH7JFwUm 29 | 1Ct3tcaqiTMmz0ngHVqBTauzgqKDvzwdVqdfg05H364nJMay/3omR6GayIb5CwSo 30 | xdNVwG3myPPradT9MP09mDr4ys2zcnQmCkvTVBF6cMZ1Eh6PQQ8CyQWv0zkaBnqj 31 | JrM1hRpgW4ZlRosSIjCaaJjolN5QDcXBM9TbW9ww+ZYstazN2bV1ZQ7BEjlHQPa1 32 | BhzMsvqkbETHsIpDNF52gZKn3Q9eIX05BeadzpHUb5/XOheIHVIdhSaTlgl/qQW5 33 | hQgPGSzSV6KhXEY7aevTdvOgq++WiELkjfz2f2lQFesTjFoQWEvxVDUmLxHtEhaN 34 | DOuh4H3mX5Opn3pLQmqWVhJTbFdx+g5qQd0NCW4mDaTFWTRLFLZQsSJxDSeg9xrY 35 | gmaii8NhMZRwquADW+6iU6KfraBhngi7HRz4TfqPr9ma/KUY464cqim1fnwXejyx 36 | jsb5YHR9R66i+F6P/ysF5w+QuVdDt1fnf9GLay0r6qxpA8ft2vGPcDs4806Huj+7 37 | Aq5VeJaNkCuh3GR3xVnCFAz/7AtkO6xKuZm8B3q904UuMdSmkhWbaobIuF/B2B6S 38 | eawIXQHEOplK3ic26d8Ckf4gbjeORfELcMAEi5nGXpTThCdmxQApCLxAYYnTfQT1 39 | xhlDwT9xPEabo98mIwJJsAU5VsTDYW+qfo4qIx8gYoSKc9Xu3yVh3n+9k43Gcm5V 40 | 9lvK1slijf+TzODZt/jsmkF8mPjXyP5KOI+xQp/m4PxW3pp57YrYj/Rnwga+8DKX 41 | jMsW7mLAAZ/e+PY6z/s3x1Krfk+Bb5Ph4mI0zjw5weQdtyEToRgveda0GEpvZSBU 42 | ZXN0ZXIgPGpvZUBmb28uYmFyPokCNgQQAQgAIAUCXq3NoAYLCQcIAwIEFQgKAgQW 43 | AgEAAhkBAhsDAh4BAAoJEH2FHrctc72gxtQP/AulaClIcn/kDt43mhYnyLglPfbo 44 | AqPlU26chXolBg0Wo0frFY3aIs5SrcWEf8aR4XLwCFGyi3vya0CUxjghN5tZBYqo 45 | vswbT00zP3ohxxlJFCRRR9bc7OZXCgTddtfVf6EKrUAzIkbWyAhaJnwJy/1UGpSw 46 | SEO/KpastrVKf3sv1wqOeFQ4DFyjaNda+xv3dVWS8db7KogqJiPFZXrQK3FKVIxS 47 | fxRSmKaYN7//d+xwVAEY++RrnL/o8B2kV6N68cCpQWJELyYnJzis9LBcWd/3wiYh 48 | efTyY+ePKUjcB+kEZnyJfLc7C2hll2e7UJ0fxv+k8vHReRhrNWmGRXsjNRxiw3U0 49 | hfvxD/C8nyqAbeTHp4XDX78Tc3XCysAqIYboIL+RyewDMjjLj5vzUYAdUdtyNaD7 50 | C6M2R6pN1GAt52CJmC/Z6F7W7GFGoYOdEkVdMQDsjCwScyEUNlGj9Zagw5M2EgSe 51 | 6gaHgMgTzsMzCc4W6WV5RcS55cfDNOXtxPsMJTt4FmXrjl11prBzpMfpU5a9zxDZ 52 | oi54ZZ8VPE6jsT4Lzw3sni3c83wm28ArM20AzZ1vh7fk3Sfd0u4Yaz7s9JlEm5+D 53 | 34tEyli28+QjCQc18EfQUiJqiYEJRxJXJ3esvMHfYi45pV/Eh5DgRW1305fUJV/6 54 | +rGpg0NejsHoZdZPnQdGBF6tzaABEAC4mVXTkVk6Kdfa4r5zlzsoIrR27laUlMkb 55 | OBMt+aokqS+BEbmTnMg6xIAmcUT5uvGAc8S/WhrPoYfc15fTUyHIz8ZbDoAg0LO6 56 | 0Io4VkAvNJNEnsSV9VdLBh/XYlc4K49JqKyWTL4/FJFAGbsmHY3b+QU90AS6FYRv 57 | KeBAoiyebrjx0vmzb8E8h3xthVLN+AfMlR1ickY62zvnpkbncSMY/skur1D2KfbF 58 | 3sFprty2pEtjFcyB5+18l2IyyHGOlEUw1PZdOAV4/Myh1EZRgYBPs80lYTJALCVF 59 | IdOakH33WJCImtNZB0AbDTABG+JtMjQGscOa0qzf1Y/7tlhgCrynBBdaIJTx95TD 60 | 21BUHcHOu5yTIS6Ulysxfkv611+BiOKHgdq7DVGP78VuzA7bCjlP1+vHqIt3cnIa 61 | t2tEyuZ/XF4uc3/i4g0uP9r7AmtET7Z6SKECWjpVv+UEgLx5Cv+ql+LSKYQMvU9a 62 | i3B1F9fatn3FSLVYrL4aRxu4TSw9POb0/lgDNmN3lGQOsjGCZPibkHjgPEVxKuiq 63 | 9Oi38/VTQ0ZKAmHwBTq1WTZIrPrCW0/YMQ6yIJZulwQ9Yx1cgzYzEfg04fPXlXMi 64 | vkvNpKbYIICzqj0/DVztz9wgpW6mnd0A2VX2dqbMM0fJUCHA6pj8AvXY4R+9Q4rj 65 | eWRK9ycInQARAQAB/gcDApjt7biRO0PEyrrAiUwDMsJL4/CVMu11qUWEPjKe2Grh 66 | ZTW3N+m3neKPRULu+LUtndUcEdVWUCoDzAJ7MwihZtV5vKST/5Scd2inonOaJqoA 67 | nS3wnEMN/Sc93HAZiZnFx3NKjQVNCwbuEs45mXkkcjLm2iadrTL8fL4acsu5IsvD 68 | LbDwVOPeNnHKl6Hr20e39fK0FuJEyH49JM6U3B1/8385sJB8+E24+hvSF81aMddh 69 | Ne4Bc3ZYiYaKxe1quPNKC0CQhAZiT7LsMfkInXr0hY1I+kISNXEJ1dPYOEWiv0Ze 70 | jD5Pupn34okKNEeBCx+dK8BmUCi6Jgs7McUA7hN0D/YUS++5fuR55UQq2j8Ui0tS 71 | P8GDr86upH3PgEL0STh9fYfJ7TesxurwonWjlmmT62Myl4Pr+RmpS6PXOnhtcADm 72 | eGLpzhTveFj4JBLMpyYHgBTqcs12zfprATOpsI/89kmQoGCZpG6+AbfSHqNNPdy2 73 | eqUCBhOZlIIda1z/cexmU3f/gBqyflFf8fkvmlO4AvI8aMH3OpgHdWnzh+AB51xj 74 | kmdD/oWel9v7Dz4HoZUfwFaLZ0fE3P9voD8e+sCwqQwVqRY4L/BOYPD5noVOKgOj 75 | ABNKu5uKrobj6rFUi6DTUCjFGcmoF1Sc06xFNaagUNggRbmlC/dz22RWdDUYv5ra 76 | N6TxIDkGC0cK6ujyK0nes3DN0aHjgwWuMXDYkN3UckiebI4Cv/eF9jvUKOSiIcy1 77 | RtxdazZS4dYg2LBMeJKVkPi5elsNyw2812nEY3du/nEkQYXfYgWOF27OR+g4Y9Yw 78 | 1BiqJ1TTjbQnd/khOCrrbzDH1mw00+1XVsT6wjObuYqqxPPS87UrqmMf6OdoYfPm 79 | zEOnNLBnsJ5VQM3A3pcT40RfdBrZRO8LjGhzKTreyq3C+jz0RLa5HNE8GgOhGyck 80 | ME4h+RhXlE8KGM+tTo6PA1NJSrEt+8kZzxjP4rIEn0aVthCkNXK12inuXtnHm0ao 81 | iLUlQOsfPFEnzl0TUPd7+z7j/wB+XiKU/AyEUuB0mvdxdKtqXvajahOyhLjzHQhz 82 | ZnNlgANGtiqcSoJmkJ8yAvhrtQX51fQLftxbArRW1RYk/5l+Gy3azR+gUC17M6JN 83 | jrUYxn0zlAxDGFH7gACHUONwVekcuEffHzgu2lk7MyO1Y+lPnwabqjG0eWWHuU00 84 | hskJlXyhj7DeR12bwjYkyyjG62GvOH02g3OMvUgNGH+K321Dz539csCh/xwtg7Wt 85 | U3YAphU7htQ1dPDfk1IRs7DQo2L+ZTE57vmL5m0l6fTataEWBPUXkygfQFUJOM6Q 86 | yY76UEZww1OSDujNeY171NSTzXCVkUeAdAMXgjaHXWLK2QUQUoXbYX/Kr7Vvt9Fu 87 | Jh6eGjjp7dSjQ9+DW8CAB8vxd93gsQQGWYjmGu8khkEmx6OdZhmSbDbe915LQTb9 88 | sPhk2s5/Szsvr5W2JJ2321JI6KXBJMZvPC5jEBWmRzOYkRd2vloft+CSMfXF+Zfd 89 | nYtc6R3dvb9vcjo+a9wFtfcoDsO0MaPSM+9GB25MamdatmGX6iLOy9Re1UABwUi/ 90 | VhTWNkP5uzqx0sDwHEIa2rYOwxpIZDwwjM3oOASCW1DDBQ0BI9KNjfIeL3ubx2mS 91 | 2x8hFU9qSK4umoDNbzOqGPSlkdbiPcNjF2ZcSN1qQZiYdwLL5dw6APNyBVjxTN1J 92 | gkCdJ/HwAY+r93Lbl5g8gz8d0vJEyfn//34sn9u+toSTw55GcG9Ks1kSKIeDNh0h 93 | MiPm3HmJAh8EGAEIAAkFAl6tzaACGwwACgkQfYUety1zvaBV9hAAgliX36pXJ59g 94 | 3I9/4R68e/fGg0FMM6D+01yCeiKApOYRrJ0cYKn7ITDYmHhlGGpBAie90UsqX12h 95 | hdLP7LoQx7sjTyzQt6JmpA8krIwi2ON7FKBkdYb8IYx4mE/5vKnYT4/SFnwTmnZY 96 | +m+NzK2U/qmhq8JyO8gozdAKJUcgz49IVv2Ij0tQ4qaPbyPwQxIDyKnT758nJhB1 97 | jTqo+oWtER8q3okzIlqcArqn5rDaNJx+DRYL4E/IddyHQAiUWUka8usIUqeW5reu 98 | zoPUE2CCfOJSGArkqHQQqMx0WEzjQTwAPaHrQbera4SbiV/o4CLCV/u5p1Qnig+Q 99 | iUsakmlD299t//125LIQEa5qzd9hRC7u1uJS7VdW8eGIEcZ0/XT/sr+z23z0kpZH 100 | D3dXPX0BwM4IP9xu31CNg10x0rKwjbxy8VaskFEelpqpu+gpAnxqMd1evpeUHcOd 101 | r5RgPgkNFfba9Nbxf7uEX+HOmsOM+kdtSmdGIvsBZjVnW31nnoDMp49jG4OynjrH 102 | cRuoM9sxdr6UDqb22CZ3/e0YN4UaZM3YDWMVaP/QBVgvIFcdByqNWezpd9T4ZUII 103 | MZlaV1uRnHg6B/zTzhIdMM80AXz6Uv6kw4S+Lt7HlbrnMT7uKLuvzH7cle0hcIUa 104 | PejgXO0uIRolYQ3sz2tMGhx1MfBqH64= 105 | =WbwB 106 | -----END PGP PRIVATE KEY BLOCK----- -------------------------------------------------------------------------------- /.github/workflows/fixtures/7D851EB72D73BDA0.pass: -------------------------------------------------------------------------------- 1 | with stupid passphrase -------------------------------------------------------------------------------- /.github/workflows/fixtures/generate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | set -ex 3 | 4 | gpg --batch --gen-key <<-EOF 5 | %echo Generating a standard key 6 | Key-Type: DSA 7 | Key-Length: 1024 8 | Subkey-Type: ELG-E 9 | Subkey-Length: 1024 10 | Name-Real: Meshuggah Rocks 11 | Name-Email: meshuggah@example.com 12 | Expire-Date: 0 13 | # Do a commit here, so that we can later print "done" :-) 14 | %commit 15 | %echo done 16 | EOF 17 | -------------------------------------------------------------------------------- /.github/workflows/fixtures/gpg-agent.conf: -------------------------------------------------------------------------------- 1 | default-cache-ttl 21600 2 | max-cache-ttl 31536000 3 | allow-preset-passphrase -------------------------------------------------------------------------------- /.github/workflows/fixtures/gpg.conf: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # GnuPG Options 3 | 4 | # (OpenPGP-Configuration-Options) 5 | # Assume that command line arguments are given as UTF8 strings. 6 | utf8-strings 7 | 8 | # (OpenPGP-Protocol-Options) 9 | # Set the list of personal digest/cipher/compression preferences. This allows 10 | # the user to safely override the algorithm chosen by the recipient key 11 | # preferences, as GPG will only select an algorithm that is usable by all 12 | # recipients. 13 | personal-digest-preferences SHA512 SHA384 SHA256 SHA224 14 | personal-cipher-preferences AES256 AES192 AES CAST5 CAMELLIA192 BLOWFISH TWOFISH CAMELLIA128 3DES 15 | personal-compress-preferences ZLIB BZIP2 ZIP 16 | 17 | # Set the list of default preferences to string. This preference list is used 18 | # for new keys and becomes the default for "setpref" in the edit menu. 19 | default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 ZLIB BZIP2 ZIP Uncompressed 20 | 21 | # (OpenPGP-Esoteric-Options) 22 | # Use name as the message digest algorithm used when signing a key. Running the 23 | # program with the command --version yields a list of supported algorithms. Be 24 | # aware that if you choose an algorithm that GnuPG supports but other OpenPGP 25 | # implementations do not, then some users will not be able to use the key 26 | # signatures you make, or quite possibly your entire key. 27 | # 28 | # SHA-1 is the only algorithm specified for OpenPGP V4. By changing the 29 | # cert-digest-algo, the OpenPGP V4 specification is not met but with even 30 | # GnuPG 1.4.10 (release 2009) supporting SHA-2 algorithm, this should be safe. 31 | # Source: https://tools.ietf.org/html/rfc4880#section-12.2 32 | cert-digest-algo SHA512 33 | digest-algo SHA256 34 | 35 | # Selects how passphrases for symmetric encryption are mangled. 3 (the default) 36 | # iterates the whole process a number of times (see --s2k-count). 37 | s2k-mode 3 38 | 39 | # (OpenPGP-Protocol-Options) 40 | # Use name as the cipher algorithm for symmetric encryption with a passphrase 41 | # if --personal-cipher-preferences and --cipher-algo are not given. The 42 | # default is AES-128. 43 | s2k-cipher-algo AES256 44 | 45 | # (OpenPGP-Protocol-Options) 46 | # Use name as the digest algorithm used to mangle the passphrases for symmetric 47 | # encryption. The default is SHA-1. 48 | s2k-digest-algo SHA512 49 | 50 | # (OpenPGP-Protocol-Options) 51 | # Specify how many times the passphrases mangling for symmetric encryption is 52 | # repeated. This value may range between 1024 and 65011712 inclusive. The 53 | # default is inquired from gpg-agent. Note that not all values in the 54 | # 1024-65011712 range are legal and if an illegal value is selected, GnuPG will 55 | # round up to the nearest legal value. This option is only meaningful if 56 | # --s2k-mode is set to the default of 3. 57 | s2k-count 1015808 58 | 59 | ################################################################################ 60 | # GnuPG View Options 61 | 62 | # Select how to display key IDs. "long" is the more accurate (but less 63 | # convenient) 16-character key ID. Add an "0x" to include an "0x" at the 64 | # beginning of the key ID. 65 | keyid-format 0xlong 66 | 67 | # List all keys with their fingerprints. This is the same output as --list-keys 68 | # but with the additional output of a line with the fingerprint. If this 69 | # command is given twice, the fingerprints of all secondary keys are listed too. 70 | with-fingerprint 71 | with-fingerprint 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 10m 3 | modules-download-mode: vendor 4 | 5 | linters: 6 | enable: 7 | - gofmt 8 | - govet 9 | - depguard 10 | - goimports 11 | - ineffassign 12 | - misspell 13 | - unused 14 | - revive 15 | - staticcheck 16 | - typecheck 17 | disable-all: true 18 | 19 | linters-settings: 20 | depguard: 21 | rules: 22 | main: 23 | deny: 24 | - pkg: "io/ioutil" 25 | desc: The io/ioutil package has been deprecated. See https://go.dev/doc/go1.16#ioutil 26 | 27 | issues: 28 | exclude-rules: 29 | - linters: 30 | - revive 31 | text: "stutters" 32 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | ARG GO_VERSION=1.23.6 4 | ARG DEBIAN_VERSION=bookworm 5 | 6 | ARG XX_VERSION=1.6.1 7 | ARG OSXCROSS_VERSION=11.3-r7-debian 8 | ARG GOLANGCI_LINT_VERSION=v1.64.5 9 | ARG DEBIAN_FRONTEND=noninteractive 10 | 11 | ARG PACKAGE=github.com/docker/docker-credential-helpers 12 | 13 | # xx is a helper for cross-compilation 14 | FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx 15 | 16 | # osxcross contains the MacOSX cross toolchain for xx 17 | FROM crazymax/osxcross:${OSXCROSS_VERSION} AS osxcross 18 | 19 | FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-${DEBIAN_VERSION} AS gobase 20 | COPY --from=xx / / 21 | ARG DEBIAN_FRONTEND 22 | RUN apt-get update && apt-get install -y --no-install-recommends clang dpkg-dev file git lld llvm make pkg-config rsync 23 | ENV GOFLAGS="-mod=vendor" 24 | ENV CGO_ENABLED="1" 25 | WORKDIR /src 26 | 27 | FROM gobase AS vendored 28 | RUN --mount=target=/context \ 29 | --mount=target=.,type=tmpfs \ 30 | --mount=target=/go/pkg/mod,type=cache <&2 'ERROR: Vendor result differs. Please vendor your package with "make vendor"' 52 | git status --porcelain -- go.mod go.sum vendor 53 | exit 1 54 | fi 55 | EOT 56 | 57 | FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION} AS golangci-lint 58 | FROM gobase AS lint 59 | ARG DEBIAN_FRONTEND 60 | RUN apt-get install -y binutils gcc libc6-dev libgcc-11-dev libsecret-1-dev pkg-config 61 | RUN --mount=type=bind,target=. \ 62 | --mount=type=cache,target=/root/.cache \ 63 | --mount=from=golangci-lint,source=/usr/bin/golangci-lint,target=/usr/bin/golangci-lint \ 64 | golangci-lint run ./... 65 | 66 | FROM gobase AS base 67 | ARG TARGETPLATFORM 68 | ARG DEBIAN_FRONTEND 69 | RUN xx-apt-get install -y binutils gcc libc6-dev libgcc-11-dev libsecret-1-dev pkg-config 70 | 71 | FROM base AS test 72 | ARG DEBIAN_FRONTEND 73 | RUN xx-apt-get install -y dbus-x11 gnome-keyring gpg-agent gpgconf libsecret-1-dev pass 74 | RUN --mount=type=bind,target=. \ 75 | --mount=type=cache,target=/root/.cache \ 76 | --mount=type=cache,target=/go/pkg/mod <", "Secret": "abcd1234"}`), nil 54 | case missingCredsAddress: 55 | return []byte(credentials.NewErrCredentialsNotFound().Error()), errProgramExited 56 | case invalidServerAddress: 57 | return []byte("program failed"), errProgramExited 58 | case "": 59 | return []byte(credentials.NewErrCredentialsMissingServerURL().Error()), errProgramExited 60 | } 61 | case "store": 62 | var c credentials.Credentials 63 | err := json.NewDecoder(strings.NewReader(inS)).Decode(&c) 64 | if err != nil { 65 | return []byte("error storing credentials"), errProgramExited 66 | } 67 | switch c.ServerURL { 68 | case validServerAddress: 69 | return nil, nil 70 | case validServerAddress2: 71 | return nil, nil 72 | default: 73 | return []byte("error storing credentials"), errProgramExited 74 | } 75 | case "list": 76 | return []byte(fmt.Sprintf(`{"%s": "%s"}`, validServerAddress, validUsername)), nil 77 | 78 | } 79 | 80 | return []byte(fmt.Sprintf("unknown argument %q with %q", m.arg, inS)), errProgramExited 81 | } 82 | 83 | // Input sets the input to send to a remote credentials-helper. 84 | func (m *mockProgram) Input(in io.Reader) { 85 | m.input = in 86 | } 87 | 88 | func mockProgramFn(args ...string) Program { 89 | return &mockProgram{ 90 | arg: args[0], 91 | } 92 | } 93 | 94 | func ExampleStore() { 95 | p := NewShellProgramFunc("docker-credential-pass") 96 | 97 | c := &credentials.Credentials{ 98 | ServerURL: "https://registry.example.com", 99 | Username: "exampleuser", 100 | Secret: "my super secret token", 101 | } 102 | 103 | if err := Store(p, c); err != nil { 104 | _, _ = fmt.Println(err) 105 | } 106 | } 107 | 108 | func TestStore(t *testing.T) { 109 | valid := []credentials.Credentials{ 110 | {ServerURL: validServerAddress, Username: "foo", Secret: "bar"}, 111 | {ServerURL: validServerAddress2, Username: "", Secret: "abcd1234"}, 112 | } 113 | 114 | for _, v := range valid { 115 | if err := Store(mockProgramFn, &v); err != nil { 116 | t.Error(err) 117 | } 118 | } 119 | 120 | invalid := []credentials.Credentials{ 121 | {ServerURL: invalidServerAddress, Username: "foo", Secret: "bar"}, 122 | } 123 | 124 | for _, v := range invalid { 125 | if err := Store(mockProgramFn, &v); err == nil { 126 | t.Errorf("Expected error for server %s, got nil", v.ServerURL) 127 | } 128 | } 129 | } 130 | 131 | func ExampleGet() { 132 | p := NewShellProgramFunc("docker-credential-pass") 133 | 134 | creds, err := Get(p, "https://registry.example.com") 135 | if err != nil { 136 | _, _ = fmt.Println(err) 137 | } 138 | 139 | _, _ = fmt.Printf("Got credentials for user `%s` in `%s`\n", creds.Username, creds.ServerURL) 140 | } 141 | 142 | func TestGet(t *testing.T) { 143 | valid := []credentials.Credentials{ 144 | {ServerURL: validServerAddress, Username: "foo", Secret: "bar"}, 145 | {ServerURL: validServerAddress2, Username: "", Secret: "abcd1234"}, 146 | } 147 | 148 | for _, v := range valid { 149 | c, err := Get(mockProgramFn, v.ServerURL) 150 | if err != nil { 151 | t.Fatal(err) 152 | } 153 | 154 | if c.Username != v.Username { 155 | t.Errorf("expected username `%s`, got %s", v.Username, c.Username) 156 | } 157 | if c.Secret != v.Secret { 158 | t.Errorf("expected secret `%s`, got %s", v.Secret, c.Secret) 159 | } 160 | } 161 | 162 | missingServerURLErr := credentials.NewErrCredentialsMissingServerURL() 163 | 164 | invalid := []struct { 165 | serverURL string 166 | err string 167 | }{ 168 | { 169 | serverURL: missingCredsAddress, 170 | err: credentials.NewErrCredentialsNotFound().Error(), 171 | }, 172 | { 173 | serverURL: invalidServerAddress, 174 | err: "error getting credentials - err: exited 1, out: `program failed`", 175 | }, 176 | { 177 | err: fmt.Sprintf("error getting credentials - err: %s, out: `%s`", missingServerURLErr.Error(), missingServerURLErr.Error()), 178 | }, 179 | } 180 | 181 | for _, v := range invalid { 182 | _, err := Get(mockProgramFn, v.serverURL) 183 | if err == nil { 184 | t.Fatalf("Expected error for server %s, got nil", v.serverURL) 185 | } 186 | if err.Error() != v.err { 187 | t.Errorf("Expected error `%s`, got `%v`", v.err, err) 188 | } 189 | } 190 | } 191 | 192 | func ExampleErase() { 193 | p := NewShellProgramFunc("docker-credential-pass") 194 | 195 | if err := Erase(p, "https://registry.example.com"); err != nil { 196 | _, _ = fmt.Println(err) 197 | } 198 | } 199 | 200 | func TestErase(t *testing.T) { 201 | if err := Erase(mockProgramFn, validServerAddress); err != nil { 202 | t.Error(err) 203 | } 204 | 205 | if err := Erase(mockProgramFn, invalidServerAddress); err == nil { 206 | t.Errorf("Expected error for server %s, got nil", invalidServerAddress) 207 | } 208 | } 209 | 210 | func TestList(t *testing.T) { 211 | auths, err := List(mockProgramFn) 212 | if err != nil { 213 | t.Fatal(err) 214 | } 215 | 216 | if username, exists := auths[validServerAddress]; !exists || username != validUsername { 217 | t.Errorf("auths[%s] returned %s, %t; expected %s, %t", validServerAddress, username, exists, validUsername, true) 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /client/command.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "os/exec" 7 | ) 8 | 9 | // Program is an interface to execute external programs. 10 | type Program interface { 11 | Output() ([]byte, error) 12 | Input(in io.Reader) 13 | } 14 | 15 | // ProgramFunc is a type of function that initializes programs based on arguments. 16 | type ProgramFunc func(args ...string) Program 17 | 18 | // NewShellProgramFunc creates a [ProgramFunc] to run command in a [Shell]. 19 | func NewShellProgramFunc(command string) ProgramFunc { 20 | return func(args ...string) Program { 21 | return createProgramCmdRedirectErr(command, args, nil) 22 | } 23 | } 24 | 25 | // NewShellProgramFuncWithEnv creates a [ProgramFunc] tu run command 26 | // in a [Shell] with the given environment variables. 27 | func NewShellProgramFuncWithEnv(command string, env *map[string]string) ProgramFunc { 28 | return func(args ...string) Program { 29 | return createProgramCmdRedirectErr(command, args, env) 30 | } 31 | } 32 | 33 | func createProgramCmdRedirectErr(command string, args []string, env *map[string]string) *Shell { 34 | ec := exec.Command(command, args...) 35 | if env != nil { 36 | for k, v := range *env { 37 | ec.Env = append(ec.Environ(), k+"="+v) 38 | } 39 | } 40 | ec.Stderr = os.Stderr 41 | return &Shell{cmd: ec} 42 | } 43 | 44 | // Shell invokes shell commands to talk with a remote credentials-helper. 45 | type Shell struct { 46 | cmd *exec.Cmd 47 | } 48 | 49 | // Output returns responses from the remote credentials-helper. 50 | func (s *Shell) Output() ([]byte, error) { 51 | return s.cmd.Output() 52 | } 53 | 54 | // Input sets the input to send to a remote credentials-helper. 55 | func (s *Shell) Input(in io.Reader) { 56 | s.cmd.Stdin = in 57 | } 58 | -------------------------------------------------------------------------------- /credentials/credentials.go: -------------------------------------------------------------------------------- 1 | package credentials 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "os" 10 | "strings" 11 | ) 12 | 13 | // Action defines the name of an action (sub-command) supported by a 14 | // credential-helper binary. It is an alias for "string", and mostly 15 | // for convenience. 16 | type Action = string 17 | 18 | // List of actions (sub-commands) supported by credential-helper binaries. 19 | const ( 20 | ActionStore Action = "store" 21 | ActionGet Action = "get" 22 | ActionErase Action = "erase" 23 | ActionList Action = "list" 24 | ActionVersion Action = "version" 25 | ) 26 | 27 | // Credentials holds the information shared between docker and the credentials store. 28 | type Credentials struct { 29 | ServerURL string 30 | Username string 31 | Secret string 32 | } 33 | 34 | // isValid checks the integrity of Credentials object such that no credentials lack 35 | // a server URL or a username. 36 | // It returns whether the credentials are valid and the error if it isn't. 37 | // error values can be errCredentialsMissingServerURL or errCredentialsMissingUsername 38 | func (c *Credentials) isValid() (bool, error) { 39 | if len(c.ServerURL) == 0 { 40 | return false, NewErrCredentialsMissingServerURL() 41 | } 42 | 43 | if len(c.Username) == 0 { 44 | return false, NewErrCredentialsMissingUsername() 45 | } 46 | 47 | return true, nil 48 | } 49 | 50 | // CredsLabel holds the way Docker credentials should be labeled as such in credentials stores that allow labelling. 51 | // That label allows to filter out non-Docker credentials too at lookup/search in macOS keychain, 52 | // Windows credentials manager and Linux libsecret. Default value is "Docker Credentials" 53 | var CredsLabel = "Docker Credentials" 54 | 55 | // SetCredsLabel is a simple setter for CredsLabel 56 | func SetCredsLabel(label string) { 57 | CredsLabel = label 58 | } 59 | 60 | // Serve initializes the credentials-helper and parses the action argument. 61 | // This function is designed to be called from a command line interface. 62 | // It uses os.Args[1] as the key for the action. 63 | // It uses os.Stdin as input and os.Stdout as output. 64 | // This function terminates the program with os.Exit(1) if there is an error. 65 | func Serve(helper Helper) { 66 | if len(os.Args) != 2 { 67 | _, _ = fmt.Fprintln(os.Stdout, usage()) 68 | os.Exit(1) 69 | } 70 | 71 | switch os.Args[1] { 72 | case "--version", "-v": 73 | _ = PrintVersion(os.Stdout) 74 | os.Exit(0) 75 | case "--help", "-h": 76 | _, _ = fmt.Fprintln(os.Stdout, usage()) 77 | os.Exit(0) 78 | } 79 | 80 | if err := HandleCommand(helper, os.Args[1], os.Stdin, os.Stdout); err != nil { 81 | _, _ = fmt.Fprintln(os.Stdout, err) 82 | os.Exit(1) 83 | } 84 | } 85 | 86 | func usage() string { 87 | return fmt.Sprintf("Usage: %s ", Name) 88 | } 89 | 90 | // HandleCommand runs a helper to execute a credential action. 91 | func HandleCommand(helper Helper, action Action, in io.Reader, out io.Writer) error { 92 | switch action { 93 | case ActionStore: 94 | return Store(helper, in) 95 | case ActionGet: 96 | return Get(helper, in, out) 97 | case ActionErase: 98 | return Erase(helper, in) 99 | case ActionList: 100 | return List(helper, out) 101 | case ActionVersion: 102 | return PrintVersion(out) 103 | default: 104 | return fmt.Errorf("%s: unknown action: %s", Name, action) 105 | } 106 | } 107 | 108 | // Store uses a helper and an input reader to save credentials. 109 | // The reader must contain the JSON serialization of a Credentials struct. 110 | func Store(helper Helper, reader io.Reader) error { 111 | scanner := bufio.NewScanner(reader) 112 | 113 | buffer := new(bytes.Buffer) 114 | for scanner.Scan() { 115 | buffer.Write(scanner.Bytes()) 116 | } 117 | 118 | if err := scanner.Err(); err != nil && err != io.EOF { 119 | return err 120 | } 121 | 122 | var creds Credentials 123 | if err := json.NewDecoder(buffer).Decode(&creds); err != nil { 124 | return err 125 | } 126 | 127 | if ok, err := creds.isValid(); !ok { 128 | return err 129 | } 130 | 131 | return helper.Add(&creds) 132 | } 133 | 134 | // Get retrieves the credentials for a given server url. 135 | // The reader must contain the server URL to search. 136 | // The writer is used to write the JSON serialization of the credentials. 137 | func Get(helper Helper, reader io.Reader, writer io.Writer) error { 138 | scanner := bufio.NewScanner(reader) 139 | 140 | buffer := new(bytes.Buffer) 141 | for scanner.Scan() { 142 | buffer.Write(scanner.Bytes()) 143 | } 144 | 145 | if err := scanner.Err(); err != nil && err != io.EOF { 146 | return err 147 | } 148 | 149 | serverURL := strings.TrimSpace(buffer.String()) 150 | if len(serverURL) == 0 { 151 | return NewErrCredentialsMissingServerURL() 152 | } 153 | 154 | username, secret, err := helper.Get(serverURL) 155 | if err != nil { 156 | return err 157 | } 158 | 159 | buffer.Reset() 160 | err = json.NewEncoder(buffer).Encode(Credentials{ 161 | ServerURL: serverURL, 162 | Username: username, 163 | Secret: secret, 164 | }) 165 | if err != nil { 166 | return err 167 | } 168 | 169 | _, _ = fmt.Fprint(writer, buffer.String()) 170 | return nil 171 | } 172 | 173 | // Erase removes credentials from the store. 174 | // The reader must contain the server URL to remove. 175 | func Erase(helper Helper, reader io.Reader) error { 176 | scanner := bufio.NewScanner(reader) 177 | 178 | buffer := new(bytes.Buffer) 179 | for scanner.Scan() { 180 | buffer.Write(scanner.Bytes()) 181 | } 182 | 183 | if err := scanner.Err(); err != nil && err != io.EOF { 184 | return err 185 | } 186 | 187 | serverURL := strings.TrimSpace(buffer.String()) 188 | if len(serverURL) == 0 { 189 | return NewErrCredentialsMissingServerURL() 190 | } 191 | 192 | return helper.Delete(serverURL) 193 | } 194 | 195 | // List returns all the serverURLs of keys in 196 | // the OS store as a list of strings 197 | func List(helper Helper, writer io.Writer) error { 198 | accts, err := helper.List() 199 | if err != nil { 200 | return err 201 | } 202 | return json.NewEncoder(writer).Encode(accts) 203 | } 204 | 205 | // PrintVersion outputs the current version. 206 | func PrintVersion(writer io.Writer) error { 207 | _, _ = fmt.Fprintf(writer, "%s (%s) %s\n", Name, Package, Version) 208 | return nil 209 | } 210 | -------------------------------------------------------------------------------- /credentials/credentials_test.go: -------------------------------------------------------------------------------- 1 | package credentials 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | type memoryStore struct { 12 | creds map[string]*Credentials 13 | } 14 | 15 | func newMemoryStore() *memoryStore { 16 | return &memoryStore{ 17 | creds: make(map[string]*Credentials), 18 | } 19 | } 20 | 21 | func (m *memoryStore) Add(creds *Credentials) error { 22 | m.creds[creds.ServerURL] = creds 23 | return nil 24 | } 25 | 26 | func (m *memoryStore) Delete(serverURL string) error { 27 | delete(m.creds, serverURL) 28 | return nil 29 | } 30 | 31 | func (m *memoryStore) Get(serverURL string) (string, string, error) { 32 | c, ok := m.creds[serverURL] 33 | if !ok { 34 | return "", "", fmt.Errorf("creds not found for %s", serverURL) 35 | } 36 | return c.Username, c.Secret, nil 37 | } 38 | 39 | func (m *memoryStore) List() (map[string]string, error) { 40 | // Simply a placeholder to let memoryStore be a valid implementation of Helper interface 41 | return nil, nil 42 | } 43 | 44 | func TestStore(t *testing.T) { 45 | const serverURL = "https://registry.example.com/v1/" 46 | creds := &Credentials{ 47 | ServerURL: serverURL, 48 | Username: "foo", 49 | Secret: "bar", 50 | } 51 | b, err := json.Marshal(creds) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | in := bytes.NewReader(b) 56 | 57 | h := newMemoryStore() 58 | if err := Store(h, in); err != nil { 59 | t.Fatal(err) 60 | } 61 | 62 | c, ok := h.creds[serverURL] 63 | if !ok { 64 | t.Fatalf("creds not found for %s\n", serverURL) 65 | } 66 | 67 | if c.Username != "foo" { 68 | t.Errorf("expected username foo, got %s\n", c.Username) 69 | } 70 | 71 | if c.Secret != "bar" { 72 | t.Errorf("expected username bar, got %s\n", c.Secret) 73 | } 74 | } 75 | 76 | func TestStoreMissingServerURL(t *testing.T) { 77 | creds := &Credentials{ 78 | ServerURL: "", 79 | Username: "foo", 80 | Secret: "bar", 81 | } 82 | 83 | b, err := json.Marshal(creds) 84 | if err != nil { 85 | t.Fatal(err) 86 | } 87 | in := bytes.NewReader(b) 88 | 89 | h := newMemoryStore() 90 | 91 | if err := Store(h, in); !IsCredentialsMissingServerURL(err) { 92 | t.Error(err) 93 | } 94 | } 95 | 96 | func TestStoreMissingUsername(t *testing.T) { 97 | creds := &Credentials{ 98 | ServerURL: "https://registry.example.com/v1/", 99 | Username: "", 100 | Secret: "bar", 101 | } 102 | 103 | b, err := json.Marshal(creds) 104 | if err != nil { 105 | t.Fatal(err) 106 | } 107 | in := bytes.NewReader(b) 108 | 109 | h := newMemoryStore() 110 | 111 | if err := Store(h, in); !IsCredentialsMissingUsername(err) { 112 | t.Error(err) 113 | } 114 | } 115 | 116 | func TestGet(t *testing.T) { 117 | const serverURL = "https://registry.example.com/v1/" 118 | creds := &Credentials{ 119 | ServerURL: serverURL, 120 | Username: "foo", 121 | Secret: "bar", 122 | } 123 | b, err := json.Marshal(creds) 124 | if err != nil { 125 | t.Fatal(err) 126 | } 127 | in := bytes.NewReader(b) 128 | 129 | h := newMemoryStore() 130 | if err := Store(h, in); err != nil { 131 | t.Fatal(err) 132 | } 133 | 134 | buf := strings.NewReader(serverURL) 135 | w := new(bytes.Buffer) 136 | if err := Get(h, buf, w); err != nil { 137 | t.Fatal(err) 138 | } 139 | 140 | if w.Len() == 0 { 141 | t.Fatalf("expected output in the writer, got %d", w.Len()) 142 | } 143 | 144 | var c Credentials 145 | if err := json.NewDecoder(w).Decode(&c); err != nil { 146 | t.Fatal(err) 147 | } 148 | 149 | if c.Username != "foo" { 150 | t.Errorf("expected username foo, got %s\n", c.Username) 151 | } 152 | 153 | if c.Secret != "bar" { 154 | t.Errorf("expected username bar, got %s\n", c.Secret) 155 | } 156 | } 157 | 158 | func TestGetMissingServerURL(t *testing.T) { 159 | const serverURL = "https://registry.example.com/v1/" 160 | creds := &Credentials{ 161 | ServerURL: serverURL, 162 | Username: "foo", 163 | Secret: "bar", 164 | } 165 | b, err := json.Marshal(creds) 166 | if err != nil { 167 | t.Fatal(err) 168 | } 169 | in := bytes.NewReader(b) 170 | 171 | h := newMemoryStore() 172 | if err := Store(h, in); err != nil { 173 | t.Fatal(err) 174 | } 175 | 176 | buf := strings.NewReader("") 177 | w := new(bytes.Buffer) 178 | 179 | if err := Get(h, buf, w); !IsCredentialsMissingServerURL(err) { 180 | t.Error(err) 181 | } 182 | } 183 | 184 | func TestErase(t *testing.T) { 185 | const serverURL = "https://registry.example.com/v1/" 186 | creds := &Credentials{ 187 | ServerURL: serverURL, 188 | Username: "foo", 189 | Secret: "bar", 190 | } 191 | b, err := json.Marshal(creds) 192 | if err != nil { 193 | t.Fatal(err) 194 | } 195 | in := bytes.NewReader(b) 196 | 197 | h := newMemoryStore() 198 | if err := Store(h, in); err != nil { 199 | t.Fatal(err) 200 | } 201 | 202 | buf := strings.NewReader(serverURL) 203 | if err := Erase(h, buf); err != nil { 204 | t.Fatal(err) 205 | } 206 | 207 | w := new(bytes.Buffer) 208 | if err := Get(h, buf, w); err == nil { 209 | t.Error("expected error getting missing creds, got empty") 210 | } 211 | } 212 | 213 | func TestEraseMissingServerURL(t *testing.T) { 214 | const serverURL = "https://registry.example.com/v1/" 215 | creds := &Credentials{ 216 | ServerURL: serverURL, 217 | Username: "foo", 218 | Secret: "bar", 219 | } 220 | b, err := json.Marshal(creds) 221 | if err != nil { 222 | t.Fatal(err) 223 | } 224 | in := bytes.NewReader(b) 225 | 226 | h := newMemoryStore() 227 | if err := Store(h, in); err != nil { 228 | t.Fatal(err) 229 | } 230 | 231 | buf := strings.NewReader("") 232 | if err := Erase(h, buf); !IsCredentialsMissingServerURL(err) { 233 | t.Error(err) 234 | } 235 | } 236 | 237 | func TestList(t *testing.T) { 238 | // This tests that there is proper input an output into the byte stream 239 | // Individual stores are very OS specific and have been tested in osxkeychain and secretservice respectively 240 | out := new(bytes.Buffer) 241 | h := newMemoryStore() 242 | if err := List(h, out); err != nil { 243 | t.Fatal(err) 244 | } 245 | // testing that there is an output 246 | if out.Len() == 0 { 247 | t.Error("expected output in the writer, got 0") 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /credentials/error.go: -------------------------------------------------------------------------------- 1 | package credentials 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | const ( 9 | // ErrCredentialsNotFound standardizes the not found error, so every helper returns 10 | // the same message and docker can handle it properly. 11 | errCredentialsNotFoundMessage = "credentials not found in native keychain" 12 | 13 | // ErrCredentialsMissingServerURL and ErrCredentialsMissingUsername standardize 14 | // invalid credentials or credentials management operations 15 | errCredentialsMissingServerURLMessage = "no credentials server URL" 16 | errCredentialsMissingUsernameMessage = "no credentials username" 17 | ) 18 | 19 | // errCredentialsNotFound represents an error 20 | // raised when credentials are not in the store. 21 | type errCredentialsNotFound struct{} 22 | 23 | // Error returns the standard error message 24 | // for when the credentials are not in the store. 25 | func (errCredentialsNotFound) Error() string { 26 | return errCredentialsNotFoundMessage 27 | } 28 | 29 | // NotFound implements the [ErrNotFound][errdefs.ErrNotFound] interface. 30 | // 31 | // [errdefs.ErrNotFound]: https://pkg.go.dev/github.com/docker/docker@v24.0.1+incompatible/errdefs#ErrNotFound 32 | func (errCredentialsNotFound) NotFound() {} 33 | 34 | // NewErrCredentialsNotFound creates a new error 35 | // for when the credentials are not in the store. 36 | func NewErrCredentialsNotFound() error { 37 | return errCredentialsNotFound{} 38 | } 39 | 40 | // IsErrCredentialsNotFound returns true if the error 41 | // was caused by not having a set of credentials in a store. 42 | func IsErrCredentialsNotFound(err error) bool { 43 | var target errCredentialsNotFound 44 | return errors.As(err, &target) 45 | } 46 | 47 | // IsErrCredentialsNotFoundMessage returns true if the error 48 | // was caused by not having a set of credentials in a store. 49 | // 50 | // This function helps to check messages returned by an 51 | // external program via its standard output. 52 | func IsErrCredentialsNotFoundMessage(err string) bool { 53 | return strings.TrimSpace(err) == errCredentialsNotFoundMessage 54 | } 55 | 56 | // errCredentialsMissingServerURL represents an error raised 57 | // when the credentials object has no server URL or when no 58 | // server URL is provided to a credentials operation requiring 59 | // one. 60 | type errCredentialsMissingServerURL struct{} 61 | 62 | func (errCredentialsMissingServerURL) Error() string { 63 | return errCredentialsMissingServerURLMessage 64 | } 65 | 66 | // InvalidParameter implements the [ErrInvalidParameter][errdefs.ErrInvalidParameter] 67 | // interface. 68 | // 69 | // [errdefs.ErrInvalidParameter]: https://pkg.go.dev/github.com/docker/docker@v24.0.1+incompatible/errdefs#ErrInvalidParameter 70 | func (errCredentialsMissingServerURL) InvalidParameter() {} 71 | 72 | // errCredentialsMissingUsername represents an error raised 73 | // when the credentials object has no username or when no 74 | // username is provided to a credentials operation requiring 75 | // one. 76 | type errCredentialsMissingUsername struct{} 77 | 78 | func (errCredentialsMissingUsername) Error() string { 79 | return errCredentialsMissingUsernameMessage 80 | } 81 | 82 | // InvalidParameter implements the [ErrInvalidParameter][errdefs.ErrInvalidParameter] 83 | // interface. 84 | // 85 | // [errdefs.ErrInvalidParameter]: https://pkg.go.dev/github.com/docker/docker@v24.0.1+incompatible/errdefs#ErrInvalidParameter 86 | func (errCredentialsMissingUsername) InvalidParameter() {} 87 | 88 | // NewErrCredentialsMissingServerURL creates a new error for 89 | // errCredentialsMissingServerURL. 90 | func NewErrCredentialsMissingServerURL() error { 91 | return errCredentialsMissingServerURL{} 92 | } 93 | 94 | // NewErrCredentialsMissingUsername creates a new error for 95 | // errCredentialsMissingUsername. 96 | func NewErrCredentialsMissingUsername() error { 97 | return errCredentialsMissingUsername{} 98 | } 99 | 100 | // IsCredentialsMissingServerURL returns true if the error 101 | // was an errCredentialsMissingServerURL. 102 | func IsCredentialsMissingServerURL(err error) bool { 103 | var target errCredentialsMissingServerURL 104 | return errors.As(err, &target) 105 | } 106 | 107 | // IsCredentialsMissingServerURLMessage checks for an 108 | // errCredentialsMissingServerURL in the error message. 109 | func IsCredentialsMissingServerURLMessage(err string) bool { 110 | return strings.TrimSpace(err) == errCredentialsMissingServerURLMessage 111 | } 112 | 113 | // IsCredentialsMissingUsername returns true if the error 114 | // was an errCredentialsMissingUsername. 115 | func IsCredentialsMissingUsername(err error) bool { 116 | var target errCredentialsMissingUsername 117 | return errors.As(err, &target) 118 | } 119 | 120 | // IsCredentialsMissingUsernameMessage checks for an 121 | // errCredentialsMissingUsername in the error message. 122 | func IsCredentialsMissingUsernameMessage(err string) bool { 123 | return strings.TrimSpace(err) == errCredentialsMissingUsernameMessage 124 | } 125 | -------------------------------------------------------------------------------- /credentials/helper.go: -------------------------------------------------------------------------------- 1 | package credentials 2 | 3 | // Helper is the interface a credentials store helper must implement. 4 | type Helper interface { 5 | // Add appends credentials to the store. 6 | Add(*Credentials) error 7 | // Delete removes credentials from the store. 8 | Delete(serverURL string) error 9 | // Get retrieves credentials from the store. 10 | // It returns username and secret as strings. 11 | Get(serverURL string) (string, string, error) 12 | // List returns the stored serverURLs and their associated usernames. 13 | List() (map[string]string, error) 14 | } 15 | -------------------------------------------------------------------------------- /credentials/version.go: -------------------------------------------------------------------------------- 1 | package credentials 2 | 3 | var ( 4 | // Name is filled at linking time 5 | Name = "" 6 | 7 | // Package is filled at linking time 8 | Package = "github.com/docker/docker-credential-helpers" 9 | 10 | // Version holds the complete version number. Filled in at linking time. 11 | Version = "v0.0.0+unknown" 12 | 13 | // Revision is filled with the VCS (e.g. git) revision being used to build 14 | // the program at linking time. 15 | Revision = "" 16 | ) 17 | -------------------------------------------------------------------------------- /deb/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | ARG GO_VERSION=1.23.6 4 | ARG DISTRO=ubuntu 5 | ARG SUITE=focal 6 | 7 | FROM golang:${GO_VERSION}-bullseye AS golang 8 | 9 | FROM ${DISTRO}:${SUITE} 10 | 11 | ARG DEBIAN_FRONTEND=noninteractive 12 | RUN apt-get update && apt-get install -yy debhelper dh-make libsecret-1-dev 13 | RUN mkdir -p /build 14 | 15 | WORKDIR /build 16 | ENV GOPROXY=https://proxy.golang.org|direct 17 | ENV GO111MODULE=off 18 | ENV GOPATH=/build 19 | ENV PATH=$PATH:/usr/local/go/bin:$GOPATH/bin 20 | COPY --from=golang /usr/local/go /usr/local/go 21 | 22 | COPY Makefile . 23 | COPY credentials credentials 24 | COPY secretservice secretservice 25 | COPY pass pass 26 | COPY deb/debian ./debian 27 | COPY deb/build-deb . 28 | 29 | ARG VERSION 30 | ENV VERSION=${VERSION} 31 | ARG REVISION 32 | ENV REVISION=${REVISION} 33 | ARG DISTRO 34 | ENV DISTRO=${DISTRO} 35 | ARG SUITE 36 | ENV SUITE=${SUITE} 37 | RUN /build/build-deb 38 | -------------------------------------------------------------------------------- /deb/build-deb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ex 3 | 4 | maintainer=$(awk -F ': ' '$1 == "Maintainer" { print $2; exit }' debian/control) 5 | 6 | cat > "debian/changelog" <<-EOF 7 | docker-credential-helpers ($VERSION) $DISTRO-$SUITE; urgency=low 8 | 9 | * New upstream version 10 | 11 | -- $maintainer $(date --rfc-2822) 12 | EOF 13 | 14 | mkdir -p src/github.com/docker/docker-credential-helpers 15 | ln -s /build/credentials /build/src/github.com/docker/docker-credential-helpers/credentials 16 | ln -s /build/secretservice /build/src/github.com/docker/docker-credential-helpers/secretservice 17 | ln -s /build/pass /build/src/github.com/docker/docker-credential-helpers/pass 18 | 19 | dpkg-buildpackage -us -uc 20 | 21 | mkdir /release 22 | mv /docker-credential-* /release 23 | -------------------------------------------------------------------------------- /deb/debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /deb/debian/control: -------------------------------------------------------------------------------- 1 | Source: docker-credential-helpers 2 | Section: admin 3 | Priority: optional 4 | Maintainer: Docker 5 | Homepage: https://dockerproject.org 6 | Standards-Version: 3.9.6 7 | Vcs-Browser: https://github.com/docker/docker-credential-helpers 8 | Vcs-Git: git://github.com/docker/docker-credential-helpers.git 9 | Build-Depends: debhelper 10 | , dh-make 11 | , libsecret-1-dev 12 | 13 | Package: docker-credential-secretservice 14 | Architecture: any 15 | Depends: libsecret-1-0 16 | , ${misc:Depends} 17 | Description: docker-credential-secretservice is a credential helper backend 18 | which uses libsecret to keep Docker credentials safe. 19 | 20 | Package: docker-credential-pass 21 | Architecture: any 22 | Depends: pass 23 | , ${misc:Depends} 24 | Description: docker-credential-secretservice is a credential helper backend 25 | which uses the pass utility to keep Docker credentials safe. 26 | -------------------------------------------------------------------------------- /deb/debian/docker-credential-pass.install: -------------------------------------------------------------------------------- 1 | debian/tmp/usr/bin/docker-credential-pass 2 | -------------------------------------------------------------------------------- /deb/debian/docker-credential-secretservice.install: -------------------------------------------------------------------------------- 1 | debian/tmp/usr/bin/docker-credential-secretservice 2 | -------------------------------------------------------------------------------- /deb/debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | DESTDIR := $(CURDIR)/debian/tmp 4 | 5 | override_dh_auto_build: 6 | make build-secretservice DESTDIR=bin 7 | make build-pass DESTDIR=bin 8 | 9 | override_dh_auto_install: 10 | install -D bin/docker-credential-secretservice $(DESTDIR)/usr/bin/docker-credential-secretservice 11 | install -D bin/docker-credential-pass $(DESTDIR)/usr/bin/docker-credential-pass 12 | 13 | %: 14 | dh $@ 15 | 16 | override_dh_auto_test: 17 | # no tests 18 | 19 | -------------------------------------------------------------------------------- /docker-bake.hcl: -------------------------------------------------------------------------------- 1 | variable "GO_VERSION" { 2 | default = null 3 | } 4 | 5 | # Defines the output folder 6 | variable "DESTDIR" { 7 | default = "" 8 | } 9 | function "bindir" { 10 | params = [defaultdir] 11 | result = DESTDIR != "" ? DESTDIR : "./bin/${defaultdir}" 12 | } 13 | 14 | target "_common" { 15 | args = { 16 | GO_VERSION = GO_VERSION 17 | } 18 | } 19 | 20 | group "default" { 21 | targets = ["binaries"] 22 | } 23 | 24 | group "validate" { 25 | targets = ["lint", "vendor-validate"] 26 | } 27 | 28 | target "lint" { 29 | inherits = ["_common"] 30 | target = "lint" 31 | output = ["type=cacheonly"] 32 | } 33 | 34 | target "vendor-validate" { 35 | inherits = ["_common"] 36 | target = "vendor-validate" 37 | output = ["type=cacheonly"] 38 | } 39 | 40 | target "vendor" { 41 | inherits = ["_common"] 42 | target = "vendor-update" 43 | output = ["."] 44 | } 45 | 46 | target "test" { 47 | inherits = ["_common"] 48 | target = "test-coverage" 49 | output = [bindir("coverage")] 50 | } 51 | 52 | target "binaries" { 53 | inherits = ["_common"] 54 | target = "binaries" 55 | output = [bindir("build")] 56 | platforms = [ 57 | "darwin/amd64", 58 | "darwin/arm64", 59 | "linux/amd64", 60 | "linux/arm64", 61 | "linux/arm/v7", 62 | "linux/arm/v6", 63 | "linux/ppc64le", 64 | "linux/s390x", 65 | "windows/amd64", 66 | "windows/arm64" 67 | ] 68 | } 69 | 70 | target "release" { 71 | inherits = ["binaries"] 72 | target = "release" 73 | output = [bindir("release")] 74 | } 75 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/docker/docker-credential-helpers 2 | 3 | go 1.21 4 | 5 | retract ( 6 | v0.9.1 // osxkeychain: a regression caused backward-incompatibility with earlier versions 7 | v0.9.0 // osxkeychain: a regression caused backward-incompatibility with earlier versions 8 | ) 9 | 10 | require ( 11 | github.com/danieljoos/wincred v1.2.2 12 | github.com/keybase/go-keychain v0.0.1 13 | ) 14 | 15 | require golang.org/x/sys v0.20.0 // indirect 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0= 2 | github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8= 3 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= 6 | github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= 7 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 10 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 11 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 12 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 13 | golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= 14 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 15 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 16 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 17 | -------------------------------------------------------------------------------- /hack/git-meta: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e 4 | 5 | case $1 in 6 | "version") 7 | git describe --match 'v[0-9]*' --dirty='.m' --always --tags 8 | ;; 9 | "revision") 10 | echo "$(git rev-parse HEAD)$(if ! git diff --no-ext-diff --quiet --exit-code; then echo .m; fi)" 11 | ;; 12 | *) 13 | echo "usage: ./hack/git-meta " 14 | exit 1 15 | ;; 16 | esac 17 | -------------------------------------------------------------------------------- /hack/release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | : "${BUILDX_CMD=docker buildx}" 6 | : "${DESTDIR=./bin/release}" 7 | : "${CACHE_FROM=}" 8 | : "${CACHE_TO=}" 9 | 10 | : "${SIGN=}" 11 | : "${PFX=}" 12 | : "${PFXPASSWORD=}" 13 | 14 | if [ -n "$CACHE_FROM" ]; then 15 | for cfrom in $CACHE_FROM; do 16 | cacheFlags+=(--set "*.cache-from=$cfrom") 17 | done 18 | fi 19 | if [ -n "$CACHE_TO" ]; then 20 | for cto in $CACHE_TO; do 21 | cacheFlags+=(--set "*.cache-to=$cto") 22 | done 23 | fi 24 | 25 | dockerpfx=$(mktemp -t dockercredhelper-pfx.XXXXXXXXXX) 26 | function clean { 27 | rm -f "$dockerpfx" 28 | } 29 | trap clean EXIT 30 | 31 | # release 32 | ( 33 | set -x 34 | ${BUILDX_CMD} bake "${cacheFlags[@]}" --set "*.output=$DESTDIR" release 35 | ) 36 | 37 | # wrap binaries 38 | mv -f ./${DESTDIR}/**/* ./${DESTDIR}/ 39 | find ./${DESTDIR} -type d -empty -delete 40 | 41 | # sign binaries 42 | if [ -n "$SIGN" ]; then 43 | for f in "${DESTDIR}"/*".darwin-"*; do 44 | SIGNINGHASH=$(security find-identity -v -p codesigning | grep "Developer ID Application: Docker Inc" | cut -d ' ' -f 4) 45 | xcrun -log codesign -s "$SIGNINGHASH" --force --verbose "$f" 46 | xcrun codesign --verify --deep --strict --verbose=2 --display "$f" 47 | done 48 | for f in "${DESTDIR}"/*".windows-"*; do 49 | echo ${PFX} | base64 -d > "$dockerpfx" 50 | signtool sign /fd SHA256 /a /f pfx /p ${PFXPASSWORD} /d Docker /du https://www.docker.com /t http://timestamp.verisign.com/scripts/timestamp.dll "$f" 51 | done 52 | fi 53 | 54 | # checksums 55 | ( 56 | cd ${DESTDIR} 57 | sha256sum -b docker-credential-* > ./checksums.txt 58 | sha256sum -c --strict checksums.txt 59 | ) 60 | -------------------------------------------------------------------------------- /osxkeychain/cmd/main.go: -------------------------------------------------------------------------------- 1 | //go:build darwin && cgo 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/docker/docker-credential-helpers/credentials" 7 | "github.com/docker/docker-credential-helpers/osxkeychain" 8 | ) 9 | 10 | func main() { 11 | credentials.Serve(osxkeychain.Osxkeychain{}) 12 | } 13 | -------------------------------------------------------------------------------- /osxkeychain/osxkeychain.go: -------------------------------------------------------------------------------- 1 | //go:build darwin && cgo 2 | 3 | package osxkeychain 4 | 5 | /* 6 | #cgo LDFLAGS: -framework Security -framework CoreFoundation 7 | 8 | #include 9 | #include 10 | */ 11 | import "C" 12 | 13 | import ( 14 | "errors" 15 | "net" 16 | "net/url" 17 | "strconv" 18 | 19 | "github.com/docker/docker-credential-helpers/credentials" 20 | "github.com/docker/docker-credential-helpers/registryurl" 21 | "github.com/keybase/go-keychain" 22 | ) 23 | 24 | // https://opensource.apple.com/source/Security/Security-55471/sec/Security/SecBase.h.auto.html 25 | const ( 26 | // errCredentialsNotFound is the specific error message returned by OS X 27 | // when the credentials are not in the keychain. 28 | errCredentialsNotFound = "The specified item could not be found in the keychain. (-25300)" 29 | // errInteractionNotAllowed is the specific error message returned by OS X 30 | // when environment does not allow showing dialog to unlock keychain. 31 | errInteractionNotAllowed = "User interaction is not allowed. (-25308)" 32 | ) 33 | 34 | // ErrInteractionNotAllowed is returned if keychain password prompt can not be shown. 35 | var ErrInteractionNotAllowed = errors.New(`keychain cannot be accessed because the current session does not allow user interaction. The keychain may be locked; unlock it by running "security -v unlock-keychain ~/Library/Keychains/login.keychain-db" and try again`) 36 | 37 | // Osxkeychain handles secrets using the OS X Keychain as store. 38 | type Osxkeychain struct{} 39 | 40 | // Add adds new credentials to the keychain. 41 | func (h Osxkeychain) Add(creds *credentials.Credentials) error { 42 | _ = h.Delete(creds.ServerURL) // ignore errors as existing credential may not exist. 43 | 44 | item := keychain.NewItem() 45 | item.SetSecClass(keychain.SecClassInternetPassword) 46 | item.SetLabel(credentials.CredsLabel) 47 | item.SetAccount(creds.Username) 48 | item.SetData([]byte(creds.Secret)) 49 | // Prior to v0.9, the credential helper was searching for credentials with 50 | // the "dflt" authentication type (see [1]). Since v0.9.0, Get doesn't use 51 | // that attribute anymore, and v0.9.0 - v0.9.2 were not setting it here 52 | // either. 53 | // 54 | // In order to keep compatibility with older versions, we need to store 55 | // credentials with this attribute set. This way, credentials stored with 56 | // newer versions can be retrieved by older versions. 57 | // 58 | // [1]: https://github.com/docker/docker-credential-helpers/blob/v0.8.2/osxkeychain/osxkeychain.c#L66 59 | item.SetAuthenticationType("dflt") 60 | if err := splitServer(creds.ServerURL, item); err != nil { 61 | return err 62 | } 63 | 64 | return keychain.AddItem(item) 65 | } 66 | 67 | // Delete removes credentials from the keychain. 68 | func (h Osxkeychain) Delete(serverURL string) error { 69 | item := keychain.NewItem() 70 | item.SetSecClass(keychain.SecClassInternetPassword) 71 | if err := splitServer(serverURL, item); err != nil { 72 | return err 73 | } 74 | if err := keychain.DeleteItem(item); err != nil { 75 | switch err.Error() { 76 | case errCredentialsNotFound: 77 | return credentials.NewErrCredentialsNotFound() 78 | case errInteractionNotAllowed: 79 | return ErrInteractionNotAllowed 80 | default: 81 | return err 82 | } 83 | } 84 | return nil 85 | } 86 | 87 | // Get returns the username and secret to use for a given registry server URL. 88 | func (h Osxkeychain) Get(serverURL string) (string, string, error) { 89 | item := keychain.NewItem() 90 | item.SetSecClass(keychain.SecClassInternetPassword) 91 | item.SetMatchLimit(keychain.MatchLimitOne) 92 | item.SetReturnAttributes(true) 93 | item.SetReturnData(true) 94 | if err := splitServer(serverURL, item); err != nil { 95 | return "", "", err 96 | } 97 | 98 | res, err := keychain.QueryItem(item) 99 | if err != nil { 100 | switch err.Error() { 101 | case errCredentialsNotFound: 102 | return "", "", credentials.NewErrCredentialsNotFound() 103 | case errInteractionNotAllowed: 104 | return "", "", ErrInteractionNotAllowed 105 | default: 106 | return "", "", err 107 | } 108 | } else if len(res) == 0 { 109 | return "", "", credentials.NewErrCredentialsNotFound() 110 | } 111 | 112 | return res[0].Account, string(res[0].Data), nil 113 | } 114 | 115 | // List returns the stored URLs and corresponding usernames. 116 | func (h Osxkeychain) List() (map[string]string, error) { 117 | item := keychain.NewItem() 118 | item.SetSecClass(keychain.SecClassInternetPassword) 119 | item.SetMatchLimit(keychain.MatchLimitAll) 120 | item.SetReturnAttributes(true) 121 | item.SetLabel(credentials.CredsLabel) 122 | 123 | res, err := keychain.QueryItem(item) 124 | if err != nil { 125 | switch err.Error() { 126 | case errCredentialsNotFound: 127 | return make(map[string]string), nil 128 | case errInteractionNotAllowed: 129 | return nil, ErrInteractionNotAllowed 130 | default: 131 | return nil, err 132 | } 133 | } 134 | 135 | resp := make(map[string]string) 136 | for _, r := range res { 137 | proto := "http" 138 | if r.Protocol == kSecProtocolTypeHTTPS { 139 | proto = "https" 140 | } 141 | host := r.Server 142 | if r.Port != 0 { 143 | host = net.JoinHostPort(host, strconv.Itoa(int(r.Port))) 144 | } 145 | u := url.URL{ 146 | Scheme: proto, 147 | Host: host, 148 | Path: r.Path, 149 | } 150 | resp[u.String()] = r.Account 151 | } 152 | return resp, nil 153 | } 154 | 155 | const ( 156 | // Hardcoded protocol types matching their Objective-C equivalents. 157 | // https://developer.apple.com/documentation/security/ksecattrprotocolhttps?language=objc 158 | kSecProtocolTypeHTTPS = "htps" // This is NOT a typo. 159 | // https://developer.apple.com/documentation/security/ksecattrprotocolhttp?language=objc 160 | kSecProtocolTypeHTTP = "http" 161 | ) 162 | 163 | func splitServer(serverURL string, item keychain.Item) error { 164 | u, err := registryurl.Parse(serverURL) 165 | if err != nil { 166 | return err 167 | } 168 | item.SetProtocol(kSecProtocolTypeHTTPS) 169 | if u.Scheme == "http" { 170 | item.SetProtocol(kSecProtocolTypeHTTP) 171 | } 172 | item.SetServer(u.Hostname()) 173 | if p := u.Port(); p != "" { 174 | port, err := strconv.Atoi(p) 175 | if err != nil { 176 | return err 177 | } 178 | item.SetPort(int32(port)) 179 | } 180 | item.SetPath(u.Path) 181 | return nil 182 | } 183 | -------------------------------------------------------------------------------- /osxkeychain/osxkeychain_test.go: -------------------------------------------------------------------------------- 1 | //go:build darwin && cgo 2 | 3 | package osxkeychain 4 | 5 | import ( 6 | "fmt" 7 | "testing" 8 | 9 | "github.com/docker/docker-credential-helpers/credentials" 10 | ) 11 | 12 | func TestOSXKeychainHelper(t *testing.T) { 13 | creds := &credentials.Credentials{ 14 | ServerURL: "https://foobar.example.com:2376/v1", 15 | Username: "foobar", 16 | Secret: "foobarbaz", 17 | } 18 | helper := Osxkeychain{} 19 | if err := helper.Add(creds); err != nil { 20 | t.Fatal(err) 21 | } 22 | 23 | username, secret, err := helper.Get(creds.ServerURL) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | 28 | if username != "foobar" { 29 | t.Fatalf("expected %s, got %s\n", "foobar", username) 30 | } 31 | 32 | if secret != "foobarbaz" { 33 | t.Fatalf("expected %s, got %s\n", "foobarbaz", secret) 34 | } 35 | 36 | auths, err := helper.List() 37 | if err != nil || len(auths) == 0 { 38 | t.Fatal(err) 39 | } 40 | 41 | if _, ok := auths[creds.ServerURL]; !ok { 42 | t.Fatalf("server %s not found in list, got: %+v", creds.ServerURL, auths) 43 | } 44 | 45 | // Insert another token and check if it is in the list 46 | creds1 := &credentials.Credentials{ 47 | ServerURL: "https://foobar.example.com:2376/v2", 48 | Username: "foobarbaz", 49 | Secret: "foobar", 50 | } 51 | helper.Add(creds1) 52 | defer helper.Delete(creds1.ServerURL) 53 | 54 | auths, err = helper.List() 55 | if err != nil { 56 | t.Fatalf("operation List failed: %+v", err) 57 | } 58 | 59 | if _, ok := auths[creds.ServerURL]; !ok { 60 | t.Fatalf("server %s not found in list, got: %+v", creds.ServerURL, auths) 61 | } 62 | if _, ok := auths[creds1.ServerURL]; !ok { 63 | t.Fatalf("server %s not found in list, got: %+v", creds1.ServerURL, auths) 64 | } 65 | 66 | // Delete the 1st token inserted 67 | if err := helper.Delete(creds.ServerURL); err != nil { 68 | t.Fatal(err) 69 | } 70 | 71 | auths, err = helper.List() 72 | if err != nil { 73 | t.Fatalf("operation List failed: %+v", err) 74 | } 75 | 76 | // First token should have been deleted 77 | if _, ok := auths[creds.ServerURL]; ok { 78 | t.Fatalf("server %s was not deleted, got: %+v", creds.ServerURL, auths) 79 | } 80 | // Second token should still be there 81 | if _, ok := auths[creds1.ServerURL]; !ok { 82 | t.Fatalf("server %s not found in list, got: %+v", creds1.ServerURL, auths) 83 | } 84 | } 85 | 86 | // TestOSXKeychainHelperRetrieveAliases verifies that secrets can be accessed 87 | // through variations on the URL 88 | func TestOSXKeychainHelperRetrieveAliases(t *testing.T) { 89 | tests := []struct { 90 | doc string 91 | storeURL string 92 | readURL string 93 | }{ 94 | { 95 | doc: "stored with port, retrieved without", 96 | storeURL: "https://foobar.example.com:2376", 97 | readURL: "https://foobar.example.com", 98 | }, 99 | { 100 | doc: "stored as https, retrieved without scheme", 101 | storeURL: "https://foobar.example.com:2376", 102 | readURL: "foobar.example.com", 103 | }, 104 | { 105 | doc: "stored with path, retrieved without", 106 | storeURL: "https://foobar.example.com:1234/one/two", 107 | readURL: "https://foobar.example.com:1234", 108 | }, 109 | } 110 | 111 | helper := Osxkeychain{} 112 | t.Cleanup(func() { 113 | for _, tc := range tests { 114 | if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) { 115 | t.Errorf("cleanup: failed to delete '%s': %v", tc.storeURL, err) 116 | } 117 | } 118 | }) 119 | 120 | // Clean store before testing. 121 | for _, tc := range tests { 122 | if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) { 123 | t.Errorf("prepare: failed to delete '%s': %v", tc.storeURL, err) 124 | } 125 | } 126 | 127 | for _, tc := range tests { 128 | tc := tc 129 | t.Run(tc.doc, func(t *testing.T) { 130 | c := &credentials.Credentials{ServerURL: tc.storeURL, Username: "hello", Secret: "world"} 131 | if err := helper.Add(c); err != nil { 132 | t.Fatalf("Error: failed to store secret for URL %q: %s", tc.storeURL, err) 133 | } 134 | if _, _, err := helper.Get(tc.readURL); err != nil { 135 | t.Errorf("Error: failed to read secret for URL %q using %q: %s", tc.storeURL, tc.readURL, err) 136 | } 137 | if err := helper.Delete(tc.storeURL); err != nil { 138 | t.Error(err) 139 | } 140 | }) 141 | } 142 | } 143 | 144 | func TestOSXKeychainHelperStoreWithUncleanPath(t *testing.T) { 145 | helper := Osxkeychain{} 146 | creds := &credentials.Credentials{ 147 | ServerURL: "https://::1:8080//////location/../../hello", 148 | Username: "testuser", 149 | Secret: "testsecret", 150 | } 151 | 152 | // Clean store before and after the test. 153 | defer helper.Delete(creds.ServerURL) 154 | if err := helper.Delete(creds.ServerURL); err != nil && !credentials.IsErrCredentialsNotFound(err) { 155 | t.Errorf("prepare: failed to delete '%s': %v", creds.ServerURL, err) 156 | } 157 | 158 | // Store the credentials 159 | if err := helper.Add(creds); err != nil { 160 | t.Fatalf("Error: failed to store credentials with unclean path %q: %s", creds.ServerURL, err) 161 | } 162 | 163 | // Retrieve and verify credentials 164 | username, secret, err := helper.Get(creds.ServerURL) 165 | if err != nil { 166 | t.Fatalf("Error: failed to retrieve credentials with unclean path %q: %s", creds.ServerURL, err) 167 | } 168 | 169 | if username != creds.Username { 170 | t.Errorf("Error: expected username %s, got %s", creds.Username, username) 171 | } 172 | if secret != creds.Secret { 173 | t.Errorf("Error: expected secret %s, got %s", creds.Secret, secret) 174 | } 175 | } 176 | 177 | // TestOSXKeychainHelperRetrieveStrict verifies that only matching secrets are 178 | // returned. 179 | func TestOSXKeychainHelperRetrieveStrict(t *testing.T) { 180 | tests := []struct { 181 | doc string 182 | storeURL string 183 | readURL string 184 | }{ 185 | { 186 | doc: "stored as https, retrieved using http", 187 | storeURL: "https://foobar.example.com:2376", 188 | readURL: "http://foobar.example.com:2376", 189 | }, 190 | { 191 | doc: "stored as http, retrieved using https", 192 | storeURL: "http://foobar.example.com:2376", 193 | readURL: "https://foobar.example.com:2376", 194 | }, 195 | { 196 | // stored as http, retrieved without a scheme specified (hence, using the default https://) 197 | doc: "stored as http, retrieved without scheme", 198 | storeURL: "http://foobar.example.com", 199 | readURL: "foobar.example.com:5678", 200 | }, 201 | { 202 | doc: "non-matching ports", 203 | storeURL: "https://foobar.example.com:1234", 204 | readURL: "https://foobar.example.com:5678", 205 | }, 206 | // TODO: is this desired behavior? The other way round does work 207 | // { 208 | // doc: "non-matching ports (stored without port)", 209 | // storeURL: "https://foobar.example.com", 210 | // readURL: "https://foobar.example.com:5678", 211 | // }, 212 | { 213 | doc: "non-matching paths", 214 | storeURL: "https://foobar.example.com:1234/one/two", 215 | readURL: "https://foobar.example.com:1234/five/six", 216 | }, 217 | } 218 | 219 | helper := Osxkeychain{} 220 | t.Cleanup(func() { 221 | for _, tc := range tests { 222 | if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) { 223 | t.Errorf("cleanup: failed to delete '%s': %v", tc.storeURL, err) 224 | } 225 | } 226 | }) 227 | 228 | // Clean store before testing. 229 | for _, tc := range tests { 230 | if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) { 231 | t.Errorf("prepare: failed to delete '%s': %v", tc.storeURL, err) 232 | } 233 | } 234 | 235 | for _, tc := range tests { 236 | tc := tc 237 | t.Run(tc.doc, func(t *testing.T) { 238 | c := &credentials.Credentials{ServerURL: tc.storeURL, Username: "hello", Secret: "world"} 239 | if err := helper.Add(c); err != nil { 240 | t.Fatalf("Error: failed to store secret for URL %q: %s", tc.storeURL, err) 241 | } 242 | if _, _, err := helper.Get(tc.readURL); err == nil { 243 | t.Errorf("Error: managed to read secret for URL %q using %q, but should not be able to", tc.storeURL, tc.readURL) 244 | } 245 | if err := helper.Delete(tc.storeURL); err != nil { 246 | t.Error(err) 247 | } 248 | }) 249 | } 250 | } 251 | 252 | // TestOSXKeychainHelperStoreRetrieve verifies that secrets stored in the 253 | // the keychain can be read back using the URL that was used to store them. 254 | func TestOSXKeychainHelperStoreRetrieve(t *testing.T) { 255 | tests := []struct { 256 | url string 257 | }{ 258 | {url: "foobar.example.com"}, 259 | {url: "foobar.example.com:2376"}, 260 | {url: "//foobar.example.com:2376"}, 261 | {url: "https://foobar.example.com:2376"}, 262 | {url: "http://foobar.example.com:2376"}, 263 | {url: "https://foobar.example.com:2376/some/path"}, 264 | {url: "https://foobar.example.com:2376/some/other/path"}, 265 | {url: "https://foobar.example.com:2376/some/other/path?foo=bar"}, 266 | } 267 | 268 | helper := Osxkeychain{} 269 | t.Cleanup(func() { 270 | for _, tc := range tests { 271 | if err := helper.Delete(tc.url); err != nil && !credentials.IsErrCredentialsNotFound(err) { 272 | t.Errorf("cleanup: failed to delete '%s': %v", tc.url, err) 273 | } 274 | } 275 | }) 276 | 277 | // Clean store before testing. 278 | for _, tc := range tests { 279 | if err := helper.Delete(tc.url); err != nil && !credentials.IsErrCredentialsNotFound(err) { 280 | t.Errorf("prepare: failed to delete '%s': %v", tc.url, err) 281 | } 282 | } 283 | 284 | // Note that we don't delete between individual tests here, to verify that 285 | // subsequent stores/overwrites don't affect storing / retrieving secrets. 286 | for i, tc := range tests { 287 | tc := tc 288 | t.Run(tc.url, func(t *testing.T) { 289 | c := &credentials.Credentials{ 290 | ServerURL: tc.url, 291 | Username: fmt.Sprintf("user-%d", i), 292 | Secret: fmt.Sprintf("secret-%d", i), 293 | } 294 | 295 | if err := helper.Add(c); err != nil { 296 | t.Fatalf("Error: failed to store secret for URL: %s: %s", tc.url, err) 297 | } 298 | user, secret, err := helper.Get(tc.url) 299 | if err != nil { 300 | t.Fatalf("Error: failed to read secret for URL %q: %s", tc.url, err) 301 | } 302 | if user != c.Username { 303 | t.Errorf("Error: expected username %s, got username %s for URL: %s", c.Username, user, tc.url) 304 | } 305 | if secret != c.Secret { 306 | t.Errorf("Error: expected secret %s, got secret %s for URL: %s", c.Secret, secret, tc.url) 307 | } 308 | }) 309 | } 310 | } 311 | 312 | func TestMissingCredentials(t *testing.T) { 313 | const nonExistingCred = "https://adsfasdf.invalid/asdfsdddd" 314 | helper := Osxkeychain{} 315 | _, _, err := helper.Get(nonExistingCred) 316 | if !credentials.IsErrCredentialsNotFound(err) { 317 | t.Errorf("expected ErrCredentialsNotFound, got %v", err) 318 | } 319 | err = helper.Delete(nonExistingCred) 320 | if !credentials.IsErrCredentialsNotFound(err) { 321 | t.Errorf("expected ErrCredentialsNotFound, got %v", err) 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /pass/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/docker/docker-credential-helpers/credentials" 5 | "github.com/docker/docker-credential-helpers/pass" 6 | ) 7 | 8 | func main() { 9 | credentials.Serve(pass.Pass{}) 10 | } 11 | -------------------------------------------------------------------------------- /pass/pass.go: -------------------------------------------------------------------------------- 1 | // Package pass implements a `pass` based credential helper. Passwords are stored 2 | // as arguments to pass of the form: "$PASS_FOLDER/base64-url(serverURL)/username". 3 | // We base64-url encode the serverURL, because under the hood pass uses files and 4 | // folders, so /s will get translated into additional folders. 5 | package pass 6 | 7 | import ( 8 | "bytes" 9 | "encoding/base64" 10 | "errors" 11 | "fmt" 12 | "io/fs" 13 | "os" 14 | "os/exec" 15 | "path" 16 | "path/filepath" 17 | "strings" 18 | "sync" 19 | 20 | "github.com/docker/docker-credential-helpers/credentials" 21 | ) 22 | 23 | // PASS_FOLDER contains the directory where credentials are stored 24 | const PASS_FOLDER = "docker-credential-helpers" //nolint:revive 25 | 26 | // Pass handles secrets using pass as a store. 27 | type Pass struct{} 28 | 29 | // Ideally these would be stored as members of Pass, but since all of Pass's 30 | // methods have value receivers, not pointer receivers, and changing that is 31 | // backwards incompatible, we assume that all Pass instances share the same configuration 32 | var ( 33 | // initializationMutex is held while initializing so that only one 'pass' 34 | // round-tripping is done to check pass is functioning. 35 | initializationMutex sync.Mutex 36 | passInitialized bool 37 | ) 38 | 39 | // CheckInitialized checks whether the password helper can be used. It 40 | // internally caches and so may be safely called multiple times with no impact 41 | // on performance, though the first call may take longer. 42 | func (p Pass) CheckInitialized() bool { 43 | return p.checkInitialized() == nil 44 | } 45 | 46 | func (p Pass) checkInitialized() error { 47 | initializationMutex.Lock() 48 | defer initializationMutex.Unlock() 49 | if passInitialized { 50 | return nil 51 | } 52 | // We just run a `pass ls`, if it fails then pass is not initialized. 53 | _, err := p.runPassHelper("", "ls") 54 | if err != nil { 55 | return fmt.Errorf("pass not initialized: %v", err) 56 | } 57 | passInitialized = true 58 | return nil 59 | } 60 | 61 | func (p Pass) runPass(stdinContent string, args ...string) (string, error) { 62 | if err := p.checkInitialized(); err != nil { 63 | return "", err 64 | } 65 | return p.runPassHelper(stdinContent, args...) 66 | } 67 | 68 | func (p Pass) runPassHelper(stdinContent string, args ...string) (string, error) { 69 | var stdout, stderr bytes.Buffer 70 | cmd := exec.Command("pass", args...) 71 | cmd.Stdin = strings.NewReader(stdinContent) 72 | cmd.Stdout = &stdout 73 | cmd.Stderr = &stderr 74 | 75 | err := cmd.Run() 76 | if err != nil { 77 | return "", fmt.Errorf("%s: %s", err, stderr.String()) 78 | } 79 | 80 | // trim newlines; pass v1.7.1+ includes a newline at the end of `show` output 81 | return strings.TrimRight(stdout.String(), "\n\r"), nil 82 | } 83 | 84 | // Add adds new credentials to the keychain. 85 | func (p Pass) Add(creds *credentials.Credentials) error { 86 | if creds == nil { 87 | return errors.New("missing credentials") 88 | } 89 | 90 | encoded := encodeServerURL(creds.ServerURL) 91 | _, err := p.runPass(creds.Secret, "insert", "-f", "-m", path.Join(PASS_FOLDER, encoded, creds.Username)) 92 | return err 93 | } 94 | 95 | // Delete removes credentials from the store. 96 | func (p Pass) Delete(serverURL string) error { 97 | if serverURL == "" { 98 | return errors.New("missing server url") 99 | } 100 | 101 | encoded := encodeServerURL(serverURL) 102 | _, err := p.runPass("", "rm", "-rf", path.Join(PASS_FOLDER, encoded)) 103 | return err 104 | } 105 | 106 | func getPassDir() string { 107 | if passDir := os.Getenv("PASSWORD_STORE_DIR"); passDir != "" { 108 | return passDir 109 | } 110 | home, _ := os.UserHomeDir() 111 | return filepath.Join(home, ".password-store") 112 | } 113 | 114 | // listPassDir lists all the contents of a directory in the password store. 115 | // Pass uses fancy unicode to emit stuff to stdout, so rather than try 116 | // and parse this, let's just look at the directory structure instead. 117 | func listPassDir(args ...string) ([]os.FileInfo, error) { 118 | passDir := getPassDir() 119 | p := path.Join(append([]string{passDir, PASS_FOLDER}, args...)...) 120 | entries, err := os.ReadDir(p) 121 | if err != nil { 122 | if os.IsNotExist(err) { 123 | return []os.FileInfo{}, nil 124 | } 125 | return nil, err 126 | } 127 | infos := make([]fs.FileInfo, 0, len(entries)) 128 | for _, entry := range entries { 129 | info, err := entry.Info() 130 | if err != nil { 131 | return nil, err 132 | } 133 | infos = append(infos, info) 134 | } 135 | return infos, nil 136 | } 137 | 138 | // Get returns the username and secret to use for a given registry server URL. 139 | func (p Pass) Get(serverURL string) (string, string, error) { 140 | if serverURL == "" { 141 | return "", "", errors.New("missing server url") 142 | } 143 | 144 | encoded := encodeServerURL(serverURL) 145 | usernames, err := listPassDir(encoded) 146 | if err != nil { 147 | return "", "", err 148 | } 149 | 150 | if len(usernames) < 1 { 151 | return "", "", credentials.NewErrCredentialsNotFound() 152 | } 153 | 154 | actual := strings.TrimSuffix(usernames[0].Name(), ".gpg") 155 | secret, err := p.runPass("", "show", path.Join(PASS_FOLDER, encoded, actual)) 156 | return actual, secret, err 157 | } 158 | 159 | // List returns the stored URLs and corresponding usernames for a given credentials label 160 | func (p Pass) List() (map[string]string, error) { 161 | servers, err := listPassDir() 162 | if err != nil { 163 | return nil, err 164 | } 165 | 166 | resp := map[string]string{} 167 | 168 | for _, server := range servers { 169 | if !server.IsDir() { 170 | continue 171 | } 172 | 173 | serverURL, err := decodeServerURL(server.Name()) 174 | if err != nil { 175 | return nil, err 176 | } 177 | 178 | usernames, err := listPassDir(server.Name()) 179 | if err != nil { 180 | return nil, err 181 | } 182 | 183 | if len(usernames) < 1 { 184 | continue 185 | } 186 | 187 | resp[serverURL] = strings.TrimSuffix(usernames[0].Name(), ".gpg") 188 | } 189 | 190 | return resp, nil 191 | } 192 | 193 | // encodeServerURL returns the serverURL in base64-URL encoding to use 194 | // as directory-name in pass storage. 195 | func encodeServerURL(serverURL string) string { 196 | return base64.URLEncoding.EncodeToString([]byte(serverURL)) 197 | } 198 | 199 | // decodeServerURL decodes base64-URL encoded serverURL. ServerURLs are 200 | // used in encoded format for directory-names in pass storage. 201 | func decodeServerURL(encodedServerURL string) (string, error) { 202 | serverURL, err := base64.URLEncoding.DecodeString(encodedServerURL) 203 | if err != nil { 204 | return "", err 205 | } 206 | return string(serverURL), nil 207 | } 208 | -------------------------------------------------------------------------------- /pass/pass_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package pass 4 | 5 | import ( 6 | "os" 7 | "path" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/docker/docker-credential-helpers/credentials" 12 | ) 13 | 14 | func TestPassHelper(t *testing.T) { 15 | creds := &credentials.Credentials{ 16 | ServerURL: "https://foobar.example.com:2376/v1", 17 | Username: "nothing", 18 | Secret: "isthebestmeshuggahalbum", 19 | } 20 | 21 | helper := Pass{} 22 | if err := helper.checkInitialized(); err != nil { 23 | t.Error(err) 24 | } 25 | 26 | if err := helper.Add(creds); err != nil { 27 | t.Error(err) 28 | } 29 | 30 | u, s, err := helper.Get(creds.ServerURL) 31 | if err != nil { 32 | t.Error(err) 33 | } 34 | if u != creds.Username { 35 | t.Errorf("invalid username %s", u) 36 | } 37 | if s != creds.Secret { 38 | t.Errorf("invalid secret: %s", s) 39 | } 40 | 41 | if err := helper.Delete(creds.ServerURL); err != nil { 42 | t.Error(err) 43 | } 44 | if _, _, err := helper.Get(creds.ServerURL); !credentials.IsErrCredentialsNotFound(err) { 45 | t.Errorf("expected credentials not found, actual: %v", err) 46 | } 47 | } 48 | 49 | func TestPassHelperCheckInit(t *testing.T) { 50 | helper := Pass{} 51 | if v := helper.CheckInitialized(); !v { 52 | t.Errorf("expected true, actual: %v", v) 53 | } 54 | } 55 | 56 | func TestPassHelperList(t *testing.T) { 57 | creds := []*credentials.Credentials{ 58 | { 59 | ServerURL: "https://foobar.example.com:2376/v1", 60 | Username: "foo", 61 | Secret: "isthebestmeshuggahalbum", 62 | }, 63 | { 64 | ServerURL: "https://foobar.example.com:2375/v1", 65 | Username: "bar", 66 | Secret: "isthebestmeshuggahalbum", 67 | }, 68 | } 69 | 70 | helper := Pass{} 71 | if err := helper.checkInitialized(); err != nil { 72 | t.Error(err) 73 | } 74 | 75 | for _, cred := range creds { 76 | if err := helper.Add(cred); err != nil { 77 | t.Error(err) 78 | } 79 | } 80 | 81 | credsList, err := helper.List() 82 | if err != nil { 83 | t.Error(err) 84 | } 85 | for server, username := range credsList { 86 | if !(strings.HasSuffix(server, "2376/v1") || strings.HasSuffix(server, "2375/v1")) { 87 | t.Errorf("invalid url: %s", server) 88 | } 89 | if !(username == "foo" || username == "bar") { 90 | t.Errorf("invalid username: %v", username) 91 | } 92 | 93 | u, s, err := helper.Get(server) 94 | if err != nil { 95 | t.Error(err) 96 | } 97 | if u != username { 98 | t.Errorf("invalid username %s", u) 99 | } 100 | if s != "isthebestmeshuggahalbum" { 101 | t.Errorf("invalid secret: %s", s) 102 | } 103 | 104 | if err := helper.Delete(server); err != nil { 105 | t.Error(err) 106 | } 107 | if _, _, err := helper.Get(server); !credentials.IsErrCredentialsNotFound(err) { 108 | t.Errorf("expected credentials not found, actual: %v", err) 109 | } 110 | } 111 | 112 | credsList, err = helper.List() 113 | if err != nil { 114 | t.Error(err) 115 | } 116 | if len(credsList) != 0 { 117 | t.Error("didn't delete all creds?") 118 | } 119 | } 120 | 121 | // TestPassHelperWithEmptyServer verifies that empty directories (servers 122 | // without credentials) are ignored, but still returns credentials for other 123 | // servers. 124 | func TestPassHelperWithEmptyServer(t *testing.T) { 125 | helper := Pass{} 126 | if err := helper.checkInitialized(); err != nil { 127 | t.Error(err) 128 | } 129 | 130 | creds := []*credentials.Credentials{ 131 | { 132 | ServerURL: "https://myreqistry.example.com:2375/v1", 133 | Username: "foo", 134 | Secret: "isthebestmeshuggahalbum", 135 | }, 136 | { 137 | ServerURL: "https://index.example.com/v1//access-token", 138 | }, 139 | } 140 | 141 | t.Cleanup(func() { 142 | for _, cred := range creds { 143 | _ = helper.Delete(cred.ServerURL) 144 | } 145 | }) 146 | 147 | for _, cred := range creds { 148 | if cred.Username != "" { 149 | if err := helper.Add(cred); err != nil { 150 | t.Error(err) 151 | } 152 | } else { 153 | // No credentials; create an empty directory for this server. 154 | serverURL := encodeServerURL(cred.ServerURL) 155 | p := path.Join(getPassDir(), PASS_FOLDER, serverURL) 156 | if err := os.Mkdir(p, 0o755); err != nil { 157 | t.Error(err) 158 | } 159 | } 160 | } 161 | 162 | credsList, err := helper.List() 163 | if err != nil { 164 | t.Error(err) 165 | } 166 | if len(credsList) == 0 { 167 | t.Error("expected credentials to be returned, but got none") 168 | } 169 | for _, cred := range creds { 170 | if cred.Username != "" { 171 | userName, secret, err := helper.Get(cred.ServerURL) 172 | if err != nil { 173 | t.Error(err) 174 | } 175 | if userName != cred.Username { 176 | t.Errorf("expected username %q, actual: %q", cred.Username, userName) 177 | } 178 | if secret != cred.Secret { 179 | t.Errorf("expected secret %q, actual: %q", cred.Secret, secret) 180 | } 181 | } else { 182 | _, _, err := helper.Get(cred.ServerURL) 183 | if !credentials.IsErrCredentialsNotFound(err) { 184 | t.Errorf("expected credentials not found, actual: %v", err) 185 | } 186 | } 187 | } 188 | } 189 | 190 | func TestMissingCred(t *testing.T) { 191 | helper := Pass{} 192 | if _, _, err := helper.Get("garbage"); !credentials.IsErrCredentialsNotFound(err) { 193 | t.Errorf("expected credentials not found, actual: %v", err) 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /registryurl/parse.go: -------------------------------------------------------------------------------- 1 | package registryurl 2 | 3 | import ( 4 | "errors" 5 | "net/url" 6 | "strings" 7 | ) 8 | 9 | // Parse parses and validates a given serverURL to an url.URL, and 10 | // returns an error if validation failed. Querystring parameters are 11 | // omitted in the resulting URL, because they are not used in the helper. 12 | // 13 | // If serverURL does not have a valid scheme, `//` is used as scheme 14 | // before parsing. This prevents the hostname being used as path, 15 | // and the credentials being stored without host. 16 | func Parse(registryURL string) (*url.URL, error) { 17 | // Check if registryURL has a scheme, otherwise add `//` as scheme. 18 | if !strings.Contains(registryURL, "://") && !strings.HasPrefix(registryURL, "//") { 19 | registryURL = "//" + registryURL 20 | } 21 | 22 | u, err := url.Parse(registryURL) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | if u.Scheme != "" && u.Scheme != "https" && u.Scheme != "http" { 28 | return nil, errors.New("unsupported scheme: " + u.Scheme) 29 | } 30 | 31 | if u.Hostname() == "" { 32 | return nil, errors.New("no hostname in URL") 33 | } 34 | 35 | u.RawQuery = "" 36 | return u, nil 37 | } 38 | 39 | // GetHostname returns the hostname of the URL 40 | // 41 | // Deprecated: use url.Hostname() 42 | func GetHostname(u *url.URL) string { 43 | return u.Hostname() 44 | } 45 | 46 | // GetPort returns the port number of the URL 47 | // 48 | // Deprecated: use url.Port() 49 | func GetPort(u *url.URL) string { 50 | return u.Port() 51 | } 52 | -------------------------------------------------------------------------------- /registryurl/parse_test.go: -------------------------------------------------------------------------------- 1 | package registryurl 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | ) 7 | 8 | // TestHelperParseURL verifies that a // "scheme" is added to URLs, 9 | // and that invalid URLs produce an error. 10 | func TestHelperParseURL(t *testing.T) { 11 | tests := []struct { 12 | url string 13 | expectedURL string 14 | err error 15 | }{ 16 | { 17 | url: "foobar.example.com", 18 | expectedURL: "//foobar.example.com", 19 | }, 20 | { 21 | url: "foobar.example.com:2376", 22 | expectedURL: "//foobar.example.com:2376", 23 | }, 24 | { 25 | url: "//foobar.example.com:2376", 26 | expectedURL: "//foobar.example.com:2376", 27 | }, 28 | { 29 | url: "http://foobar.example.com:2376", 30 | expectedURL: "http://foobar.example.com:2376", 31 | }, 32 | { 33 | url: "https://foobar.example.com:2376", 34 | expectedURL: "https://foobar.example.com:2376", 35 | }, 36 | { 37 | url: "https://foobar.example.com:2376/some/path", 38 | expectedURL: "https://foobar.example.com:2376/some/path", 39 | }, 40 | { 41 | url: "https://foobar.example.com:2376/some/other/path?foo=bar", 42 | expectedURL: "https://foobar.example.com:2376/some/other/path", 43 | }, 44 | { 45 | url: "/foobar.example.com", 46 | err: errors.New("no hostname in URL"), 47 | }, 48 | { 49 | url: "ftp://foobar.example.com:2376", 50 | err: errors.New("unsupported scheme: ftp"), 51 | }, 52 | } 53 | 54 | for _, tc := range tests { 55 | tc := tc 56 | t.Run(tc.url, func(t *testing.T) { 57 | u, err := Parse(tc.url) 58 | 59 | if tc.err == nil && err != nil { 60 | t.Fatalf("Error: failed to parse URL %q: %s", tc.url, err) 61 | } 62 | if tc.err != nil && err == nil { 63 | t.Fatalf("Error: expected error %q, got none when parsing URL %q", tc.err, tc.url) 64 | } 65 | if tc.err != nil && err.Error() != tc.err.Error() { 66 | t.Fatalf("Error: expected error %q, got %q when parsing URL %q", tc.err, err, tc.url) 67 | } 68 | if u != nil && u.String() != tc.expectedURL { 69 | t.Errorf("Error: expected URL: %q, but got %q for URL: %q", tc.expectedURL, u.String(), tc.url) 70 | } 71 | }) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /secretservice/cmd/main.go: -------------------------------------------------------------------------------- 1 | //go:build linux && cgo 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/docker/docker-credential-helpers/credentials" 7 | "github.com/docker/docker-credential-helpers/secretservice" 8 | ) 9 | 10 | func main() { 11 | credentials.Serve(secretservice.Secretservice{}) 12 | } 13 | -------------------------------------------------------------------------------- /secretservice/secretservice.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "secretservice.h" 4 | 5 | const SecretSchema *docker_get_schema(void) 6 | { 7 | static const SecretSchema docker_schema = { 8 | "io.docker.Credentials", SECRET_SCHEMA_NONE, 9 | { 10 | { "label", SECRET_SCHEMA_ATTRIBUTE_STRING }, 11 | { "server", SECRET_SCHEMA_ATTRIBUTE_STRING }, 12 | { "username", SECRET_SCHEMA_ATTRIBUTE_STRING }, 13 | { "docker_cli", SECRET_SCHEMA_ATTRIBUTE_STRING }, 14 | { "NULL", 0 }, 15 | } 16 | }; 17 | return &docker_schema; 18 | } 19 | 20 | GError *add(char *label, char *server, char *username, char *secret, char *displaylabel) { 21 | GError *err = NULL; 22 | 23 | secret_password_store_sync (DOCKER_SCHEMA, SECRET_COLLECTION_DEFAULT, 24 | displaylabel, secret, NULL, &err, 25 | "label", label, 26 | "server", server, 27 | "username", username, 28 | "docker_cli", "1", 29 | NULL); 30 | return err; 31 | } 32 | 33 | GError *delete(char *server) { 34 | GError *err = NULL; 35 | 36 | secret_password_clear_sync(DOCKER_SCHEMA, NULL, &err, 37 | "server", server, 38 | "docker_cli", "1", 39 | NULL); 40 | if (err != NULL) 41 | return err; 42 | return NULL; 43 | } 44 | 45 | char *get_attribute(const char *attribute, SecretItem *item) { 46 | GHashTable *attributes; 47 | GHashTableIter iter; 48 | gchar *value, *key; 49 | 50 | attributes = secret_item_get_attributes(item); 51 | g_hash_table_iter_init(&iter, attributes); 52 | while (g_hash_table_iter_next(&iter, (void **)&key, (void **)&value)) { 53 | if (strncmp(key, attribute, strlen(key)) == 0) 54 | return (char *)value; 55 | } 56 | g_hash_table_unref(attributes); 57 | return NULL; 58 | } 59 | 60 | GError *get(char *server, char **username, char **secret) { 61 | GError *err = NULL; 62 | GHashTable *attributes; 63 | SecretService *service; 64 | GList *items, *l; 65 | SecretSearchFlags flags = SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK; 66 | SecretValue *secretValue; 67 | gsize length; 68 | gchar *value; 69 | 70 | attributes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); 71 | g_hash_table_insert(attributes, g_strdup("server"), g_strdup(server)); 72 | g_hash_table_insert(attributes, g_strdup("docker_cli"), g_strdup("1")); 73 | 74 | service = secret_service_get_sync(SECRET_SERVICE_NONE, NULL, &err); 75 | if (err == NULL) { 76 | items = secret_service_search_sync(service, DOCKER_SCHEMA, attributes, flags, NULL, &err); 77 | if (err == NULL) { 78 | for (l = items; l != NULL; l = g_list_next(l)) { 79 | value = secret_item_get_schema_name(l->data); 80 | if (strncmp(value, "io.docker.Credentials", strlen(value)) != 0) { 81 | g_free(value); 82 | continue; 83 | } 84 | g_free(value); 85 | secretValue = secret_item_get_secret(l->data); 86 | if (secretValue == NULL) { 87 | continue; 88 | } 89 | if (secret != NULL) { 90 | *secret = strdup(secret_value_get(secretValue, &length)); 91 | secret_value_unref(secretValue); 92 | } 93 | *username = get_attribute("username", l->data); 94 | } 95 | g_list_free_full(items, g_object_unref); 96 | } 97 | g_object_unref(service); 98 | } 99 | g_hash_table_unref(attributes); 100 | if (err != NULL) { 101 | return err; 102 | } 103 | return NULL; 104 | } 105 | 106 | GError *list(char *ref_label, char *** paths, char *** accts, unsigned int *list_l) { 107 | GList *items; 108 | GError *err = NULL; 109 | SecretService *service; 110 | SecretSearchFlags flags = SECRET_SEARCH_LOAD_SECRETS | SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK; 111 | GHashTable *attributes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); 112 | 113 | // List credentials with the right label only 114 | g_hash_table_insert(attributes, g_strdup("label"), g_strdup(ref_label)); 115 | 116 | service = secret_service_get_sync(SECRET_SERVICE_NONE, NULL, &err); 117 | if (err != NULL) { 118 | return err; 119 | } 120 | 121 | items = secret_service_search_sync(service, NULL, attributes, flags, NULL, &err); 122 | int numKeys = g_list_length(items); 123 | if (err != NULL) { 124 | return err; 125 | } 126 | 127 | char **tmp_paths = (char **) calloc(1,(int)sizeof(char *)*numKeys); 128 | char **tmp_accts = (char **) calloc(1,(int)sizeof(char *)*numKeys); 129 | 130 | // items now contains our keys from the gnome keyring 131 | // we will now put it in our two lists to return it to go 132 | GList *current; 133 | int listNumber = 0; 134 | for(current = items; current!=NULL; current = current->next) { 135 | char *pathTmp = secret_item_get_label(current->data); 136 | // you cannot have a key without a label in the gnome keyring 137 | char *acctTmp = get_attribute("username",current->data); 138 | if (acctTmp==NULL) { 139 | acctTmp = "account not defined"; 140 | } 141 | 142 | tmp_paths[listNumber] = (char *) calloc(1, sizeof(char)*(strlen(pathTmp)+1)); 143 | tmp_accts[listNumber] = (char *) calloc(1, sizeof(char)*(strlen(acctTmp)+1)); 144 | 145 | memcpy(tmp_paths[listNumber], pathTmp, sizeof(char)*(strlen(pathTmp)+1)); 146 | memcpy(tmp_accts[listNumber], acctTmp, sizeof(char)*(strlen(acctTmp)+1)); 147 | 148 | listNumber = listNumber + 1; 149 | } 150 | 151 | *paths = (char **) realloc(tmp_paths, (int)sizeof(char *)*listNumber); 152 | *accts = (char **) realloc(tmp_accts, (int)sizeof(char *)*listNumber); 153 | 154 | *list_l = listNumber; 155 | 156 | return NULL; 157 | } 158 | 159 | void freeListData(char *** data, unsigned int length) { 160 | int i; 161 | for(i=0; i 10 | */ 11 | import "C" 12 | 13 | import ( 14 | "errors" 15 | "unsafe" 16 | 17 | "github.com/docker/docker-credential-helpers/credentials" 18 | ) 19 | 20 | // Secretservice handles secrets using Linux secret-service as a store. 21 | type Secretservice struct{} 22 | 23 | // Add adds new credentials to the keychain. 24 | func (h Secretservice) Add(creds *credentials.Credentials) error { 25 | if creds == nil { 26 | return errors.New("missing credentials") 27 | } 28 | credsLabel := C.CString(credentials.CredsLabel) 29 | defer C.free(unsafe.Pointer(credsLabel)) 30 | server := C.CString(creds.ServerURL) 31 | defer C.free(unsafe.Pointer(server)) 32 | username := C.CString(creds.Username) 33 | defer C.free(unsafe.Pointer(username)) 34 | secret := C.CString(creds.Secret) 35 | defer C.free(unsafe.Pointer(secret)) 36 | displayLabel := C.CString("Registry credentials for " + creds.ServerURL) 37 | defer C.free(unsafe.Pointer(displayLabel)) 38 | 39 | if err := C.add(credsLabel, server, username, secret, displayLabel); err != nil { 40 | defer C.g_error_free(err) 41 | errMsg := (*C.char)(unsafe.Pointer(err.message)) 42 | return errors.New(C.GoString(errMsg)) 43 | } 44 | return nil 45 | } 46 | 47 | // Delete removes credentials from the store. 48 | func (h Secretservice) Delete(serverURL string) error { 49 | if serverURL == "" { 50 | return errors.New("missing server url") 51 | } 52 | server := C.CString(serverURL) 53 | defer C.free(unsafe.Pointer(server)) 54 | 55 | if err := C.delete(server); err != nil { 56 | defer C.g_error_free(err) 57 | errMsg := (*C.char)(unsafe.Pointer(err.message)) 58 | return errors.New(C.GoString(errMsg)) 59 | } 60 | return nil 61 | } 62 | 63 | // Get returns the username and secret to use for a given registry server URL. 64 | func (h Secretservice) Get(serverURL string) (string, string, error) { 65 | if serverURL == "" { 66 | return "", "", errors.New("missing server url") 67 | } 68 | var username *C.char 69 | defer C.free(unsafe.Pointer(username)) 70 | var secret *C.char 71 | defer C.free(unsafe.Pointer(secret)) 72 | server := C.CString(serverURL) 73 | defer C.free(unsafe.Pointer(server)) 74 | 75 | err := C.get(server, &username, &secret) 76 | if err != nil { 77 | defer C.g_error_free(err) 78 | errMsg := (*C.char)(unsafe.Pointer(err.message)) 79 | return "", "", errors.New(C.GoString(errMsg)) 80 | } 81 | user := C.GoString(username) 82 | pass := C.GoString(secret) 83 | if pass == "" { 84 | return "", "", credentials.NewErrCredentialsNotFound() 85 | } 86 | return user, pass, nil 87 | } 88 | 89 | // List returns the stored URLs and corresponding usernames for a given credentials label 90 | func (h Secretservice) List() (map[string]string, error) { 91 | credsLabelC := C.CString(credentials.CredsLabel) 92 | defer C.free(unsafe.Pointer(credsLabelC)) 93 | 94 | var pathsC **C.char 95 | defer C.free(unsafe.Pointer(pathsC)) 96 | var acctsC **C.char 97 | defer C.free(unsafe.Pointer(acctsC)) 98 | var listLenC C.uint 99 | err := C.list(credsLabelC, &pathsC, &acctsC, &listLenC) 100 | defer C.freeListData(&pathsC, listLenC) 101 | defer C.freeListData(&acctsC, listLenC) 102 | if err != nil { 103 | defer C.g_error_free(err) 104 | errMsg := (*C.char)(unsafe.Pointer(err.message)) 105 | return nil, errors.New(C.GoString(errMsg)) 106 | } 107 | 108 | resp := make(map[string]string) 109 | 110 | listLen := int(listLenC) 111 | if listLen == 0 { 112 | return resp, nil 113 | } 114 | // The maximum capacity of the following two slices is limited to (2^29)-1 to remain compatible 115 | // with 32-bit platforms. The size of a `*C.char` (a pointer) is 4 Byte on a 32-bit system 116 | // and (2^29)*4 == math.MaxInt32 + 1. -- See issue golang/go#13656 117 | pathTmp := (*[(1 << 29) - 1]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen] 118 | acctTmp := (*[(1 << 29) - 1]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen] 119 | for i := 0; i < listLen; i++ { 120 | resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i]) 121 | } 122 | 123 | return resp, nil 124 | } 125 | -------------------------------------------------------------------------------- /secretservice/secretservice.h: -------------------------------------------------------------------------------- 1 | #define SECRET_WITH_UNSTABLE 1 2 | #define SECRET_API_SUBJECT_TO_CHANGE 1 3 | #include 4 | 5 | const SecretSchema *docker_get_schema(void) G_GNUC_CONST; 6 | 7 | #define DOCKER_SCHEMA docker_get_schema() 8 | 9 | GError *add(char *label, char *server, char *username, char *secret, char *displaylabel); 10 | GError *delete(char *server); 11 | GError *get(char *server, char **username, char **secret); 12 | GError *list(char *label, char *** paths, char *** accts, unsigned int *list_l); 13 | void freeListData(char *** data, unsigned int length); 14 | -------------------------------------------------------------------------------- /secretservice/secretservice_test.go: -------------------------------------------------------------------------------- 1 | //go:build linux && cgo 2 | 3 | package secretservice 4 | 5 | import ( 6 | "strings" 7 | "testing" 8 | 9 | "github.com/docker/docker-credential-helpers/credentials" 10 | ) 11 | 12 | func TestSecretServiceHelper(t *testing.T) { 13 | t.Skip("test requires gnome-keyring but travis CI doesn't have it") 14 | 15 | creds := &credentials.Credentials{ 16 | ServerURL: "https://foobar.example.com:2376/v1", 17 | Username: "foobar", 18 | Secret: "foobarbaz", 19 | } 20 | 21 | helper := Secretservice{} 22 | 23 | // Check how many docker credentials we have when starting the test 24 | oldAuths, err := helper.List() 25 | if err != nil { 26 | t.Fatal(err) 27 | } 28 | 29 | // If any docker credentials with the tests values we are providing, we 30 | // remove them as they probably come from a previous failed test 31 | for k, v := range oldAuths { 32 | if strings.Compare(k, creds.ServerURL) == 0 && strings.Compare(v, creds.Username) == 0 { 33 | if err := helper.Delete(creds.ServerURL); err != nil { 34 | t.Fatal(err) 35 | } 36 | } 37 | } 38 | 39 | // Check again how many docker credentials we have when starting the test 40 | oldAuths, err = helper.List() 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | 45 | // Add new credentials 46 | if err := helper.Add(creds); err != nil { 47 | t.Fatal(err) 48 | } 49 | 50 | // Verify that it is inside the secret service store 51 | username, secret, err := helper.Get(creds.ServerURL) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | 56 | if username != "foobar" { 57 | t.Fatalf("expected %s, got %s\n", "foobar", username) 58 | } 59 | 60 | if secret != "foobarbaz" { 61 | t.Fatalf("expected %s, got %s\n", "foobarbaz", secret) 62 | } 63 | 64 | // We should have one more credential than before adding 65 | newAuths, err := helper.List() 66 | if err != nil || (len(newAuths)-len(oldAuths) != 1) { 67 | t.Fatal(err) 68 | } 69 | oldAuths = newAuths 70 | 71 | // Deleting the credentials associated to current server url should succeed 72 | if err := helper.Delete(creds.ServerURL); err != nil { 73 | t.Fatal(err) 74 | } 75 | 76 | // We should have one less credential than before deleting 77 | newAuths, err = helper.List() 78 | if err != nil || (len(oldAuths)-len(newAuths) != 1) { 79 | t.Fatal(err) 80 | } 81 | } 82 | 83 | func TestMissingCredentials(t *testing.T) { 84 | t.Skip("test requires gnome-keyring but travis CI doesn't have it") 85 | 86 | helper := Secretservice{} 87 | _, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd") 88 | if !credentials.IsErrCredentialsNotFound(err) { 89 | t.Fatalf("expected ErrCredentialsNotFound, got %v", err) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /vendor/github.com/danieljoos/wincred/.gitattributes: -------------------------------------------------------------------------------- 1 | *.go text eol=lf 2 | -------------------------------------------------------------------------------- /vendor/github.com/danieljoos/wincred/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | 25 | coverage.txt 26 | -------------------------------------------------------------------------------- /vendor/github.com/danieljoos/wincred/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Daniel Joos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /vendor/github.com/danieljoos/wincred/README.md: -------------------------------------------------------------------------------- 1 | wincred 2 | ======= 3 | 4 | Go wrapper around the Windows Credential Manager API functions. 5 | 6 | [![GitHub release](https://img.shields.io/github/release/danieljoos/wincred.svg?style=flat-square)](https://github.com/danieljoos/wincred/releases/latest) 7 | [![Test Status](https://img.shields.io/github/actions/workflow/status/danieljoos/wincred/test.yml?label=test&logo=github&style=flat-square)](https://github.com/danieljoos/wincred/actions?query=workflow%3Atest) 8 | [![Go Report Card](https://goreportcard.com/badge/github.com/danieljoos/wincred)](https://goreportcard.com/report/github.com/danieljoos/wincred) 9 | [![Codecov](https://img.shields.io/codecov/c/github/danieljoos/wincred?logo=codecov&style=flat-square)](https://codecov.io/gh/danieljoos/wincred) 10 | [![PkgGoDev](https://img.shields.io/badge/go.dev-docs-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/danieljoos/wincred) 11 | 12 | Installation 13 | ------------ 14 | 15 | ```Go 16 | go get github.com/danieljoos/wincred 17 | ``` 18 | 19 | 20 | Usage 21 | ----- 22 | 23 | See the following examples: 24 | 25 | ### Create and store a new generic credential object 26 | ```Go 27 | package main 28 | 29 | import ( 30 | "fmt" 31 | "github.com/danieljoos/wincred" 32 | ) 33 | 34 | func main() { 35 | cred := wincred.NewGenericCredential("myGoApplication") 36 | cred.CredentialBlob = []byte("my secret") 37 | err := cred.Write() 38 | 39 | if err != nil { 40 | fmt.Println(err) 41 | } 42 | } 43 | ``` 44 | 45 | ### Retrieve a credential object 46 | ```Go 47 | package main 48 | 49 | import ( 50 | "fmt" 51 | "github.com/danieljoos/wincred" 52 | ) 53 | 54 | func main() { 55 | cred, err := wincred.GetGenericCredential("myGoApplication") 56 | if err == nil { 57 | fmt.Println(string(cred.CredentialBlob)) 58 | } 59 | } 60 | ``` 61 | 62 | ### Remove a credential object 63 | ```Go 64 | package main 65 | 66 | import ( 67 | "fmt" 68 | "github.com/danieljoos/wincred" 69 | ) 70 | 71 | func main() { 72 | cred, err := wincred.GetGenericCredential("myGoApplication") 73 | if err != nil { 74 | fmt.Println(err) 75 | return 76 | } 77 | cred.Delete() 78 | } 79 | ``` 80 | 81 | ### List all available credentials 82 | ```Go 83 | package main 84 | 85 | import ( 86 | "fmt" 87 | "github.com/danieljoos/wincred" 88 | ) 89 | 90 | func main() { 91 | creds, err := wincred.List() 92 | if err != nil { 93 | fmt.Println(err) 94 | return 95 | } 96 | for i := range(creds) { 97 | fmt.Println(creds[i].TargetName) 98 | } 99 | } 100 | ``` 101 | 102 | Hints 103 | ----- 104 | 105 | ### Encoding 106 | 107 | The credential objects simply store byte arrays without specific meaning or encoding. 108 | For sharing between different applications, it might make sense to apply an explicit string encoding - for example **UTF-16 LE** (used nearly everywhere in the Win32 API). 109 | 110 | ```Go 111 | package main 112 | 113 | import ( 114 | "fmt" 115 | "os" 116 | 117 | "github.com/danieljoos/wincred" 118 | "golang.org/x/text/encoding/unicode" 119 | "golang.org/x/text/transform" 120 | ) 121 | 122 | func main() { 123 | cred := wincred.NewGenericCredential("myGoApplication") 124 | 125 | encoder := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder() 126 | blob, _, err := transform.Bytes(encoder, []byte("mysecret")) 127 | if err != nil { 128 | fmt.Println(err) 129 | os.Exit(1) 130 | } 131 | 132 | cred.CredentialBlob = blob 133 | err = cred.Write() 134 | 135 | if err != nil { 136 | fmt.Println(err) 137 | os.Exit(1) 138 | } 139 | } 140 | 141 | ``` 142 | 143 | ### Limitations 144 | 145 | The size of a credential blob is limited to **2560 Bytes** by the Windows API. 146 | -------------------------------------------------------------------------------- /vendor/github.com/danieljoos/wincred/conversion.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package wincred 4 | 5 | import ( 6 | "encoding/binary" 7 | "reflect" 8 | "time" 9 | "unsafe" 10 | 11 | syscall "golang.org/x/sys/windows" 12 | ) 13 | 14 | // utf16ToByte creates a byte array from a given UTF 16 char array. 15 | func utf16ToByte(wstr []uint16) (result []byte) { 16 | result = make([]byte, len(wstr)*2) 17 | for i := range wstr { 18 | binary.LittleEndian.PutUint16(result[(i*2):(i*2)+2], wstr[i]) 19 | } 20 | return 21 | } 22 | 23 | // utf16FromString creates a UTF16 char array from a string. 24 | func utf16FromString(str string) []uint16 { 25 | res, err := syscall.UTF16FromString(str) 26 | if err != nil { 27 | return []uint16{} 28 | } 29 | return res 30 | } 31 | 32 | // goBytes copies the given C byte array to a Go byte array (see `C.GoBytes`). 33 | // This function avoids having cgo as dependency. 34 | func goBytes(src uintptr, len uint32) []byte { 35 | if src == uintptr(0) { 36 | return []byte{} 37 | } 38 | rv := make([]byte, len) 39 | copy(rv, *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ 40 | Data: src, 41 | Len: int(len), 42 | Cap: int(len), 43 | }))) 44 | return rv 45 | } 46 | 47 | // Convert the given CREDENTIAL struct to a more usable structure 48 | func sysToCredential(cred *sysCREDENTIAL) (result *Credential) { 49 | if cred == nil { 50 | return nil 51 | } 52 | result = new(Credential) 53 | result.Comment = syscall.UTF16PtrToString(cred.Comment) 54 | result.TargetName = syscall.UTF16PtrToString(cred.TargetName) 55 | result.TargetAlias = syscall.UTF16PtrToString(cred.TargetAlias) 56 | result.UserName = syscall.UTF16PtrToString(cred.UserName) 57 | result.LastWritten = time.Unix(0, cred.LastWritten.Nanoseconds()) 58 | result.Persist = CredentialPersistence(cred.Persist) 59 | result.CredentialBlob = goBytes(cred.CredentialBlob, cred.CredentialBlobSize) 60 | result.Attributes = make([]CredentialAttribute, cred.AttributeCount) 61 | attrSlice := *(*[]sysCREDENTIAL_ATTRIBUTE)(unsafe.Pointer(&reflect.SliceHeader{ 62 | Data: cred.Attributes, 63 | Len: int(cred.AttributeCount), 64 | Cap: int(cred.AttributeCount), 65 | })) 66 | for i, attr := range attrSlice { 67 | resultAttr := &result.Attributes[i] 68 | resultAttr.Keyword = syscall.UTF16PtrToString(attr.Keyword) 69 | resultAttr.Value = goBytes(attr.Value, attr.ValueSize) 70 | } 71 | return result 72 | } 73 | 74 | // Convert the given Credential object back to a CREDENTIAL struct, which can be used for calling the 75 | // Windows APIs 76 | func sysFromCredential(cred *Credential) (result *sysCREDENTIAL) { 77 | if cred == nil { 78 | return nil 79 | } 80 | result = new(sysCREDENTIAL) 81 | result.Flags = 0 82 | result.Type = 0 83 | result.TargetName, _ = syscall.UTF16PtrFromString(cred.TargetName) 84 | result.Comment, _ = syscall.UTF16PtrFromString(cred.Comment) 85 | result.LastWritten = syscall.NsecToFiletime(cred.LastWritten.UnixNano()) 86 | result.CredentialBlobSize = uint32(len(cred.CredentialBlob)) 87 | if len(cred.CredentialBlob) > 0 { 88 | result.CredentialBlob = uintptr(unsafe.Pointer(&cred.CredentialBlob[0])) 89 | } else { 90 | result.CredentialBlob = 0 91 | } 92 | result.Persist = uint32(cred.Persist) 93 | result.AttributeCount = uint32(len(cred.Attributes)) 94 | attributes := make([]sysCREDENTIAL_ATTRIBUTE, len(cred.Attributes)) 95 | if len(attributes) > 0 { 96 | result.Attributes = uintptr(unsafe.Pointer(&attributes[0])) 97 | } else { 98 | result.Attributes = 0 99 | } 100 | for i := range cred.Attributes { 101 | inAttr := &cred.Attributes[i] 102 | outAttr := &attributes[i] 103 | outAttr.Keyword, _ = syscall.UTF16PtrFromString(inAttr.Keyword) 104 | outAttr.Flags = 0 105 | outAttr.ValueSize = uint32(len(inAttr.Value)) 106 | if len(inAttr.Value) > 0 { 107 | outAttr.Value = uintptr(unsafe.Pointer(&inAttr.Value[0])) 108 | } else { 109 | outAttr.Value = 0 110 | } 111 | } 112 | result.TargetAlias, _ = syscall.UTF16PtrFromString(cred.TargetAlias) 113 | result.UserName, _ = syscall.UTF16PtrFromString(cred.UserName) 114 | 115 | return 116 | } 117 | -------------------------------------------------------------------------------- /vendor/github.com/danieljoos/wincred/conversion_unsupported.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package wincred 4 | 5 | func utf16ToByte(...interface{}) []byte { 6 | return nil 7 | } 8 | 9 | func utf16FromString(...interface{}) []uint16 { 10 | return nil 11 | } 12 | -------------------------------------------------------------------------------- /vendor/github.com/danieljoos/wincred/sys.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package wincred 5 | 6 | import ( 7 | "reflect" 8 | "syscall" 9 | "unsafe" 10 | 11 | "golang.org/x/sys/windows" 12 | ) 13 | 14 | var ( 15 | modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") 16 | procCredRead = modadvapi32.NewProc("CredReadW") 17 | procCredWrite proc = modadvapi32.NewProc("CredWriteW") 18 | procCredDelete proc = modadvapi32.NewProc("CredDeleteW") 19 | procCredFree proc = modadvapi32.NewProc("CredFree") 20 | procCredEnumerate = modadvapi32.NewProc("CredEnumerateW") 21 | ) 22 | 23 | // Interface for syscall.Proc: helps testing 24 | type proc interface { 25 | Call(a ...uintptr) (r1, r2 uintptr, lastErr error) 26 | } 27 | 28 | // https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentialw 29 | type sysCREDENTIAL struct { 30 | Flags uint32 31 | Type uint32 32 | TargetName *uint16 33 | Comment *uint16 34 | LastWritten windows.Filetime 35 | CredentialBlobSize uint32 36 | CredentialBlob uintptr 37 | Persist uint32 38 | AttributeCount uint32 39 | Attributes uintptr 40 | TargetAlias *uint16 41 | UserName *uint16 42 | } 43 | 44 | // https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credential_attributew 45 | type sysCREDENTIAL_ATTRIBUTE struct { 46 | Keyword *uint16 47 | Flags uint32 48 | ValueSize uint32 49 | Value uintptr 50 | } 51 | 52 | // https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentialw 53 | type sysCRED_TYPE uint32 54 | 55 | const ( 56 | sysCRED_TYPE_GENERIC sysCRED_TYPE = 0x1 57 | sysCRED_TYPE_DOMAIN_PASSWORD sysCRED_TYPE = 0x2 58 | sysCRED_TYPE_DOMAIN_CERTIFICATE sysCRED_TYPE = 0x3 59 | sysCRED_TYPE_DOMAIN_VISIBLE_PASSWORD sysCRED_TYPE = 0x4 60 | sysCRED_TYPE_GENERIC_CERTIFICATE sysCRED_TYPE = 0x5 61 | sysCRED_TYPE_DOMAIN_EXTENDED sysCRED_TYPE = 0x6 62 | 63 | // https://docs.microsoft.com/en-us/windows/desktop/Debug/system-error-codes 64 | sysERROR_NOT_FOUND = windows.Errno(1168) 65 | sysERROR_INVALID_PARAMETER = windows.Errno(87) 66 | sysERROR_BAD_USERNAME = windows.Errno(2202) 67 | ) 68 | 69 | // https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-credreadw 70 | func sysCredRead(targetName string, typ sysCRED_TYPE) (*Credential, error) { 71 | var pcred *sysCREDENTIAL 72 | targetNamePtr, _ := windows.UTF16PtrFromString(targetName) 73 | ret, _, err := syscall.SyscallN( 74 | procCredRead.Addr(), 75 | uintptr(unsafe.Pointer(targetNamePtr)), 76 | uintptr(typ), 77 | 0, 78 | uintptr(unsafe.Pointer(&pcred)), 79 | ) 80 | if ret == 0 { 81 | return nil, err 82 | } 83 | defer procCredFree.Call(uintptr(unsafe.Pointer(pcred))) 84 | 85 | return sysToCredential(pcred), nil 86 | } 87 | 88 | // https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-credwritew 89 | func sysCredWrite(cred *Credential, typ sysCRED_TYPE) error { 90 | ncred := sysFromCredential(cred) 91 | ncred.Type = uint32(typ) 92 | ret, _, err := procCredWrite.Call( 93 | uintptr(unsafe.Pointer(ncred)), 94 | 0, 95 | ) 96 | if ret == 0 { 97 | return err 98 | } 99 | 100 | return nil 101 | } 102 | 103 | // https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-creddeletew 104 | func sysCredDelete(cred *Credential, typ sysCRED_TYPE) error { 105 | targetNamePtr, _ := windows.UTF16PtrFromString(cred.TargetName) 106 | ret, _, err := procCredDelete.Call( 107 | uintptr(unsafe.Pointer(targetNamePtr)), 108 | uintptr(typ), 109 | 0, 110 | ) 111 | if ret == 0 { 112 | return err 113 | } 114 | 115 | return nil 116 | } 117 | 118 | // https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-credenumeratew 119 | func sysCredEnumerate(filter string, all bool) ([]*Credential, error) { 120 | var count int 121 | var pcreds uintptr 122 | var filterPtr *uint16 123 | if !all { 124 | filterPtr, _ = windows.UTF16PtrFromString(filter) 125 | } 126 | ret, _, err := syscall.SyscallN( 127 | procCredEnumerate.Addr(), 128 | uintptr(unsafe.Pointer(filterPtr)), 129 | 0, 130 | uintptr(unsafe.Pointer(&count)), 131 | uintptr(unsafe.Pointer(&pcreds)), 132 | ) 133 | if ret == 0 { 134 | return nil, err 135 | } 136 | defer procCredFree.Call(pcreds) 137 | credsSlice := *(*[]*sysCREDENTIAL)(unsafe.Pointer(&reflect.SliceHeader{ 138 | Data: pcreds, 139 | Len: count, 140 | Cap: count, 141 | })) 142 | creds := make([]*Credential, count, count) 143 | for i, cred := range credsSlice { 144 | creds[i] = sysToCredential(cred) 145 | } 146 | 147 | return creds, nil 148 | } 149 | -------------------------------------------------------------------------------- /vendor/github.com/danieljoos/wincred/sys_unsupported.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package wincred 5 | 6 | import ( 7 | "errors" 8 | "syscall" 9 | ) 10 | 11 | const ( 12 | sysCRED_TYPE_GENERIC = 0 13 | sysCRED_TYPE_DOMAIN_PASSWORD = 0 14 | sysCRED_TYPE_DOMAIN_CERTIFICATE = 0 15 | sysCRED_TYPE_DOMAIN_VISIBLE_PASSWORD = 0 16 | sysCRED_TYPE_GENERIC_CERTIFICATE = 0 17 | sysCRED_TYPE_DOMAIN_EXTENDED = 0 18 | 19 | sysERROR_NOT_FOUND = syscall.Errno(1) 20 | sysERROR_INVALID_PARAMETER = syscall.Errno(1) 21 | sysERROR_BAD_USERNAME = syscall.Errno(1) 22 | ) 23 | 24 | func sysCredRead(...interface{}) (*Credential, error) { 25 | return nil, errors.New("Operation not supported") 26 | } 27 | 28 | func sysCredWrite(...interface{}) error { 29 | return errors.New("Operation not supported") 30 | } 31 | 32 | func sysCredDelete(...interface{}) error { 33 | return errors.New("Operation not supported") 34 | } 35 | 36 | func sysCredEnumerate(...interface{}) ([]*Credential, error) { 37 | return nil, errors.New("Operation not supported") 38 | } 39 | -------------------------------------------------------------------------------- /vendor/github.com/danieljoos/wincred/types.go: -------------------------------------------------------------------------------- 1 | package wincred 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | // CredentialPersistence describes one of three persistence modes of a credential. 8 | // A detailed description of the available modes can be found on 9 | // Docs: https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentialw 10 | type CredentialPersistence uint32 11 | 12 | const ( 13 | // PersistSession indicates that the credential only persists for the life 14 | // of the current Windows login session. Such a credential is not visible in 15 | // any other logon session, even from the same user. 16 | PersistSession CredentialPersistence = 0x1 17 | 18 | // PersistLocalMachine indicates that the credential persists for this and 19 | // all subsequent logon sessions on this local machine/computer. It is 20 | // however not visible for logon sessions of this user on a different 21 | // machine. 22 | PersistLocalMachine CredentialPersistence = 0x2 23 | 24 | // PersistEnterprise indicates that the credential persists for this and all 25 | // subsequent logon sessions for this user. It is also visible for logon 26 | // sessions on different computers. 27 | PersistEnterprise CredentialPersistence = 0x3 28 | ) 29 | 30 | // CredentialAttribute represents an application-specific attribute of a credential. 31 | type CredentialAttribute struct { 32 | Keyword string 33 | Value []byte 34 | } 35 | 36 | // Credential is the basic credential structure. 37 | // A credential is identified by its target name. 38 | // The actual credential secret is available in the CredentialBlob field. 39 | type Credential struct { 40 | TargetName string 41 | Comment string 42 | LastWritten time.Time 43 | CredentialBlob []byte 44 | Attributes []CredentialAttribute 45 | TargetAlias string 46 | UserName string 47 | Persist CredentialPersistence 48 | } 49 | 50 | // GenericCredential holds a credential for generic usage. 51 | // It is typically defined and used by applications that need to manage user 52 | // secrets. 53 | // 54 | // More information about the available kinds of credentials of the Windows 55 | // Credential Management API can be found on Docs: 56 | // https://docs.microsoft.com/en-us/windows/desktop/SecAuthN/kinds-of-credentials 57 | type GenericCredential struct { 58 | Credential 59 | } 60 | 61 | // DomainPassword holds a domain credential that is typically used by the 62 | // operating system for user logon. 63 | // 64 | // More information about the available kinds of credentials of the Windows 65 | // Credential Management API can be found on Docs: 66 | // https://docs.microsoft.com/en-us/windows/desktop/SecAuthN/kinds-of-credentials 67 | type DomainPassword struct { 68 | Credential 69 | } 70 | -------------------------------------------------------------------------------- /vendor/github.com/danieljoos/wincred/wincred.go: -------------------------------------------------------------------------------- 1 | // Package wincred provides primitives for accessing the Windows Credentials Management API. 2 | // This includes functions for retrieval, listing and storage of credentials as well as Go structures for convenient access to the credential data. 3 | // 4 | // A more detailed description of Windows Credentials Management can be found on 5 | // Docs: https://docs.microsoft.com/en-us/windows/desktop/SecAuthN/credentials-management 6 | package wincred 7 | 8 | import "errors" 9 | 10 | const ( 11 | // ErrElementNotFound is the error that is returned if a requested element cannot be found. 12 | // This error constant can be used to check if a credential could not be found. 13 | ErrElementNotFound = sysERROR_NOT_FOUND 14 | 15 | // ErrInvalidParameter is the error that is returned for invalid parameters. 16 | // This error constant can be used to check if the given function parameters were invalid. 17 | // For example when trying to create a new generic credential with an empty target name. 18 | ErrInvalidParameter = sysERROR_INVALID_PARAMETER 19 | 20 | // ErrBadUsername is returned when the credential's username is invalid. 21 | ErrBadUsername = sysERROR_BAD_USERNAME 22 | ) 23 | 24 | // GetGenericCredential fetches the generic credential with the given name from Windows credential manager. 25 | // It returns nil and an error if the credential could not be found or an error occurred. 26 | func GetGenericCredential(targetName string) (*GenericCredential, error) { 27 | cred, err := sysCredRead(targetName, sysCRED_TYPE_GENERIC) 28 | if cred != nil { 29 | return &GenericCredential{Credential: *cred}, err 30 | } 31 | return nil, err 32 | } 33 | 34 | // NewGenericCredential creates a new generic credential object with the given name. 35 | // The persist mode of the newly created object is set to a default value that indicates local-machine-wide storage. 36 | // The credential object is NOT yet persisted to the Windows credential vault. 37 | func NewGenericCredential(targetName string) (result *GenericCredential) { 38 | result = new(GenericCredential) 39 | result.TargetName = targetName 40 | result.Persist = PersistLocalMachine 41 | return 42 | } 43 | 44 | // Write persists the generic credential object to Windows credential manager. 45 | func (t *GenericCredential) Write() (err error) { 46 | err = sysCredWrite(&t.Credential, sysCRED_TYPE_GENERIC) 47 | return 48 | } 49 | 50 | // Delete removes the credential object from Windows credential manager. 51 | func (t *GenericCredential) Delete() (err error) { 52 | err = sysCredDelete(&t.Credential, sysCRED_TYPE_GENERIC) 53 | return 54 | } 55 | 56 | // GetDomainPassword fetches the domain-password credential with the given target host name from Windows credential manager. 57 | // It returns nil and an error if the credential could not be found or an error occurred. 58 | func GetDomainPassword(targetName string) (*DomainPassword, error) { 59 | cred, err := sysCredRead(targetName, sysCRED_TYPE_DOMAIN_PASSWORD) 60 | if cred != nil { 61 | return &DomainPassword{Credential: *cred}, err 62 | } 63 | return nil, err 64 | } 65 | 66 | // NewDomainPassword creates a new domain-password credential used for login to the given target host name. 67 | // The persist mode of the newly created object is set to a default value that indicates local-machine-wide storage. 68 | // The credential object is NOT yet persisted to the Windows credential vault. 69 | func NewDomainPassword(targetName string) (result *DomainPassword) { 70 | result = new(DomainPassword) 71 | result.TargetName = targetName 72 | result.Persist = PersistLocalMachine 73 | return 74 | } 75 | 76 | // Write persists the domain-password credential to Windows credential manager. 77 | func (t *DomainPassword) Write() (err error) { 78 | err = sysCredWrite(&t.Credential, sysCRED_TYPE_DOMAIN_PASSWORD) 79 | return 80 | } 81 | 82 | // Delete removes the domain-password credential from Windows credential manager. 83 | func (t *DomainPassword) Delete() (err error) { 84 | err = sysCredDelete(&t.Credential, sysCRED_TYPE_DOMAIN_PASSWORD) 85 | return 86 | } 87 | 88 | // SetPassword sets the CredentialBlob field of a domain password credential to the given string. 89 | func (t *DomainPassword) SetPassword(pw string) { 90 | t.CredentialBlob = utf16ToByte(utf16FromString(pw)) 91 | } 92 | 93 | // List retrieves all credentials of the Credentials store. 94 | func List() ([]*Credential, error) { 95 | creds, err := sysCredEnumerate("", true) 96 | if err != nil && errors.Is(err, ErrElementNotFound) { 97 | // Ignore ERROR_NOT_FOUND and return an empty list instead 98 | creds = []*Credential{} 99 | err = nil 100 | } 101 | return creds, err 102 | } 103 | 104 | // FilteredList retrieves the list of credentials from the Credentials store that match the given filter. 105 | // The filter string defines the prefix followed by an asterisk for the `TargetName` attribute of the credentials. 106 | func FilteredList(filter string) ([]*Credential, error) { 107 | creds, err := sysCredEnumerate(filter, false) 108 | if err != nil && errors.Is(err, ErrElementNotFound) { 109 | // Ignore ERROR_NOT_FOUND and return an empty list instead 110 | creds = []*Credential{} 111 | err = nil 112 | } 113 | return creds, err 114 | } 115 | -------------------------------------------------------------------------------- /vendor/github.com/keybase/go-keychain/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | vendor 27 | -------------------------------------------------------------------------------- /vendor/github.com/keybase/go-keychain/.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | gocritic: 3 | disabled-checks: 4 | - ifElseChain 5 | - elseif 6 | 7 | linters: 8 | enable: 9 | - gofmt 10 | - gocritic 11 | - unconvert 12 | - revive 13 | - govet 14 | -------------------------------------------------------------------------------- /vendor/github.com/keybase/go-keychain/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Keybase 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /vendor/github.com/keybase/go-keychain/README.md: -------------------------------------------------------------------------------- 1 | # Go Keychain 2 | 3 | [![Build Status](https://github.com/keybase/go-keychain/actions/workflows/ci.yml/badge.svg)](https://github.com/keybase/go-keychain/actions) 4 | 5 | A library for accessing the Keychain for macOS, iOS, and Linux in Go (golang). 6 | 7 | Requires macOS 10.9 or greater and iOS 8 or greater. On Linux, communicates to 8 | a provider of the DBUS SecretService spec like gnome-keyring or ksecretservice. 9 | 10 | ```go 11 | import "github.com/keybase/go-keychain" 12 | ``` 13 | 14 | ## Mac/iOS Usage 15 | 16 | The API is meant to mirror the macOS/iOS Keychain API and is not necessarily idiomatic go. 17 | 18 | #### Add Item 19 | 20 | ```go 21 | item := keychain.NewItem() 22 | item.SetSecClass(keychain.SecClassGenericPassword) 23 | item.SetService("MyService") 24 | item.SetAccount("gabriel") 25 | item.SetLabel("A label") 26 | item.SetAccessGroup("A123456789.group.com.mycorp") 27 | item.SetData([]byte("toomanysecrets")) 28 | item.SetSynchronizable(keychain.SynchronizableNo) 29 | item.SetAccessible(keychain.AccessibleWhenUnlocked) 30 | err := keychain.AddItem(item) 31 | 32 | if err == keychain.ErrorDuplicateItem { 33 | // Duplicate 34 | } 35 | ``` 36 | 37 | #### Query Item 38 | 39 | Query for multiple results, returning attributes: 40 | 41 | ```go 42 | query := keychain.NewItem() 43 | query.SetSecClass(keychain.SecClassGenericPassword) 44 | query.SetService(service) 45 | query.SetAccount(account) 46 | query.SetAccessGroup(accessGroup) 47 | query.SetMatchLimit(keychain.MatchLimitAll) 48 | query.SetReturnAttributes(true) 49 | results, err := keychain.QueryItem(query) 50 | if err != nil { 51 | // Error 52 | } else { 53 | for _, r := range results { 54 | fmt.Printf("%#v\n", r) 55 | } 56 | } 57 | ``` 58 | 59 | Query for a single result, returning data: 60 | 61 | ```go 62 | query := keychain.NewItem() 63 | query.SetSecClass(keychain.SecClassGenericPassword) 64 | query.SetService(service) 65 | query.SetAccount(account) 66 | query.SetAccessGroup(accessGroup) 67 | query.SetMatchLimit(keychain.MatchLimitOne) 68 | query.SetReturnData(true) 69 | results, err := keychain.QueryItem(query) 70 | if err != nil { 71 | // Error 72 | } else if len(results) != 1 { 73 | // Not found 74 | } else { 75 | password := string(results[0].Data) 76 | } 77 | ``` 78 | 79 | #### Delete Item 80 | 81 | Delete a generic password item with service and account: 82 | 83 | ```go 84 | item := keychain.NewItem() 85 | item.SetSecClass(keychain.SecClassGenericPassword) 86 | item.SetService(service) 87 | item.SetAccount(account) 88 | err := keychain.DeleteItem(item) 89 | ``` 90 | 91 | ### Other 92 | 93 | There are some convenience methods for generic password: 94 | 95 | ```go 96 | // Create generic password item with service, account, label, password, access group 97 | item := keychain.NewGenericPassword("MyService", "gabriel", "A label", []byte("toomanysecrets"), "A123456789.group.com.mycorp") 98 | item.SetSynchronizable(keychain.SynchronizableNo) 99 | item.SetAccessible(keychain.AccessibleWhenUnlocked) 100 | err := keychain.AddItem(item) 101 | if err == keychain.ErrorDuplicateItem { 102 | // Duplicate 103 | } 104 | 105 | password, err := keychain.GetGenericPassword("MyService", "gabriel", "A label", "A123456789.group.com.mycorp") 106 | 107 | accounts, err := keychain.GetGenericPasswordAccounts("MyService") 108 | // Should have 1 account == "gabriel" 109 | 110 | err := keychain.DeleteGenericPasswordItem("MyService", "gabriel") 111 | if err == keychain.ErrorItemNotFound { 112 | // Not found 113 | } 114 | ``` 115 | 116 | ## iOS 117 | 118 | Bindable package in `bind`. iOS project in `ios`. Run that project to test iOS. 119 | 120 | To re-generate framework: 121 | 122 | ``` 123 | (cd bind && gomobile bind -target=ios -tags=ios -o ../ios/bind.framework) 124 | ``` 125 | 126 | Post issues to: https://github.com/keybase/keybase-issues 127 | -------------------------------------------------------------------------------- /vendor/github.com/keybase/go-keychain/datetime.go: -------------------------------------------------------------------------------- 1 | //go:build darwin || ios 2 | // +build darwin ios 3 | 4 | package keychain 5 | 6 | /* 7 | #cgo LDFLAGS: -framework CoreFoundation 8 | 9 | #include 10 | */ 11 | import "C" 12 | import ( 13 | "math" 14 | "time" 15 | ) 16 | 17 | const nsPerSec = 1000 * 1000 * 1000 18 | 19 | // absoluteTimeIntervalSince1970() returns the number of seconds from 20 | // the Unix epoch (1970-01-01T00:00:00+00:00) to the Core Foundation 21 | // absolute reference date (2001-01-01T00:00:00+00:00). It should be 22 | // exactly 978307200. 23 | func absoluteTimeIntervalSince1970() int64 { 24 | return int64(C.kCFAbsoluteTimeIntervalSince1970) 25 | } 26 | 27 | func unixToAbsoluteTime(s int64, ns int64) C.CFAbsoluteTime { 28 | // Subtract as int64s first before converting to floating 29 | // point to minimize precision loss (assuming the given time 30 | // isn't much earlier than the Core Foundation absolute 31 | // reference date). 32 | abs := s - absoluteTimeIntervalSince1970() 33 | return C.CFAbsoluteTime(abs) + C.CFTimeInterval(ns)/nsPerSec 34 | } 35 | 36 | func absoluteTimeToUnix(abs C.CFAbsoluteTime) (int64, int64) { 37 | i, frac := math.Modf(float64(abs)) 38 | return int64(i) + absoluteTimeIntervalSince1970(), int64(frac * nsPerSec) 39 | } 40 | 41 | // TimeToCFDate will convert the given time.Time to a CFDateRef, which 42 | // must be released with Release(ref). 43 | func TimeToCFDate(t time.Time) C.CFDateRef { 44 | s := t.Unix() 45 | ns := int64(t.Nanosecond()) 46 | abs := unixToAbsoluteTime(s, ns) 47 | return C.CFDateCreate(C.kCFAllocatorDefault, abs) 48 | } 49 | 50 | // CFDateToTime will convert the given CFDateRef to a time.Time. 51 | func CFDateToTime(d C.CFDateRef) time.Time { 52 | abs := C.CFDateGetAbsoluteTime(d) 53 | s, ns := absoluteTimeToUnix(abs) 54 | return time.Unix(s, ns) 55 | } 56 | 57 | // Wrappers around C functions for testing. 58 | 59 | func cfDateToAbsoluteTime(d C.CFDateRef) C.CFAbsoluteTime { 60 | return C.CFDateGetAbsoluteTime(d) 61 | } 62 | 63 | func absoluteTimeToCFDate(abs C.CFAbsoluteTime) C.CFDateRef { 64 | return C.CFDateCreate(C.kCFAllocatorDefault, abs) 65 | } 66 | 67 | func releaseCFDate(d C.CFDateRef) { 68 | Release(C.CFTypeRef(d)) 69 | } 70 | -------------------------------------------------------------------------------- /vendor/github.com/keybase/go-keychain/ios.go: -------------------------------------------------------------------------------- 1 | //go:build darwin && ios 2 | // +build darwin,ios 3 | 4 | package keychain 5 | 6 | /* 7 | #cgo LDFLAGS: -framework CoreFoundation -framework Security 8 | 9 | #include 10 | #include 11 | */ 12 | import "C" 13 | 14 | var AccessibleKey = attrKey(C.CFTypeRef(C.kSecAttrAccessible)) 15 | var accessibleTypeRef = map[Accessible]C.CFTypeRef{ 16 | AccessibleWhenUnlocked: C.CFTypeRef(C.kSecAttrAccessibleWhenUnlocked), 17 | AccessibleAfterFirstUnlock: C.CFTypeRef(C.kSecAttrAccessibleAfterFirstUnlock), 18 | AccessibleAlways: C.CFTypeRef(C.kSecAttrAccessibleAlways), 19 | AccessibleWhenPasscodeSetThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly), 20 | AccessibleWhenUnlockedThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleWhenUnlockedThisDeviceOnly), 21 | AccessibleAfterFirstUnlockThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly), 22 | AccessibleAccessibleAlwaysThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleAlwaysThisDeviceOnly), 23 | } 24 | -------------------------------------------------------------------------------- /vendor/github.com/keybase/go-keychain/macos.go: -------------------------------------------------------------------------------- 1 | //go:build darwin && !ios 2 | // +build darwin,!ios 3 | 4 | package keychain 5 | 6 | /* 7 | #cgo LDFLAGS: -framework CoreFoundation -framework Security 8 | #include 9 | #include 10 | */ 11 | import "C" 12 | 13 | // AccessibleKey is key for kSecAttrAccessible 14 | var AccessibleKey = attrKey(C.CFTypeRef(C.kSecAttrAccessible)) 15 | var accessibleTypeRef = map[Accessible]C.CFTypeRef{ 16 | AccessibleWhenUnlocked: C.CFTypeRef(C.kSecAttrAccessibleWhenUnlocked), 17 | AccessibleAfterFirstUnlock: C.CFTypeRef(C.kSecAttrAccessibleAfterFirstUnlock), 18 | AccessibleAlways: C.CFTypeRef(C.kSecAttrAccessibleAlways), 19 | AccessibleWhenUnlockedThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleWhenUnlockedThisDeviceOnly), 20 | AccessibleAfterFirstUnlockThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly), 21 | AccessibleAccessibleAlwaysThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleAlwaysThisDeviceOnly), 22 | 23 | // Only available in 10.10 24 | //AccessibleWhenPasscodeSetThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly), 25 | } 26 | -------------------------------------------------------------------------------- /vendor/github.com/keybase/go-keychain/util.go: -------------------------------------------------------------------------------- 1 | package keychain 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/base32" 6 | "strings" 7 | ) 8 | 9 | var randRead = rand.Read 10 | 11 | // RandomID returns random ID (base32) string with prefix, using 256 bits as 12 | // recommended by tptacek: https://gist.github.com/tqbf/be58d2d39690c3b366ad 13 | func RandomID(prefix string) (string, error) { 14 | buf, err := RandBytes(32) 15 | if err != nil { 16 | return "", err 17 | } 18 | str := base32.StdEncoding.EncodeToString(buf) 19 | str = strings.ReplaceAll(str, "=", "") 20 | str = prefix + str 21 | return str, nil 22 | } 23 | 24 | // RandBytes returns random bytes of length 25 | func RandBytes(length int) ([]byte, error) { 26 | buf := make([]byte, length) 27 | if _, err := randRead(buf); err != nil { 28 | return nil, err 29 | } 30 | return buf, nil 31 | } 32 | -------------------------------------------------------------------------------- /vendor/golang.org/x/sys/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /vendor/golang.org/x/sys/PATENTS: -------------------------------------------------------------------------------- 1 | Additional IP Rights Grant (Patents) 2 | 3 | "This implementation" means the copyrightable works distributed by 4 | Google as part of the Go project. 5 | 6 | Google hereby grants to You a perpetual, worldwide, non-exclusive, 7 | no-charge, royalty-free, irrevocable (except as stated in this section) 8 | patent license to make, have made, use, offer to sell, sell, import, 9 | transfer and otherwise run, modify and propagate the contents of this 10 | implementation of Go, where such license applies only to those patent 11 | claims, both currently owned or controlled by Google and acquired in 12 | the future, licensable by Google that are necessarily infringed by this 13 | implementation of Go. This grant does not include claims that would be 14 | infringed only as a consequence of further modification of this 15 | implementation. If you or your agent or exclusive licensee institute or 16 | order or agree to the institution of patent litigation against any 17 | entity (including a cross-claim or counterclaim in a lawsuit) alleging 18 | that this implementation of Go or any code incorporated within this 19 | implementation of Go constitutes direct or contributory patent 20 | infringement, or inducement of patent infringement, then any patent 21 | rights granted to you under this License for this implementation of Go 22 | shall terminate as of the date such litigation is filed. 23 | -------------------------------------------------------------------------------- /vendor/golang.org/x/sys/windows/aliases.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build windows 6 | 7 | package windows 8 | 9 | import "syscall" 10 | 11 | type Errno = syscall.Errno 12 | type SysProcAttr = syscall.SysProcAttr 13 | -------------------------------------------------------------------------------- /vendor/golang.org/x/sys/windows/env_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Windows environment variables. 6 | 7 | package windows 8 | 9 | import ( 10 | "syscall" 11 | "unsafe" 12 | ) 13 | 14 | func Getenv(key string) (value string, found bool) { 15 | return syscall.Getenv(key) 16 | } 17 | 18 | func Setenv(key, value string) error { 19 | return syscall.Setenv(key, value) 20 | } 21 | 22 | func Clearenv() { 23 | syscall.Clearenv() 24 | } 25 | 26 | func Environ() []string { 27 | return syscall.Environ() 28 | } 29 | 30 | // Returns a default environment associated with the token, rather than the current 31 | // process. If inheritExisting is true, then this environment also inherits the 32 | // environment of the current process. 33 | func (token Token) Environ(inheritExisting bool) (env []string, err error) { 34 | var block *uint16 35 | err = CreateEnvironmentBlock(&block, token, inheritExisting) 36 | if err != nil { 37 | return nil, err 38 | } 39 | defer DestroyEnvironmentBlock(block) 40 | size := unsafe.Sizeof(*block) 41 | for *block != 0 { 42 | // find NUL terminator 43 | end := unsafe.Pointer(block) 44 | for *(*uint16)(end) != 0 { 45 | end = unsafe.Add(end, size) 46 | } 47 | 48 | entry := unsafe.Slice(block, (uintptr(end)-uintptr(unsafe.Pointer(block)))/size) 49 | env = append(env, UTF16ToString(entry)) 50 | block = (*uint16)(unsafe.Add(end, size)) 51 | } 52 | return env, nil 53 | } 54 | 55 | func Unsetenv(key string) error { 56 | return syscall.Unsetenv(key) 57 | } 58 | -------------------------------------------------------------------------------- /vendor/golang.org/x/sys/windows/eventlog.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build windows 6 | 7 | package windows 8 | 9 | const ( 10 | EVENTLOG_SUCCESS = 0 11 | EVENTLOG_ERROR_TYPE = 1 12 | EVENTLOG_WARNING_TYPE = 2 13 | EVENTLOG_INFORMATION_TYPE = 4 14 | EVENTLOG_AUDIT_SUCCESS = 8 15 | EVENTLOG_AUDIT_FAILURE = 16 16 | ) 17 | 18 | //sys RegisterEventSource(uncServerName *uint16, sourceName *uint16) (handle Handle, err error) [failretval==0] = advapi32.RegisterEventSourceW 19 | //sys DeregisterEventSource(handle Handle) (err error) = advapi32.DeregisterEventSource 20 | //sys ReportEvent(log Handle, etype uint16, category uint16, eventId uint32, usrSId uintptr, numStrings uint16, dataSize uint32, strings **uint16, rawData *byte) (err error) = advapi32.ReportEventW 21 | -------------------------------------------------------------------------------- /vendor/golang.org/x/sys/windows/exec_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Fork, exec, wait, etc. 6 | 7 | package windows 8 | 9 | import ( 10 | errorspkg "errors" 11 | "unsafe" 12 | ) 13 | 14 | // EscapeArg rewrites command line argument s as prescribed 15 | // in http://msdn.microsoft.com/en-us/library/ms880421. 16 | // This function returns "" (2 double quotes) if s is empty. 17 | // Alternatively, these transformations are done: 18 | // - every back slash (\) is doubled, but only if immediately 19 | // followed by double quote ("); 20 | // - every double quote (") is escaped by back slash (\); 21 | // - finally, s is wrapped with double quotes (arg -> "arg"), 22 | // but only if there is space or tab inside s. 23 | func EscapeArg(s string) string { 24 | if len(s) == 0 { 25 | return `""` 26 | } 27 | n := len(s) 28 | hasSpace := false 29 | for i := 0; i < len(s); i++ { 30 | switch s[i] { 31 | case '"', '\\': 32 | n++ 33 | case ' ', '\t': 34 | hasSpace = true 35 | } 36 | } 37 | if hasSpace { 38 | n += 2 // Reserve space for quotes. 39 | } 40 | if n == len(s) { 41 | return s 42 | } 43 | 44 | qs := make([]byte, n) 45 | j := 0 46 | if hasSpace { 47 | qs[j] = '"' 48 | j++ 49 | } 50 | slashes := 0 51 | for i := 0; i < len(s); i++ { 52 | switch s[i] { 53 | default: 54 | slashes = 0 55 | qs[j] = s[i] 56 | case '\\': 57 | slashes++ 58 | qs[j] = s[i] 59 | case '"': 60 | for ; slashes > 0; slashes-- { 61 | qs[j] = '\\' 62 | j++ 63 | } 64 | qs[j] = '\\' 65 | j++ 66 | qs[j] = s[i] 67 | } 68 | j++ 69 | } 70 | if hasSpace { 71 | for ; slashes > 0; slashes-- { 72 | qs[j] = '\\' 73 | j++ 74 | } 75 | qs[j] = '"' 76 | j++ 77 | } 78 | return string(qs[:j]) 79 | } 80 | 81 | // ComposeCommandLine escapes and joins the given arguments suitable for use as a Windows command line, 82 | // in CreateProcess's CommandLine argument, CreateService/ChangeServiceConfig's BinaryPathName argument, 83 | // or any program that uses CommandLineToArgv. 84 | func ComposeCommandLine(args []string) string { 85 | if len(args) == 0 { 86 | return "" 87 | } 88 | 89 | // Per https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw: 90 | // “This function accepts command lines that contain a program name; the 91 | // program name can be enclosed in quotation marks or not.” 92 | // 93 | // Unfortunately, it provides no means of escaping interior quotation marks 94 | // within that program name, and we have no way to report them here. 95 | prog := args[0] 96 | mustQuote := len(prog) == 0 97 | for i := 0; i < len(prog); i++ { 98 | c := prog[i] 99 | if c <= ' ' || (c == '"' && i == 0) { 100 | // Force quotes for not only the ASCII space and tab as described in the 101 | // MSDN article, but also ASCII control characters. 102 | // The documentation for CommandLineToArgvW doesn't say what happens when 103 | // the first argument is not a valid program name, but it empirically 104 | // seems to drop unquoted control characters. 105 | mustQuote = true 106 | break 107 | } 108 | } 109 | var commandLine []byte 110 | if mustQuote { 111 | commandLine = make([]byte, 0, len(prog)+2) 112 | commandLine = append(commandLine, '"') 113 | for i := 0; i < len(prog); i++ { 114 | c := prog[i] 115 | if c == '"' { 116 | // This quote would interfere with our surrounding quotes. 117 | // We have no way to report an error, so just strip out 118 | // the offending character instead. 119 | continue 120 | } 121 | commandLine = append(commandLine, c) 122 | } 123 | commandLine = append(commandLine, '"') 124 | } else { 125 | if len(args) == 1 { 126 | // args[0] is a valid command line representing itself. 127 | // No need to allocate a new slice or string for it. 128 | return prog 129 | } 130 | commandLine = []byte(prog) 131 | } 132 | 133 | for _, arg := range args[1:] { 134 | commandLine = append(commandLine, ' ') 135 | // TODO(bcmills): since we're already appending to a slice, it would be nice 136 | // to avoid the intermediate allocations of EscapeArg. 137 | // Perhaps we can factor out an appendEscapedArg function. 138 | commandLine = append(commandLine, EscapeArg(arg)...) 139 | } 140 | return string(commandLine) 141 | } 142 | 143 | // DecomposeCommandLine breaks apart its argument command line into unescaped parts using CommandLineToArgv, 144 | // as gathered from GetCommandLine, QUERY_SERVICE_CONFIG's BinaryPathName argument, or elsewhere that 145 | // command lines are passed around. 146 | // DecomposeCommandLine returns an error if commandLine contains NUL. 147 | func DecomposeCommandLine(commandLine string) ([]string, error) { 148 | if len(commandLine) == 0 { 149 | return []string{}, nil 150 | } 151 | utf16CommandLine, err := UTF16FromString(commandLine) 152 | if err != nil { 153 | return nil, errorspkg.New("string with NUL passed to DecomposeCommandLine") 154 | } 155 | var argc int32 156 | argv, err := commandLineToArgv(&utf16CommandLine[0], &argc) 157 | if err != nil { 158 | return nil, err 159 | } 160 | defer LocalFree(Handle(unsafe.Pointer(argv))) 161 | 162 | var args []string 163 | for _, p := range unsafe.Slice(argv, argc) { 164 | args = append(args, UTF16PtrToString(p)) 165 | } 166 | return args, nil 167 | } 168 | 169 | // CommandLineToArgv parses a Unicode command line string and sets 170 | // argc to the number of parsed arguments. 171 | // 172 | // The returned memory should be freed using a single call to LocalFree. 173 | // 174 | // Note that although the return type of CommandLineToArgv indicates 8192 175 | // entries of up to 8192 characters each, the actual count of parsed arguments 176 | // may exceed 8192, and the documentation for CommandLineToArgvW does not mention 177 | // any bound on the lengths of the individual argument strings. 178 | // (See https://go.dev/issue/63236.) 179 | func CommandLineToArgv(cmd *uint16, argc *int32) (argv *[8192]*[8192]uint16, err error) { 180 | argp, err := commandLineToArgv(cmd, argc) 181 | argv = (*[8192]*[8192]uint16)(unsafe.Pointer(argp)) 182 | return argv, err 183 | } 184 | 185 | func CloseOnExec(fd Handle) { 186 | SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0) 187 | } 188 | 189 | // FullPath retrieves the full path of the specified file. 190 | func FullPath(name string) (path string, err error) { 191 | p, err := UTF16PtrFromString(name) 192 | if err != nil { 193 | return "", err 194 | } 195 | n := uint32(100) 196 | for { 197 | buf := make([]uint16, n) 198 | n, err = GetFullPathName(p, uint32(len(buf)), &buf[0], nil) 199 | if err != nil { 200 | return "", err 201 | } 202 | if n <= uint32(len(buf)) { 203 | return UTF16ToString(buf[:n]), nil 204 | } 205 | } 206 | } 207 | 208 | // NewProcThreadAttributeList allocates a new ProcThreadAttributeListContainer, with the requested maximum number of attributes. 209 | func NewProcThreadAttributeList(maxAttrCount uint32) (*ProcThreadAttributeListContainer, error) { 210 | var size uintptr 211 | err := initializeProcThreadAttributeList(nil, maxAttrCount, 0, &size) 212 | if err != ERROR_INSUFFICIENT_BUFFER { 213 | if err == nil { 214 | return nil, errorspkg.New("unable to query buffer size from InitializeProcThreadAttributeList") 215 | } 216 | return nil, err 217 | } 218 | alloc, err := LocalAlloc(LMEM_FIXED, uint32(size)) 219 | if err != nil { 220 | return nil, err 221 | } 222 | // size is guaranteed to be ≥1 by InitializeProcThreadAttributeList. 223 | al := &ProcThreadAttributeListContainer{data: (*ProcThreadAttributeList)(unsafe.Pointer(alloc))} 224 | err = initializeProcThreadAttributeList(al.data, maxAttrCount, 0, &size) 225 | if err != nil { 226 | return nil, err 227 | } 228 | return al, err 229 | } 230 | 231 | // Update modifies the ProcThreadAttributeList using UpdateProcThreadAttribute. 232 | func (al *ProcThreadAttributeListContainer) Update(attribute uintptr, value unsafe.Pointer, size uintptr) error { 233 | al.pointers = append(al.pointers, value) 234 | return updateProcThreadAttribute(al.data, 0, attribute, value, size, nil, nil) 235 | } 236 | 237 | // Delete frees ProcThreadAttributeList's resources. 238 | func (al *ProcThreadAttributeListContainer) Delete() { 239 | deleteProcThreadAttributeList(al.data) 240 | LocalFree(Handle(unsafe.Pointer(al.data))) 241 | al.data = nil 242 | al.pointers = nil 243 | } 244 | 245 | // List returns the actual ProcThreadAttributeList to be passed to StartupInfoEx. 246 | func (al *ProcThreadAttributeListContainer) List() *ProcThreadAttributeList { 247 | return al.data 248 | } 249 | -------------------------------------------------------------------------------- /vendor/golang.org/x/sys/windows/memory_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package windows 6 | 7 | const ( 8 | MEM_COMMIT = 0x00001000 9 | MEM_RESERVE = 0x00002000 10 | MEM_DECOMMIT = 0x00004000 11 | MEM_RELEASE = 0x00008000 12 | MEM_RESET = 0x00080000 13 | MEM_TOP_DOWN = 0x00100000 14 | MEM_WRITE_WATCH = 0x00200000 15 | MEM_PHYSICAL = 0x00400000 16 | MEM_RESET_UNDO = 0x01000000 17 | MEM_LARGE_PAGES = 0x20000000 18 | 19 | PAGE_NOACCESS = 0x00000001 20 | PAGE_READONLY = 0x00000002 21 | PAGE_READWRITE = 0x00000004 22 | PAGE_WRITECOPY = 0x00000008 23 | PAGE_EXECUTE = 0x00000010 24 | PAGE_EXECUTE_READ = 0x00000020 25 | PAGE_EXECUTE_READWRITE = 0x00000040 26 | PAGE_EXECUTE_WRITECOPY = 0x00000080 27 | PAGE_GUARD = 0x00000100 28 | PAGE_NOCACHE = 0x00000200 29 | PAGE_WRITECOMBINE = 0x00000400 30 | PAGE_TARGETS_INVALID = 0x40000000 31 | PAGE_TARGETS_NO_UPDATE = 0x40000000 32 | 33 | QUOTA_LIMITS_HARDWS_MIN_DISABLE = 0x00000002 34 | QUOTA_LIMITS_HARDWS_MIN_ENABLE = 0x00000001 35 | QUOTA_LIMITS_HARDWS_MAX_DISABLE = 0x00000008 36 | QUOTA_LIMITS_HARDWS_MAX_ENABLE = 0x00000004 37 | ) 38 | 39 | type MemoryBasicInformation struct { 40 | BaseAddress uintptr 41 | AllocationBase uintptr 42 | AllocationProtect uint32 43 | PartitionId uint16 44 | RegionSize uintptr 45 | State uint32 46 | Protect uint32 47 | Type uint32 48 | } 49 | -------------------------------------------------------------------------------- /vendor/golang.org/x/sys/windows/mkerrors.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2019 The Go Authors. All rights reserved. 4 | # Use of this source code is governed by a BSD-style 5 | # license that can be found in the LICENSE file. 6 | 7 | set -e 8 | shopt -s nullglob 9 | 10 | winerror="$(printf '%s\n' "/mnt/c/Program Files (x86)/Windows Kits/"/*/Include/*/shared/winerror.h | sort -Vr | head -n 1)" 11 | [[ -n $winerror ]] || { echo "Unable to find winerror.h" >&2; exit 1; } 12 | ntstatus="$(printf '%s\n' "/mnt/c/Program Files (x86)/Windows Kits/"/*/Include/*/shared/ntstatus.h | sort -Vr | head -n 1)" 13 | [[ -n $ntstatus ]] || { echo "Unable to find ntstatus.h" >&2; exit 1; } 14 | 15 | declare -A errors 16 | 17 | { 18 | echo "// Code generated by 'mkerrors.bash'; DO NOT EDIT." 19 | echo 20 | echo "package windows" 21 | echo "import \"syscall\"" 22 | echo "const (" 23 | 24 | while read -r line; do 25 | unset vtype 26 | if [[ $line =~ ^#define\ +([A-Z0-9_]+k?)\ +([A-Z0-9_]+\()?([A-Z][A-Z0-9_]+k?)\)? ]]; then 27 | key="${BASH_REMATCH[1]}" 28 | value="${BASH_REMATCH[3]}" 29 | elif [[ $line =~ ^#define\ +([A-Z0-9_]+k?)\ +([A-Z0-9_]+\()?((0x)?[0-9A-Fa-f]+)L?\)? ]]; then 30 | key="${BASH_REMATCH[1]}" 31 | value="${BASH_REMATCH[3]}" 32 | vtype="${BASH_REMATCH[2]}" 33 | elif [[ $line =~ ^#define\ +([A-Z0-9_]+k?)\ +\(\(([A-Z]+)\)((0x)?[0-9A-Fa-f]+)L?\) ]]; then 34 | key="${BASH_REMATCH[1]}" 35 | value="${BASH_REMATCH[3]}" 36 | vtype="${BASH_REMATCH[2]}" 37 | else 38 | continue 39 | fi 40 | [[ -n $key && -n $value ]] || continue 41 | [[ -z ${errors["$key"]} ]] || continue 42 | errors["$key"]="$value" 43 | if [[ -v vtype ]]; then 44 | if [[ $key == FACILITY_* || $key == NO_ERROR ]]; then 45 | vtype="" 46 | elif [[ $vtype == *HANDLE* || $vtype == *HRESULT* ]]; then 47 | vtype="Handle" 48 | else 49 | vtype="syscall.Errno" 50 | fi 51 | last_vtype="$vtype" 52 | else 53 | vtype="" 54 | if [[ $last_vtype == Handle && $value == NO_ERROR ]]; then 55 | value="S_OK" 56 | elif [[ $last_vtype == syscall.Errno && $value == NO_ERROR ]]; then 57 | value="ERROR_SUCCESS" 58 | fi 59 | fi 60 | 61 | echo "$key $vtype = $value" 62 | done < "$winerror" 63 | 64 | while read -r line; do 65 | [[ $line =~ ^#define\ (STATUS_[^\s]+)\ +\(\(NTSTATUS\)((0x)?[0-9a-fA-F]+)L?\) ]] || continue 66 | echo "${BASH_REMATCH[1]} NTStatus = ${BASH_REMATCH[2]}" 67 | done < "$ntstatus" 68 | 69 | echo ")" 70 | } | gofmt > "zerrors_windows.go" 71 | -------------------------------------------------------------------------------- /vendor/golang.org/x/sys/windows/mkknownfolderids.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2019 The Go Authors. All rights reserved. 4 | # Use of this source code is governed by a BSD-style 5 | # license that can be found in the LICENSE file. 6 | 7 | set -e 8 | shopt -s nullglob 9 | 10 | knownfolders="$(printf '%s\n' "/mnt/c/Program Files (x86)/Windows Kits/"/*/Include/*/um/KnownFolders.h | sort -Vr | head -n 1)" 11 | [[ -n $knownfolders ]] || { echo "Unable to find KnownFolders.h" >&2; exit 1; } 12 | 13 | { 14 | echo "// Code generated by 'mkknownfolderids.bash'; DO NOT EDIT." 15 | echo 16 | echo "package windows" 17 | echo "type KNOWNFOLDERID GUID" 18 | echo "var (" 19 | while read -r line; do 20 | [[ $line =~ DEFINE_KNOWN_FOLDER\((FOLDERID_[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+),[\t\ ]*(0x[^,]+)\) ]] || continue 21 | printf "%s = &KNOWNFOLDERID{0x%08x, 0x%04x, 0x%04x, [8]byte{0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x}}\n" \ 22 | "${BASH_REMATCH[1]}" $(( "${BASH_REMATCH[2]}" )) $(( "${BASH_REMATCH[3]}" )) $(( "${BASH_REMATCH[4]}" )) \ 23 | $(( "${BASH_REMATCH[5]}" )) $(( "${BASH_REMATCH[6]}" )) $(( "${BASH_REMATCH[7]}" )) $(( "${BASH_REMATCH[8]}" )) \ 24 | $(( "${BASH_REMATCH[9]}" )) $(( "${BASH_REMATCH[10]}" )) $(( "${BASH_REMATCH[11]}" )) $(( "${BASH_REMATCH[12]}" )) 25 | done < "$knownfolders" 26 | echo ")" 27 | } | gofmt > "zknownfolderids_windows.go" 28 | -------------------------------------------------------------------------------- /vendor/golang.org/x/sys/windows/mksyscall.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build generate 6 | 7 | package windows 8 | 9 | //go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go eventlog.go service.go syscall_windows.go security_windows.go setupapi_windows.go 10 | -------------------------------------------------------------------------------- /vendor/golang.org/x/sys/windows/race.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build windows && race 6 | 7 | package windows 8 | 9 | import ( 10 | "runtime" 11 | "unsafe" 12 | ) 13 | 14 | const raceenabled = true 15 | 16 | func raceAcquire(addr unsafe.Pointer) { 17 | runtime.RaceAcquire(addr) 18 | } 19 | 20 | func raceReleaseMerge(addr unsafe.Pointer) { 21 | runtime.RaceReleaseMerge(addr) 22 | } 23 | 24 | func raceReadRange(addr unsafe.Pointer, len int) { 25 | runtime.RaceReadRange(addr, len) 26 | } 27 | 28 | func raceWriteRange(addr unsafe.Pointer, len int) { 29 | runtime.RaceWriteRange(addr, len) 30 | } 31 | -------------------------------------------------------------------------------- /vendor/golang.org/x/sys/windows/race0.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build windows && !race 6 | 7 | package windows 8 | 9 | import ( 10 | "unsafe" 11 | ) 12 | 13 | const raceenabled = false 14 | 15 | func raceAcquire(addr unsafe.Pointer) { 16 | } 17 | 18 | func raceReleaseMerge(addr unsafe.Pointer) { 19 | } 20 | 21 | func raceReadRange(addr unsafe.Pointer, len int) { 22 | } 23 | 24 | func raceWriteRange(addr unsafe.Pointer, len int) { 25 | } 26 | -------------------------------------------------------------------------------- /vendor/golang.org/x/sys/windows/service.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build windows 6 | 7 | package windows 8 | 9 | const ( 10 | SC_MANAGER_CONNECT = 1 11 | SC_MANAGER_CREATE_SERVICE = 2 12 | SC_MANAGER_ENUMERATE_SERVICE = 4 13 | SC_MANAGER_LOCK = 8 14 | SC_MANAGER_QUERY_LOCK_STATUS = 16 15 | SC_MANAGER_MODIFY_BOOT_CONFIG = 32 16 | SC_MANAGER_ALL_ACCESS = 0xf003f 17 | ) 18 | 19 | const ( 20 | SERVICE_KERNEL_DRIVER = 1 21 | SERVICE_FILE_SYSTEM_DRIVER = 2 22 | SERVICE_ADAPTER = 4 23 | SERVICE_RECOGNIZER_DRIVER = 8 24 | SERVICE_WIN32_OWN_PROCESS = 16 25 | SERVICE_WIN32_SHARE_PROCESS = 32 26 | SERVICE_WIN32 = SERVICE_WIN32_OWN_PROCESS | SERVICE_WIN32_SHARE_PROCESS 27 | SERVICE_INTERACTIVE_PROCESS = 256 28 | SERVICE_DRIVER = SERVICE_KERNEL_DRIVER | SERVICE_FILE_SYSTEM_DRIVER | SERVICE_RECOGNIZER_DRIVER 29 | SERVICE_TYPE_ALL = SERVICE_WIN32 | SERVICE_ADAPTER | SERVICE_DRIVER | SERVICE_INTERACTIVE_PROCESS 30 | 31 | SERVICE_BOOT_START = 0 32 | SERVICE_SYSTEM_START = 1 33 | SERVICE_AUTO_START = 2 34 | SERVICE_DEMAND_START = 3 35 | SERVICE_DISABLED = 4 36 | 37 | SERVICE_ERROR_IGNORE = 0 38 | SERVICE_ERROR_NORMAL = 1 39 | SERVICE_ERROR_SEVERE = 2 40 | SERVICE_ERROR_CRITICAL = 3 41 | 42 | SC_STATUS_PROCESS_INFO = 0 43 | 44 | SC_ACTION_NONE = 0 45 | SC_ACTION_RESTART = 1 46 | SC_ACTION_REBOOT = 2 47 | SC_ACTION_RUN_COMMAND = 3 48 | 49 | SERVICE_STOPPED = 1 50 | SERVICE_START_PENDING = 2 51 | SERVICE_STOP_PENDING = 3 52 | SERVICE_RUNNING = 4 53 | SERVICE_CONTINUE_PENDING = 5 54 | SERVICE_PAUSE_PENDING = 6 55 | SERVICE_PAUSED = 7 56 | SERVICE_NO_CHANGE = 0xffffffff 57 | 58 | SERVICE_ACCEPT_STOP = 1 59 | SERVICE_ACCEPT_PAUSE_CONTINUE = 2 60 | SERVICE_ACCEPT_SHUTDOWN = 4 61 | SERVICE_ACCEPT_PARAMCHANGE = 8 62 | SERVICE_ACCEPT_NETBINDCHANGE = 16 63 | SERVICE_ACCEPT_HARDWAREPROFILECHANGE = 32 64 | SERVICE_ACCEPT_POWEREVENT = 64 65 | SERVICE_ACCEPT_SESSIONCHANGE = 128 66 | SERVICE_ACCEPT_PRESHUTDOWN = 256 67 | 68 | SERVICE_CONTROL_STOP = 1 69 | SERVICE_CONTROL_PAUSE = 2 70 | SERVICE_CONTROL_CONTINUE = 3 71 | SERVICE_CONTROL_INTERROGATE = 4 72 | SERVICE_CONTROL_SHUTDOWN = 5 73 | SERVICE_CONTROL_PARAMCHANGE = 6 74 | SERVICE_CONTROL_NETBINDADD = 7 75 | SERVICE_CONTROL_NETBINDREMOVE = 8 76 | SERVICE_CONTROL_NETBINDENABLE = 9 77 | SERVICE_CONTROL_NETBINDDISABLE = 10 78 | SERVICE_CONTROL_DEVICEEVENT = 11 79 | SERVICE_CONTROL_HARDWAREPROFILECHANGE = 12 80 | SERVICE_CONTROL_POWEREVENT = 13 81 | SERVICE_CONTROL_SESSIONCHANGE = 14 82 | SERVICE_CONTROL_PRESHUTDOWN = 15 83 | 84 | SERVICE_ACTIVE = 1 85 | SERVICE_INACTIVE = 2 86 | SERVICE_STATE_ALL = 3 87 | 88 | SERVICE_QUERY_CONFIG = 1 89 | SERVICE_CHANGE_CONFIG = 2 90 | SERVICE_QUERY_STATUS = 4 91 | SERVICE_ENUMERATE_DEPENDENTS = 8 92 | SERVICE_START = 16 93 | SERVICE_STOP = 32 94 | SERVICE_PAUSE_CONTINUE = 64 95 | SERVICE_INTERROGATE = 128 96 | SERVICE_USER_DEFINED_CONTROL = 256 97 | SERVICE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG | SERVICE_QUERY_STATUS | SERVICE_ENUMERATE_DEPENDENTS | SERVICE_START | SERVICE_STOP | SERVICE_PAUSE_CONTINUE | SERVICE_INTERROGATE | SERVICE_USER_DEFINED_CONTROL 98 | 99 | SERVICE_RUNS_IN_SYSTEM_PROCESS = 1 100 | 101 | SERVICE_CONFIG_DESCRIPTION = 1 102 | SERVICE_CONFIG_FAILURE_ACTIONS = 2 103 | SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 3 104 | SERVICE_CONFIG_FAILURE_ACTIONS_FLAG = 4 105 | SERVICE_CONFIG_SERVICE_SID_INFO = 5 106 | SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO = 6 107 | SERVICE_CONFIG_PRESHUTDOWN_INFO = 7 108 | SERVICE_CONFIG_TRIGGER_INFO = 8 109 | SERVICE_CONFIG_PREFERRED_NODE = 9 110 | SERVICE_CONFIG_LAUNCH_PROTECTED = 12 111 | 112 | SERVICE_SID_TYPE_NONE = 0 113 | SERVICE_SID_TYPE_UNRESTRICTED = 1 114 | SERVICE_SID_TYPE_RESTRICTED = 2 | SERVICE_SID_TYPE_UNRESTRICTED 115 | 116 | SC_ENUM_PROCESS_INFO = 0 117 | 118 | SERVICE_NOTIFY_STATUS_CHANGE = 2 119 | SERVICE_NOTIFY_STOPPED = 0x00000001 120 | SERVICE_NOTIFY_START_PENDING = 0x00000002 121 | SERVICE_NOTIFY_STOP_PENDING = 0x00000004 122 | SERVICE_NOTIFY_RUNNING = 0x00000008 123 | SERVICE_NOTIFY_CONTINUE_PENDING = 0x00000010 124 | SERVICE_NOTIFY_PAUSE_PENDING = 0x00000020 125 | SERVICE_NOTIFY_PAUSED = 0x00000040 126 | SERVICE_NOTIFY_CREATED = 0x00000080 127 | SERVICE_NOTIFY_DELETED = 0x00000100 128 | SERVICE_NOTIFY_DELETE_PENDING = 0x00000200 129 | 130 | SC_EVENT_DATABASE_CHANGE = 0 131 | SC_EVENT_PROPERTY_CHANGE = 1 132 | SC_EVENT_STATUS_CHANGE = 2 133 | 134 | SERVICE_START_REASON_DEMAND = 0x00000001 135 | SERVICE_START_REASON_AUTO = 0x00000002 136 | SERVICE_START_REASON_TRIGGER = 0x00000004 137 | SERVICE_START_REASON_RESTART_ON_FAILURE = 0x00000008 138 | SERVICE_START_REASON_DELAYEDAUTO = 0x00000010 139 | 140 | SERVICE_DYNAMIC_INFORMATION_LEVEL_START_REASON = 1 141 | ) 142 | 143 | type ENUM_SERVICE_STATUS struct { 144 | ServiceName *uint16 145 | DisplayName *uint16 146 | ServiceStatus SERVICE_STATUS 147 | } 148 | 149 | type SERVICE_STATUS struct { 150 | ServiceType uint32 151 | CurrentState uint32 152 | ControlsAccepted uint32 153 | Win32ExitCode uint32 154 | ServiceSpecificExitCode uint32 155 | CheckPoint uint32 156 | WaitHint uint32 157 | } 158 | 159 | type SERVICE_TABLE_ENTRY struct { 160 | ServiceName *uint16 161 | ServiceProc uintptr 162 | } 163 | 164 | type QUERY_SERVICE_CONFIG struct { 165 | ServiceType uint32 166 | StartType uint32 167 | ErrorControl uint32 168 | BinaryPathName *uint16 169 | LoadOrderGroup *uint16 170 | TagId uint32 171 | Dependencies *uint16 172 | ServiceStartName *uint16 173 | DisplayName *uint16 174 | } 175 | 176 | type SERVICE_DESCRIPTION struct { 177 | Description *uint16 178 | } 179 | 180 | type SERVICE_DELAYED_AUTO_START_INFO struct { 181 | IsDelayedAutoStartUp uint32 182 | } 183 | 184 | type SERVICE_STATUS_PROCESS struct { 185 | ServiceType uint32 186 | CurrentState uint32 187 | ControlsAccepted uint32 188 | Win32ExitCode uint32 189 | ServiceSpecificExitCode uint32 190 | CheckPoint uint32 191 | WaitHint uint32 192 | ProcessId uint32 193 | ServiceFlags uint32 194 | } 195 | 196 | type ENUM_SERVICE_STATUS_PROCESS struct { 197 | ServiceName *uint16 198 | DisplayName *uint16 199 | ServiceStatusProcess SERVICE_STATUS_PROCESS 200 | } 201 | 202 | type SERVICE_NOTIFY struct { 203 | Version uint32 204 | NotifyCallback uintptr 205 | Context uintptr 206 | NotificationStatus uint32 207 | ServiceStatus SERVICE_STATUS_PROCESS 208 | NotificationTriggered uint32 209 | ServiceNames *uint16 210 | } 211 | 212 | type SERVICE_FAILURE_ACTIONS struct { 213 | ResetPeriod uint32 214 | RebootMsg *uint16 215 | Command *uint16 216 | ActionsCount uint32 217 | Actions *SC_ACTION 218 | } 219 | 220 | type SERVICE_FAILURE_ACTIONS_FLAG struct { 221 | FailureActionsOnNonCrashFailures int32 222 | } 223 | 224 | type SC_ACTION struct { 225 | Type uint32 226 | Delay uint32 227 | } 228 | 229 | type QUERY_SERVICE_LOCK_STATUS struct { 230 | IsLocked uint32 231 | LockOwner *uint16 232 | LockDuration uint32 233 | } 234 | 235 | //sys OpenSCManager(machineName *uint16, databaseName *uint16, access uint32) (handle Handle, err error) [failretval==0] = advapi32.OpenSCManagerW 236 | //sys CloseServiceHandle(handle Handle) (err error) = advapi32.CloseServiceHandle 237 | //sys CreateService(mgr Handle, serviceName *uint16, displayName *uint16, access uint32, srvType uint32, startType uint32, errCtl uint32, pathName *uint16, loadOrderGroup *uint16, tagId *uint32, dependencies *uint16, serviceStartName *uint16, password *uint16) (handle Handle, err error) [failretval==0] = advapi32.CreateServiceW 238 | //sys OpenService(mgr Handle, serviceName *uint16, access uint32) (handle Handle, err error) [failretval==0] = advapi32.OpenServiceW 239 | //sys DeleteService(service Handle) (err error) = advapi32.DeleteService 240 | //sys StartService(service Handle, numArgs uint32, argVectors **uint16) (err error) = advapi32.StartServiceW 241 | //sys QueryServiceStatus(service Handle, status *SERVICE_STATUS) (err error) = advapi32.QueryServiceStatus 242 | //sys QueryServiceLockStatus(mgr Handle, lockStatus *QUERY_SERVICE_LOCK_STATUS, bufSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceLockStatusW 243 | //sys ControlService(service Handle, control uint32, status *SERVICE_STATUS) (err error) = advapi32.ControlService 244 | //sys StartServiceCtrlDispatcher(serviceTable *SERVICE_TABLE_ENTRY) (err error) = advapi32.StartServiceCtrlDispatcherW 245 | //sys SetServiceStatus(service Handle, serviceStatus *SERVICE_STATUS) (err error) = advapi32.SetServiceStatus 246 | //sys ChangeServiceConfig(service Handle, serviceType uint32, startType uint32, errorControl uint32, binaryPathName *uint16, loadOrderGroup *uint16, tagId *uint32, dependencies *uint16, serviceStartName *uint16, password *uint16, displayName *uint16) (err error) = advapi32.ChangeServiceConfigW 247 | //sys QueryServiceConfig(service Handle, serviceConfig *QUERY_SERVICE_CONFIG, bufSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceConfigW 248 | //sys ChangeServiceConfig2(service Handle, infoLevel uint32, info *byte) (err error) = advapi32.ChangeServiceConfig2W 249 | //sys QueryServiceConfig2(service Handle, infoLevel uint32, buff *byte, buffSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceConfig2W 250 | //sys EnumServicesStatusEx(mgr Handle, infoLevel uint32, serviceType uint32, serviceState uint32, services *byte, bufSize uint32, bytesNeeded *uint32, servicesReturned *uint32, resumeHandle *uint32, groupName *uint16) (err error) = advapi32.EnumServicesStatusExW 251 | //sys QueryServiceStatusEx(service Handle, infoLevel uint32, buff *byte, buffSize uint32, bytesNeeded *uint32) (err error) = advapi32.QueryServiceStatusEx 252 | //sys NotifyServiceStatusChange(service Handle, notifyMask uint32, notifier *SERVICE_NOTIFY) (ret error) = advapi32.NotifyServiceStatusChangeW 253 | //sys SubscribeServiceChangeNotifications(service Handle, eventType uint32, callback uintptr, callbackCtx uintptr, subscription *uintptr) (ret error) = sechost.SubscribeServiceChangeNotifications? 254 | //sys UnsubscribeServiceChangeNotifications(subscription uintptr) = sechost.UnsubscribeServiceChangeNotifications? 255 | //sys RegisterServiceCtrlHandlerEx(serviceName *uint16, handlerProc uintptr, context uintptr) (handle Handle, err error) = advapi32.RegisterServiceCtrlHandlerExW 256 | //sys QueryServiceDynamicInformation(service Handle, infoLevel uint32, dynamicInfo unsafe.Pointer) (err error) = advapi32.QueryServiceDynamicInformation? 257 | //sys EnumDependentServices(service Handle, activityState uint32, services *ENUM_SERVICE_STATUS, buffSize uint32, bytesNeeded *uint32, servicesReturned *uint32) (err error) = advapi32.EnumDependentServicesW 258 | -------------------------------------------------------------------------------- /vendor/golang.org/x/sys/windows/str.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build windows 6 | 7 | package windows 8 | 9 | func itoa(val int) string { // do it here rather than with fmt to avoid dependency 10 | if val < 0 { 11 | return "-" + itoa(-val) 12 | } 13 | var buf [32]byte // big enough for int64 14 | i := len(buf) - 1 15 | for val >= 10 { 16 | buf[i] = byte(val%10 + '0') 17 | i-- 18 | val /= 10 19 | } 20 | buf[i] = byte(val + '0') 21 | return string(buf[i:]) 22 | } 23 | -------------------------------------------------------------------------------- /vendor/golang.org/x/sys/windows/syscall.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | //go:build windows 6 | 7 | // Package windows contains an interface to the low-level operating system 8 | // primitives. OS details vary depending on the underlying system, and 9 | // by default, godoc will display the OS-specific documentation for the current 10 | // system. If you want godoc to display syscall documentation for another 11 | // system, set $GOOS and $GOARCH to the desired system. For example, if 12 | // you want to view documentation for freebsd/arm on linux/amd64, set $GOOS 13 | // to freebsd and $GOARCH to arm. 14 | // 15 | // The primary use of this package is inside other packages that provide a more 16 | // portable interface to the system, such as "os", "time" and "net". Use 17 | // those packages rather than this one if you can. 18 | // 19 | // For details of the functions and data types in this package consult 20 | // the manuals for the appropriate operating system. 21 | // 22 | // These calls return err == nil to indicate success; otherwise 23 | // err represents an operating system error describing the failure and 24 | // holds a value of type syscall.Errno. 25 | package windows // import "golang.org/x/sys/windows" 26 | 27 | import ( 28 | "bytes" 29 | "strings" 30 | "syscall" 31 | "unsafe" 32 | ) 33 | 34 | // ByteSliceFromString returns a NUL-terminated slice of bytes 35 | // containing the text of s. If s contains a NUL byte at any 36 | // location, it returns (nil, syscall.EINVAL). 37 | func ByteSliceFromString(s string) ([]byte, error) { 38 | if strings.IndexByte(s, 0) != -1 { 39 | return nil, syscall.EINVAL 40 | } 41 | a := make([]byte, len(s)+1) 42 | copy(a, s) 43 | return a, nil 44 | } 45 | 46 | // BytePtrFromString returns a pointer to a NUL-terminated array of 47 | // bytes containing the text of s. If s contains a NUL byte at any 48 | // location, it returns (nil, syscall.EINVAL). 49 | func BytePtrFromString(s string) (*byte, error) { 50 | a, err := ByteSliceFromString(s) 51 | if err != nil { 52 | return nil, err 53 | } 54 | return &a[0], nil 55 | } 56 | 57 | // ByteSliceToString returns a string form of the text represented by the slice s, with a terminating NUL and any 58 | // bytes after the NUL removed. 59 | func ByteSliceToString(s []byte) string { 60 | if i := bytes.IndexByte(s, 0); i != -1 { 61 | s = s[:i] 62 | } 63 | return string(s) 64 | } 65 | 66 | // BytePtrToString takes a pointer to a sequence of text and returns the corresponding string. 67 | // If the pointer is nil, it returns the empty string. It assumes that the text sequence is terminated 68 | // at a zero byte; if the zero byte is not present, the program may crash. 69 | func BytePtrToString(p *byte) string { 70 | if p == nil { 71 | return "" 72 | } 73 | if *p == 0 { 74 | return "" 75 | } 76 | 77 | // Find NUL terminator. 78 | n := 0 79 | for ptr := unsafe.Pointer(p); *(*byte)(ptr) != 0; n++ { 80 | ptr = unsafe.Pointer(uintptr(ptr) + 1) 81 | } 82 | 83 | return string(unsafe.Slice(p, n)) 84 | } 85 | 86 | // Single-word zero for use when we need a valid pointer to 0 bytes. 87 | // See mksyscall.pl. 88 | var _zero uintptr 89 | 90 | func (ts *Timespec) Unix() (sec int64, nsec int64) { 91 | return int64(ts.Sec), int64(ts.Nsec) 92 | } 93 | 94 | func (tv *Timeval) Unix() (sec int64, nsec int64) { 95 | return int64(tv.Sec), int64(tv.Usec) * 1000 96 | } 97 | 98 | func (ts *Timespec) Nano() int64 { 99 | return int64(ts.Sec)*1e9 + int64(ts.Nsec) 100 | } 101 | 102 | func (tv *Timeval) Nano() int64 { 103 | return int64(tv.Sec)*1e9 + int64(tv.Usec)*1000 104 | } 105 | -------------------------------------------------------------------------------- /vendor/golang.org/x/sys/windows/types_windows_386.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package windows 6 | 7 | type WSAData struct { 8 | Version uint16 9 | HighVersion uint16 10 | Description [WSADESCRIPTION_LEN + 1]byte 11 | SystemStatus [WSASYS_STATUS_LEN + 1]byte 12 | MaxSockets uint16 13 | MaxUdpDg uint16 14 | VendorInfo *byte 15 | } 16 | 17 | type Servent struct { 18 | Name *byte 19 | Aliases **byte 20 | Port uint16 21 | Proto *byte 22 | } 23 | 24 | type JOBOBJECT_BASIC_LIMIT_INFORMATION struct { 25 | PerProcessUserTimeLimit int64 26 | PerJobUserTimeLimit int64 27 | LimitFlags uint32 28 | MinimumWorkingSetSize uintptr 29 | MaximumWorkingSetSize uintptr 30 | ActiveProcessLimit uint32 31 | Affinity uintptr 32 | PriorityClass uint32 33 | SchedulingClass uint32 34 | _ uint32 // pad to 8 byte boundary 35 | } 36 | -------------------------------------------------------------------------------- /vendor/golang.org/x/sys/windows/types_windows_amd64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package windows 6 | 7 | type WSAData struct { 8 | Version uint16 9 | HighVersion uint16 10 | MaxSockets uint16 11 | MaxUdpDg uint16 12 | VendorInfo *byte 13 | Description [WSADESCRIPTION_LEN + 1]byte 14 | SystemStatus [WSASYS_STATUS_LEN + 1]byte 15 | } 16 | 17 | type Servent struct { 18 | Name *byte 19 | Aliases **byte 20 | Proto *byte 21 | Port uint16 22 | } 23 | 24 | type JOBOBJECT_BASIC_LIMIT_INFORMATION struct { 25 | PerProcessUserTimeLimit int64 26 | PerJobUserTimeLimit int64 27 | LimitFlags uint32 28 | MinimumWorkingSetSize uintptr 29 | MaximumWorkingSetSize uintptr 30 | ActiveProcessLimit uint32 31 | Affinity uintptr 32 | PriorityClass uint32 33 | SchedulingClass uint32 34 | } 35 | -------------------------------------------------------------------------------- /vendor/golang.org/x/sys/windows/types_windows_arm.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package windows 6 | 7 | type WSAData struct { 8 | Version uint16 9 | HighVersion uint16 10 | Description [WSADESCRIPTION_LEN + 1]byte 11 | SystemStatus [WSASYS_STATUS_LEN + 1]byte 12 | MaxSockets uint16 13 | MaxUdpDg uint16 14 | VendorInfo *byte 15 | } 16 | 17 | type Servent struct { 18 | Name *byte 19 | Aliases **byte 20 | Port uint16 21 | Proto *byte 22 | } 23 | 24 | type JOBOBJECT_BASIC_LIMIT_INFORMATION struct { 25 | PerProcessUserTimeLimit int64 26 | PerJobUserTimeLimit int64 27 | LimitFlags uint32 28 | MinimumWorkingSetSize uintptr 29 | MaximumWorkingSetSize uintptr 30 | ActiveProcessLimit uint32 31 | Affinity uintptr 32 | PriorityClass uint32 33 | SchedulingClass uint32 34 | _ uint32 // pad to 8 byte boundary 35 | } 36 | -------------------------------------------------------------------------------- /vendor/golang.org/x/sys/windows/types_windows_arm64.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package windows 6 | 7 | type WSAData struct { 8 | Version uint16 9 | HighVersion uint16 10 | MaxSockets uint16 11 | MaxUdpDg uint16 12 | VendorInfo *byte 13 | Description [WSADESCRIPTION_LEN + 1]byte 14 | SystemStatus [WSASYS_STATUS_LEN + 1]byte 15 | } 16 | 17 | type Servent struct { 18 | Name *byte 19 | Aliases **byte 20 | Proto *byte 21 | Port uint16 22 | } 23 | 24 | type JOBOBJECT_BASIC_LIMIT_INFORMATION struct { 25 | PerProcessUserTimeLimit int64 26 | PerJobUserTimeLimit int64 27 | LimitFlags uint32 28 | MinimumWorkingSetSize uintptr 29 | MaximumWorkingSetSize uintptr 30 | ActiveProcessLimit uint32 31 | Affinity uintptr 32 | PriorityClass uint32 33 | SchedulingClass uint32 34 | } 35 | -------------------------------------------------------------------------------- /vendor/modules.txt: -------------------------------------------------------------------------------- 1 | # github.com/danieljoos/wincred v1.2.2 2 | ## explicit; go 1.18 3 | github.com/danieljoos/wincred 4 | # github.com/keybase/go-keychain v0.0.1 5 | ## explicit; go 1.21 6 | github.com/keybase/go-keychain 7 | # golang.org/x/sys v0.20.0 8 | ## explicit; go 1.18 9 | golang.org/x/sys/windows 10 | -------------------------------------------------------------------------------- /wincred/cmd/main.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/docker/docker-credential-helpers/credentials" 7 | "github.com/docker/docker-credential-helpers/wincred" 8 | ) 9 | 10 | func main() { 11 | credentials.Serve(wincred.Wincred{}) 12 | } 13 | -------------------------------------------------------------------------------- /wincred/wincred.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package wincred 4 | 5 | import ( 6 | "bytes" 7 | "net/url" 8 | "strings" 9 | 10 | winc "github.com/danieljoos/wincred" 11 | "github.com/docker/docker-credential-helpers/credentials" 12 | "github.com/docker/docker-credential-helpers/registryurl" 13 | ) 14 | 15 | // Wincred handles secrets using the Windows credential service. 16 | type Wincred struct{} 17 | 18 | // Add adds new credentials to the windows credentials manager. 19 | func (h Wincred) Add(creds *credentials.Credentials) error { 20 | credsLabels := []byte(credentials.CredsLabel) 21 | g := winc.NewGenericCredential(creds.ServerURL) 22 | g.UserName = creds.Username 23 | g.CredentialBlob = []byte(creds.Secret) 24 | g.Persist = winc.PersistLocalMachine 25 | g.Attributes = []winc.CredentialAttribute{{Keyword: "label", Value: credsLabels}} 26 | 27 | return g.Write() 28 | } 29 | 30 | // Delete removes credentials from the windows credentials manager. 31 | func (h Wincred) Delete(serverURL string) error { 32 | g, err := winc.GetGenericCredential(serverURL) 33 | if g == nil { 34 | return nil 35 | } 36 | if err != nil { 37 | return err 38 | } 39 | return g.Delete() 40 | } 41 | 42 | // Get retrieves credentials from the windows credentials manager. 43 | func (h Wincred) Get(serverURL string) (string, string, error) { 44 | target, err := getTarget(serverURL) 45 | if err != nil { 46 | return "", "", err 47 | } else if target == "" { 48 | return "", "", credentials.NewErrCredentialsNotFound() 49 | } 50 | 51 | g, _ := winc.GetGenericCredential(target) 52 | if g == nil { 53 | return "", "", credentials.NewErrCredentialsNotFound() 54 | } 55 | 56 | for _, attr := range g.Attributes { 57 | if strings.Compare(attr.Keyword, "label") == 0 && 58 | bytes.Compare(attr.Value, []byte(credentials.CredsLabel)) == 0 { 59 | 60 | return g.UserName, string(g.CredentialBlob), nil 61 | } 62 | } 63 | return "", "", credentials.NewErrCredentialsNotFound() 64 | } 65 | 66 | func getTarget(serverURL string) (string, error) { 67 | s, err := registryurl.Parse(serverURL) 68 | if err != nil { 69 | return serverURL, nil 70 | } 71 | 72 | creds, err := winc.List() 73 | if err != nil { 74 | return "", err 75 | } 76 | 77 | var targets []string 78 | for i := range creds { 79 | attrs := creds[i].Attributes 80 | for _, attr := range attrs { 81 | if attr.Keyword == "label" && bytes.Equal(attr.Value, []byte(credentials.CredsLabel)) { 82 | targets = append(targets, creds[i].TargetName) 83 | } 84 | } 85 | } 86 | 87 | if target, found := findMatch(s, targets, exactMatch); found { 88 | return target, nil 89 | } 90 | 91 | if target, found := findMatch(s, targets, approximateMatch); found { 92 | return target, nil 93 | } 94 | 95 | return "", nil 96 | } 97 | 98 | func findMatch(serverUrl *url.URL, targets []string, matches func(url.URL, url.URL) bool) (string, bool) { 99 | for _, target := range targets { 100 | tURL, err := registryurl.Parse(target) 101 | if err != nil { 102 | continue 103 | } 104 | if matches(*serverUrl, *tURL) { 105 | return target, true 106 | } 107 | } 108 | return "", false 109 | } 110 | 111 | func exactMatch(serverURL, target url.URL) bool { 112 | return serverURL.String() == target.String() 113 | } 114 | 115 | func approximateMatch(serverURL, target url.URL) bool { 116 | // if scheme is missing assume it is the same as target 117 | if serverURL.Scheme == "" { 118 | serverURL.Scheme = target.Scheme 119 | } 120 | // if port is missing assume it is the same as target 121 | if serverURL.Port() == "" && target.Port() != "" { 122 | serverURL.Host = serverURL.Host + ":" + target.Port() 123 | } 124 | // if path is missing assume it is the same as target 125 | if serverURL.Path == "" { 126 | serverURL.Path = target.Path 127 | } 128 | return serverURL.String() == target.String() 129 | } 130 | 131 | // List returns the stored URLs and corresponding usernames for a given credentials label. 132 | func (h Wincred) List() (map[string]string, error) { 133 | creds, err := winc.List() 134 | if err != nil { 135 | return nil, err 136 | } 137 | 138 | resp := make(map[string]string) 139 | for i := range creds { 140 | attrs := creds[i].Attributes 141 | for _, attr := range attrs { 142 | if strings.Compare(attr.Keyword, "label") == 0 && 143 | bytes.Compare(attr.Value, []byte(credentials.CredsLabel)) == 0 { 144 | 145 | resp[creds[i].TargetName] = creds[i].UserName 146 | } 147 | } 148 | 149 | } 150 | 151 | return resp, nil 152 | } 153 | -------------------------------------------------------------------------------- /wincred/wincred_test.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package wincred 4 | 5 | import ( 6 | "fmt" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/docker/docker-credential-helpers/credentials" 11 | ) 12 | 13 | func TestWinCredHelper(t *testing.T) { 14 | creds := &credentials.Credentials{ 15 | ServerURL: "https://foobar.docker.io:2376/v1", 16 | Username: "foobar", 17 | Secret: "foobarbaz", 18 | } 19 | creds1 := &credentials.Credentials{ 20 | ServerURL: "https://foobar.docker.io:2376/v2", 21 | Username: "foobarbaz", 22 | Secret: "foobar", 23 | } 24 | 25 | helper := Wincred{} 26 | 27 | // check for and remove remaining credentials from previous fail tests 28 | oldauths, err := helper.List() 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | 33 | for k, v := range oldauths { 34 | if strings.Compare(k, creds.ServerURL) == 0 && strings.Compare(v, creds.Username) == 0 { 35 | if err := helper.Delete(creds.ServerURL); err != nil { 36 | t.Fatal(err) 37 | } 38 | } else if strings.Compare(k, creds1.ServerURL) == 0 && strings.Compare(v, creds1.Username) == 0 { 39 | if err := helper.Delete(creds1.ServerURL); err != nil { 40 | t.Fatal(err) 41 | } 42 | } 43 | } 44 | 45 | // recount for credentials 46 | oldauths, err = helper.List() 47 | if err != nil { 48 | t.Fatal(err) 49 | } 50 | 51 | if err := helper.Add(creds); err != nil { 52 | t.Fatal(err) 53 | } 54 | 55 | username, secret, err := helper.Get(creds.ServerURL) 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | 60 | if username != "foobar" { 61 | t.Fatalf("expected %s, got %s\n", "foobar", username) 62 | } 63 | 64 | if secret != "foobarbaz" { 65 | t.Fatalf("expected %s, got %s\n", "foobarbaz", secret) 66 | } 67 | 68 | auths, err := helper.List() 69 | if err != nil || len(auths)-len(oldauths) != 1 { 70 | t.Fatal(err) 71 | } 72 | 73 | helper.Add(creds1) 74 | defer helper.Delete(creds1.ServerURL) 75 | newauths, err := helper.List() 76 | if err != nil { 77 | t.Fatal(err) 78 | } 79 | 80 | if len(newauths)-len(auths) != 1 { 81 | if err == nil { 82 | t.Fatalf("Error: len(newauths): %d, len(auths): %d", len(newauths), len(auths)) 83 | } 84 | t.Fatalf("Error: len(newauths): %d, len(auths): %d\n Error= %v", len(newauths), len(auths), err) 85 | } 86 | 87 | if err := helper.Delete(creds.ServerURL); err != nil { 88 | t.Fatal(err) 89 | } 90 | } 91 | 92 | // TestWinCredHelperRetrieveAliases verifies that secrets can be accessed 93 | // through variations on the URL 94 | func TestWinCredHelperRetrieveAliases(t *testing.T) { 95 | tests := []struct { 96 | doc string 97 | storeURL string 98 | readURL string 99 | }{ 100 | { 101 | doc: "stored with port, retrieved without", 102 | storeURL: "https://foobar.docker.io:2376", 103 | readURL: "https://foobar.docker.io", 104 | }, 105 | { 106 | doc: "stored as https, retrieved without scheme", 107 | storeURL: "https://foobar.docker.io", 108 | readURL: "foobar.docker.io", 109 | }, 110 | { 111 | doc: "stored with path, retrieved without", 112 | storeURL: "https://foobar.docker.io/one/two", 113 | readURL: "https://foobar.docker.io", 114 | }, 115 | } 116 | 117 | helper := Wincred{} 118 | t.Cleanup(func() { 119 | for _, tc := range tests { 120 | if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) { 121 | t.Errorf("cleanup: failed to delete '%s': %v", tc.storeURL, err) 122 | } 123 | } 124 | }) 125 | 126 | // Clean store before testing. 127 | for _, tc := range tests { 128 | if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) { 129 | t.Errorf("prepare: failed to delete '%s': %v", tc.storeURL, err) 130 | } 131 | } 132 | 133 | for _, tc := range tests { 134 | tc := tc 135 | t.Run(tc.doc, func(t *testing.T) { 136 | c := &credentials.Credentials{ServerURL: tc.storeURL, Username: "hello", Secret: "world"} 137 | if err := helper.Add(c); err != nil { 138 | t.Fatalf("Error: failed to store secret for URL %q: %s", tc.storeURL, err) 139 | } 140 | if _, _, err := helper.Get(tc.readURL); err != nil { 141 | t.Errorf("Error: failed to read secret for URL %q using %q", tc.storeURL, tc.readURL) 142 | } 143 | if err := helper.Delete(tc.storeURL); err != nil { 144 | t.Error(err) 145 | } 146 | }) 147 | } 148 | } 149 | 150 | // TestWinCredHelperRetrieveStrict verifies that only matching secrets are 151 | // returned. 152 | func TestWinCredHelperRetrieveStrict(t *testing.T) { 153 | tests := []struct { 154 | doc string 155 | storeURL string 156 | readURL string 157 | }{ 158 | { 159 | doc: "stored as https, retrieved using http", 160 | storeURL: "https://foobar.docker.io:2376", 161 | readURL: "http://foobar.docker.io:2376", 162 | }, 163 | { 164 | doc: "stored as http, retrieved using https", 165 | storeURL: "http://foobar.docker.io:2376", 166 | readURL: "https://foobar.docker.io:2376", 167 | }, 168 | { 169 | // stored as http, retrieved without a scheme specified (hence, using the default https://) 170 | doc: "stored as http, retrieved without scheme", 171 | storeURL: "http://foobar.docker.io", 172 | readURL: "foobar.docker.io:5678", 173 | }, 174 | { 175 | doc: "non-matching ports", 176 | storeURL: "https://foobar.docker.io:1234", 177 | readURL: "https://foobar.docker.io:5678", 178 | }, 179 | // TODO: is this desired behavior? The other way round does work 180 | // { 181 | // doc: "non-matching ports (stored without port)", 182 | // storeURL: "https://foobar.docker.io", 183 | // readURL: "https://foobar.docker.io:5678", 184 | // }, 185 | { 186 | doc: "non-matching paths", 187 | storeURL: "https://foobar.docker.io:1234/one/two", 188 | readURL: "https://foobar.docker.io:1234/five/six", 189 | }, 190 | } 191 | 192 | helper := Wincred{} 193 | t.Cleanup(func() { 194 | for _, tc := range tests { 195 | if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) { 196 | t.Errorf("cleanup: failed to delete '%s': %v", tc.storeURL, err) 197 | } 198 | } 199 | }) 200 | 201 | // Clean store before testing. 202 | for _, tc := range tests { 203 | if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) { 204 | t.Errorf("prepare: failed to delete '%s': %v", tc.storeURL, err) 205 | } 206 | } 207 | 208 | for _, tc := range tests { 209 | tc := tc 210 | t.Run(tc.doc, func(t *testing.T) { 211 | c := &credentials.Credentials{ServerURL: tc.storeURL, Username: "hello", Secret: "world"} 212 | if err := helper.Add(c); err != nil { 213 | t.Fatalf("Error: failed to store secret for URL %q: %s", tc.storeURL, err) 214 | } 215 | if _, _, err := helper.Get(tc.readURL); err == nil { 216 | t.Errorf("Error: managed to read secret for URL %q using %q, but should not be able to", tc.storeURL, tc.readURL) 217 | } 218 | if err := helper.Delete(tc.storeURL); err != nil { 219 | t.Error(err) 220 | } 221 | }) 222 | } 223 | } 224 | 225 | // TestWinCredHelperStoreRetrieve verifies that secrets stored in the 226 | // the keychain can be read back using the URL that was used to store them. 227 | func TestWinCredHelperStoreRetrieve(t *testing.T) { 228 | tests := []struct { 229 | url string 230 | }{ 231 | {url: "foobar.docker.io"}, 232 | {url: "foobar.docker.io:2376"}, 233 | {url: "//foobar.docker.io:2376"}, 234 | {url: "https://foobar.docker.io:2376"}, 235 | {url: "http://foobar.docker.io:2376"}, 236 | {url: "https://foobar.docker.io:2376/some/path"}, 237 | {url: "https://foobar.docker.io:2376/some/other/path"}, 238 | {url: "https://foobar.docker.io:2376/some/other/path?foo=bar"}, 239 | } 240 | 241 | helper := Wincred{} 242 | t.Cleanup(func() { 243 | for _, tc := range tests { 244 | if err := helper.Delete(tc.url); err != nil && !credentials.IsErrCredentialsNotFound(err) { 245 | t.Errorf("cleanup: failed to delete '%s': %v", tc.url, err) 246 | } 247 | } 248 | }) 249 | 250 | // Clean store before testing. 251 | for _, tc := range tests { 252 | if err := helper.Delete(tc.url); err != nil && !credentials.IsErrCredentialsNotFound(err) { 253 | t.Errorf("prepare: failed to delete '%s': %v", tc.url, err) 254 | } 255 | } 256 | 257 | // Note that we don't delete between individual tests here, to verify that 258 | // subsequent stores/overwrites don't affect storing / retrieving secrets. 259 | for i, tc := range tests { 260 | tc := tc 261 | t.Run(tc.url, func(t *testing.T) { 262 | c := &credentials.Credentials{ 263 | ServerURL: tc.url, 264 | Username: fmt.Sprintf("user-%d", i), 265 | Secret: fmt.Sprintf("secret-%d", i), 266 | } 267 | 268 | if err := helper.Add(c); err != nil { 269 | t.Fatalf("Error: failed to store secret for URL: %s: %s", tc.url, err) 270 | } 271 | user, secret, err := helper.Get(tc.url) 272 | if err != nil { 273 | t.Fatalf("Error: failed to read secret for URL %q: %s", tc.url, err) 274 | } 275 | if user != c.Username { 276 | t.Errorf("Error: expected username %s, got username %s for URL: %s", c.Username, user, tc.url) 277 | } 278 | if secret != c.Secret { 279 | t.Errorf("Error: expected secret %s, got secret %s for URL: %s", c.Secret, secret, tc.url) 280 | } 281 | }) 282 | } 283 | } 284 | 285 | func TestMissingCredentials(t *testing.T) { 286 | helper := Wincred{} 287 | _, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd") 288 | if !credentials.IsErrCredentialsNotFound(err) { 289 | t.Fatalf("expected ErrCredentialsNotFound, got %v", err) 290 | } 291 | } 292 | --------------------------------------------------------------------------------