1
0
mirror of https://github.com/docker/docker-credential-helpers.git synced 2026-06-28 15:21:29 +05:30

Compare commits

..

170 Commits

Author SHA1 Message Date
Albin Kerouanton cfd6d21216 Merge pull request #366 from thaJeztah/gha_bump_ubuntu
gha: add ubuntu 24.04, remove 20.04
2025-03-04 17:29:11 +01:00
Sebastiaan van Stijn ab29a6c87b gha: add ubuntu 24.04, remove 20.04
Github is phasing out Ubuntu 20.04, and currently is doing brownouts;
https://github.com/actions/runner-images/issues/11101

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-03-04 17:23:42 +01:00
Sebastiaan van Stijn 576efaa084 Merge pull request #363 from akerouanton/retract-v0.9.1
go.mod: retract v0.9.1
2025-03-04 17:21:35 +01:00
Sebastiaan van Stijn 9d6cdddf25 Merge pull request #364 from akerouanton/fix-regression-v0.9.0
osxkeychain: list: return full server URIs
2025-03-04 13:42:33 +01:00
Albin Kerouanton d8e34f8743 osxkeychain: tests: uncleaned paths are preserved
Signed-off-by: Albin Kerouanton <albinker@gmail.com>
2025-03-04 11:43:16 +01:00
Albin Kerouanton b1d5bf0326 osxkeychain: list: return full server URIs
Commit 4cdcdc2 changed the format of `list` output. Before that commit,
the json keys were containing full URIs (scheme://host/path[:port]),
but afterward, the keys were only containing the path component.

With this commit, the `list` operation now returns full URIs (fixing the
regression), and also fixes the malformed URIs issue when a port is
specified (introduced by 19ec1c3, and affecting >=v0.4.2,<v0.9.0).

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
2025-03-04 11:20:57 +01:00
Albin Kerouanton 50b162c340 go.mod: retract v0.9.1
`osxkeychain` in v0.9.1 still doesn't list credentials as prior versions
did. We're retracting this version too.

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
2025-02-28 19:40:33 +01:00
Albin Kerouanton 833d2c334f Merge pull request #362 from akerouanton/retract-v0.9.0
go.mod: retract v0.9.0
2025-02-28 13:10:46 +01:00
Albin Kerouanton 9651bf7802 go.mod: retract v0.9.0
Commit 4cdcdc2 introduced two regressions in the `osxkeychain`
credential helper,  on `list` and `get` operations. These were addressed
in:

- Commit c7514a0: osxkeychain: list: do not error out when keychain is empty
- Commit f4cdabf: osxkeychain: store: use Apple's proto consts

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
2025-02-28 13:05:40 +01:00
Sebastiaan van Stijn 26274da6cf Merge pull request #361 from akerouanton/fix-regressions-v0.9.0
[v0.9.0] osxkeychain: fix regressions on get and list
2025-02-28 12:51:02 +01:00
Albin Kerouanton f4cdabf916 osxkeychain: store: use Apple's proto consts
Commit 4cdcdc2 swapped consts `kSecProtocolTypeHTTPS` and
`kSecProtocolTypeHTTP` with plain-text "https" and "http" strings.

This is causing a regression where credentials stored with prior
versions (< v0.9.0) can't be fetched anymore.

Unfortunately we can't just revert back to using Objective-C consts, as
these are unsigned integers that need to be converted into `CFStringRef`
and then passed to an helper like `keychain.CFStringToString`.

Although `keychain.CFStringToString` is exported, it takes a C type
`C.CFStringRef` so it's not consumable from other packages due to Cgo
restrictions:

> Cgo translates C types into equivalent unexported Go types. Because
> the translations are unexported, a Go package should not expose C
> types in its exported API: a C type used in one Go package is
> different from the same C type used in another.

We could alternatively copy `keychain.CFStringToString` into the
`osxkeychain` package, but this commit takes a simpler approach: just
hardcode the value of `kSecProtocolTypeHTTPS` and `kSecProtocolTypeHTTP`
as strings. (These consts are very unlikely to ever change since it'd
break all existing consumers.)

This is **NOT** handling backward compatibility with v0.9.0, since it
was released only 12hrs ago. So this fix won't work with credentials
created with v0.9.0.

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
2025-02-28 12:19:53 +01:00
Albin Kerouanton c7514a0999 osxkeychain: list: do not error out when keychain is empty
Commit 4cdcdc2 replaced the in-tree Objective-C code with github.com/keybase/go-keychain
and inadvertently introduced a new failure mode on the `List` operation -
it now fails when the keychain is empty.

Before:

```
$ ./bin/build/docker-credential-osxkeychain list
{}
```

After:

```
$ ./bin/build/docker-credential-osxkeychain list
credentials not found in native keychain
```

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
2025-02-28 10:00:35 +01:00
Sebastiaan van Stijn 36a3c50452 Merge pull request #359 from thaJeztah/bump_golangci_lint
Dockerfile: update golangci-lint to v1.64.5
2025-02-28 00:13:45 +01:00
Sebastiaan van Stijn 4e957ecd1b Merge pull request #340 from thaJeztah/bump_golang_1.22.8
update to go1.23.6
2025-02-28 00:13:26 +01:00
Sebastiaan van Stijn f7f8554b4c Merge pull request #258 from The-Alchemist/patch-1
minor formatting tweaks to README
2025-02-28 00:06:58 +01:00
The Alchemist 1a77fa667f minor formatting tweaks to README
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-02-28 00:00:29 +01:00
Sebastiaan van Stijn 8a779f2b11 Dockerfile: update golangci-lint to v1.64.5
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-02-27 23:47:17 +01:00
Sebastiaan van Stijn a767624e34 update to go1.23.6
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-02-27 23:40:02 +01:00
Sebastiaan van Stijn a5569fbfff update to go1.22.11 (fix CVE-2024-45341, CVE-2024-45336)
go1.22.11 (released 2025-01-16) includes security fixes to the crypto/x509 and
net/http packages, as well as bug fixes to the runtime. See the Go 1.22.11
milestone on our issue tracker for details.

- https://github.com/golang/go/issues?q=milestone%3AGo1.22.11+label%3ACherryPickApproved
- full diff: https://github.com/golang/go/compare/go1.22.10...go1.22.11

Hello gophers,

We have just released Go versions 1.23.5 and 1.22.11, minor point releases.

These minor releases include 2 security fixes following the security policy:

- crypto/x509: usage of IPv6 zone IDs can bypass URI name constraints

  A certificate with a URI which has a IPv6 address with a zone ID may
  incorrectly satisfy a URI name constraint that applies to the certificate
  chain.

  Certificates containing URIs are not permitted in the web PKI, so this
  only affects users of private PKIs which make use of URIs.

  Thanks to Juho Forsén of Mattermost for reporting this issue.

  This is CVE-2024-45341 and Go issue https://go.dev/issue/71156.

- net/http: sensitive headers incorrectly sent after cross-domain redirect

  The HTTP client drops sensitive headers after following a cross-domain redirect.
  For example, a request to a.com/ containing an Authorization header which is
  redirected to b.com/ will not send that header to b.com.

  In the event that the client received a subsequent same-domain redirect, however,
  the sensitive headers would be restored. For example, a chain of redirects from
  a.com/, to b.com/1, and finally to b.com/2 would incorrectly send the Authorization
  header to b.com/2.

  Thanks to Kyle Seely for reporting this issue.

  This is CVE-2024-45336 and Go issue https://go.dev/issue/70530.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-02-27 23:40:02 +01:00
Albin Kerouanton 99bf48e5f8 Makefile: set CGO_LDFLAGS=-latomic on arm/v6
Compiling with Go >= 1.22 on arm/v6 is failing with the following error
message:

27.84 gcc_libinit.c:44:8: error: large atomic operation may incur significant performance penalty; the access size (4 bytes) exceeds the max lock-free size (0  bytes) [-Werror,-Watomic-alignment]

For these Go versions, we need to manually link to libatomic as arm/v6
does not support atomic intrinsics and neither the CGo, nor the C
toolchain automatically link to that library.

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
2025-02-27 23:39:54 +01:00
Sebastiaan van Stijn 1041211a6e Merge pull request #358 from thaJeztah/bump_go_keychain
vendor: github.com/keybase/go-keychain v0.0.1
2025-02-27 19:38:01 +01:00
Sebastiaan van Stijn 8c804df56c vendor: github.com/keybase/go-keychain v0.0.1
- removes pkg/errors as dependency

full diff: https://github.com/keybase/go-keychain/compare/7f41edfa9689...v0.0.1

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-02-27 19:19:36 +01:00
Sebastiaan van Stijn a7974e91c5 Merge pull request #282 from crazy-max/darwin-go-keychain
osxkeychain: switch to github.com/keybase/go-keychain
2025-02-27 18:35:34 +01:00
CrazyMax ffe5a9835c ci: add macOS-15 to test matrix
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2025-02-27 14:39:23 +01:00
CrazyMax e79a8203ca osxkeychain: TestOSXKeychainHelperRetrieveAliases print err
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2025-02-27 14:39:23 +01:00
CrazyMax 4cdcdc29eb osxkeychain: switch to github.com/keybase/go-keychain
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2025-02-27 14:39:22 +01:00
CrazyMax 8438667191 Merge pull request #357 from thaJeztah/bump_wincred
vendor:  github.com/danieljoos/wincred v1.2.2
2025-02-27 14:38:31 +01:00
Sebastiaan van Stijn fc66c3f02c Merge pull request #356 from WanzenBug/master
secretservice: fix null derefence on locked collections
2025-02-27 14:18:14 +01:00
Sebastiaan van Stijn 7810dc4db9 vendor: github.com/danieljoos/wincred v1.2.2
- Bump golang.org/x/sys from 0.17.0 to 0.20.0
- Bump github.com/stretchr/testify from 1.8.4 to 1.9.0
- Added missing constant to sys_unsupported.go to avoid breaking builds on non-Windows platforms.

full diff: https://github.com/danieljoos/wincred/compare/v1.2.1...v1.2.2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-02-27 13:07:17 +01:00
Moritz "WanzenBug" Wanzenböck 28e893e56d secretservice: fix null derefence on locked collections
secret_item_get_secret() may return null if an item is locked or not loaded.
While we set SECRET_SEARCH_LOAD_SECRETS and SECRET_SEARCH_UNLOCK, there may
still be locked items, for example the user may refuse the unlock request.

So we still need to check if the secret data is NULL before we can try to
reference it.

Signed-off-by: Moritz "WanzenBug" Wanzenböck <moritz@wanzenbug.xyz>
2025-02-19 15:56:07 +01:00
Sebastiaan van Stijn 1161e9c157 Merge pull request #353 from thaJeztah/bump_xx
Dockerfile: bump XX_VERSION to 1.6.1
2025-01-20 17:37:43 +01:00
Sebastiaan van Stijn a17e9a013b Dockerfile: bump XX_VERSION to 1.6.1
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-01-20 16:54:10 +01:00
Sebastiaan van Stijn fe0e8e3a01 Merge pull request #324 from thaJeztah/carry_207_friendlier_labels
Set a better displaylabel for secretservice
2025-01-20 16:35:26 +01:00
Hugo Osvaldo Barrera c2ca986943 Set a better displaylabel for secretservice
Secretservice entries have a "label". This is intended to be a
human-readable description. It's actually called "Description" in UIs
like seahorse, and the listing of existing secrets shows this as a name
for each one.

The entries stored by the credential helper set this to simply the
repository URL. This is rather unfriendly, since entries like
"gitlab.com" and "index.docker.io/v1" show up. Mixed in with
entries from all other applications, it's hard to figure out what
application owns each entry.

This commit changes the label used when saving entries to be something
human-readable (this is the intent of the "label" field, btw). Because
of the naming scheme, this also results in all entries being shown
together by default (since UIs tend to sort lexicographically).

New entries will now be stores as:

  Registry credentials for $REGISTRY_URL

Note that items stored by the secret service have multiple fields inside
of them. One of those fields is called "label", and is used by the
helper to filter items from the secret service. This "label" field is
entirely unrelated to the items' label. The naming is most unfortunate.

Signed-off-by: Hugo Osvaldo Barrera <hugo@barrera.io>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-01-20 16:28:06 +01:00
Sebastiaan van Stijn 407e50d36e Merge pull request #352 from crazy-max/bake-v6
ci: update bake-action to v6
2025-01-13 22:28:27 +01:00
CrazyMax 10845d8f94 Merge pull request #350 from docker/dependabot/github_actions/softprops/action-gh-release-2.2.1
build(deps): bump softprops/action-gh-release from 2.0.8 to 2.2.1
2025-01-10 11:00:25 +01:00
CrazyMax 43ae7f3412 Merge pull request #345 from docker/dependabot/github_actions/codecov/codecov-action-5
build(deps): bump codecov/codecov-action from 4 to 5
2025-01-10 11:00:08 +01:00
CrazyMax 1d9eaaa4ef ci: fix deprecated codecov-action input
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-01-10 10:55:01 +01:00
CrazyMax 7346714456 ci: update bake-action to v6
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-01-10 10:53:21 +01:00
dependabot[bot] ab3fc5283d build(deps): bump softprops/action-gh-release from 2.0.8 to 2.2.1
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.0.8 to 2.2.1.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/c062e08bd532815e2082a85e87e3ef29c3e6d191...c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-08 09:38:33 +00:00
dependabot[bot] 713df50a2d build(deps): bump codecov/codecov-action from 4 to 5
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4 to 5.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-15 10:06:35 +00:00
Sebastiaan van Stijn 7e7c5576ba Merge pull request #342 from thaJeztah/less_indirection
client: remove some indirection and touch-up GoDoc
2024-10-28 10:43:29 +01:00
Sebastiaan van Stijn ad253f54a5 client: remove some indirection and touch-up GoDoc
Both NewShellProgramFunc and NewShellProgramFuncWithEnv were using
createProgramCmdRedirectErr under the hood, but NewShellProgramFunc
had an extra indirection through NewShellProgramFuncWithEnv.

Make both a direct wrapper for createProgramCmdRedirectErr instead.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-25 14:41:30 +02:00
Sebastiaan van Stijn fa991bcbeb Merge pull request #338 from thaJeztah/bump_golangci_lint
Dockerfile: update golangci-lint to v1.61.0
2024-10-25 14:22:44 +02:00
Sebastiaan van Stijn db1da9da5d Merge pull request #339 from thaJeztah/bump_xx
Dockerfile: bump xx to v1.5.0
2024-10-25 13:54:32 +02:00
Sebastiaan van Stijn c23b2d6e4f Merge pull request #331 from docker/dependabot/github_actions/softprops/action-gh-release-2.0.8
build(deps): bump softprops/action-gh-release from 2.0.5 to 2.0.8
2024-10-25 13:49:14 +02:00
Sebastiaan van Stijn 13e62f3bbe Dockerfile: update golangci-lint to v1.61.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-25 13:48:02 +02:00
Sebastiaan van Stijn 2d241f3602 Dockerfile: bump xx to v1.5.0
full diff: https://github.com/tonistiigi/xx/compare/v1.4.0...v1.5.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-10-25 13:47:42 +02:00
Sebastiaan van Stijn 58c87f0952 Merge pull request #341 from crazy-max/update-debian
dockerfile: update debian to bookworm
2024-10-25 13:46:27 +02:00
CrazyMax dbb72e35c3 dockerfile: update debian to bookworm
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2024-10-25 12:41:21 +02:00
Sebastiaan van Stijn 6e5b45e59f Merge pull request #328 from docker/dependabot/github_actions/docker/bake-action-5
build(deps): bump docker/bake-action from 4 to 5
2024-10-24 21:45:11 +02:00
dependabot[bot] 2ed5a274b6 build(deps): bump softprops/action-gh-release from 2.0.5 to 2.0.8
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.0.5 to 2.0.8.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/69320dbe05506a9a39fc8ae11030b214ec2d1f87...c062e08bd532815e2082a85e87e3ef29c3e6d191)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-19 09:38:39 +00:00
dependabot[bot] 7dbcc1c472 build(deps): bump docker/bake-action from 4 to 5
Bumps [docker/bake-action](https://github.com/docker/bake-action) from 4 to 5.
- [Release notes](https://github.com/docker/bake-action/releases)
- [Commits](https://github.com/docker/bake-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: docker/bake-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-17 09:24:10 +00:00
Sebastiaan van Stijn 6b9df3ebb5 Merge pull request #323 from thaJeztah/pass_simplify_get
pass: Get: remove redundant stat
2024-05-10 14:15:26 +02:00
Sebastiaan van Stijn dc10c50685 Merge pull request #317 from docker/dependabot/github_actions/softprops/action-gh-release-2
build(deps): bump softprops/action-gh-release from 1 to 2
2024-05-10 14:14:16 +02:00
CrazyMax 896eb37d47 build(deps): bump softprops/action-gh-release to 2.0.5
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2024-05-10 11:04:52 +02:00
Sebastiaan van Stijn a14669f4ff pass: Get: remove redundant stat
listPassdir already handles "not found" errors, in which case it returns
an [empty result][1]. Previously this would return a custom error, but
since 1bb9aa3210, an empty result produces
a `errCredentialsNotFound`, making this check redundant.

This patch removes the redundant check.

[1]: https://github.com/docker/docker-credential-helpers/blob/f64d6b131b3da07a6337dc63a882e08ce541d1c1/pass/pass.go#L118-L125

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-05-10 10:28:57 +02:00
Sebastiaan van Stijn 74840b3740 Merge pull request #322 from thaJeztah/pass_dry
pass: add utilities for encoding/decoding serverURL
2024-05-10 10:23:32 +02:00
Sebastiaan van Stijn d3ef442f59 pass: add utilities for encoding/decoding serverURL
While the implementation of these is fairly trivial, we want them
to remain the same. This patch adds utilities to handle the encoding
and decoding of the server-URLs.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-05-09 16:58:34 +02:00
Sebastiaan van Stijn f64d6b131b Merge pull request #321 from thaJeztah/fix_pass_errors
pass: return correct error, and ignore empty stores on list
2024-05-09 16:44:30 +02:00
Sebastiaan van Stijn 1bb9aa3210 pass: return correct error, and ignore empty stores on list
commit 2fc2313bb1 changed the errors returned
by the pass credentials-helper to use a errCredentialsNotFound. This error
string is used in the client to distinguish a "not found" error from other
errors. (see [client.Get][1]).

However, there were additional second code-paths that returned a custom error,
which would not be detected as a "not found" error, resulting in an error when
logging out;

    Removing login credentials for https://index.docker.io/v1/
    WARNING: could not erase credentials:
    https://index.docker.io/v1/: error erasing credentials - err: exit status 1, out: `error getting credentials - err: exit status 1, out: `no usernames for https://index.docker.io/v1/``

This patch:

- updates Pass.Get() to return a errCredentialsNotFound if no credentials
  were found
- updates Pass.List() to not return an error if any of the domains had no
  credentials stored.

[1]: https://github.com/docker/docker-credential-helpers/blob/73b9e5d51f8dc9f598e08a0f2171c5d5a828e76b/client/client.go#L51-L55

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-05-09 12:19:23 +02:00
Sebastiaan van Stijn 73b9e5d51f Merge pull request #320 from thaJeztah/update_gha
update GHA to macOS-13, macOS-14, and update to go1.21.10
2024-05-08 22:48:19 +02:00
Sebastiaan van Stijn 0c43fede6d update to go1.21.10
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-05-08 15:59:23 +02:00
Sebastiaan van Stijn a941c5247f gha: update to use macos-13, macos-14
macos-11 runners are being deprecated; updating to use
macos-13 (x86) and macos-14 (arm64)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-05-08 15:56:13 +02:00
Sebastiaan van Stijn 097f945536 Merge pull request #318 from thaJeztah/pr_template
add pull-request template
2024-03-18 12:07:36 +01:00
Sebastiaan van Stijn 9272dcb90a add pull-request template
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-03-18 11:17:06 +01:00
dependabot[bot] ecacf8cdcf build(deps): bump softprops/action-gh-release from 1 to 2
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 1 to 2.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/de2c0eb89ae2a093876385947365aca7b0e5f844...d99959edae48b5ffffd7b00da66dcdb0a33a52ee)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-11 09:10:25 +00:00
Sebastiaan van Stijn 5be670a285 Merge pull request #316 from crazy-max/codecov-token
ci: set codecov token
2024-02-22 23:44:22 +01:00
CrazyMax 73aa8c0daa ci: set codecov token
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2024-02-22 23:25:37 +01:00
Sebastiaan van Stijn c23afb6c37 Merge pull request #313 from crazy-max/bump-gha
ci: update github actions to latest stable
2024-02-06 10:11:41 +01:00
CrazyMax d622133060 Merge pull request #310 from thaJeztah/update_xx
Dockerfile: update xx to v1.4.0
2024-02-06 10:05:26 +01:00
CrazyMax 12500fb753 chore: dependabot to keep gha up to date
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2024-02-06 10:03:08 +01:00
CrazyMax bf726a0656 ci: update github actions to latest stable
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2024-02-06 10:02:31 +01:00
Sebastiaan van Stijn d9632f6a08 Dockerfile: update xx to v1.4.0
full diff: https://github.com/tonistiigi/xx/compare/v1.2.1...v1.4.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-02-06 09:55:36 +01:00
Sebastiaan van Stijn 292722b503 Merge pull request #308 from thaJeztah/update_golang_1.21.6
update to go1.21.6
2024-01-10 18:01:53 +01:00
Sebastiaan van Stijn 979dcc4762 Merge pull request #309 from thaJeztah/update_golangci
Dockerfile: update golangci-lint to v1.55.2
2024-01-10 18:00:03 +01:00
Sebastiaan van Stijn f411a65c31 Dockerfile: update golangci-lint to v1.55.2
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-01-10 16:18:18 +01:00
Sebastiaan van Stijn 9629bd77ad update to go1.21.6
go1.20 is reaching EOL soon; let's update to the current version.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-01-10 14:42:35 +01:00
Sebastiaan van Stijn f642c26173 Merge pull request #306 from thaJeztah/err_checks
move trimming whitespace to error-check helpers
2024-01-10 14:40:13 +01:00
Sebastiaan van Stijn 8fc330691f Merge pull request #307 from thaJeztah/bump_wincred
vendor: github.com/danieljoos/wincred v1.2.1
2024-01-09 18:00:45 +01:00
Sebastiaan van Stijn 6a3e64c0b4 move trimming whitespace to error-check helpers
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-01-09 16:45:09 +01:00
Sebastiaan van Stijn 218f1787ac vendor: github.com/danieljoos/wincred v1.2.1
- Credential Helpers [v0.8.0](https://github.com/docker/docker-credential-helpers/releases/tag/v0.8.0)

full diff: https://github.com/danieljoos/wincred/compare/v1.2.0...v1.2.1

- Updated dependency golang.org/x/sys to version 0.15.0
- Updated dependency github.com/stretchr/testify to version 1.8.4
- Added error constant ErrBadUsername that can be used when dealing with domain password credentials.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-01-09 16:42:24 +01:00
Sebastiaan van Stijn 8396edb35f Merge pull request #297 from thaJeztah/update_go_1.20.6
update go to go1.20.6, debian bullseye
2023-07-17 02:13:55 +02:00
Sebastiaan van Stijn a3d1ffcdb2 update go to go1.20.6
go1.20.6 (released 2023-07-11) includes a security fix to the net/http package,
as well as bug fixes to the compiler, cgo, the cover tool, the go command,
the runtime, and the crypto/ecdsa, go/build, go/printer, net/mail, and text/template
packages. See the Go 1.20.6 milestone on our issue tracker for details.

https://github.com/golang/go/issues?q=milestone%3AGo1.20.6+label%3ACherryPickApproved

Full diff: https://github.com/golang/go/compare/go1.20.5...go1.20.6

These minor releases include 1 security fixes following the security policy:

net/http: insufficient sanitization of Host header

The HTTP/1 client did not fully validate the contents of the Host header.
A maliciously crafted Host header could inject additional headers or entire
requests. The HTTP/1 client now refuses to send requests containing an
invalid Request.Host or Request.URL.Host value.

Thanks to Bartek Nowotarski for reporting this issue.

Includes security fixes for [CVE-2023-29406 ][1] and Go issue https://go.dev/issue/60374

[1]: https://github.com/advisories/GHSA-f8f7-69v5-w4vx

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-07-17 00:40:48 +02:00
Sebastiaan van Stijn c03d56cfdc deb: update to golang bullseye
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-07-17 00:40:29 +02:00
Sebastiaan van Stijn 7f484550bc Merge pull request #294 from thaJeztah/use_designated_domains_step1
use designated domains in tests (RFC2606) (step 1)
2023-06-27 13:48:45 +02:00
Sebastiaan van Stijn a90e3fa153 secretservice: use designated domains in tests (RFC2606)
Update domains used in tests to used domains that are designated for this
purpose as described in [RFC2606, section 3][1]

[1]: https://www.rfc-editor.org/rfc/rfc2606.html#section-3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-26 13:59:01 +02:00
Sebastiaan van Stijn ffb3232f6c pass: use designated domains in tests (RFC2606)
Update domains used in tests to used domains that are designated for this
purpose as described in [RFC2606, section 3][1]

[1]: https://www.rfc-editor.org/rfc/rfc2606.html#section-3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-26 13:59:01 +02:00
Sebastiaan van Stijn 1050848357 client: use designated domains in tests (RFC2606)
Update domains used in tests to used domains that are designated for this
purpose as described in [RFC2606, section 3][1]

[1]: https://www.rfc-editor.org/rfc/rfc2606.html#section-3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-26 13:59:01 +02:00
Sebastiaan van Stijn 7d66ae02a6 osxkeychain: use designated domains in tests (RFC2606)
Update domains used in tests to used domains that are designated for this
purpose as described in [RFC2606, section 3][1]

[1]: https://www.rfc-editor.org/rfc/rfc2606.html#section-3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-26 13:59:01 +02:00
Sebastiaan van Stijn 13475b4387 credentials: use designated domains in tests (RFC2606)
Update domains used in tests to used domains that are designated for this
purpose as described in [RFC2606, section 3][1]

[1]: https://www.rfc-editor.org/rfc/rfc2606.html#section-3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-26 13:59:01 +02:00
Sebastiaan van Stijn 91af1de9af registryurl: use designated domains in tests (RFC2606)
Update domains used in tests to used domains that are designated for this
purpose as described in [RFC2606, section 3][1]

[1]: https://www.rfc-editor.org/rfc/rfc2606.html#section-3

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-26 13:59:01 +02:00
Sebastiaan van Stijn 4977273244 Merge pull request #292 from thaJeztah/errors_improvements
credentials: improve errors and error-handling
2023-06-15 15:18:04 +02:00
Sebastiaan van Stijn a228f55121 credentials: implement errdefs types for typed errors
This allows for checking the error-type returned to be matched with
the errdefs utilities.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-15 14:59:44 +02:00
Sebastiaan van Stijn dbfb389f83 credentials: use errors.As() to match error-types
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-15 14:59:35 +02:00
Sebastiaan van Stijn bd83e02ad0 Merge pull request #293 from thaJeztah/update_go_1.20.5
update go to go1.20.5
2023-06-15 14:57:52 +02:00
Sebastiaan van Stijn a6e03c60ab update go to go1.20.5
go1.20.5 (released 2023-06-06) includes four security fixes to the cmd/go and
runtime packages, as well as bug fixes to the compiler, the go command, the
runtime, and the crypto/rsa, net, and os packages. See the Go 1.20.5 milestone
on our issue tracker for details:

https://github.com/golang/go/issues?q=milestone%3AGo1.20.5+label%3ACherryPickApproved

full diff: https://github.com/golang/go/compare/go1.20.4...go1.20.5

These minor releases include 3 security fixes following the security policy:

- cmd/go: cgo code injection
  The go command may generate unexpected code at build time when using cgo. This
  may result in unexpected behavior when running a go program which uses cgo.

  This may occur when running an untrusted module which contains directories with
  newline characters in their names. Modules which are retrieved using the go command,
  i.e. via "go get", are not affected (modules retrieved using GOPATH-mode, i.e.
  GO111MODULE=off, may be affected).

  Thanks to Juho Nurminen of Mattermost for reporting this issue.

  This is CVE-2023-29402 and Go issue https://go.dev/issue/60167.

- runtime: unexpected behavior of setuid/setgid binaries

  The Go runtime didn't act any differently when a binary had the setuid/setgid
  bit set. On Unix platforms, if a setuid/setgid binary was executed with standard
  I/O file descriptors closed, opening any files could result in unexpected
  content being read/written with elevated prilieges. Similarly if a setuid/setgid
  program was terminated, either via panic or signal, it could leak the contents
  of its registers.

  Thanks to Vincent Dehors from Synacktiv for reporting this issue.

  This is CVE-2023-29403 and Go issue https://go.dev/issue/60272.

- cmd/go: improper sanitization of LDFLAGS

  The go command may execute arbitrary code at build time when using cgo. This may
  occur when running "go get" on a malicious module, or when running any other
  command which builds untrusted code. This is can by triggered by linker flags,
  specified via a "#cgo LDFLAGS" directive.

  Thanks to Juho Nurminen of Mattermost for reporting this issue.

  This is CVE-2023-29404 and CVE-2023-29405 and Go issues https://go.dev/issue/60305 and https://go.dev/issue/60306.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-06-15 12:11:09 +02:00
Sebastiaan van Stijn c842499594 Merge pull request #291 from thaJeztah/test_nits
credentials: fix minor nits in tests
2023-05-30 15:21:24 +02:00
Sebastiaan van Stijn c1c7dd7011 credentials: fix minor nits in tests
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-30 11:02:44 +02:00
Sebastiaan van Stijn 83d38ea5e6 Merge pull request #289 from crazy-max/build-constraint
chore: use go build constraint
2023-05-29 17:54:04 +02:00
CrazyMax 72391b37df pass: properly handle errors in tests
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-29 12:02:49 +02:00
CrazyMax d0668939bb pass: exclude tests on windows
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-29 12:02:49 +02:00
CrazyMax a51d46e82c ci: set gpg key trust level with import-gpg action
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-29 12:02:48 +02:00
CrazyMax ea29253d2a ci: install pass on macOS for testing
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-29 12:02:48 +02:00
CrazyMax 90bf5da1c9 wincred: use go build constraint
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-29 12:02:48 +02:00
CrazyMax 6a2f53622b secretservice: use go build constraint
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-29 12:02:48 +02:00
CrazyMax 9f5511c8d5 osxkeychain: use go build constraint
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-29 12:02:48 +02:00
CrazyMax c740b990c2 Merge pull request #287 from thaJeztah/pass_no_interpolate
pass: fix interpolation of $PASSWORD_STORE_DIR, and use os.UserHomeDir()
2023-05-29 12:02:13 +02:00
Sebastiaan van Stijn 372315b138 pass: make home-dir resolution platform agnostic
Use stdlib's os.UserHomeDir() instead of depending only on $HOME. Note that
this does not yet does nss lookups for situations where $HOME / $USERPROFILE
is not set.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-29 08:54:49 +02:00
Sebastiaan van Stijn c8c415f9f7 pass: fix interpolation of $PASSWORD_STORE_DIR
commit a13ff50017 simplified the handling of
env-vars in getPassDir(), but moved interpolation of env-vars to the end
of the function.

As a result, a custom path passed through `$PASSWORD_STORE_DIR` would now
be interpolated, instead of taken as-is. For example;

    PASSWORD_STORE_DIR=$PWD/world

Would now interpolate `$PWD`, instead of using a literal `$PWD`.

This patch changes the logic to only expand env-vars for the default location.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-28 18:42:06 +02:00
Sebastiaan van Stijn a652f8e7e6 Merge pull request #286 from crazy-max/update-readme
readme: install emulators when building with docker
2023-05-28 18:05:09 +02:00
Sebastiaan van Stijn 2860ca4b4d Merge pull request #283 from crazy-max/fix-osx-min-version
osxkeychain: match min macos version for xx
2023-05-28 18:00:17 +02:00
CrazyMax 2103f1bcee readme: install emulators when building with docker
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-28 17:40:41 +02:00
Sebastiaan van Stijn bdd92dd0d3 Merge pull request #284 from thaJeztah/various_cleanups
Assorted improvements, and add  "--version, -v", and "--help, -h" flags
2023-05-28 15:56:28 +02:00
CrazyMax 5944f8a485 osxkeychain: match min macos version for xx
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-28 15:55:56 +02:00
CrazyMax 0b9180511f Merge pull request #285 from crazy-max/test-target
chore: use same target for sandboxed and native tests
2023-05-28 15:20:22 +02:00
CrazyMax d440e5916e chore: use same target for sandboxed and native tests
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-28 15:13:50 +02:00
Sebastiaan van Stijn 129017a3cd credentials: define consts for supported actions (sub-commands)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-28 13:53:34 +02:00
Sebastiaan van Stijn 99079cafd2 credentials: Serve(): implement "--version, -v", and "--help, -h" flags
As recommended in the GNU documentation;

- https://www.gnu.org/prep/standards/standards.html#g_t_002d_002dversion
- https://www.gnu.org/prep/standards/standards.html#g_t_002d_002dhelp

With this patch:

    $ docker-credential-osxkeychain --version
    docker-credential-osxkeychain (github.com/docker/docker-credential-helpers) v0.7.0-51-g26c426e.m

    $ docker-credential-osxkeychain -v
    docker-credential-osxkeychain (github.com/docker/docker-credential-helpers) v0.7.0-51-g26c426e.m

    $ docker-credential-osxkeychain --help
    Usage: docker-credential-osxkeychain <store|get|erase|list|version>

    $ docker-credential-osxkeychain -h
    Usage: docker-credential-osxkeychain <store|get|erase|list|version>

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-28 13:53:34 +02:00
Sebastiaan van Stijn ae1d1ec013 credentials: HandleCommand(): improve error for unknown command/action
- renamed the "key" variable, which was slightly confusing
- include the name of the binary in the error

Before this change:

    docker-credential-osxkeychain nosuchaction
    Unknown credential action `nosuchaction`

After this change:

    docker-credential-osxkeychain nosuchaction
    docker-credential-osxkeychain: unknown action: nosuchaction

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-28 13:53:34 +02:00
Sebastiaan van Stijn db0ac44c97 credentials: Serve(): simplify error-handling logic
Don't use an err if we can print the error immediately :)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-28 13:53:34 +02:00
Sebastiaan van Stijn 0dbcdb66a7 credentials: Serve(): use "Name instead of "os.Args[0]" for usage output
GNU guidelines describes; https://www.gnu.org/prep/standards/html_node/_002d_002dversion.html#g_t_002d_002dversion

    The program’s name should be a constant string; don’t compute it from argv[0].
    The idea is to state the standard or canonical name for the program, not its
    file name.

Although the above recommendation is for `--version` output, it probably makes
sense to do the same for the "usage" output.

Before this change:

    /usr/local/bin/docker-credential-osxkeychain invalid command
    Usage: /usr/local/bin/docker-credential-osxkeychain <store|get|erase|list|version>

    /Applications/Docker.app/Contents/Resources/bin/docker-credential-osxkeychain invalid command
    Usage: /Applications/Docker.app/Contents/Resources/bin/docker-credential-osxkeychain <store|get|erase|list|version>

With this patch:

    /usr/local/bin/docker-credential-osxkeychain invalid command
    Usage: docker-credential-osxkeychain <store|get|erase|list|version>

    /Applications/Docker.app/Contents/Resources/bin/docker-credential-osxkeychain invalid command
    Usage: docker-credential-osxkeychain <store|get|erase|list|version>

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-28 13:53:23 +02:00
Sebastiaan van Stijn c324fe0a6f credentials: Get(): remove intermediate variable
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-28 12:38:05 +02:00
Sebastiaan van Stijn 19557f8fff fix some errCheck warnings, and update examples
- Explicitly suppress some unhandled errors
- Use "pass" credentials helper in examples, which is available
  on more platforms than "secretservice" (only supporte on Linux)
- Update domain and username in examples.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-28 12:33:58 +02:00
Sebastiaan van Stijn 94483d2d23 godoc: credentials helper -> credentials-helper
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-28 12:13:49 +02:00
Sebastiaan van Stijn 4c9fc240ed client: use os/exec/Cmd.Environ() instead of os.Environ()
Don't set Env if not set; the default is already handled if it's nil; from
the documentation: https://pkg.go.dev/os/exec@go1.20.4#Cmd.Env

    // If Env is nil, the new process uses the current process's
    // environment.

Use `os/exec/Cmd.Environ()` instead of `os.Environ()`, which was added in
go1.19, and handles additional environment variables, such as `PWD` on POSIX
systems, and `SYSTEMROOT` on Windows. https://pkg.go.dev/os/exec@go1.20.4#Cmd.Environ

Also remove a redundant `fmt.Sprintf()`, as we're only concatenating strings.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-28 12:11:06 +02:00
Sebastiaan van Stijn f8e94d91c0 Merge pull request #275 from thaJeztah/subtests
rewrite tests to use sub-tests, improve error-handling in tests, and use t.Cleanup()
2023-05-27 19:18:19 +02:00
Sebastiaan van Stijn 6f4e3abfc8 Merge pull request #281 from thaJeztah/bump_go_version
update go to go1.20.4
2023-05-27 19:17:16 +02:00
Sebastiaan van Stijn ec695cee3c Merge pull request #279 from crazy-max/win-arm64
windows/arm64 support for wincred
2023-05-27 19:00:36 +02:00
Sebastiaan van Stijn fa89a70db3 update go to go1.20.4
go1.20.4 (released 2023-05-02) includes three security fixes to the html/template
package, as well as bug fixes to the compiler, the runtime, and the crypto/subtle,
crypto/tls, net/http, and syscall packages. See the Go 1.20.4 milestone on our
issue tracker for details:

https://github.com/golang/go/issues?q=milestone%3AGo1.20.4+label%3ACherryPickApproved

release notes: https://go.dev/doc/devel/release#go1.20.4
full diff: https://github.com/golang/go/compare/go1.20.3...go1.20.4

from the announcement:

> These minor releases include 3 security fixes following the security policy:
>
> - html/template: improper sanitization of CSS values
>
>   Angle brackets (`<>`) were not considered dangerous characters when inserted
>   into CSS contexts. Templates containing multiple actions separated by a '/'
>   character could result in unexpectedly closing the CSS context and allowing
>   for injection of unexpected HMTL, if executed with untrusted input.
>
>   Thanks to Juho Nurminen of Mattermost for reporting this issue.
>
>   This is CVE-2023-24539 and Go issue https://go.dev/issue/59720.
>
> - html/template: improper handling of JavaScript whitespace
>
>   Not all valid JavaScript whitespace characters were considered to be
>   whitespace. Templates containing whitespace characters outside of the character
>   set "\t\n\f\r\u0020\u2028\u2029" in JavaScript contexts that also contain
>   actions may not be properly sanitized during execution.
>
>   Thanks to Juho Nurminen of Mattermost for reporting this issue.
>
>   This is CVE-2023-24540 and Go issue https://go.dev/issue/59721.
>
> - html/template: improper handling of empty HTML attributes
>
>   Templates containing actions in unquoted HTML attributes (e.g. "attr={{.}}")
>   executed with empty input could result in output that would have unexpected
>   results when parsed due to HTML normalization rules. This may allow injection
>   of arbitrary attributes into tags.
>
>   Thanks to Juho Nurminen of Mattermost for reporting this issue.
>
>   This is CVE-2023-29400 and Go issue https://go.dev/issue/59722.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 18:57:55 +02:00
Sebastiaan van Stijn 14d46ffd7e credentials: don't fail tests early, and use consts
- use consts for fixed values in tests
- don't fail tests early if there's more we can test

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 17:14:13 +02:00
Sebastiaan van Stijn c20f883316 client: don't use un-keyed literals in tests, and don't fail early
- don't fail tests early if there's more we can test
- don't user un-keyed literals in tests

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 17:14:13 +02:00
Sebastiaan van Stijn 7a60d70114 wincred: use sub-tests
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 17:14:13 +02:00
Sebastiaan van Stijn 814dbb3b5a wincred: use t.Cleanup(), and don't ignore errors
Make sure we don't drop errors when cleaning up state before/after
tests.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 17:14:13 +02:00
Sebastiaan van Stijn ed91395f20 wincred: don't use un-keyed literals in test-tables
Also moving the comments into the test-tables.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 17:14:12 +02:00
Sebastiaan van Stijn 5c5b09e7f8 osxkeychain: use sub-tests
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 17:14:12 +02:00
Sebastiaan van Stijn 8282d3336a osxkeychain: use t.Cleanup(), and don't ignore errors
Make sure we don't drop errors when cleaning up state before/after
tests.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 17:14:12 +02:00
Sebastiaan van Stijn a7ff1c7d16 osxkeychain: don't use un-keyed literals in test-tables
Also moving the comments into the test-tables.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 17:14:12 +02:00
Sebastiaan van Stijn 4a8c2d1d81 registryurl: use sub-tests
Also reformat the test-table to easier see what fields are used for
each test in the table.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 17:14:12 +02:00
Sebastiaan van Stijn 810dcd4ed5 use "tc" for test-cases
Mostly for my own sanity; just about every repository we have
started to converge to using "tc" as variable name for this, so
updating this repository as well to help reduce cognitive load.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 17:14:09 +02:00
Sebastiaan van Stijn f09e79d741 Merge pull request #278 from thaJeztah/osxkeychain_typed_error
osxkeychain: Delete(): return typed errors
2023-05-27 17:13:23 +02:00
Sebastiaan van Stijn b21b69c8ee osxkeychain: Delete(): return typed errors
This allows a Delete for non-existing credentials to be handled.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 16:54:37 +02:00
Sebastiaan van Stijn 7f00c5c8bd osxkeychain: use a switch for handling errors
Makes the error-handling slightly cleaner.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 16:54:37 +02:00
Sebastiaan van Stijn 1ed95cb020 osxkeychain: explicitly ignore error
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 16:54:32 +02:00
CrazyMax 88cb947f19 windows/arm64 support for wincred
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-27 16:46:54 +02:00
Sebastiaan van Stijn 9ff5b6126b Merge pull request #276 from crazy-max/ci-bump-os
ci: bump runners to ubuntu-22.04
2023-05-27 16:07:51 +02:00
CrazyMax 62d8c84526 ci: add ubuntu-22.04 to test matrix
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-27 15:58:02 +02:00
CrazyMax 2749e559d9 ci: bump runners to ubuntu-22.04
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-05-27 15:51:14 +02:00
Sebastiaan van Stijn 4ede49ca78 Merge pull request #273 from thaJeztah/gofumpt
format code with gofumpt
2023-05-27 13:44:47 +02:00
Sebastiaan van Stijn a001d639ae Merge pull request #272 from thaJeztah/add_project_files
Add CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md
2023-05-27 13:30:15 +02:00
Sebastiaan van Stijn 9817a23b17 format code with gofumpt
Doing a one-pass of formatting the code with gofumpt, which provides a
superset of gofmt.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 13:01:49 +02:00
Sebastiaan van Stijn 11e6d3772c Merge pull request #271 from thaJeztah/bump_wincred
vendor: github.com/danieljoos/wincred v1.2.0
2023-05-27 12:42:22 +02:00
Sebastiaan van Stijn a649a36b27 project: add CONTRIBUTING.md
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 12:38:04 +02:00
Sebastiaan van Stijn 9ddc7c7f86 project: add CODE_OF_CONDUCT.md
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 12:37:46 +02:00
Sebastiaan van Stijn e6a96be547 project: Add SECURITY.md
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 12:37:21 +02:00
Sebastiaan van Stijn 069ceb8b56 vendor: github.com/danieljoos/wincred v1.2.0
full diff: https://github.com/danieljoos/wincred/compare/v1.1.2...v1.2.0

- Calling SyscallN directly when dealing with pointer-pointers to try to address
  "Unexpected (nil, nil) result from wincred.GetGenericCredential".
- Bumped required Go version to 1.18.
- Bumped dependency to golang.org/x/sys to version 0.8.0.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-27 11:52:12 +02:00
Sebastiaan van Stijn 7ce5629658 Merge pull request #270 from thaJeztah/remove_execabs
remove uses of golang.org/x/sys/execabs
2023-05-27 11:47:10 +02:00
Sebastiaan van Stijn 37c4a6b158 remove uses of golang.org/x/sys/execabs
the "golang.org/x/sys/execabs" package was introduced to address a security
issue on Windows, and changing the default behavior of os/exec was considered
a breaking change. go1.19 applied the behavior that was previously implemented
in the execabs package;

from the release notes: https://go.dev/doc/go1.19#os-exec-path

> Command and LookPath no longer allow results from a PATH search to be found
> relative to the current directory. This removes a common source of security
> problems but may also break existing programs that depend on using, say,
> exec.Command("prog") to run a binary named prog (or, on Windows, prog.exe)
> in the current directory. See the os/exec package documentation for information
> about how best to update such programs.
>
> On Windows, Command and LookPath now respect the NoDefaultCurrentDirectoryInExePath
> environment variable, making it possible to disable the default implicit search
> of “.” in PATH lookups on Windows systems.

With those changes, we no longer need to use the execabs package, and we can
switch back to os/exec.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-05-26 02:11:17 +02:00
Sebastiaan van Stijn da93839996 Merge pull request #263 from crazy-max/update-go
update go to 1.20
2023-04-16 12:34:50 +02:00
Sebastiaan van Stijn da7f673a5e Merge pull request #265 from crazy-max/bump-golang-sys
Bump golang.org/x/sys to v0.7.0
2023-04-16 12:29:22 +02:00
CrazyMax d5c91a2f56 remove deprecated golangci-lint linters
Linters 'deadcode', 'structcheck', 'varcheck' are deprecated
since v1.49.0 and had been replaced by 'unused'.

Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-04-16 12:29:03 +02:00
CrazyMax e9656dd67f update golangci-lint to v1.51.1
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-04-16 12:29:03 +02:00
CrazyMax 52f0cb6de6 update go to 1.20
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-04-16 12:29:03 +02:00
Sebastiaan van Stijn bde49b32dd Merge pull request #264 from crazy-max/update-xx
Dockerfile: update xx to 1.2.1
2023-04-16 12:26:14 +02:00
CrazyMax b3ec48855b Bump golang.org/x/sys to v0.7.0
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-04-16 12:07:42 +02:00
CrazyMax 0db06514b9 update xx to 1.2.1
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-04-16 11:24:27 +02:00
CrazyMax d7835111fd Merge pull request #252 from okrc/patch-1
Fix build status badge being broken
2023-01-30 13:38:11 +01:00
okrc be409725f3 Fix build status badge being broken
Signed-off-by: okrc <okrc@hexo.dev>
2023-01-30 12:19:07 +01:00
Sebastiaan van Stijn 20454add32 Merge pull request #253 from crazy-max/fix-dockerfile
Dockerfile: fix vendor validation
2023-01-30 12:02:01 +01:00
CrazyMax 5674caebaf Dockerfile: fix vendor validation
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
2023-01-22 21:47:18 +01:00
73 changed files with 3873 additions and 1488 deletions
+4
View File
@@ -0,0 +1,4 @@
# Code of conduct
- [Moby community guidelines](https://github.com/moby/moby/blob/master/CONTRIBUTING.md#moby-community-guidelines)
- [Docker Code of Conduct](https://github.com/docker/code-of-conduct)
+289
View File
@@ -0,0 +1,289 @@
# Contribute to this repository
This page contains information about reporting issues as well as some tips and
guidelines useful to experienced open source contributors.
## Reporting security issues
The project maintainers take security seriously. If you discover a security
issue, please bring it to their attention right away!
**Please _DO NOT_ file a public issue**, instead send your report privately to
[security@docker.com](mailto:security@docker.com).
Security reports are greatly appreciated and we will publicly thank you for it.
We also like to send gifts&mdash;if you're into schwag, make sure to let
us know. We currently do not offer a paid security bounty program, but are not
ruling it out in the future.
## Reporting other issues
A great way to contribute to the project is to send a detailed report when you
encounter an issue. We always appreciate a well-written, thorough bug report,
and will thank you for it!
Check that [the issue database](https://github.com/docker/docker-credential-helpers/issues)
doesn't already include that problem or suggestion before submitting an issue.
If you find a match, you can use the "subscribe" button to get notified on
updates. Do *not* leave random "+1" or "I have this too" comments, as they
only clutter the discussion, and don't help resolving it. However, if you
have ways to reproduce the issue or have additional information that may help
resolving the issue, please leave a comment.
Include the steps required to reproduce the problem if possible and applicable.
This information will help us review and fix your issue faster. When sending
lengthy log-files, consider posting them as an attachment, instead of posting
inline.
**Do not forget to remove sensitive data from your logfiles before submitting**
(you can replace those parts with "REDACTED").
### Pull requests are always welcome
Not sure if that typo is worth a pull request? Found a bug and know how to fix
it? Do it! We will appreciate it.
If your pull request is not accepted on the first try, don't be discouraged! If
there's a problem with the implementation, hopefully you received feedback on
what to improve.
We're trying very hard to keep this project lean and focused. We don't want it to
do everything for everybody. This means that we might decide against
incorporating a new feature. However, there might be a way to implement that
feature *on top of* code in this project.
### Design and cleanup proposals
You can propose new designs for existing features. You can also design
entirely new features. We really appreciate contributors who want to refactor or
otherwise cleanup our project.
### Sign your work
The sign-off is a simple line at the end of the explanation for the patch. Your
signature certifies that you wrote the patch or otherwise have the right to pass
it on as an open-source patch. The rules are pretty simple: if you can certify
the below (from [developercertificate.org](https://developercertificate.org)):
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
Then you just add a line to every git commit message:
Signed-off-by: Joe Smith <joe.smith@email.com>
**Use your real name** (sorry, no pseudonyms or anonymous contributions.)
If you set your `user.name` and `user.email` git configs, you can sign your
commit automatically with `git commit -s`.
### Run the unit- and integration-tests
To validate PRs before submitting them you should run:
```bash
$ make validate
```
To run the tests:
```bash
$ make test
```
To generate new vendored files with go modules run:
```bash
$ make vendor
```
### Conventions
- Fork the repository and make changes on your fork in a feature branch
- Submit tests for your changes. See [run the unit- and integration-tests](#run-the-unit--and-integration-tests)
for details.
- [Sign your work](#sign-your-work)
Write clean code. Universally formatted code promotes ease of writing, reading,
and maintenance. Always run `gofmt -s -w file.go` on each changed file before
committing your changes. Most editors have plug-ins that do this automatically.
Pull request descriptions should be as clear as possible and include a
reference to all the issues that they address. Be sure that the [commit
messages](#commit-messages) also contain the relevant information.
### Successful Changes
Before contributing large or high impact changes, make the effort to coordinate
with the maintainers of the project before submitting a pull request. This
prevents you from doing extra work that may or may not be merged.
Large PRs that are just submitted without any prior communication are unlikely
to be successful.
While pull requests are the methodology for submitting changes to code, changes
are much more likely to be accepted if they are accompanied by additional
engineering work. While we don't define this explicitly, most of these goals
are accomplished through communication of the design goals and subsequent
solutions. Often times, it helps to first state the problem before presenting
solutions.
Typically, the best methods of accomplishing this are to submit an issue,
stating the problem. This issue can include a problem statement and a
checklist with requirements. If solutions are proposed, alternatives should be
listed and eliminated. Even if the criteria for elimination of a solution is
frivolous, say so.
Larger changes typically work best with design documents. These are focused on
providing context to the design at the time the feature was conceived and can
inform future documentation contributions.
### Commit Messages
Commit messages must start with a capitalized and short summary (max. 50 chars)
written in the imperative, followed by an optional, more detailed explanatory
text which is separated from the summary by an empty line.
Commit messages should follow best practices, including explaining the context
of the problem and how it was solved, including in caveats or follow up changes
required. They should tell the story of the change and provide readers
understanding of what led to it.
If you're lost about what this even means, please see [How to Write a Git
Commit Message](http://chris.beams.io/posts/git-commit/) for a start.
In practice, the best approach to maintaining a nice commit message is to
leverage a `git add -p` and `git commit --amend` to formulate a solid
changeset. This allows one to piece together a change, as information becomes
available.
If you squash a series of commits, don't just submit that. Re-write the commit
message, as if the series of commits was a single stroke of brilliance.
That said, there is no requirement to have a single commit for a PR, as long as
each commit tells the story. For example, if there is a feature that requires a
package, it might make sense to have the package in a separate commit then have
a subsequent commit that uses it.
Remember, you're telling part of the story with the commit message. Don't make
your chapter weird.
### Review
Code review comments may be added to your pull request. Discuss, then make the
suggested modifications and push additional commits to your feature branch. Post
a comment after pushing. New commits show up in the pull request automatically,
but the reviewers are notified only when you comment.
Pull requests must be cleanly rebased on top of master without multiple branches
mixed into the PR.
> **Git tip**: If your PR no longer merges cleanly, use `rebase master` in your
> feature branch to update your pull request rather than `merge master`.
Before you make a pull request, squash your commits into logical units of work
using `git rebase -i` and `git push -f`. A logical unit of work is a consistent
set of patches that should be reviewed together: for example, upgrading the
version of a vendored dependency and taking advantage of its now available new
feature constitute two separate units of work. Implementing a new function and
calling it in another file constitute a single logical unit of work. The very
high majority of submissions should have a single commit, so if in doubt: squash
down to one.
- After every commit, [make sure the test suite passes](#run-the-unit--and-integration-tests).
Include documentation changes in the same pull request so that a revert would
remove all traces of the feature or fix.
- Include an issue reference like `closes #XXXX` or `fixes #XXXX` in the PR
description that close an issue. Including references automatically closes
the issue on a merge.
- Do not add yourself to the `AUTHORS` file, as it is regenerated regularly
from the Git history.
- See the [Coding Style](#coding-style) for further guidelines.
### Merge approval
Project maintainers use LGTM (Looks Good To Me) in comments on the code review to
indicate acceptance, or use the Github review approval feature.
## Coding Style
Unless explicitly stated, we follow all coding guidelines from the Go
community. While some of these standards may seem arbitrary, they somehow seem
to result in a solid, consistent codebase.
It is possible that the code base does not currently comply with these
guidelines. We are not looking for a massive PR that fixes this, since that
goes against the spirit of the guidelines. All new contributions should make a
best effort to clean up and make the code base better than they left it.
Obviously, apply your best judgement. Remember, the goal here is to make the
code base easier for humans to navigate and understand. Always keep that in
mind when nudging others to comply.
The rules:
1. All code should be formatted with `gofmt -s`.
2. All code should pass the default levels of
[`golint`](https://github.com/golang/lint).
3. All code should follow the guidelines covered in [Effective Go](http://golang.org/doc/effective_go.html)
and [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments).
4. Comment the code. Tell us the why, the history and the context.
5. Document _all_ declarations and methods, even private ones. Declare
expectations, caveats and anything else that may be important. If a type
gets exported, having the comments already there will ensure it's ready.
6. Variable name length should be proportional to its context and no longer.
`noCommaALongVariableNameLikeThisIsNotMoreClearWhenASimpleCommentWouldDo`.
In practice, short methods will have short variable names and globals will
have longer names.
7. No underscores in package names. If you need a compound name, step back,
and re-examine why you need a compound name. If you still think you need a
compound name, lose the underscore.
8. No utils or helpers packages. If a function is not general enough to
warrant its own package, it has not been written generally enough to be a
part of a util package. Just leave it unexported and well-documented.
9. All tests should run with `go test` and outside tooling should not be
required. No, we don't need another unit testing framework. Assertion
packages are acceptable if they provide _real_ incremental value.
10. Even though we call these "rules" above, they are actually just
guidelines. Since you've read all the rules, you now know that.
If you are having trouble getting into the mood of idiomatic Go, we recommend
reading through [Effective Go](https://golang.org/doc/effective_go.html). The
[Go Blog](https://blog.golang.org) is also a great resource.
+30
View File
@@ -0,0 +1,30 @@
<!--
Please make sure you've read and understood our contributing guidelines;
https://github.com/docker/cli/blob/master/CONTRIBUTING.md
** Make sure all your commits include a signature generated with `git commit -s` **
For additional information on our contributing process, read our contributing
guide https://docs.docker.com/opensource/code/
If this is a bug fix, make sure your description includes "fixes #xxxx", or
"closes #xxxx"
Please provide the following information:
-->
**- What I did**
**- How I did it**
**- How to verify it**
**- Description for the changelog**
<!--
Write a short (one line) summary that describes the changes in this
pull request for inclusion in the changelog:
-->
**- A picture of a cute animal (not mandatory but encouraged)**
+12
View File
@@ -0,0 +1,12 @@
# Reporting security issues
The project maintainers take security seriously. If you discover a security
issue, please bring it to their attention right away!
**Please _DO NOT_ file a public issue**, instead send your report privately to
[security@docker.com](mailto:security@docker.com).
Security reports are greatly appreciated, and we will publicly thank you for it.
We also like to send gifts&mdash;if you're into schwag, make sure to let
us know. We currently do not offer a paid security bounty program, but are not
ruling it out in the future.
+10
View File
@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: "github-actions"
open-pull-requests-limit: 10
directory: "/"
schedule:
interval: "daily"
labels:
- "dependencies"
- "bot"
+47 -39
View File
@@ -15,11 +15,11 @@ on:
env: env:
DESTDIR: ./bin DESTDIR: ./bin
GO_VERSION: 1.18.5 GO_VERSION: 1.23.6
jobs: jobs:
validate: validate:
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -29,10 +29,10 @@ jobs:
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- -
name: Set up Docker Buildx name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3
- -
name: Run name: Run
run: | run: |
@@ -44,29 +44,36 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: os:
- ubuntu-20.04 - ubuntu-24.04
- macOS-11 - ubuntu-22.04
- macOS-15
- macOS-14
- macOS-13
- windows-2022 - windows-2022
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- -
name: Set up Go name: Set up Go
uses: actions/setup-go@v3 uses: actions/setup-go@v5
with: with:
go-version: ${{ env.GO_VERSION }} go-version: ${{ env.GO_VERSION }}
cache: true
- -
name: Install deps name: Install deps (ubuntu)
if: ${{ matrix.os == 'ubuntu-20.04' }} if: startsWith(matrix.os, 'ubuntu-')
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y dbus-x11 gnome-keyring libsecret-1-dev pass sudo apt-get install -y dbus-x11 gnome-keyring libsecret-1-dev pass
-
name: Install deps (macOS)
if: startsWith(matrix.os, 'macOS-')
run: |
brew install pass
- -
name: GPG conf name: GPG conf
if: ${{ matrix.os == 'ubuntu-20.04' }} if: ${{ !startsWith(matrix.os, 'windows-') }}
uses: actions/github-script@v6 uses: actions/github-script@v7
id: gpg id: gpg
with: with:
script: | script: |
@@ -82,39 +89,39 @@ jobs:
core.setOutput('passphrase', fs.readFileSync('.github/workflows/fixtures/7D851EB72D73BDA0.pass', {encoding: 'utf8'})); core.setOutput('passphrase', fs.readFileSync('.github/workflows/fixtures/7D851EB72D73BDA0.pass', {encoding: 'utf8'}));
- -
name: Import GPG key name: Import GPG key
if: ${{ matrix.os == 'ubuntu-20.04' }} if: ${{ !startsWith(matrix.os, 'windows-') }}
uses: crazy-max/ghaction-import-gpg@v5 uses: crazy-max/ghaction-import-gpg@v6
with: with:
gpg_private_key: ${{ steps.gpg.outputs.key }} gpg_private_key: ${{ steps.gpg.outputs.key }}
passphrase: ${{ steps.gpg.outputs.passphrase }} passphrase: ${{ steps.gpg.outputs.passphrase }}
trust_level: 5
-
name: Init pass
if: ${{ !startsWith(matrix.os, 'windows-') }}
run: |
pass init 7D851EB72D73BDA0
shell: bash
- -
name: Test name: Test
run: | run: |
if [ "${{ matrix.os }}" = "ubuntu-20.04" ]; then make test COVERAGEDIR=${{ env.DESTDIR }}
echo -e "trust\n5\ny" | gpg --batch --no-tty --command-fd 0 --edit-key 7D851EB72D73BDA0
pass init 7D851EB72D73BDA0
fi
go test -short -v -coverprofile=./coverage.txt -covermode=atomic ./...
go tool cover -func=./coverage.txt
shell: bash shell: bash
- -
name: Upload coverage name: Upload coverage
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v5
with: with:
file: ./coverage.txt files: ${{ env.DESTDIR }}/coverage.txt
token: ${{ secrets.CODECOV_TOKEN }}
test-sandboxed: test-sandboxed:
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
steps: steps:
-
name: Checkout
uses: actions/checkout@v3
- -
name: Set up Docker Buildx name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3
- -
name: Test name: Test
uses: docker/bake-action@v2 uses: docker/bake-action@v6
with: with:
targets: test targets: test
set: | set: |
@@ -122,24 +129,25 @@ jobs:
*.cache-to=type=gha,scope=test,mode=max *.cache-to=type=gha,scope=test,mode=max
- -
name: Upload coverage name: Upload coverage
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v5
with: with:
file: ${{ env.DESTDIR }}//coverage.txt files: ${{ env.DESTDIR }}//coverage.txt
token: ${{ secrets.CODECOV_TOKEN }}
build: build:
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- -
name: Set up QEMU name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v3
- -
name: Set up Docker Buildx name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3
- -
name: Build name: Build
run: | run: |
@@ -157,7 +165,7 @@ jobs:
find ${{ env.DESTDIR }} -type f -exec file -e ascii -e text -- {} + find ${{ env.DESTDIR }} -type f -exec file -e ascii -e text -- {} +
- -
name: Upload artifacts name: Upload artifacts
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: docker-credential-helpers name: docker-credential-helpers
path: ${{ env.DESTDIR }}/* path: ${{ env.DESTDIR }}/*
@@ -165,7 +173,7 @@ jobs:
- -
name: GitHub Release name: GitHub Release
if: startsWith(github.ref, 'refs/tags/v') if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5 uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda # v2.2.1
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
@@ -173,11 +181,11 @@ jobs:
files: ${{ env.DESTDIR }}/* files: ${{ env.DESTDIR }}/*
build-deb: build-deb:
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
steps: steps:
- -
name: Checkout name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- -
+5 -9
View File
@@ -6,27 +6,23 @@ linters:
enable: enable:
- gofmt - gofmt
- govet - govet
- deadcode
- depguard - depguard
- goimports - goimports
- ineffassign - ineffassign
- misspell - misspell
- unused - unused
- varcheck
- revive - revive
- staticcheck - staticcheck
- typecheck - typecheck
- structcheck
disable-all: true disable-all: true
linters-settings: linters-settings:
depguard: depguard:
list-type: blacklist rules:
include-go-root: true main:
packages: deny:
# The io/ioutil package has been deprecated. - pkg: "io/ioutil"
# https://go.dev/doc/go1.16#ioutil desc: The io/ioutil package has been deprecated. See https://go.dev/doc/go1.16#ioutil
- io/ioutil
issues: issues:
exclude-rules: exclude-rules:
+13 -9
View File
@@ -1,9 +1,11 @@
# syntax=docker/dockerfile:1 # syntax=docker/dockerfile:1
ARG GO_VERSION=1.18.5 ARG GO_VERSION=1.23.6
ARG XX_VERSION=1.1.2 ARG DEBIAN_VERSION=bookworm
ARG XX_VERSION=1.6.1
ARG OSXCROSS_VERSION=11.3-r7-debian ARG OSXCROSS_VERSION=11.3-r7-debian
ARG GOLANGCI_LINT_VERSION=v1.47.3 ARG GOLANGCI_LINT_VERSION=v1.64.5
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ARG PACKAGE=github.com/docker/docker-credential-helpers ARG PACKAGE=github.com/docker/docker-credential-helpers
@@ -14,7 +16,7 @@ FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
# osxcross contains the MacOSX cross toolchain for xx # osxcross contains the MacOSX cross toolchain for xx
FROM crazymax/osxcross:${OSXCROSS_VERSION} AS osxcross FROM crazymax/osxcross:${OSXCROSS_VERSION} AS osxcross
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-bullseye AS gobase FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-${DEBIAN_VERSION} AS gobase
COPY --from=xx / / COPY --from=xx / /
ARG DEBIAN_FRONTEND ARG DEBIAN_FRONTEND
RUN apt-get update && apt-get install -y --no-install-recommends clang dpkg-dev file git lld llvm make pkg-config rsync RUN apt-get update && apt-get install -y --no-install-recommends clang dpkg-dev file git lld llvm make pkg-config rsync
@@ -38,7 +40,8 @@ FROM scratch AS vendor-update
COPY --from=vendored /out / COPY --from=vendored /out /
FROM vendored AS vendor-validate FROM vendored AS vendor-validate
RUN --mount=type=bind,target=.,rw <<EOT RUN --mount=target=/context \
--mount=target=.,type=tmpfs <<EOT
set -e set -e
rsync -a /context/. . rsync -a /context/. .
git add -A git add -A
@@ -54,7 +57,7 @@ EOT
FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION} AS golangci-lint FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION} AS golangci-lint
FROM gobase AS lint FROM gobase AS lint
ARG DEBIAN_FRONTEND ARG DEBIAN_FRONTEND
RUN apt-get install -y binutils gcc libc6-dev libgcc-10-dev libsecret-1-dev pkg-config RUN apt-get install -y binutils gcc libc6-dev libgcc-11-dev libsecret-1-dev pkg-config
RUN --mount=type=bind,target=. \ RUN --mount=type=bind,target=. \
--mount=type=cache,target=/root/.cache \ --mount=type=cache,target=/root/.cache \
--mount=from=golangci-lint,source=/usr/bin/golangci-lint,target=/usr/bin/golangci-lint \ --mount=from=golangci-lint,source=/usr/bin/golangci-lint,target=/usr/bin/golangci-lint \
@@ -63,7 +66,7 @@ RUN --mount=type=bind,target=. \
FROM gobase AS base FROM gobase AS base
ARG TARGETPLATFORM ARG TARGETPLATFORM
ARG DEBIAN_FRONTEND ARG DEBIAN_FRONTEND
RUN xx-apt-get install -y binutils gcc libc6-dev libgcc-10-dev libsecret-1-dev pkg-config RUN xx-apt-get install -y binutils gcc libc6-dev libgcc-11-dev libsecret-1-dev pkg-config
FROM base AS test FROM base AS test
ARG DEBIAN_FRONTEND ARG DEBIAN_FRONTEND
@@ -85,8 +88,8 @@ RUN --mount=type=bind,target=. \
gpg -k gpg -k
mkdir /out mkdir /out
xx-go test -short -v -coverprofile=/out/coverage.txt -covermode=atomic ./... xx-go --wrap
xx-go tool cover -func=/out/coverage.txt make test COVERAGEDIR=/out
EOT EOT
FROM scratch AS test-coverage FROM scratch AS test-coverage
@@ -119,6 +122,7 @@ RUN --mount=type=bind,target=. \
--mount=type=bind,source=/tmp/.version,target=/tmp/.version,from=version \ --mount=type=bind,source=/tmp/.version,target=/tmp/.version,from=version \
--mount=type=bind,source=/tmp/.revision,target=/tmp/.revision,from=version <<EOT --mount=type=bind,source=/tmp/.revision,target=/tmp/.revision,from=version <<EOT
set -ex set -ex
export MACOSX_VERSION_MIN=$(make print-MACOSX_DEPLOYMENT_TARGET)
xx-go --wrap xx-go --wrap
go install std go install std
make build-osxkeychain build-pass PACKAGE=$PACKAGE VERSION=$(cat /tmp/.version) REVISION=$(cat /tmp/.revision) DESTDIR=/out make build-osxkeychain build-pass PACKAGE=$PACKAGE VERSION=$(cat /tmp/.version) REVISION=$(cat /tmp/.revision) DESTDIR=/out
+29 -2
View File
@@ -7,6 +7,28 @@ GO_LDFLAGS = -s -w -X ${GO_PKG}/credentials.Version=${VERSION} -X ${GO_PKG}/cred
BUILDX_CMD ?= docker buildx BUILDX_CMD ?= docker buildx
DESTDIR ?= ./bin/build DESTDIR ?= ./bin/build
COVERAGEDIR ?= ./bin/coverage
# 10.11 is the minimum supported version for osxkeychain
export MACOSX_DEPLOYMENT_TARGET = 10.11
ifeq "$(shell go env GOOS)" "darwin"
export CGO_CFLAGS = -Wno-atomic-alignment -mmacosx-version-min=$(MACOSX_DEPLOYMENT_TARGET)
else
# prevent warnings; see https://github.com/docker/docker-credential-helpers/pull/340#issuecomment-2437593837
# gcc_libinit.c:44:8: error: large atomic operation may incur significant performance penalty; the access size (4 bytes) exceeds the max lock-free size (0 bytes) [-Werror,-Watomic-alignment]
export CGO_CFLAGS = -Wno-atomic-alignment
endif
ifeq "$(shell go env GOOS)/$(shell go env GOARCH)/$(shell go env GOARM)" "linux/arm/6"
# Neither the CGo compiler, nor the C toolchain automatically link to
# libatomic when the architecture doesn't support atomic intrinsics, as is
# the case for arm/v6.
#
# Here's the error we get when this is not done (see https://github.com/docker/docker-credential-helpers/pull/340#issuecomment-2437593837):
#
# gcc_libinit.c:44:8: error: large atomic operation may incur significant performance penalty; the access size (4 bytes) exceeds the max lock-free size (0 bytes) [-Werror,-Watomic-alignment]
export CGO_LDFLAGS=-latomic
endif
.PHONY: all .PHONY: all
all: cross all: cross
@@ -35,8 +57,10 @@ release: # create release
./hack/release ./hack/release
.PHONY: test .PHONY: test
test: # tests all packages except vendor test:
go test -v `go list ./... | grep -v /vendor/` mkdir -p $(COVERAGEDIR)
go test -short -v -coverprofile=$(COVERAGEDIR)/coverage.txt -covermode=atomic ./...
go tool cover -func=$(COVERAGEDIR)/coverage.txt
.PHONY: lint .PHONY: lint
lint: lint:
@@ -72,3 +96,6 @@ vendor:
rm -rf ./vendor rm -rf ./vendor
cp -R "$($@_TMP_OUT)"/* . cp -R "$($@_TMP_OUT)"/* .
rm -rf "$($@_TMP_OUT)" rm -rf "$($@_TMP_OUT)"
.PHONY: print-%
print-%: ; @echo $($*)
+7 -4
View File
@@ -1,6 +1,6 @@
[![GitHub release](https://img.shields.io/github/release/docker/docker-credential-helpers.svg?style=flat-square)](https://github.com/docker/docker-credential-helpers/releases/latest) [![GitHub release](https://img.shields.io/github/release/docker/docker-credential-helpers.svg?style=flat-square)](https://github.com/docker/docker-credential-helpers/releases/latest)
[![PkgGoDev](https://img.shields.io/badge/go.dev-docs-007d9c?style=flat-square&logo=go&logoColor=white)](https://pkg.go.dev/github.com/docker/docker-credential-helpers) [![PkgGoDev](https://img.shields.io/badge/go.dev-docs-007d9c?style=flat-square&logo=go&logoColor=white)](https://pkg.go.dev/github.com/docker/docker-credential-helpers)
[![Build Status](https://img.shields.io/github/workflow/status/docker/docker-credential-helpers/build?label=build&logo=github&style=flat-square)](https://github.com/docker/docker-credential-helpers/actions?query=workflow%3Abuild) [![Build Status](https://img.shields.io/github/actions/workflow/status/docker/docker-credential-helpers/build.yml?label=build&logo=github&style=flat-square)](https://github.com/docker/docker-credential-helpers/actions?query=workflow%3Abuild)
[![Codecov](https://img.shields.io/codecov/c/github/docker/docker-credential-helpers?logo=codecov&style=flat-square)](https://codecov.io/gh/docker/docker-credential-helpers) [![Codecov](https://img.shields.io/codecov/c/github/docker/docker-credential-helpers?logo=codecov&style=flat-square)](https://codecov.io/gh/docker/docker-credential-helpers)
[![Go Report Card](https://goreportcard.com/badge/github.com/docker/docker-credential-helpers?style=flat-square)](https://goreportcard.com/report/github.com/docker/docker-credential-helpers) [![Go Report Card](https://goreportcard.com/badge/github.com/docker/docker-credential-helpers?style=flat-square)](https://goreportcard.com/report/github.com/docker/docker-credential-helpers)
@@ -17,6 +17,9 @@ Go to the [Releases](https://github.com/docker/docker-credential-helpers/release
You can build the credential helpers using Docker: You can build the credential helpers using Docker:
```shell ```shell
# install emulators
$ docker run --privileged --rm tonistiigi/binfmt --install all
# create builder # create builder
$ docker buildx create --use $ docker buildx create --use
@@ -31,20 +34,20 @@ $ docker buildx bake
Or if the toolchain is already installed on your machine: Or if the toolchain is already installed on your machine:
1 - Download the source. 1. Download the source.
```shell ```shell
$ git clone https://github.com/docker/docker-credential-helpers.git $ git clone https://github.com/docker/docker-credential-helpers.git
$ cd docker-credential-helpers $ cd docker-credential-helpers
``` ```
2 - Use `make` to build the program you want. That will leave an executable in the `bin` directory inside the repository. 2. Use `make` to build the program you want. That will leave an executable in the `bin` directory inside the repository.
```shell ```shell
$ make osxkeychain $ make osxkeychain
``` ```
3 - Put that binary in your `$PATH`, so Docker can find it. 3. Put that binary in your `$PATH`, so Docker can find it.
```shell ```shell
$ cp bin/build/docker-credential-osxkeychain /usr/local/bin/ $ cp bin/build/docker-credential-osxkeychain /usr/local/bin/
+9 -16
View File
@@ -16,17 +16,15 @@ func isValidCredsMessage(msg string) error {
if credentials.IsCredentialsMissingServerURLMessage(msg) { if credentials.IsCredentialsMissingServerURLMessage(msg) {
return credentials.NewErrCredentialsMissingServerURL() return credentials.NewErrCredentialsMissingServerURL()
} }
if credentials.IsCredentialsMissingUsernameMessage(msg) { if credentials.IsCredentialsMissingUsernameMessage(msg) {
return credentials.NewErrCredentialsMissingUsername() return credentials.NewErrCredentialsMissingUsername()
} }
return nil return nil
} }
// Store uses an external program to save credentials. // Store uses an external program to save credentials.
func Store(program ProgramFunc, creds *credentials.Credentials) error { func Store(program ProgramFunc, creds *credentials.Credentials) error {
cmd := program("store") cmd := program(credentials.ActionStore)
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
if err := json.NewEncoder(buffer).Encode(creds); err != nil { if err := json.NewEncoder(buffer).Encode(creds); err != nil {
@@ -36,13 +34,10 @@ func Store(program ProgramFunc, creds *credentials.Credentials) error {
out, err := cmd.Output() out, err := cmd.Output()
if err != nil { if err != nil {
t := strings.TrimSpace(string(out)) if isValidErr := isValidCredsMessage(string(out)); isValidErr != nil {
if isValidErr := isValidCredsMessage(t); isValidErr != nil {
err = isValidErr err = isValidErr
} }
return fmt.Errorf("error storing credentials - err: %v, out: `%s`", err, strings.TrimSpace(string(out)))
return fmt.Errorf("error storing credentials - err: %v, out: `%s`", err, t)
} }
return nil return nil
@@ -50,22 +45,20 @@ func Store(program ProgramFunc, creds *credentials.Credentials) error {
// Get executes an external program to get the credentials from a native store. // Get executes an external program to get the credentials from a native store.
func Get(program ProgramFunc, serverURL string) (*credentials.Credentials, error) { func Get(program ProgramFunc, serverURL string) (*credentials.Credentials, error) {
cmd := program("get") cmd := program(credentials.ActionGet)
cmd.Input(strings.NewReader(serverURL)) cmd.Input(strings.NewReader(serverURL))
out, err := cmd.Output() out, err := cmd.Output()
if err != nil { if err != nil {
t := strings.TrimSpace(string(out)) if credentials.IsErrCredentialsNotFoundMessage(string(out)) {
if credentials.IsErrCredentialsNotFoundMessage(t) {
return nil, credentials.NewErrCredentialsNotFound() return nil, credentials.NewErrCredentialsNotFound()
} }
if isValidErr := isValidCredsMessage(t); isValidErr != nil { if isValidErr := isValidCredsMessage(string(out)); isValidErr != nil {
err = isValidErr err = isValidErr
} }
return nil, fmt.Errorf("error getting credentials - err: %v, out: `%s`", err, t) return nil, fmt.Errorf("error getting credentials - err: %v, out: `%s`", err, strings.TrimSpace(string(out)))
} }
resp := &credentials.Credentials{ resp := &credentials.Credentials{
@@ -81,7 +74,7 @@ func Get(program ProgramFunc, serverURL string) (*credentials.Credentials, error
// Erase executes a program to remove the server credentials from the native store. // Erase executes a program to remove the server credentials from the native store.
func Erase(program ProgramFunc, serverURL string) error { func Erase(program ProgramFunc, serverURL string) error {
cmd := program("erase") cmd := program(credentials.ActionErase)
cmd.Input(strings.NewReader(serverURL)) cmd.Input(strings.NewReader(serverURL))
out, err := cmd.Output() out, err := cmd.Output()
if err != nil { if err != nil {
@@ -99,7 +92,7 @@ func Erase(program ProgramFunc, serverURL string) error {
// List executes a program to list server credentials in the native store. // List executes a program to list server credentials in the native store.
func List(program ProgramFunc) (map[string]string, error) { func List(program ProgramFunc) (map[string]string, error) {
cmd := program("list") cmd := program(credentials.ActionList)
cmd.Input(strings.NewReader("unused")) cmd.Input(strings.NewReader("unused"))
out, err := cmd.Output() out, err := cmd.Output()
if err != nil { if err != nil {
+35 -28
View File
@@ -11,24 +11,24 @@ import (
) )
const ( const (
validServerAddress = "https://index.docker.io/v1" validServerAddress = "https://registry.example.com/v1"
validUsername = "linus" validUsername = "linus"
validServerAddress2 = "https://example.com:5002" validServerAddress2 = "https://example.com:5002"
invalidServerAddress = "https://foobar.example.com" invalidServerAddress = "https://foobar.example.com"
missingCredsAddress = "https://missing.docker.io/v1" missingCredsAddress = "https://missing.example.com/v1"
) )
var errProgramExited = fmt.Errorf("exited 1") var errProgramExited = fmt.Errorf("exited 1")
// mockProgram simulates interactions between the docker client and a remote // mockProgram simulates interactions between the docker client and a remote
// credentials helper. // credentials-helper.
// Unit tests inject this mocked command into the remote to control execution. // Unit tests inject this mocked command into the remote to control execution.
type mockProgram struct { type mockProgram struct {
arg string arg string
input io.Reader input io.Reader
} }
// Output returns responses from the remote credentials helper. // Output returns responses from the remote credentials-helper.
// It mocks those responses based in the input in the mock. // It mocks those responses based in the input in the mock.
func (m *mockProgram) Output() ([]byte, error) { func (m *mockProgram) Output() ([]byte, error) {
in, err := io.ReadAll(m.input) in, err := io.ReadAll(m.input)
@@ -80,7 +80,7 @@ func (m *mockProgram) Output() ([]byte, error) {
return []byte(fmt.Sprintf("unknown argument %q with %q", m.arg, inS)), errProgramExited return []byte(fmt.Sprintf("unknown argument %q with %q", m.arg, inS)), errProgramExited
} }
// Input sets the input to send to a remote credentials helper. // Input sets the input to send to a remote credentials-helper.
func (m *mockProgram) Input(in io.Reader) { func (m *mockProgram) Input(in io.Reader) {
m.input = in m.input = in
} }
@@ -92,16 +92,16 @@ func mockProgramFn(args ...string) Program {
} }
func ExampleStore() { func ExampleStore() {
p := NewShellProgramFunc("docker-credential-secretservice") p := NewShellProgramFunc("docker-credential-pass")
c := &credentials.Credentials{ c := &credentials.Credentials{
ServerURL: "https://example.com", ServerURL: "https://registry.example.com",
Username: "calavera", Username: "exampleuser",
Secret: "my super secret token", Secret: "my super secret token",
} }
if err := Store(p, c); err != nil { if err := Store(p, c); err != nil {
fmt.Println(err) _, _ = fmt.Println(err)
} }
} }
@@ -113,7 +113,7 @@ func TestStore(t *testing.T) {
for _, v := range valid { for _, v := range valid {
if err := Store(mockProgramFn, &v); err != nil { if err := Store(mockProgramFn, &v); err != nil {
t.Fatal(err) t.Error(err)
} }
} }
@@ -123,20 +123,20 @@ func TestStore(t *testing.T) {
for _, v := range invalid { for _, v := range invalid {
if err := Store(mockProgramFn, &v); err == nil { if err := Store(mockProgramFn, &v); err == nil {
t.Fatalf("Expected error for server %s, got nil", v.ServerURL) t.Errorf("Expected error for server %s, got nil", v.ServerURL)
} }
} }
} }
func ExampleGet() { func ExampleGet() {
p := NewShellProgramFunc("docker-credential-secretservice") p := NewShellProgramFunc("docker-credential-pass")
creds, err := Get(p, "https://example.com") creds, err := Get(p, "https://registry.example.com")
if err != nil { if err != nil {
fmt.Println(err) _, _ = fmt.Println(err)
} }
fmt.Printf("Got credentials for user `%s` in `%s`\n", creds.Username, creds.ServerURL) _, _ = fmt.Printf("Got credentials for user `%s` in `%s`\n", creds.Username, creds.ServerURL)
} }
func TestGet(t *testing.T) { func TestGet(t *testing.T) {
@@ -152,10 +152,10 @@ func TestGet(t *testing.T) {
} }
if c.Username != v.Username { if c.Username != v.Username {
t.Fatalf("expected username `%s`, got %s", v.Username, c.Username) t.Errorf("expected username `%s`, got %s", v.Username, c.Username)
} }
if c.Secret != v.Secret { if c.Secret != v.Secret {
t.Fatalf("expected secret `%s`, got %s", v.Secret, c.Secret) t.Errorf("expected secret `%s`, got %s", v.Secret, c.Secret)
} }
} }
@@ -165,10 +165,17 @@ func TestGet(t *testing.T) {
serverURL string serverURL string
err string err string
}{ }{
{missingCredsAddress, credentials.NewErrCredentialsNotFound().Error()}, {
{invalidServerAddress, "error getting credentials - err: exited 1, out: `program failed`"}, serverURL: missingCredsAddress,
{"", fmt.Sprintf("error getting credentials - err: %s, out: `%s`", err: credentials.NewErrCredentialsNotFound().Error(),
missingServerURLErr.Error(), missingServerURLErr.Error())}, },
{
serverURL: invalidServerAddress,
err: "error getting credentials - err: exited 1, out: `program failed`",
},
{
err: fmt.Sprintf("error getting credentials - err: %s, out: `%s`", missingServerURLErr.Error(), missingServerURLErr.Error()),
},
} }
for _, v := range invalid { for _, v := range invalid {
@@ -177,26 +184,26 @@ func TestGet(t *testing.T) {
t.Fatalf("Expected error for server %s, got nil", v.serverURL) t.Fatalf("Expected error for server %s, got nil", v.serverURL)
} }
if err.Error() != v.err { if err.Error() != v.err {
t.Fatalf("Expected error `%s`, got `%v`", v.err, err) t.Errorf("Expected error `%s`, got `%v`", v.err, err)
} }
} }
} }
func ExampleErase() { func ExampleErase() {
p := NewShellProgramFunc("docker-credential-secretservice") p := NewShellProgramFunc("docker-credential-pass")
if err := Erase(p, "https://example.com"); err != nil { if err := Erase(p, "https://registry.example.com"); err != nil {
fmt.Println(err) _, _ = fmt.Println(err)
} }
} }
func TestErase(t *testing.T) { func TestErase(t *testing.T) {
if err := Erase(mockProgramFn, validServerAddress); err != nil { if err := Erase(mockProgramFn, validServerAddress); err != nil {
t.Fatal(err) t.Error(err)
} }
if err := Erase(mockProgramFn, invalidServerAddress); err == nil { if err := Erase(mockProgramFn, invalidServerAddress); err == nil {
t.Fatalf("Expected error for server %s, got nil", invalidServerAddress) t.Errorf("Expected error for server %s, got nil", invalidServerAddress)
} }
} }
@@ -207,6 +214,6 @@ func TestList(t *testing.T) {
} }
if username, exists := auths[validServerAddress]; !exists || username != validUsername { if username, exists := auths[validServerAddress]; !exists || username != validUsername {
t.Fatalf("auths[%s] returned %s, %t; expected %s, %t", validServerAddress, username, exists, validUsername, true) t.Errorf("auths[%s] returned %s, %t; expected %s, %t", validServerAddress, username, exists, validUsername, true)
} }
} }
+20 -20
View File
@@ -1,11 +1,9 @@
package client package client
import ( import (
"fmt"
"io" "io"
"os" "os"
"os/exec"
exec "golang.org/x/sys/execabs"
) )
// Program is an interface to execute external programs. // Program is an interface to execute external programs.
@@ -17,41 +15,43 @@ type Program interface {
// ProgramFunc is a type of function that initializes programs based on arguments. // ProgramFunc is a type of function that initializes programs based on arguments.
type ProgramFunc func(args ...string) Program type ProgramFunc func(args ...string) Program
// NewShellProgramFunc creates programs that are executed in a Shell. // NewShellProgramFunc creates a [ProgramFunc] to run command in a [Shell].
func NewShellProgramFunc(name string) ProgramFunc { func NewShellProgramFunc(command string) ProgramFunc {
return NewShellProgramFuncWithEnv(name, nil)
}
// NewShellProgramFuncWithEnv creates programs that are executed in a Shell with environment variables
func NewShellProgramFuncWithEnv(name string, env *map[string]string) ProgramFunc {
return func(args ...string) Program { return func(args ...string) Program {
return &Shell{cmd: createProgramCmdRedirectErr(name, args, env)} return createProgramCmdRedirectErr(command, args, nil)
} }
} }
func createProgramCmdRedirectErr(commandName string, args []string, env *map[string]string) *exec.Cmd { // NewShellProgramFuncWithEnv creates a [ProgramFunc] tu run command
programCmd := exec.Command(commandName, args...) // in a [Shell] with the given environment variables.
programCmd.Env = os.Environ() func NewShellProgramFuncWithEnv(command string, env *map[string]string) ProgramFunc {
return func(args ...string) Program {
return createProgramCmdRedirectErr(command, args, env)
}
}
func createProgramCmdRedirectErr(command string, args []string, env *map[string]string) *Shell {
ec := exec.Command(command, args...)
if env != nil { if env != nil {
for k, v := range *env { for k, v := range *env {
programCmd.Env = append(programCmd.Env, fmt.Sprintf("%s=%s", k, v)) ec.Env = append(ec.Environ(), k+"="+v)
} }
} }
programCmd.Stderr = os.Stderr ec.Stderr = os.Stderr
return programCmd return &Shell{cmd: ec}
} }
// Shell invokes shell commands to talk with a remote credentials helper. // Shell invokes shell commands to talk with a remote credentials-helper.
type Shell struct { type Shell struct {
cmd *exec.Cmd cmd *exec.Cmd
} }
// Output returns responses from the remote credentials helper. // Output returns responses from the remote credentials-helper.
func (s *Shell) Output() ([]byte, error) { func (s *Shell) Output() ([]byte, error) {
return s.cmd.Output() return s.cmd.Output()
} }
// Input sets the input to send to a remote credentials helper. // Input sets the input to send to a remote credentials-helper.
func (s *Shell) Input(in io.Reader) { func (s *Shell) Input(in io.Reader) {
s.cmd.Stdin = in s.cmd.Stdin = in
} }
+46 -23
View File
@@ -10,6 +10,20 @@ import (
"strings" "strings"
) )
// Action defines the name of an action (sub-command) supported by a
// credential-helper binary. It is an alias for "string", and mostly
// for convenience.
type Action = string
// List of actions (sub-commands) supported by credential-helper binaries.
const (
ActionStore Action = "store"
ActionGet Action = "get"
ActionErase Action = "erase"
ActionList Action = "list"
ActionVersion Action = "version"
)
// Credentials holds the information shared between docker and the credentials store. // Credentials holds the information shared between docker and the credentials store.
type Credentials struct { type Credentials struct {
ServerURL string ServerURL string
@@ -43,42 +57,52 @@ func SetCredsLabel(label string) {
CredsLabel = label CredsLabel = label
} }
// Serve initializes the credentials helper and parses the action argument. // Serve initializes the credentials-helper and parses the action argument.
// This function is designed to be called from a command line interface. // This function is designed to be called from a command line interface.
// It uses os.Args[1] as the key for the action. // It uses os.Args[1] as the key for the action.
// It uses os.Stdin as input and os.Stdout as output. // It uses os.Stdin as input and os.Stdout as output.
// This function terminates the program with os.Exit(1) if there is an error. // This function terminates the program with os.Exit(1) if there is an error.
func Serve(helper Helper) { func Serve(helper Helper) {
var err error
if len(os.Args) != 2 { if len(os.Args) != 2 {
err = fmt.Errorf("Usage: %s <store|get|erase|list|version>", os.Args[0]) _, _ = fmt.Fprintln(os.Stdout, usage())
os.Exit(1)
} }
if err == nil { switch os.Args[1] {
err = HandleCommand(helper, os.Args[1], os.Stdin, os.Stdout) case "--version", "-v":
_ = PrintVersion(os.Stdout)
os.Exit(0)
case "--help", "-h":
_, _ = fmt.Fprintln(os.Stdout, usage())
os.Exit(0)
} }
if err != nil { if err := HandleCommand(helper, os.Args[1], os.Stdin, os.Stdout); err != nil {
fmt.Fprintf(os.Stdout, "%v\n", err) _, _ = fmt.Fprintln(os.Stdout, err)
os.Exit(1) os.Exit(1)
} }
} }
// HandleCommand uses a helper and a key to run a credential action. func usage() string {
func HandleCommand(helper Helper, key string, in io.Reader, out io.Writer) error { return fmt.Sprintf("Usage: %s <store|get|erase|list|version>", Name)
switch key { }
case "store":
// HandleCommand runs a helper to execute a credential action.
func HandleCommand(helper Helper, action Action, in io.Reader, out io.Writer) error {
switch action {
case ActionStore:
return Store(helper, in) return Store(helper, in)
case "get": case ActionGet:
return Get(helper, in, out) return Get(helper, in, out)
case "erase": case ActionErase:
return Erase(helper, in) return Erase(helper, in)
case "list": case ActionList:
return List(helper, out) return List(helper, out)
case "version": case ActionVersion:
return PrintVersion(out) return PrintVersion(out)
default:
return fmt.Errorf("%s: unknown action: %s", Name, action)
} }
return fmt.Errorf("Unknown credential action `%s`", key)
} }
// Store uses a helper and an input reader to save credentials. // Store uses a helper and an input reader to save credentials.
@@ -132,18 +156,17 @@ func Get(helper Helper, reader io.Reader, writer io.Writer) error {
return err return err
} }
resp := Credentials{ buffer.Reset()
err = json.NewEncoder(buffer).Encode(Credentials{
ServerURL: serverURL, ServerURL: serverURL,
Username: username, Username: username,
Secret: secret, Secret: secret,
} })
if err != nil {
buffer.Reset()
if err := json.NewEncoder(buffer).Encode(resp); err != nil {
return err return err
} }
fmt.Fprint(writer, buffer.String()) _, _ = fmt.Fprint(writer, buffer.String())
return nil return nil
} }
@@ -181,6 +204,6 @@ func List(helper Helper, writer io.Writer) error {
// PrintVersion outputs the current version. // PrintVersion outputs the current version.
func PrintVersion(writer io.Writer) error { func PrintVersion(writer io.Writer) error {
fmt.Fprintf(writer, "%s (%s) %s\n", Name, Package, Version) _, _ = fmt.Fprintf(writer, "%s (%s) %s\n", Name, Package, Version)
return nil return nil
} }
+24 -24
View File
@@ -37,12 +37,12 @@ func (m *memoryStore) Get(serverURL string) (string, string, error) {
} }
func (m *memoryStore) List() (map[string]string, error) { func (m *memoryStore) List() (map[string]string, error) {
//Simply a placeholder to let memoryStore be a valid implementation of Helper interface // Simply a placeholder to let memoryStore be a valid implementation of Helper interface
return nil, nil return nil, nil
} }
func TestStore(t *testing.T) { func TestStore(t *testing.T) {
serverURL := "https://index.docker.io/v1/" const serverURL = "https://registry.example.com/v1/"
creds := &Credentials{ creds := &Credentials{
ServerURL: serverURL, ServerURL: serverURL,
Username: "foo", Username: "foo",
@@ -65,11 +65,11 @@ func TestStore(t *testing.T) {
} }
if c.Username != "foo" { if c.Username != "foo" {
t.Fatalf("expected username foo, got %s\n", c.Username) t.Errorf("expected username foo, got %s\n", c.Username)
} }
if c.Secret != "bar" { if c.Secret != "bar" {
t.Fatalf("expected username bar, got %s\n", c.Secret) t.Errorf("expected username bar, got %s\n", c.Secret)
} }
} }
@@ -88,14 +88,14 @@ func TestStoreMissingServerURL(t *testing.T) {
h := newMemoryStore() h := newMemoryStore()
if err := Store(h, in); IsCredentialsMissingServerURL(err) == false { if err := Store(h, in); !IsCredentialsMissingServerURL(err) {
t.Fatal(err) t.Error(err)
} }
} }
func TestStoreMissingUsername(t *testing.T) { func TestStoreMissingUsername(t *testing.T) {
creds := &Credentials{ creds := &Credentials{
ServerURL: "https://index.docker.io/v1/", ServerURL: "https://registry.example.com/v1/",
Username: "", Username: "",
Secret: "bar", Secret: "bar",
} }
@@ -108,13 +108,13 @@ func TestStoreMissingUsername(t *testing.T) {
h := newMemoryStore() h := newMemoryStore()
if err := Store(h, in); IsCredentialsMissingUsername(err) == false { if err := Store(h, in); !IsCredentialsMissingUsername(err) {
t.Fatal(err) t.Error(err)
} }
} }
func TestGet(t *testing.T) { func TestGet(t *testing.T) {
serverURL := "https://index.docker.io/v1/" const serverURL = "https://registry.example.com/v1/"
creds := &Credentials{ creds := &Credentials{
ServerURL: serverURL, ServerURL: serverURL,
Username: "foo", Username: "foo",
@@ -147,16 +147,16 @@ func TestGet(t *testing.T) {
} }
if c.Username != "foo" { if c.Username != "foo" {
t.Fatalf("expected username foo, got %s\n", c.Username) t.Errorf("expected username foo, got %s\n", c.Username)
} }
if c.Secret != "bar" { if c.Secret != "bar" {
t.Fatalf("expected username bar, got %s\n", c.Secret) t.Errorf("expected username bar, got %s\n", c.Secret)
} }
} }
func TestGetMissingServerURL(t *testing.T) { func TestGetMissingServerURL(t *testing.T) {
serverURL := "https://index.docker.io/v1/" const serverURL = "https://registry.example.com/v1/"
creds := &Credentials{ creds := &Credentials{
ServerURL: serverURL, ServerURL: serverURL,
Username: "foo", Username: "foo",
@@ -176,13 +176,13 @@ func TestGetMissingServerURL(t *testing.T) {
buf := strings.NewReader("") buf := strings.NewReader("")
w := new(bytes.Buffer) w := new(bytes.Buffer)
if err := Get(h, buf, w); IsCredentialsMissingServerURL(err) == false { if err := Get(h, buf, w); !IsCredentialsMissingServerURL(err) {
t.Fatal(err) t.Error(err)
} }
} }
func TestErase(t *testing.T) { func TestErase(t *testing.T) {
serverURL := "https://index.docker.io/v1/" const serverURL = "https://registry.example.com/v1/"
creds := &Credentials{ creds := &Credentials{
ServerURL: serverURL, ServerURL: serverURL,
Username: "foo", Username: "foo",
@@ -206,12 +206,12 @@ func TestErase(t *testing.T) {
w := new(bytes.Buffer) w := new(bytes.Buffer)
if err := Get(h, buf, w); err == nil { if err := Get(h, buf, w); err == nil {
t.Fatal("expected error getting missing creds, got empty") t.Error("expected error getting missing creds, got empty")
} }
} }
func TestEraseMissingServerURL(t *testing.T) { func TestEraseMissingServerURL(t *testing.T) {
serverURL := "https://index.docker.io/v1/" const serverURL = "https://registry.example.com/v1/"
creds := &Credentials{ creds := &Credentials{
ServerURL: serverURL, ServerURL: serverURL,
Username: "foo", Username: "foo",
@@ -229,21 +229,21 @@ func TestEraseMissingServerURL(t *testing.T) {
} }
buf := strings.NewReader("") buf := strings.NewReader("")
if err := Erase(h, buf); IsCredentialsMissingServerURL(err) == false { if err := Erase(h, buf); !IsCredentialsMissingServerURL(err) {
t.Fatal(err) t.Error(err)
} }
} }
func TestList(t *testing.T) { func TestList(t *testing.T) {
//This tests that there is proper input an output into the byte stream // This tests that there is proper input an output into the byte stream
//Individual stores are very OS specific and have been tested in osxkeychain and secretservice respectively // Individual stores are very OS specific and have been tested in osxkeychain and secretservice respectively
out := new(bytes.Buffer) out := new(bytes.Buffer)
h := newMemoryStore() h := newMemoryStore()
if err := List(h, out); err != nil { if err := List(h, out); err != nil {
t.Fatal(err) t.Fatal(err)
} }
//testing that there is an output // testing that there is an output
if out.Len() == 0 { if out.Len() == 0 {
t.Fatalf("expected output in the writer, got %d", 0) t.Error("expected output in the writer, got 0")
} }
} }
+31 -9
View File
@@ -1,5 +1,10 @@
package credentials package credentials
import (
"errors"
"strings"
)
const ( const (
// ErrCredentialsNotFound standardizes the not found error, so every helper returns // ErrCredentialsNotFound standardizes the not found error, so every helper returns
// the same message and docker can handle it properly. // the same message and docker can handle it properly.
@@ -21,6 +26,11 @@ func (errCredentialsNotFound) Error() string {
return errCredentialsNotFoundMessage return errCredentialsNotFoundMessage
} }
// NotFound implements the [ErrNotFound][errdefs.ErrNotFound] interface.
//
// [errdefs.ErrNotFound]: https://pkg.go.dev/github.com/docker/docker@v24.0.1+incompatible/errdefs#ErrNotFound
func (errCredentialsNotFound) NotFound() {}
// NewErrCredentialsNotFound creates a new error // NewErrCredentialsNotFound creates a new error
// for when the credentials are not in the store. // for when the credentials are not in the store.
func NewErrCredentialsNotFound() error { func NewErrCredentialsNotFound() error {
@@ -30,8 +40,8 @@ func NewErrCredentialsNotFound() error {
// IsErrCredentialsNotFound returns true if the error // IsErrCredentialsNotFound returns true if the error
// was caused by not having a set of credentials in a store. // was caused by not having a set of credentials in a store.
func IsErrCredentialsNotFound(err error) bool { func IsErrCredentialsNotFound(err error) bool {
_, ok := err.(errCredentialsNotFound) var target errCredentialsNotFound
return ok return errors.As(err, &target)
} }
// IsErrCredentialsNotFoundMessage returns true if the error // IsErrCredentialsNotFoundMessage returns true if the error
@@ -40,7 +50,7 @@ func IsErrCredentialsNotFound(err error) bool {
// This function helps to check messages returned by an // This function helps to check messages returned by an
// external program via its standard output. // external program via its standard output.
func IsErrCredentialsNotFoundMessage(err string) bool { func IsErrCredentialsNotFoundMessage(err string) bool {
return err == errCredentialsNotFoundMessage return strings.TrimSpace(err) == errCredentialsNotFoundMessage
} }
// errCredentialsMissingServerURL represents an error raised // errCredentialsMissingServerURL represents an error raised
@@ -53,6 +63,12 @@ func (errCredentialsMissingServerURL) Error() string {
return errCredentialsMissingServerURLMessage return errCredentialsMissingServerURLMessage
} }
// InvalidParameter implements the [ErrInvalidParameter][errdefs.ErrInvalidParameter]
// interface.
//
// [errdefs.ErrInvalidParameter]: https://pkg.go.dev/github.com/docker/docker@v24.0.1+incompatible/errdefs#ErrInvalidParameter
func (errCredentialsMissingServerURL) InvalidParameter() {}
// errCredentialsMissingUsername represents an error raised // errCredentialsMissingUsername represents an error raised
// when the credentials object has no username or when no // when the credentials object has no username or when no
// username is provided to a credentials operation requiring // username is provided to a credentials operation requiring
@@ -63,6 +79,12 @@ func (errCredentialsMissingUsername) Error() string {
return errCredentialsMissingUsernameMessage return errCredentialsMissingUsernameMessage
} }
// InvalidParameter implements the [ErrInvalidParameter][errdefs.ErrInvalidParameter]
// interface.
//
// [errdefs.ErrInvalidParameter]: https://pkg.go.dev/github.com/docker/docker@v24.0.1+incompatible/errdefs#ErrInvalidParameter
func (errCredentialsMissingUsername) InvalidParameter() {}
// NewErrCredentialsMissingServerURL creates a new error for // NewErrCredentialsMissingServerURL creates a new error for
// errCredentialsMissingServerURL. // errCredentialsMissingServerURL.
func NewErrCredentialsMissingServerURL() error { func NewErrCredentialsMissingServerURL() error {
@@ -78,25 +100,25 @@ func NewErrCredentialsMissingUsername() error {
// IsCredentialsMissingServerURL returns true if the error // IsCredentialsMissingServerURL returns true if the error
// was an errCredentialsMissingServerURL. // was an errCredentialsMissingServerURL.
func IsCredentialsMissingServerURL(err error) bool { func IsCredentialsMissingServerURL(err error) bool {
_, ok := err.(errCredentialsMissingServerURL) var target errCredentialsMissingServerURL
return ok return errors.As(err, &target)
} }
// IsCredentialsMissingServerURLMessage checks for an // IsCredentialsMissingServerURLMessage checks for an
// errCredentialsMissingServerURL in the error message. // errCredentialsMissingServerURL in the error message.
func IsCredentialsMissingServerURLMessage(err string) bool { func IsCredentialsMissingServerURLMessage(err string) bool {
return err == errCredentialsMissingServerURLMessage return strings.TrimSpace(err) == errCredentialsMissingServerURLMessage
} }
// IsCredentialsMissingUsername returns true if the error // IsCredentialsMissingUsername returns true if the error
// was an errCredentialsMissingUsername. // was an errCredentialsMissingUsername.
func IsCredentialsMissingUsername(err error) bool { func IsCredentialsMissingUsername(err error) bool {
_, ok := err.(errCredentialsMissingUsername) var target errCredentialsMissingUsername
return ok return errors.As(err, &target)
} }
// IsCredentialsMissingUsernameMessage checks for an // IsCredentialsMissingUsernameMessage checks for an
// errCredentialsMissingUsername in the error message. // errCredentialsMissingUsername in the error message.
func IsCredentialsMissingUsernameMessage(err string) bool { func IsCredentialsMissingUsernameMessage(err string) bool {
return err == errCredentialsMissingUsernameMessage return strings.TrimSpace(err) == errCredentialsMissingUsernameMessage
} }
+4 -2
View File
@@ -1,8 +1,10 @@
ARG GO_VERSION=1.18.5 # syntax=docker/dockerfile:1
ARG GO_VERSION=1.23.6
ARG DISTRO=ubuntu ARG DISTRO=ubuntu
ARG SUITE=focal ARG SUITE=focal
FROM golang:${GO_VERSION}-buster AS golang FROM golang:${GO_VERSION}-bullseye AS golang
FROM ${DISTRO}:${SUITE} FROM ${DISTRO}:${SUITE}
+3 -2
View File
@@ -1,5 +1,5 @@
variable "GO_VERSION" { variable "GO_VERSION" {
default = "1.18.5" default = null
} }
# Defines the output folder # Defines the output folder
@@ -62,7 +62,8 @@ target "binaries" {
"linux/arm/v6", "linux/arm/v6",
"linux/ppc64le", "linux/ppc64le",
"linux/s390x", "linux/s390x",
"windows/amd64" "windows/amd64",
"windows/arm64"
] ]
} }
+10 -3
View File
@@ -1,8 +1,15 @@
module github.com/docker/docker-credential-helpers module github.com/docker/docker-credential-helpers
go 1.18 go 1.21
retract (
v0.9.1 // osxkeychain: a regression caused backward-incompatibility with earlier versions
v0.9.0 // osxkeychain: a regression caused backward-incompatibility with earlier versions
)
require ( require (
github.com/danieljoos/wincred v1.1.2 github.com/danieljoos/wincred v1.2.2
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 github.com/keybase/go-keychain v0.0.1
) )
require golang.org/x/sys v0.20.0 // indirect
+14 -14
View File
@@ -1,16 +1,16 @@
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0=
github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -1,3 +1,5 @@
//go:build darwin && cgo
package main package main
import ( import (
+171
View File
@@ -0,0 +1,171 @@
//go:build darwin && cgo
package osxkeychain
/*
#cgo LDFLAGS: -framework Security -framework CoreFoundation
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
*/
import "C"
import (
"errors"
"net"
"net/url"
"strconv"
"github.com/docker/docker-credential-helpers/credentials"
"github.com/docker/docker-credential-helpers/registryurl"
"github.com/keybase/go-keychain"
)
// https://opensource.apple.com/source/Security/Security-55471/sec/Security/SecBase.h.auto.html
const (
// errCredentialsNotFound is the specific error message returned by OS X
// when the credentials are not in the keychain.
errCredentialsNotFound = "The specified item could not be found in the keychain. (-25300)"
// errInteractionNotAllowed is the specific error message returned by OS X
// when environment does not allow showing dialog to unlock keychain.
errInteractionNotAllowed = "User interaction is not allowed. (-25308)"
)
// ErrInteractionNotAllowed is returned if keychain password prompt can not be shown.
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`)
// Osxkeychain handles secrets using the OS X Keychain as store.
type Osxkeychain struct{}
// Add adds new credentials to the keychain.
func (h Osxkeychain) Add(creds *credentials.Credentials) error {
_ = h.Delete(creds.ServerURL) // ignore errors as existing credential may not exist.
item := keychain.NewItem()
item.SetSecClass(keychain.SecClassInternetPassword)
item.SetLabel(credentials.CredsLabel)
item.SetAccount(creds.Username)
item.SetData([]byte(creds.Secret))
if err := splitServer(creds.ServerURL, item); err != nil {
return err
}
return keychain.AddItem(item)
}
// Delete removes credentials from the keychain.
func (h Osxkeychain) Delete(serverURL string) error {
item := keychain.NewItem()
item.SetSecClass(keychain.SecClassInternetPassword)
if err := splitServer(serverURL, item); err != nil {
return err
}
if err := keychain.DeleteItem(item); err != nil {
switch err.Error() {
case errCredentialsNotFound:
return credentials.NewErrCredentialsNotFound()
case errInteractionNotAllowed:
return ErrInteractionNotAllowed
default:
return err
}
}
return nil
}
// Get returns the username and secret to use for a given registry server URL.
func (h Osxkeychain) Get(serverURL string) (string, string, error) {
item := keychain.NewItem()
item.SetSecClass(keychain.SecClassInternetPassword)
item.SetMatchLimit(keychain.MatchLimitOne)
item.SetReturnAttributes(true)
item.SetReturnData(true)
if err := splitServer(serverURL, item); err != nil {
return "", "", err
}
res, err := keychain.QueryItem(item)
if err != nil {
switch err.Error() {
case errCredentialsNotFound:
return "", "", credentials.NewErrCredentialsNotFound()
case errInteractionNotAllowed:
return "", "", ErrInteractionNotAllowed
default:
return "", "", err
}
} else if len(res) == 0 {
return "", "", credentials.NewErrCredentialsNotFound()
}
return res[0].Account, string(res[0].Data), nil
}
// List returns the stored URLs and corresponding usernames.
func (h Osxkeychain) List() (map[string]string, error) {
item := keychain.NewItem()
item.SetSecClass(keychain.SecClassInternetPassword)
item.SetMatchLimit(keychain.MatchLimitAll)
item.SetReturnAttributes(true)
item.SetLabel(credentials.CredsLabel)
res, err := keychain.QueryItem(item)
if err != nil {
switch err.Error() {
case errCredentialsNotFound:
return make(map[string]string), nil
case errInteractionNotAllowed:
return nil, ErrInteractionNotAllowed
default:
return nil, err
}
}
resp := make(map[string]string)
for _, r := range res {
proto := "http"
if r.Protocol == kSecProtocolTypeHTTPS {
proto = "https"
}
host := r.Server
if r.Port != 0 {
host = net.JoinHostPort(host, strconv.Itoa(int(r.Port)))
}
u := url.URL{
Scheme: proto,
Host: host,
Path: r.Path,
}
resp[u.String()] = r.Account
}
return resp, nil
}
const (
// Hardcoded protocol types matching their Objective-C equivalents.
// https://developer.apple.com/documentation/security/ksecattrprotocolhttps?language=objc
kSecProtocolTypeHTTPS = "htps" // This is NOT a typo.
// https://developer.apple.com/documentation/security/ksecattrprotocolhttp?language=objc
kSecProtocolTypeHTTP = "http"
)
func splitServer(serverURL string, item keychain.Item) error {
u, err := registryurl.Parse(serverURL)
if err != nil {
return err
}
item.SetProtocol(kSecProtocolTypeHTTPS)
if u.Scheme == "http" {
item.SetProtocol(kSecProtocolTypeHTTP)
}
item.SetServer(u.Hostname())
if p := u.Port(); p != "" {
port, err := strconv.Atoi(p)
if err != nil {
return err
}
item.SetPort(int32(port))
}
item.SetPath(u.Path)
return nil
}
-227
View File
@@ -1,227 +0,0 @@
#include "osxkeychain_darwin.h"
#include <CoreFoundation/CoreFoundation.h>
#include <Foundation/NSValue.h>
#include <stdio.h>
#include <string.h>
char *get_error(OSStatus status) {
char *buf = malloc(128);
CFStringRef str = SecCopyErrorMessageString(status, NULL);
int success = CFStringGetCString(str, buf, 128, kCFStringEncodingUTF8);
if (!success) {
strncpy(buf, "Unknown error", 128);
}
return buf;
}
char *keychain_add(struct Server *server, char *label, char *username, char *secret) {
SecKeychainItemRef item;
OSStatus status = SecKeychainAddInternetPassword(
NULL,
strlen(server->host), server->host,
0, NULL,
strlen(username), username,
strlen(server->path), server->path,
server->port,
server->proto,
kSecAuthenticationTypeDefault,
strlen(secret), secret,
&item
);
if (status) {
return get_error(status);
}
SecKeychainAttribute attribute;
SecKeychainAttributeList attrs;
attribute.tag = kSecLabelItemAttr;
attribute.data = label;
attribute.length = strlen(label);
attrs.count = 1;
attrs.attr = &attribute;
status = SecKeychainItemModifyContent(item, &attrs, 0, NULL);
if (status) {
return get_error(status);
}
return NULL;
}
char *keychain_get(struct Server *server, unsigned int *username_l, char **username, unsigned int *secret_l, char **secret) {
char *tmp;
SecKeychainItemRef item;
OSStatus status = SecKeychainFindInternetPassword(
NULL,
strlen(server->host), server->host,
0, NULL,
0, NULL,
strlen(server->path), server->path,
server->port,
server->proto,
kSecAuthenticationTypeDefault,
secret_l, (void **)&tmp,
&item);
if (status) {
return get_error(status);
}
*secret = strdup(tmp);
SecKeychainItemFreeContent(NULL, tmp);
SecKeychainAttributeList list;
SecKeychainAttribute attr;
list.count = 1;
list.attr = &attr;
attr.tag = kSecAccountItemAttr;
status = SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL);
if (status) {
return get_error(status);
}
*username = strdup(attr.data);
*username_l = attr.length;
SecKeychainItemFreeContent(&list, NULL);
return NULL;
}
char *keychain_delete(struct Server *server) {
SecKeychainItemRef item;
OSStatus status = SecKeychainFindInternetPassword(
NULL,
strlen(server->host), server->host,
0, NULL,
0, NULL,
strlen(server->path), server->path,
server->port,
server->proto,
kSecAuthenticationTypeDefault,
0, NULL,
&item);
if (status) {
return get_error(status);
}
status = SecKeychainItemDelete(item);
if (status) {
return get_error(status);
}
return NULL;
}
char * CFStringToCharArr(CFStringRef aString) {
if (aString == NULL) {
return NULL;
}
CFIndex length = CFStringGetLength(aString);
CFIndex maxSize =
CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
char *buffer = (char *)malloc(maxSize);
if (CFStringGetCString(aString, buffer, maxSize,
kCFStringEncodingUTF8)) {
return buffer;
}
return NULL;
}
char *keychain_list(char *credsLabel, char *** paths, char *** accts, unsigned int *list_l) {
CFStringRef credsLabelCF = CFStringCreateWithCString(NULL, credsLabel, kCFStringEncodingUTF8);
CFMutableDictionaryRef query = CFDictionaryCreateMutable (NULL, 1, NULL, NULL);
CFDictionaryAddValue(query, kSecClass, kSecClassInternetPassword);
CFDictionaryAddValue(query, kSecReturnAttributes, kCFBooleanTrue);
CFDictionaryAddValue(query, kSecMatchLimit, kSecMatchLimitAll);
CFDictionaryAddValue(query, kSecAttrLabel, credsLabelCF);
//Use this query dictionary
CFTypeRef result= NULL;
OSStatus status = SecItemCopyMatching(
query,
&result);
CFRelease(credsLabelCF);
//Ran a search and store the results in result
if (status) {
return get_error(status);
}
CFIndex numKeys = CFArrayGetCount(result);
*paths = (char **) malloc((int)sizeof(char *)*numKeys);
*accts = (char **) malloc((int)sizeof(char *)*numKeys);
//result is of type CFArray
for(CFIndex i=0; i<numKeys; i++) {
CFDictionaryRef currKey = CFArrayGetValueAtIndex(result,i);
CFStringRef protocolTmp = CFDictionaryGetValue(currKey, CFSTR("ptcl"));
if (protocolTmp != NULL) {
CFStringRef protocolStr = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@"), protocolTmp);
if (CFStringCompare(protocolStr, CFSTR("htps"), 0) == kCFCompareEqualTo) {
protocolTmp = CFSTR("https://");
}
else {
protocolTmp = CFSTR("http://");
}
CFRelease(protocolStr);
}
else {
char * path = "0";
char * acct = "0";
(*paths)[i] = (char *) malloc(sizeof(char)*(strlen(path)));
memcpy((*paths)[i], path, sizeof(char)*(strlen(path)));
(*accts)[i] = (char *) malloc(sizeof(char)*(strlen(acct)));
memcpy((*accts)[i], acct, sizeof(char)*(strlen(acct)));
continue;
}
CFMutableStringRef str = CFStringCreateMutableCopy(NULL, 0, protocolTmp);
CFStringRef serverTmp = CFDictionaryGetValue(currKey, CFSTR("srvr"));
if (serverTmp != NULL) {
CFStringAppend(str, serverTmp);
}
CFStringRef pathTmp = CFDictionaryGetValue(currKey, CFSTR("path"));
if (pathTmp != NULL) {
CFStringAppend(str, pathTmp);
}
const NSNumber * portTmp = CFDictionaryGetValue(currKey, CFSTR("port"));
if (portTmp != NULL && portTmp.integerValue != 0) {
CFStringRef portStr = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@"), portTmp);
CFStringAppend(str, CFSTR(":"));
CFStringAppend(str, portStr);
CFRelease(portStr);
}
CFStringRef acctTmp = CFDictionaryGetValue(currKey, CFSTR("acct"));
if (acctTmp == NULL) {
acctTmp = CFSTR("account not defined");
}
char * path = CFStringToCharArr(str);
char * acct = CFStringToCharArr(acctTmp);
//We now have all we need, username and servername. Now export this to .go
(*paths)[i] = (char *) malloc(sizeof(char)*(strlen(path)+1));
memcpy((*paths)[i], path, sizeof(char)*(strlen(path)+1));
(*accts)[i] = (char *) malloc(sizeof(char)*(strlen(acct)+1));
memcpy((*accts)[i], acct, sizeof(char)*(strlen(acct)+1));
CFRelease(str);
}
*list_l = (int)numKeys;
return NULL;
}
void freeListData(char *** data, unsigned int length) {
for(int i=0; i<length; i++) {
free((*data)[i]);
}
}
-182
View File
@@ -1,182 +0,0 @@
package osxkeychain
/*
#cgo CFLAGS: -x objective-c -mmacosx-version-min=10.11
#cgo LDFLAGS: -framework Security -framework Foundation -mmacosx-version-min=10.11
#include "osxkeychain_darwin.h"
#include <stdlib.h>
*/
import "C"
import (
"errors"
"strconv"
"unsafe"
"github.com/docker/docker-credential-helpers/credentials"
"github.com/docker/docker-credential-helpers/registryurl"
)
// errCredentialsNotFound is the specific error message returned by OS X
// when the credentials are not in the keychain.
const errCredentialsNotFound = "The specified item could not be found in the keychain."
// errCredentialsNotFound is the specific error message returned by OS X
// when environment does not allow showing dialog to unlock keychain.
const errInteractionNotAllowed = "User interaction is not allowed."
// ErrInteractionNotAllowed is returned if keychain password prompt can not be shown.
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`)
// Osxkeychain handles secrets using the OS X Keychain as store.
type Osxkeychain struct{}
// Add adds new credentials to the keychain.
func (h Osxkeychain) Add(creds *credentials.Credentials) error {
h.Delete(creds.ServerURL)
s, err := splitServer(creds.ServerURL)
if err != nil {
return err
}
defer freeServer(s)
label := C.CString(credentials.CredsLabel)
defer C.free(unsafe.Pointer(label))
username := C.CString(creds.Username)
defer C.free(unsafe.Pointer(username))
secret := C.CString(creds.Secret)
defer C.free(unsafe.Pointer(secret))
errMsg := C.keychain_add(s, label, username, secret)
if errMsg != nil {
defer C.free(unsafe.Pointer(errMsg))
return errors.New(C.GoString(errMsg))
}
return nil
}
// Delete removes credentials from the keychain.
func (h Osxkeychain) Delete(serverURL string) error {
s, err := splitServer(serverURL)
if err != nil {
return err
}
defer freeServer(s)
errMsg := C.keychain_delete(s)
if errMsg != nil {
defer C.free(unsafe.Pointer(errMsg))
return errors.New(C.GoString(errMsg))
}
return nil
}
// Get returns the username and secret to use for a given registry server URL.
func (h Osxkeychain) Get(serverURL string) (string, string, error) {
s, err := splitServer(serverURL)
if err != nil {
return "", "", err
}
defer freeServer(s)
var usernameLen C.uint
var username *C.char
var secretLen C.uint
var secret *C.char
defer C.free(unsafe.Pointer(username))
defer C.free(unsafe.Pointer(secret))
errMsg := C.keychain_get(s, &usernameLen, &username, &secretLen, &secret)
if errMsg != nil {
defer C.free(unsafe.Pointer(errMsg))
goMsg := C.GoString(errMsg)
if goMsg == errCredentialsNotFound {
return "", "", credentials.NewErrCredentialsNotFound()
}
if goMsg == errInteractionNotAllowed {
return "", "", ErrInteractionNotAllowed
}
return "", "", errors.New(goMsg)
}
user := C.GoStringN(username, C.int(usernameLen))
pass := C.GoStringN(secret, C.int(secretLen))
return user, pass, nil
}
// List returns the stored URLs and corresponding usernames.
func (h Osxkeychain) List() (map[string]string, error) {
credsLabelC := C.CString(credentials.CredsLabel)
defer C.free(unsafe.Pointer(credsLabelC))
var pathsC **C.char
defer C.free(unsafe.Pointer(pathsC))
var acctsC **C.char
defer C.free(unsafe.Pointer(acctsC))
var listLenC C.uint
errMsg := C.keychain_list(credsLabelC, &pathsC, &acctsC, &listLenC)
defer C.freeListData(&pathsC, listLenC)
defer C.freeListData(&acctsC, listLenC)
if errMsg != nil {
defer C.free(unsafe.Pointer(errMsg))
goMsg := C.GoString(errMsg)
if goMsg == errCredentialsNotFound {
return make(map[string]string), nil
}
if goMsg == errInteractionNotAllowed {
return nil, ErrInteractionNotAllowed
}
return nil, errors.New(goMsg)
}
var listLen int
listLen = int(listLenC)
pathTmp := (*[1 << 30]*C.char)(unsafe.Pointer(pathsC))[:listLen:listLen]
acctTmp := (*[1 << 30]*C.char)(unsafe.Pointer(acctsC))[:listLen:listLen]
// taking the array of c strings into go while ignoring all the stuff irrelevant to credentials-helper
resp := make(map[string]string)
for i := 0; i < listLen; i++ {
if C.GoString(pathTmp[i]) == "0" {
continue
}
resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i])
}
return resp, nil
}
func splitServer(serverURL string) (*C.struct_Server, error) {
u, err := registryurl.Parse(serverURL)
if err != nil {
return nil, err
}
proto := C.kSecProtocolTypeHTTPS
if u.Scheme == "http" {
proto = C.kSecProtocolTypeHTTP
}
var port int
p := u.Port()
if p != "" {
port, err = strconv.Atoi(p)
if err != nil {
return nil, err
}
}
return &C.struct_Server{
proto: C.SecProtocolType(proto),
host: C.CString(u.Hostname()),
port: C.uint(port),
path: C.CString(u.Path),
}, nil
}
func freeServer(s *C.struct_Server) {
C.free(unsafe.Pointer(s.host))
C.free(unsafe.Pointer(s.path))
}
-14
View File
@@ -1,14 +0,0 @@
#include <Security/Security.h>
struct Server {
SecProtocolType proto;
char *host;
char *path;
unsigned int port;
};
char *keychain_add(struct Server *server, char *label, char *username, char *secret);
char *keychain_get(struct Server *server, unsigned int *username_l, char **username, unsigned int *secret_l, char **secret);
char *keychain_delete(struct Server *server);
char *keychain_list(char *credsLabel, char *** data, char *** accts, unsigned int *list_l);
void freeListData(char *** data, unsigned int length);
-213
View File
@@ -1,213 +0,0 @@
package osxkeychain
import (
"fmt"
"testing"
"github.com/docker/docker-credential-helpers/credentials"
)
func TestOSXKeychainHelper(t *testing.T) {
creds := &credentials.Credentials{
ServerURL: "https://foobar.docker.io:2376/v1",
Username: "foobar",
Secret: "foobarbaz",
}
creds1 := &credentials.Credentials{
ServerURL: "https://foobar.docker.io:2376/v2",
Username: "foobarbaz",
Secret: "foobar",
}
helper := Osxkeychain{}
if err := helper.Add(creds); err != nil {
t.Fatal(err)
}
username, secret, err := helper.Get(creds.ServerURL)
if err != nil {
t.Fatal(err)
}
if username != "foobar" {
t.Fatalf("expected %s, got %s\n", "foobar", username)
}
if secret != "foobarbaz" {
t.Fatalf("expected %s, got %s\n", "foobarbaz", secret)
}
auths, err := helper.List()
if err != nil || len(auths) == 0 {
t.Fatal(err)
}
helper.Add(creds1)
defer helper.Delete(creds1.ServerURL)
newauths, err := helper.List()
if len(newauths)-len(auths) != 1 {
if err == nil {
t.Fatalf("Error: len(newauths): %d, len(auths): %d", len(newauths), len(auths))
}
t.Fatalf("Error: len(newauths): %d, len(auths): %d\n Error= %v", len(newauths), len(auths), err)
}
if err := helper.Delete(creds.ServerURL); err != nil {
t.Fatal(err)
}
}
// TestOSXKeychainHelperRetrieveAliases verifies that secrets can be accessed
// through variations on the URL
func TestOSXKeychainHelperRetrieveAliases(t *testing.T) {
tests := []struct {
storeURL string
readURL string
}{
// stored with port, retrieved without
{"https://foobar.docker.io:2376", "https://foobar.docker.io"},
// stored as https, retrieved without scheme
{"https://foobar.docker.io:2376", "foobar.docker.io"},
// stored with path, retrieved without
{"https://foobar.docker.io:1234/one/two", "https://foobar.docker.io:1234"},
}
helper := Osxkeychain{}
defer func() {
for _, te := range tests {
helper.Delete(te.storeURL)
}
}()
// Clean store before testing.
for _, te := range tests {
helper.Delete(te.storeURL)
}
for _, te := range tests {
c := &credentials.Credentials{ServerURL: te.storeURL, Username: "hello", Secret: "world"}
if err := helper.Add(c); err != nil {
t.Errorf("Error: failed to store secret for URL %q: %s", te.storeURL, err)
continue
}
if _, _, err := helper.Get(te.readURL); err != nil {
t.Errorf("Error: failed to read secret for URL %q using %q", te.storeURL, te.readURL)
}
helper.Delete(te.storeURL)
}
}
// TestOSXKeychainHelperRetrieveStrict verifies that only matching secrets are
// returned.
func TestOSXKeychainHelperRetrieveStrict(t *testing.T) {
tests := []struct {
storeURL string
readURL string
}{
// stored as https, retrieved using http
{"https://foobar.docker.io:2376", "http://foobar.docker.io:2376"},
// stored as http, retrieved using https
{"http://foobar.docker.io:2376", "https://foobar.docker.io:2376"},
// same: stored as http, retrieved without a scheme specified (hence, using the default https://)
{"http://foobar.docker.io", "foobar.docker.io:5678"},
// non-matching ports
{"https://foobar.docker.io:1234", "https://foobar.docker.io:5678"},
// non-matching ports TODO is this desired behavior? The other way round does work
//{"https://foobar.docker.io", "https://foobar.docker.io:5678"},
// non-matching paths
{"https://foobar.docker.io:1234/one/two", "https://foobar.docker.io:1234/five/six"},
}
helper := Osxkeychain{}
defer func() {
for _, te := range tests {
helper.Delete(te.storeURL)
}
}()
// Clean store before testing.
for _, te := range tests {
helper.Delete(te.storeURL)
}
for _, te := range tests {
c := &credentials.Credentials{ServerURL: te.storeURL, Username: "hello", Secret: "world"}
if err := helper.Add(c); err != nil {
t.Errorf("Error: failed to store secret for URL %q: %s", te.storeURL, err)
continue
}
if _, _, err := helper.Get(te.readURL); err == nil {
t.Errorf("Error: managed to read secret for URL %q using %q, but should not be able to", te.storeURL, te.readURL)
}
helper.Delete(te.storeURL)
}
}
// TestOSXKeychainHelperStoreRetrieve verifies that secrets stored in the
// the keychain can be read back using the URL that was used to store them.
func TestOSXKeychainHelperStoreRetrieve(t *testing.T) {
tests := []struct {
url string
}{
{url: "foobar.docker.io"},
{url: "foobar.docker.io:2376"},
{url: "//foobar.docker.io:2376"},
{url: "https://foobar.docker.io:2376"},
{url: "http://foobar.docker.io:2376"},
{url: "https://foobar.docker.io:2376/some/path"},
{url: "https://foobar.docker.io:2376/some/other/path"},
{url: "https://foobar.docker.io:2376/some/other/path?foo=bar"},
}
helper := Osxkeychain{}
defer func() {
for _, te := range tests {
helper.Delete(te.url)
}
}()
// Clean store before testing.
for _, te := range tests {
helper.Delete(te.url)
}
// Note that we don't delete between individual tests here, to verify that
// subsequent stores/overwrites don't affect storing / retrieving secrets.
for i, te := range tests {
c := &credentials.Credentials{
ServerURL: te.url,
Username: fmt.Sprintf("user-%d", i),
Secret: fmt.Sprintf("secret-%d", i),
}
if err := helper.Add(c); err != nil {
t.Errorf("Error: failed to store secret for URL: %s: %s", te.url, err)
continue
}
user, secret, err := helper.Get(te.url)
if err != nil {
t.Errorf("Error: failed to read secret for URL %q: %s", te.url, err)
continue
}
if user != c.Username {
t.Errorf("Error: expected username %s, got username %s for URL: %s", c.Username, user, te.url)
}
if secret != c.Secret {
t.Errorf("Error: expected secret %s, got secret %s for URL: %s", c.Secret, secret, te.url)
}
}
}
func TestMissingCredentials(t *testing.T) {
helper := Osxkeychain{}
_, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd")
if !credentials.IsErrCredentialsNotFound(err) {
t.Fatalf("expected ErrCredentialsNotFound, got %v", err)
}
}
+323
View File
@@ -0,0 +1,323 @@
//go:build darwin && cgo
package osxkeychain
import (
"fmt"
"testing"
"github.com/docker/docker-credential-helpers/credentials"
)
func TestOSXKeychainHelper(t *testing.T) {
creds := &credentials.Credentials{
ServerURL: "https://foobar.example.com:2376/v1",
Username: "foobar",
Secret: "foobarbaz",
}
helper := Osxkeychain{}
if err := helper.Add(creds); err != nil {
t.Fatal(err)
}
username, secret, err := helper.Get(creds.ServerURL)
if err != nil {
t.Fatal(err)
}
if username != "foobar" {
t.Fatalf("expected %s, got %s\n", "foobar", username)
}
if secret != "foobarbaz" {
t.Fatalf("expected %s, got %s\n", "foobarbaz", secret)
}
auths, err := helper.List()
if err != nil || len(auths) == 0 {
t.Fatal(err)
}
if _, ok := auths[creds.ServerURL]; !ok {
t.Fatalf("server %s not found in list, got: %+v", creds.ServerURL, auths)
}
// Insert another token and check if it is in the list
creds1 := &credentials.Credentials{
ServerURL: "https://foobar.example.com:2376/v2",
Username: "foobarbaz",
Secret: "foobar",
}
helper.Add(creds1)
defer helper.Delete(creds1.ServerURL)
auths, err = helper.List()
if err != nil {
t.Fatalf("operation List failed: %+v", err)
}
if _, ok := auths[creds.ServerURL]; !ok {
t.Fatalf("server %s not found in list, got: %+v", creds.ServerURL, auths)
}
if _, ok := auths[creds1.ServerURL]; !ok {
t.Fatalf("server %s not found in list, got: %+v", creds1.ServerURL, auths)
}
// Delete the 1st token inserted
if err := helper.Delete(creds.ServerURL); err != nil {
t.Fatal(err)
}
auths, err = helper.List()
if err != nil {
t.Fatalf("operation List failed: %+v", err)
}
// First token should have been deleted
if _, ok := auths[creds.ServerURL]; ok {
t.Fatalf("server %s was not deleted, got: %+v", creds.ServerURL, auths)
}
// Second token should still be there
if _, ok := auths[creds1.ServerURL]; !ok {
t.Fatalf("server %s not found in list, got: %+v", creds1.ServerURL, auths)
}
}
// TestOSXKeychainHelperRetrieveAliases verifies that secrets can be accessed
// through variations on the URL
func TestOSXKeychainHelperRetrieveAliases(t *testing.T) {
tests := []struct {
doc string
storeURL string
readURL string
}{
{
doc: "stored with port, retrieved without",
storeURL: "https://foobar.example.com:2376",
readURL: "https://foobar.example.com",
},
{
doc: "stored as https, retrieved without scheme",
storeURL: "https://foobar.example.com:2376",
readURL: "foobar.example.com",
},
{
doc: "stored with path, retrieved without",
storeURL: "https://foobar.example.com:1234/one/two",
readURL: "https://foobar.example.com:1234",
},
}
helper := Osxkeychain{}
t.Cleanup(func() {
for _, tc := range tests {
if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) {
t.Errorf("cleanup: failed to delete '%s': %v", tc.storeURL, err)
}
}
})
// Clean store before testing.
for _, tc := range tests {
if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) {
t.Errorf("prepare: failed to delete '%s': %v", tc.storeURL, err)
}
}
for _, tc := range tests {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
c := &credentials.Credentials{ServerURL: tc.storeURL, Username: "hello", Secret: "world"}
if err := helper.Add(c); err != nil {
t.Fatalf("Error: failed to store secret for URL %q: %s", tc.storeURL, err)
}
if _, _, err := helper.Get(tc.readURL); err != nil {
t.Errorf("Error: failed to read secret for URL %q using %q: %s", tc.storeURL, tc.readURL, err)
}
if err := helper.Delete(tc.storeURL); err != nil {
t.Error(err)
}
})
}
}
func TestOSXKeychainHelperStoreWithUncleanPath(t *testing.T) {
helper := Osxkeychain{}
creds := &credentials.Credentials{
ServerURL: "https://::1:8080//////location/../../hello",
Username: "testuser",
Secret: "testsecret",
}
// Clean store before and after the test.
defer helper.Delete(creds.ServerURL)
if err := helper.Delete(creds.ServerURL); err != nil && !credentials.IsErrCredentialsNotFound(err) {
t.Errorf("prepare: failed to delete '%s': %v", creds.ServerURL, err)
}
// Store the credentials
if err := helper.Add(creds); err != nil {
t.Fatalf("Error: failed to store credentials with unclean path %q: %s", creds.ServerURL, err)
}
// Retrieve and verify credentials
username, secret, err := helper.Get(creds.ServerURL)
if err != nil {
t.Fatalf("Error: failed to retrieve credentials with unclean path %q: %s", creds.ServerURL, err)
}
if username != creds.Username {
t.Errorf("Error: expected username %s, got %s", creds.Username, username)
}
if secret != creds.Secret {
t.Errorf("Error: expected secret %s, got %s", creds.Secret, secret)
}
}
// TestOSXKeychainHelperRetrieveStrict verifies that only matching secrets are
// returned.
func TestOSXKeychainHelperRetrieveStrict(t *testing.T) {
tests := []struct {
doc string
storeURL string
readURL string
}{
{
doc: "stored as https, retrieved using http",
storeURL: "https://foobar.example.com:2376",
readURL: "http://foobar.example.com:2376",
},
{
doc: "stored as http, retrieved using https",
storeURL: "http://foobar.example.com:2376",
readURL: "https://foobar.example.com:2376",
},
{
// stored as http, retrieved without a scheme specified (hence, using the default https://)
doc: "stored as http, retrieved without scheme",
storeURL: "http://foobar.example.com",
readURL: "foobar.example.com:5678",
},
{
doc: "non-matching ports",
storeURL: "https://foobar.example.com:1234",
readURL: "https://foobar.example.com:5678",
},
// TODO: is this desired behavior? The other way round does work
// {
// doc: "non-matching ports (stored without port)",
// storeURL: "https://foobar.example.com",
// readURL: "https://foobar.example.com:5678",
// },
{
doc: "non-matching paths",
storeURL: "https://foobar.example.com:1234/one/two",
readURL: "https://foobar.example.com:1234/five/six",
},
}
helper := Osxkeychain{}
t.Cleanup(func() {
for _, tc := range tests {
if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) {
t.Errorf("cleanup: failed to delete '%s': %v", tc.storeURL, err)
}
}
})
// Clean store before testing.
for _, tc := range tests {
if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) {
t.Errorf("prepare: failed to delete '%s': %v", tc.storeURL, err)
}
}
for _, tc := range tests {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
c := &credentials.Credentials{ServerURL: tc.storeURL, Username: "hello", Secret: "world"}
if err := helper.Add(c); err != nil {
t.Fatalf("Error: failed to store secret for URL %q: %s", tc.storeURL, err)
}
if _, _, err := helper.Get(tc.readURL); err == nil {
t.Errorf("Error: managed to read secret for URL %q using %q, but should not be able to", tc.storeURL, tc.readURL)
}
if err := helper.Delete(tc.storeURL); err != nil {
t.Error(err)
}
})
}
}
// TestOSXKeychainHelperStoreRetrieve verifies that secrets stored in the
// the keychain can be read back using the URL that was used to store them.
func TestOSXKeychainHelperStoreRetrieve(t *testing.T) {
tests := []struct {
url string
}{
{url: "foobar.example.com"},
{url: "foobar.example.com:2376"},
{url: "//foobar.example.com:2376"},
{url: "https://foobar.example.com:2376"},
{url: "http://foobar.example.com:2376"},
{url: "https://foobar.example.com:2376/some/path"},
{url: "https://foobar.example.com:2376/some/other/path"},
{url: "https://foobar.example.com:2376/some/other/path?foo=bar"},
}
helper := Osxkeychain{}
t.Cleanup(func() {
for _, tc := range tests {
if err := helper.Delete(tc.url); err != nil && !credentials.IsErrCredentialsNotFound(err) {
t.Errorf("cleanup: failed to delete '%s': %v", tc.url, err)
}
}
})
// Clean store before testing.
for _, tc := range tests {
if err := helper.Delete(tc.url); err != nil && !credentials.IsErrCredentialsNotFound(err) {
t.Errorf("prepare: failed to delete '%s': %v", tc.url, err)
}
}
// Note that we don't delete between individual tests here, to verify that
// subsequent stores/overwrites don't affect storing / retrieving secrets.
for i, tc := range tests {
tc := tc
t.Run(tc.url, func(t *testing.T) {
c := &credentials.Credentials{
ServerURL: tc.url,
Username: fmt.Sprintf("user-%d", i),
Secret: fmt.Sprintf("secret-%d", i),
}
if err := helper.Add(c); err != nil {
t.Fatalf("Error: failed to store secret for URL: %s: %s", tc.url, err)
}
user, secret, err := helper.Get(tc.url)
if err != nil {
t.Fatalf("Error: failed to read secret for URL %q: %s", tc.url, err)
}
if user != c.Username {
t.Errorf("Error: expected username %s, got username %s for URL: %s", c.Username, user, tc.url)
}
if secret != c.Secret {
t.Errorf("Error: expected secret %s, got secret %s for URL: %s", c.Secret, secret, tc.url)
}
})
}
}
func TestMissingCredentials(t *testing.T) {
const nonExistingCred = "https://adsfasdf.invalid/asdfsdddd"
helper := Osxkeychain{}
_, _, err := helper.Get(nonExistingCred)
if !credentials.IsErrCredentialsNotFound(err) {
t.Errorf("expected ErrCredentialsNotFound, got %v", err)
}
err = helper.Delete(nonExistingCred)
if !credentials.IsErrCredentialsNotFound(err) {
t.Errorf("expected ErrCredentialsNotFound, got %v", err)
}
}
+34 -26
View File
@@ -13,6 +13,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path" "path"
"path/filepath"
"strings" "strings"
"sync" "sync"
@@ -28,11 +29,12 @@ type Pass struct{}
// Ideally these would be stored as members of Pass, but since all of Pass's // Ideally these would be stored as members of Pass, but since all of Pass's
// methods have value receivers, not pointer receivers, and changing that is // methods have value receivers, not pointer receivers, and changing that is
// backwards incompatible, we assume that all Pass instances share the same configuration // backwards incompatible, we assume that all Pass instances share the same configuration
var (
// initializationMutex is held while initializing so that only one 'pass' // initializationMutex is held while initializing so that only one 'pass'
// round-tripping is done to check pass is functioning. // round-tripping is done to check pass is functioning.
var initializationMutex sync.Mutex initializationMutex sync.Mutex
var passInitialized bool passInitialized bool
)
// CheckInitialized checks whether the password helper can be used. It // CheckInitialized checks whether the password helper can be used. It
// internally caches and so may be safely called multiple times with no impact // internally caches and so may be safely called multiple times with no impact
@@ -85,8 +87,7 @@ func (p Pass) Add(creds *credentials.Credentials) error {
return errors.New("missing credentials") return errors.New("missing credentials")
} }
encoded := base64.URLEncoding.EncodeToString([]byte(creds.ServerURL)) encoded := encodeServerURL(creds.ServerURL)
_, err := p.runPass(creds.Secret, "insert", "-f", "-m", path.Join(PASS_FOLDER, encoded, creds.Username)) _, err := p.runPass(creds.Secret, "insert", "-f", "-m", path.Join(PASS_FOLDER, encoded, creds.Username))
return err return err
} }
@@ -97,17 +98,17 @@ func (p Pass) Delete(serverURL string) error {
return errors.New("missing server url") return errors.New("missing server url")
} }
encoded := base64.URLEncoding.EncodeToString([]byte(serverURL)) encoded := encodeServerURL(serverURL)
_, err := p.runPass("", "rm", "-rf", path.Join(PASS_FOLDER, encoded)) _, err := p.runPass("", "rm", "-rf", path.Join(PASS_FOLDER, encoded))
return err return err
} }
func getPassDir() string { func getPassDir() string {
passDir := "$HOME/.password-store" if passDir := os.Getenv("PASSWORD_STORE_DIR"); passDir != "" {
if envDir := os.Getenv("PASSWORD_STORE_DIR"); envDir != "" { return passDir
passDir = envDir
} }
return os.ExpandEnv(passDir) home, _ := os.UserHomeDir()
return filepath.Join(home, ".password-store")
} }
// listPassDir lists all the contents of a directory in the password store. // listPassDir lists all the contents of a directory in the password store.
@@ -140,23 +141,14 @@ func (p Pass) Get(serverURL string) (string, string, error) {
return "", "", errors.New("missing server url") return "", "", errors.New("missing server url")
} }
encoded := base64.URLEncoding.EncodeToString([]byte(serverURL)) encoded := encodeServerURL(serverURL)
if _, err := os.Stat(path.Join(getPassDir(), PASS_FOLDER, encoded)); err != nil {
if os.IsNotExist(err) {
return "", "", credentials.NewErrCredentialsNotFound()
}
return "", "", err
}
usernames, err := listPassDir(encoded) usernames, err := listPassDir(encoded)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
if len(usernames) < 1 { if len(usernames) < 1 {
return "", "", fmt.Errorf("no usernames for %s", serverURL) return "", "", credentials.NewErrCredentialsNotFound()
} }
actual := strings.TrimSuffix(usernames[0].Name(), ".gpg") actual := strings.TrimSuffix(usernames[0].Name(), ".gpg")
@@ -178,7 +170,7 @@ func (p Pass) List() (map[string]string, error) {
continue continue
} }
serverURL, err := base64.URLEncoding.DecodeString(server.Name()) serverURL, err := decodeServerURL(server.Name())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -189,11 +181,27 @@ func (p Pass) List() (map[string]string, error) {
} }
if len(usernames) < 1 { if len(usernames) < 1 {
return nil, fmt.Errorf("no usernames for %s", serverURL) continue
} }
resp[string(serverURL)] = strings.TrimSuffix(usernames[0].Name(), ".gpg") resp[serverURL] = strings.TrimSuffix(usernames[0].Name(), ".gpg")
} }
return resp, nil return resp, nil
} }
// encodeServerURL returns the serverURL in base64-URL encoding to use
// as directory-name in pass storage.
func encodeServerURL(serverURL string) string {
return base64.URLEncoding.EncodeToString([]byte(serverURL))
}
// decodeServerURL decodes base64-URL encoded serverURL. ServerURLs are
// used in encoded format for directory-names in pass storage.
func decodeServerURL(encodedServerURL string) (string, error) {
serverURL, err := base64.URLEncoding.DecodeString(encodedServerURL)
if err != nil {
return "", err
}
return string(serverURL), nil
}
+147 -34
View File
@@ -1,6 +1,10 @@
//go:build !windows
package pass package pass
import ( import (
"os"
"path"
"strings" "strings"
"testing" "testing"
@@ -8,75 +12,184 @@ import (
) )
func TestPassHelper(t *testing.T) { func TestPassHelper(t *testing.T) {
helper := Pass{}
creds := &credentials.Credentials{ creds := &credentials.Credentials{
ServerURL: "https://foobar.docker.io:2376/v1", ServerURL: "https://foobar.example.com:2376/v1",
Username: "nothing", Username: "nothing",
Secret: "isthebestmeshuggahalbum", Secret: "isthebestmeshuggahalbum",
} }
_ = helper.CheckInitialized() helper := Pass{}
if err := helper.checkInitialized(); err != nil {
t.Error(err)
}
helper.Add(creds) if err := helper.Add(creds); err != nil {
t.Error(err)
}
creds.ServerURL = "https://foobar.docker.io:9999/v2" u, s, err := helper.Get(creds.ServerURL)
helper.Add(creds) if err != nil {
t.Error(err)
}
if u != creds.Username {
t.Errorf("invalid username %s", u)
}
if s != creds.Secret {
t.Errorf("invalid secret: %s", s)
}
if err := helper.Delete(creds.ServerURL); err != nil {
t.Error(err)
}
if _, _, err := helper.Get(creds.ServerURL); !credentials.IsErrCredentialsNotFound(err) {
t.Errorf("expected credentials not found, actual: %v", err)
}
}
func TestPassHelperCheckInit(t *testing.T) {
helper := Pass{}
if v := helper.CheckInitialized(); !v {
t.Errorf("expected true, actual: %v", v)
}
}
func TestPassHelperList(t *testing.T) {
creds := []*credentials.Credentials{
{
ServerURL: "https://foobar.example.com:2376/v1",
Username: "foo",
Secret: "isthebestmeshuggahalbum",
},
{
ServerURL: "https://foobar.example.com:2375/v1",
Username: "bar",
Secret: "isthebestmeshuggahalbum",
},
}
helper := Pass{}
if err := helper.checkInitialized(); err != nil {
t.Error(err)
}
for _, cred := range creds {
if err := helper.Add(cred); err != nil {
t.Error(err)
}
}
credsList, err := helper.List() credsList, err := helper.List()
if err != nil { if err != nil {
t.Fatal(err) t.Error(err)
} }
for server, username := range credsList { for server, username := range credsList {
if !(strings.Contains(server, "2376") || if !(strings.HasSuffix(server, "2376/v1") || strings.HasSuffix(server, "2375/v1")) {
strings.Contains(server, "9999")) { t.Errorf("invalid url: %s", server)
t.Fatalf("invalid url: %s", creds.ServerURL)
} }
if !(username == "foo" || username == "bar") {
if username != "nothing" { t.Errorf("invalid username: %v", username)
t.Fatalf("invalid username: %v", username)
} }
u, s, err := helper.Get(server) u, s, err := helper.Get(server)
if err != nil { if err != nil {
t.Fatal(err) t.Error(err)
} }
if u != username { if u != username {
t.Fatalf("invalid username %s", u) t.Errorf("invalid username %s", u)
} }
if s != "isthebestmeshuggahalbum" { if s != "isthebestmeshuggahalbum" {
t.Fatalf("invalid secret: %s", s) t.Errorf("invalid secret: %s", s)
} }
err = helper.Delete(server) if err := helper.Delete(server); err != nil {
if err != nil { t.Error(err)
t.Fatal(err)
} }
if _, _, err := helper.Get(server); !credentials.IsErrCredentialsNotFound(err) {
_, _, err = helper.Get(server) t.Errorf("expected credentials not found, actual: %v", err)
if !credentials.IsErrCredentialsNotFound(err) {
t.Fatalf("expected credentials not found, actual: %v", err)
} }
} }
credsList, err = helper.List() credsList, err = helper.List()
if err != nil { if err != nil {
t.Fatal(err) t.Error(err)
}
if len(credsList) != 0 {
t.Error("didn't delete all creds?")
}
}
// TestPassHelperWithEmptyServer verifies that empty directories (servers
// without credentials) are ignored, but still returns credentials for other
// servers.
func TestPassHelperWithEmptyServer(t *testing.T) {
helper := Pass{}
if err := helper.checkInitialized(); err != nil {
t.Error(err)
} }
if len(credsList) != 0 { creds := []*credentials.Credentials{
t.Fatal("didn't delete all creds?") {
ServerURL: "https://myreqistry.example.com:2375/v1",
Username: "foo",
Secret: "isthebestmeshuggahalbum",
},
{
ServerURL: "https://index.example.com/v1//access-token",
},
}
t.Cleanup(func() {
for _, cred := range creds {
_ = helper.Delete(cred.ServerURL)
}
})
for _, cred := range creds {
if cred.Username != "" {
if err := helper.Add(cred); err != nil {
t.Error(err)
}
} else {
// No credentials; create an empty directory for this server.
serverURL := encodeServerURL(cred.ServerURL)
p := path.Join(getPassDir(), PASS_FOLDER, serverURL)
if err := os.Mkdir(p, 0o755); err != nil {
t.Error(err)
}
}
}
credsList, err := helper.List()
if err != nil {
t.Error(err)
}
if len(credsList) == 0 {
t.Error("expected credentials to be returned, but got none")
}
for _, cred := range creds {
if cred.Username != "" {
userName, secret, err := helper.Get(cred.ServerURL)
if err != nil {
t.Error(err)
}
if userName != cred.Username {
t.Errorf("expected username %q, actual: %q", cred.Username, userName)
}
if secret != cred.Secret {
t.Errorf("expected secret %q, actual: %q", cred.Secret, secret)
}
} else {
_, _, err := helper.Get(cred.ServerURL)
if !credentials.IsErrCredentialsNotFound(err) {
t.Errorf("expected credentials not found, actual: %v", err)
}
}
} }
} }
func TestMissingCred(t *testing.T) { func TestMissingCred(t *testing.T) {
helper := Pass{} helper := Pass{}
if _, _, err := helper.Get("garbage"); !credentials.IsErrCredentialsNotFound(err) {
_, _, err := helper.Get("garbage") t.Errorf("expected credentials not found, actual: %v", err)
if !credentials.IsErrCredentialsNotFound(err) {
t.Fatalf("expected credentials not found, actual: %v", err)
} }
} }
+49 -22
View File
@@ -13,34 +13,61 @@ func TestHelperParseURL(t *testing.T) {
expectedURL string expectedURL string
err error err error
}{ }{
{url: "foobar.docker.io", expectedURL: "//foobar.docker.io"}, {
{url: "foobar.docker.io:2376", expectedURL: "//foobar.docker.io:2376"}, url: "foobar.example.com",
{url: "//foobar.docker.io:2376", expectedURL: "//foobar.docker.io:2376"}, expectedURL: "//foobar.example.com",
{url: "http://foobar.docker.io:2376", expectedURL: "http://foobar.docker.io:2376"}, },
{url: "https://foobar.docker.io:2376", expectedURL: "https://foobar.docker.io:2376"}, {
{url: "https://foobar.docker.io:2376/some/path", expectedURL: "https://foobar.docker.io:2376/some/path"}, url: "foobar.example.com:2376",
{url: "https://foobar.docker.io:2376/some/other/path?foo=bar", expectedURL: "https://foobar.docker.io:2376/some/other/path"}, expectedURL: "//foobar.example.com:2376",
{url: "/foobar.docker.io", err: errors.New("no hostname in URL")}, },
{url: "ftp://foobar.docker.io:2376", err: errors.New("unsupported scheme: ftp")}, {
url: "//foobar.example.com:2376",
expectedURL: "//foobar.example.com:2376",
},
{
url: "http://foobar.example.com:2376",
expectedURL: "http://foobar.example.com:2376",
},
{
url: "https://foobar.example.com:2376",
expectedURL: "https://foobar.example.com:2376",
},
{
url: "https://foobar.example.com:2376/some/path",
expectedURL: "https://foobar.example.com:2376/some/path",
},
{
url: "https://foobar.example.com:2376/some/other/path?foo=bar",
expectedURL: "https://foobar.example.com:2376/some/other/path",
},
{
url: "/foobar.example.com",
err: errors.New("no hostname in URL"),
},
{
url: "ftp://foobar.example.com:2376",
err: errors.New("unsupported scheme: ftp"),
},
} }
for _, te := range tests { for _, tc := range tests {
u, err := Parse(te.url) tc := tc
t.Run(tc.url, func(t *testing.T) {
u, err := Parse(tc.url)
if te.err == nil && err != nil { if tc.err == nil && err != nil {
t.Errorf("Error: failed to parse URL %q: %s", te.url, err) t.Fatalf("Error: failed to parse URL %q: %s", tc.url, err)
continue
} }
if te.err != nil && err == nil { if tc.err != nil && err == nil {
t.Errorf("Error: expected error %q, got none when parsing URL %q", te.err, te.url) t.Fatalf("Error: expected error %q, got none when parsing URL %q", tc.err, tc.url)
continue
} }
if te.err != nil && err.Error() != te.err.Error() { if tc.err != nil && err.Error() != tc.err.Error() {
t.Errorf("Error: expected error %q, got %q when parsing URL %q", te.err, err, te.url) t.Fatalf("Error: expected error %q, got %q when parsing URL %q", tc.err, err, tc.url)
continue
} }
if u != nil && u.String() != te.expectedURL { if u != nil && u.String() != tc.expectedURL {
t.Errorf("Error: expected URL: %q, but got %q for URL: %q", te.expectedURL, u.String(), te.url) t.Errorf("Error: expected URL: %q, but got %q for URL: %q", tc.expectedURL, u.String(), tc.url)
} }
})
} }
} }
@@ -1,3 +1,5 @@
//go:build linux && cgo
package main package main
import ( import (
@@ -1,6 +1,6 @@
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include "secretservice_linux.h" #include "secretservice.h"
const SecretSchema *docker_get_schema(void) const SecretSchema *docker_get_schema(void)
{ {
@@ -17,11 +17,11 @@ const SecretSchema *docker_get_schema(void)
return &docker_schema; return &docker_schema;
} }
GError *add(char *label, char *server, char *username, char *secret) { GError *add(char *label, char *server, char *username, char *secret, char *displaylabel) {
GError *err = NULL; GError *err = NULL;
secret_password_store_sync (DOCKER_SCHEMA, SECRET_COLLECTION_DEFAULT, secret_password_store_sync (DOCKER_SCHEMA, SECRET_COLLECTION_DEFAULT,
server, secret, NULL, &err, displaylabel, secret, NULL, &err,
"label", label, "label", label,
"server", server, "server", server,
"username", username, "username", username,
@@ -83,6 +83,9 @@ GError *get(char *server, char **username, char **secret) {
} }
g_free(value); g_free(value);
secretValue = secret_item_get_secret(l->data); secretValue = secret_item_get_secret(l->data);
if (secretValue == NULL) {
continue;
}
if (secret != NULL) { if (secret != NULL) {
*secret = strdup(secret_value_get(secretValue, &length)); *secret = strdup(secret_value_get(secretValue, &length));
secret_value_unref(secretValue); secret_value_unref(secretValue);
@@ -1,12 +1,15 @@
//go:build linux && cgo
package secretservice package secretservice
/* /*
#cgo pkg-config: libsecret-1 #cgo pkg-config: libsecret-1
#include "secretservice_linux.h" #include "secretservice.h"
#include <stdlib.h> #include <stdlib.h>
*/ */
import "C" import "C"
import ( import (
"errors" "errors"
"unsafe" "unsafe"
@@ -30,8 +33,10 @@ func (h Secretservice) Add(creds *credentials.Credentials) error {
defer C.free(unsafe.Pointer(username)) defer C.free(unsafe.Pointer(username))
secret := C.CString(creds.Secret) secret := C.CString(creds.Secret)
defer C.free(unsafe.Pointer(secret)) defer C.free(unsafe.Pointer(secret))
displayLabel := C.CString("Registry credentials for " + creds.ServerURL)
defer C.free(unsafe.Pointer(displayLabel))
if err := C.add(credsLabel, server, username, secret); err != nil { if err := C.add(credsLabel, server, username, secret, displayLabel); err != nil {
defer C.g_error_free(err) defer C.g_error_free(err)
errMsg := (*C.char)(unsafe.Pointer(err.message)) errMsg := (*C.char)(unsafe.Pointer(err.message))
return errors.New(C.GoString(errMsg)) return errors.New(C.GoString(errMsg))
@@ -6,7 +6,7 @@ const SecretSchema *docker_get_schema(void) G_GNUC_CONST;
#define DOCKER_SCHEMA docker_get_schema() #define DOCKER_SCHEMA docker_get_schema()
GError *add(char *label, char *server, char *username, char *secret); GError *add(char *label, char *server, char *username, char *secret, char *displaylabel);
GError *delete(char *server); GError *delete(char *server);
GError *get(char *server, char **username, char **secret); GError *get(char *server, char **username, char **secret);
GError *list(char *label, char *** paths, char *** accts, unsigned int *list_l); GError *list(char *label, char *** paths, char *** accts, unsigned int *list_l);
@@ -1,3 +1,5 @@
//go:build linux && cgo
package secretservice package secretservice
import ( import (
@@ -11,7 +13,7 @@ func TestSecretServiceHelper(t *testing.T) {
t.Skip("test requires gnome-keyring but travis CI doesn't have it") t.Skip("test requires gnome-keyring but travis CI doesn't have it")
creds := &credentials.Credentials{ creds := &credentials.Credentials{
ServerURL: "https://foobar.docker.io:2376/v1", ServerURL: "https://foobar.example.com:2376/v1",
Username: "foobar", Username: "foobar",
Secret: "foobarbaz", Secret: "foobarbaz",
} }
@@ -28,7 +30,6 @@ func TestSecretServiceHelper(t *testing.T) {
// remove them as they probably come from a previous failed test // remove them as they probably come from a previous failed test
for k, v := range oldAuths { for k, v := range oldAuths {
if strings.Compare(k, creds.ServerURL) == 0 && strings.Compare(v, creds.Username) == 0 { if strings.Compare(k, creds.ServerURL) == 0 && strings.Compare(v, creds.Username) == 0 {
if err := helper.Delete(creds.ServerURL); err != nil { if err := helper.Delete(creds.ServerURL); err != nil {
t.Fatal(err) t.Fatal(err)
} }
+1 -1
View File
@@ -4,7 +4,7 @@ wincred
Go wrapper around the Windows Credential Manager API functions. Go wrapper around the Windows Credential Manager API functions.
[![GitHub release](https://img.shields.io/github/release/danieljoos/wincred.svg?style=flat-square)](https://github.com/danieljoos/wincred/releases/latest) [![GitHub release](https://img.shields.io/github/release/danieljoos/wincred.svg?style=flat-square)](https://github.com/danieljoos/wincred/releases/latest)
[![Test Status](https://img.shields.io/github/workflow/status/danieljoos/wincred/test?label=test&logo=github&style=flat-square)](https://github.com/danieljoos/wincred/actions?query=workflow%3Atest) [![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)
[![Go Report Card](https://goreportcard.com/badge/github.com/danieljoos/wincred)](https://goreportcard.com/report/github.com/danieljoos/wincred) [![Go Report Card](https://goreportcard.com/badge/github.com/danieljoos/wincred)](https://goreportcard.com/report/github.com/danieljoos/wincred)
[![Codecov](https://img.shields.io/codecov/c/github/danieljoos/wincred?logo=codecov&style=flat-square)](https://codecov.io/gh/danieljoos/wincred) [![Codecov](https://img.shields.io/codecov/c/github/danieljoos/wincred?logo=codecov&style=flat-square)](https://codecov.io/gh/danieljoos/wincred)
[![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) [![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)
+17 -12
View File
@@ -1,21 +1,23 @@
//go:build windows
// +build windows // +build windows
package wincred package wincred
import ( import (
"reflect" "reflect"
"syscall"
"unsafe" "unsafe"
syscall "golang.org/x/sys/windows" "golang.org/x/sys/windows"
) )
var ( var (
modadvapi32 = syscall.NewLazyDLL("advapi32.dll") modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
procCredRead proc = modadvapi32.NewProc("CredReadW") procCredRead = modadvapi32.NewProc("CredReadW")
procCredWrite proc = modadvapi32.NewProc("CredWriteW") procCredWrite proc = modadvapi32.NewProc("CredWriteW")
procCredDelete proc = modadvapi32.NewProc("CredDeleteW") procCredDelete proc = modadvapi32.NewProc("CredDeleteW")
procCredFree proc = modadvapi32.NewProc("CredFree") procCredFree proc = modadvapi32.NewProc("CredFree")
procCredEnumerate proc = modadvapi32.NewProc("CredEnumerateW") procCredEnumerate = modadvapi32.NewProc("CredEnumerateW")
) )
// Interface for syscall.Proc: helps testing // Interface for syscall.Proc: helps testing
@@ -29,7 +31,7 @@ type sysCREDENTIAL struct {
Type uint32 Type uint32
TargetName *uint16 TargetName *uint16
Comment *uint16 Comment *uint16
LastWritten syscall.Filetime LastWritten windows.Filetime
CredentialBlobSize uint32 CredentialBlobSize uint32
CredentialBlob uintptr CredentialBlob uintptr
Persist uint32 Persist uint32
@@ -59,15 +61,17 @@ const (
sysCRED_TYPE_DOMAIN_EXTENDED sysCRED_TYPE = 0x6 sysCRED_TYPE_DOMAIN_EXTENDED sysCRED_TYPE = 0x6
// https://docs.microsoft.com/en-us/windows/desktop/Debug/system-error-codes // https://docs.microsoft.com/en-us/windows/desktop/Debug/system-error-codes
sysERROR_NOT_FOUND = syscall.Errno(1168) sysERROR_NOT_FOUND = windows.Errno(1168)
sysERROR_INVALID_PARAMETER = syscall.Errno(87) sysERROR_INVALID_PARAMETER = windows.Errno(87)
sysERROR_BAD_USERNAME = windows.Errno(2202)
) )
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-credreadw // https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-credreadw
func sysCredRead(targetName string, typ sysCRED_TYPE) (*Credential, error) { func sysCredRead(targetName string, typ sysCRED_TYPE) (*Credential, error) {
var pcred *sysCREDENTIAL var pcred *sysCREDENTIAL
targetNamePtr, _ := syscall.UTF16PtrFromString(targetName) targetNamePtr, _ := windows.UTF16PtrFromString(targetName)
ret, _, err := procCredRead.Call( ret, _, err := syscall.SyscallN(
procCredRead.Addr(),
uintptr(unsafe.Pointer(targetNamePtr)), uintptr(unsafe.Pointer(targetNamePtr)),
uintptr(typ), uintptr(typ),
0, 0,
@@ -98,7 +102,7 @@ func sysCredWrite(cred *Credential, typ sysCRED_TYPE) error {
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-creddeletew // https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-creddeletew
func sysCredDelete(cred *Credential, typ sysCRED_TYPE) error { func sysCredDelete(cred *Credential, typ sysCRED_TYPE) error {
targetNamePtr, _ := syscall.UTF16PtrFromString(cred.TargetName) targetNamePtr, _ := windows.UTF16PtrFromString(cred.TargetName)
ret, _, err := procCredDelete.Call( ret, _, err := procCredDelete.Call(
uintptr(unsafe.Pointer(targetNamePtr)), uintptr(unsafe.Pointer(targetNamePtr)),
uintptr(typ), uintptr(typ),
@@ -117,9 +121,10 @@ func sysCredEnumerate(filter string, all bool) ([]*Credential, error) {
var pcreds uintptr var pcreds uintptr
var filterPtr *uint16 var filterPtr *uint16
if !all { if !all {
filterPtr, _ = syscall.UTF16PtrFromString(filter) filterPtr, _ = windows.UTF16PtrFromString(filter)
} }
ret, _, err := procCredEnumerate.Call( ret, _, err := syscall.SyscallN(
procCredEnumerate.Addr(),
uintptr(unsafe.Pointer(filterPtr)), uintptr(unsafe.Pointer(filterPtr)),
0, 0,
uintptr(unsafe.Pointer(&count)), uintptr(unsafe.Pointer(&count)),
+2
View File
@@ -1,3 +1,4 @@
//go:build !windows
// +build !windows // +build !windows
package wincred package wincred
@@ -17,6 +18,7 @@ const (
sysERROR_NOT_FOUND = syscall.Errno(1) sysERROR_NOT_FOUND = syscall.Errno(1)
sysERROR_INVALID_PARAMETER = syscall.Errno(1) sysERROR_INVALID_PARAMETER = syscall.Errno(1)
sysERROR_BAD_USERNAME = syscall.Errno(1)
) )
func sysCredRead(...interface{}) (*Credential, error) { func sysCredRead(...interface{}) (*Credential, error) {
+3
View File
@@ -16,6 +16,9 @@ const (
// This error constant can be used to check if the given function parameters were invalid. // This error constant can be used to check if the given function parameters were invalid.
// For example when trying to create a new generic credential with an empty target name. // For example when trying to create a new generic credential with an empty target name.
ErrInvalidParameter = sysERROR_INVALID_PARAMETER ErrInvalidParameter = sysERROR_INVALID_PARAMETER
// ErrBadUsername is returned when the credential's username is invalid.
ErrBadUsername = sysERROR_BAD_USERNAME
) )
// GetGenericCredential fetches the generic credential with the given name from Windows credential manager. // GetGenericCredential fetches the generic credential with the given name from Windows credential manager.
+26
View File
@@ -0,0 +1,26 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
vendor
+13
View File
@@ -0,0 +1,13 @@
linters-settings:
gocritic:
disabled-checks:
- ifElseChain
- elseif
linters:
enable:
- gofmt
- gocritic
- unconvert
- revive
- govet
+22
View File
@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Keybase
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+126
View File
@@ -0,0 +1,126 @@
# Go Keychain
[![Build Status](https://github.com/keybase/go-keychain/actions/workflows/ci.yml/badge.svg)](https://github.com/keybase/go-keychain/actions)
A library for accessing the Keychain for macOS, iOS, and Linux in Go (golang).
Requires macOS 10.9 or greater and iOS 8 or greater. On Linux, communicates to
a provider of the DBUS SecretService spec like gnome-keyring or ksecretservice.
```go
import "github.com/keybase/go-keychain"
```
## Mac/iOS Usage
The API is meant to mirror the macOS/iOS Keychain API and is not necessarily idiomatic go.
#### Add Item
```go
item := keychain.NewItem()
item.SetSecClass(keychain.SecClassGenericPassword)
item.SetService("MyService")
item.SetAccount("gabriel")
item.SetLabel("A label")
item.SetAccessGroup("A123456789.group.com.mycorp")
item.SetData([]byte("toomanysecrets"))
item.SetSynchronizable(keychain.SynchronizableNo)
item.SetAccessible(keychain.AccessibleWhenUnlocked)
err := keychain.AddItem(item)
if err == keychain.ErrorDuplicateItem {
// Duplicate
}
```
#### Query Item
Query for multiple results, returning attributes:
```go
query := keychain.NewItem()
query.SetSecClass(keychain.SecClassGenericPassword)
query.SetService(service)
query.SetAccount(account)
query.SetAccessGroup(accessGroup)
query.SetMatchLimit(keychain.MatchLimitAll)
query.SetReturnAttributes(true)
results, err := keychain.QueryItem(query)
if err != nil {
// Error
} else {
for _, r := range results {
fmt.Printf("%#v\n", r)
}
}
```
Query for a single result, returning data:
```go
query := keychain.NewItem()
query.SetSecClass(keychain.SecClassGenericPassword)
query.SetService(service)
query.SetAccount(account)
query.SetAccessGroup(accessGroup)
query.SetMatchLimit(keychain.MatchLimitOne)
query.SetReturnData(true)
results, err := keychain.QueryItem(query)
if err != nil {
// Error
} else if len(results) != 1 {
// Not found
} else {
password := string(results[0].Data)
}
```
#### Delete Item
Delete a generic password item with service and account:
```go
item := keychain.NewItem()
item.SetSecClass(keychain.SecClassGenericPassword)
item.SetService(service)
item.SetAccount(account)
err := keychain.DeleteItem(item)
```
### Other
There are some convenience methods for generic password:
```go
// Create generic password item with service, account, label, password, access group
item := keychain.NewGenericPassword("MyService", "gabriel", "A label", []byte("toomanysecrets"), "A123456789.group.com.mycorp")
item.SetSynchronizable(keychain.SynchronizableNo)
item.SetAccessible(keychain.AccessibleWhenUnlocked)
err := keychain.AddItem(item)
if err == keychain.ErrorDuplicateItem {
// Duplicate
}
password, err := keychain.GetGenericPassword("MyService", "gabriel", "A label", "A123456789.group.com.mycorp")
accounts, err := keychain.GetGenericPasswordAccounts("MyService")
// Should have 1 account == "gabriel"
err := keychain.DeleteGenericPasswordItem("MyService", "gabriel")
if err == keychain.ErrorItemNotFound {
// Not found
}
```
## iOS
Bindable package in `bind`. iOS project in `ios`. Run that project to test iOS.
To re-generate framework:
```
(cd bind && gomobile bind -target=ios -tags=ios -o ../ios/bind.framework)
```
Post issues to: https://github.com/keybase/keybase-issues
+370
View File
@@ -0,0 +1,370 @@
//go:build darwin || ios
// +build darwin ios
package keychain
/*
#cgo LDFLAGS: -framework CoreFoundation
#include <CoreFoundation/CoreFoundation.h>
// Can't cast a *uintptr to *unsafe.Pointer in Go, and casting
// C.CFTypeRef to unsafe.Pointer is unsafe in Go, so have shim functions to
// do the casting in C (where it's safe).
// We add a suffix to the C functions below, because we copied this
// file from go-kext, which means that any project that depends on this
// package and go-kext would run into duplicate symbol errors otherwise.
//
// TODO: Move this file into its own package depended on by go-kext
// and this package.
CFDictionaryRef CFDictionaryCreateSafe2(CFAllocatorRef allocator, const uintptr_t *keys, const uintptr_t *values, CFIndex numValues, const CFDictionaryKeyCallBacks *keyCallBacks, const CFDictionaryValueCallBacks *valueCallBacks) {
return CFDictionaryCreate(allocator, (const void **)keys, (const void **)values, numValues, keyCallBacks, valueCallBacks);
}
CFArrayRef CFArrayCreateSafe2(CFAllocatorRef allocator, const uintptr_t *values, CFIndex numValues, const CFArrayCallBacks *callBacks) {
return CFArrayCreate(allocator, (const void **)values, numValues, callBacks);
}
*/
import "C"
import (
"errors"
"fmt"
"math"
"reflect"
"unicode/utf8"
"unsafe"
)
// Release releases memory pointed to by a CFTypeRef.
func Release(ref C.CFTypeRef) {
C.CFRelease(ref)
}
// BytesToCFData will return a CFDataRef and if non-nil, must be released with
// Release(ref).
func BytesToCFData(b []byte) (C.CFDataRef, error) {
if uint64(len(b)) > math.MaxUint32 {
return 0, errors.New("Data is too large")
}
var p *C.UInt8
if len(b) > 0 {
p = (*C.UInt8)(&b[0])
}
cfData := C.CFDataCreate(C.kCFAllocatorDefault, p, C.CFIndex(len(b)))
if cfData == 0 {
return 0, fmt.Errorf("CFDataCreate failed")
}
return cfData, nil
}
// CFDataToBytes converts CFData to bytes.
func CFDataToBytes(cfData C.CFDataRef) ([]byte, error) {
return C.GoBytes(unsafe.Pointer(C.CFDataGetBytePtr(cfData)), C.int(C.CFDataGetLength(cfData))), nil
}
// MapToCFDictionary will return a CFDictionaryRef and if non-nil, must be
// released with Release(ref).
func MapToCFDictionary(m map[C.CFTypeRef]C.CFTypeRef) (C.CFDictionaryRef, error) {
var keys, values []C.uintptr_t
for key, value := range m {
keys = append(keys, C.uintptr_t(key))
values = append(values, C.uintptr_t(value))
}
numValues := len(values)
var keysPointer, valuesPointer *C.uintptr_t
if numValues > 0 {
keysPointer = &keys[0]
valuesPointer = &values[0]
}
cfDict := C.CFDictionaryCreateSafe2(C.kCFAllocatorDefault, keysPointer, valuesPointer, C.CFIndex(numValues),
&C.kCFTypeDictionaryKeyCallBacks, &C.kCFTypeDictionaryValueCallBacks) //nolint
if cfDict == 0 {
return 0, fmt.Errorf("CFDictionaryCreate failed")
}
return cfDict, nil
}
// CFDictionaryToMap converts CFDictionaryRef to a map.
func CFDictionaryToMap(cfDict C.CFDictionaryRef) (m map[C.CFTypeRef]C.CFTypeRef) {
count := C.CFDictionaryGetCount(cfDict)
if count > 0 {
keys := make([]C.CFTypeRef, count)
values := make([]C.CFTypeRef, count)
C.CFDictionaryGetKeysAndValues(cfDict, (*unsafe.Pointer)(unsafe.Pointer(&keys[0])), (*unsafe.Pointer)(unsafe.Pointer(&values[0])))
m = make(map[C.CFTypeRef]C.CFTypeRef, count)
for i := C.CFIndex(0); i < count; i++ {
m[keys[i]] = values[i]
}
}
return
}
// Int32ToCFNumber will return a CFNumberRef, must be released with Release(ref).
func Int32ToCFNumber(u int32) C.CFNumberRef {
sint := C.SInt32(u)
p := unsafe.Pointer(&sint)
return C.CFNumberCreate(C.kCFAllocatorDefault, C.kCFNumberSInt32Type, p)
}
// StringToCFString will return a CFStringRef and if non-nil, must be released with
// Release(ref).
func StringToCFString(s string) (C.CFStringRef, error) {
if !utf8.ValidString(s) {
return 0, errors.New("Invalid UTF-8 string")
}
if uint64(len(s)) > math.MaxUint32 {
return 0, errors.New("String is too large")
}
bytes := []byte(s)
var p *C.UInt8
if len(bytes) > 0 {
p = (*C.UInt8)(&bytes[0])
}
return C.CFStringCreateWithBytes(C.kCFAllocatorDefault, p, C.CFIndex(len(s)), C.kCFStringEncodingUTF8, C.false), nil
}
// CFStringToString converts a CFStringRef to a string.
func CFStringToString(s C.CFStringRef) string {
p := C.CFStringGetCStringPtr(s, C.kCFStringEncodingUTF8)
if p != nil {
return C.GoString(p)
}
length := C.CFStringGetLength(s)
if length == 0 {
return ""
}
maxBufLen := C.CFStringGetMaximumSizeForEncoding(length, C.kCFStringEncodingUTF8)
if maxBufLen == 0 {
return ""
}
buf := make([]byte, maxBufLen)
var usedBufLen C.CFIndex
_ = C.CFStringGetBytes(s, C.CFRange{0, length}, C.kCFStringEncodingUTF8, C.UInt8(0), C.false, (*C.UInt8)(&buf[0]), maxBufLen, &usedBufLen)
return string(buf[:usedBufLen])
}
// ArrayToCFArray will return a CFArrayRef and if non-nil, must be released with
// Release(ref).
func ArrayToCFArray(a []C.CFTypeRef) C.CFArrayRef {
var values []C.uintptr_t
for _, value := range a {
values = append(values, C.uintptr_t(value))
}
numValues := len(values)
var valuesPointer *C.uintptr_t
if numValues > 0 {
valuesPointer = &values[0]
}
return C.CFArrayCreateSafe2(C.kCFAllocatorDefault, valuesPointer, C.CFIndex(numValues), &C.kCFTypeArrayCallBacks) //nolint
}
// CFArrayToArray converts a CFArrayRef to an array of CFTypes.
func CFArrayToArray(cfArray C.CFArrayRef) (a []C.CFTypeRef) {
count := C.CFArrayGetCount(cfArray)
if count > 0 {
a = make([]C.CFTypeRef, count)
C.CFArrayGetValues(cfArray, C.CFRange{0, count}, (*unsafe.Pointer)(unsafe.Pointer(&a[0])))
}
return
}
// Convertable knows how to convert an instance to a CFTypeRef.
type Convertable interface {
Convert() (C.CFTypeRef, error)
}
// ConvertMapToCFDictionary converts a map to a CFDictionary and if non-nil,
// must be released with Release(ref).
func ConvertMapToCFDictionary(attr map[string]interface{}) (C.CFDictionaryRef, error) {
m := make(map[C.CFTypeRef]C.CFTypeRef)
for key, i := range attr {
var valueRef C.CFTypeRef
switch val := i.(type) {
default:
return 0, fmt.Errorf("Unsupported value type: %v", reflect.TypeOf(i))
case C.CFTypeRef:
valueRef = val
case bool:
if val {
valueRef = C.CFTypeRef(C.kCFBooleanTrue)
} else {
valueRef = C.CFTypeRef(C.kCFBooleanFalse)
}
case int32:
valueRef = C.CFTypeRef(Int32ToCFNumber(val))
defer Release(valueRef)
case []byte:
bytesRef, err := BytesToCFData(val)
if err != nil {
return 0, err
}
valueRef = C.CFTypeRef(bytesRef)
defer Release(valueRef)
case string:
stringRef, err := StringToCFString(val)
if err != nil {
return 0, err
}
valueRef = C.CFTypeRef(stringRef)
defer Release(valueRef)
case Convertable:
convertedRef, err := val.Convert()
if err != nil {
return 0, err
}
valueRef = convertedRef
defer Release(valueRef)
}
keyRef, err := StringToCFString(key)
if err != nil {
return 0, err
}
m[C.CFTypeRef(keyRef)] = valueRef
defer Release(C.CFTypeRef(keyRef))
}
cfDict, err := MapToCFDictionary(m)
if err != nil {
return 0, err
}
return cfDict, nil
}
// CFTypeDescription returns type string for CFTypeRef.
func CFTypeDescription(ref C.CFTypeRef) string {
typeID := C.CFGetTypeID(ref)
typeDesc := C.CFCopyTypeIDDescription(typeID)
defer Release(C.CFTypeRef(typeDesc))
return CFStringToString(typeDesc)
}
// Convert converts a CFTypeRef to a go instance.
func Convert(ref C.CFTypeRef) (interface{}, error) {
typeID := C.CFGetTypeID(ref)
if typeID == C.CFStringGetTypeID() {
return CFStringToString(C.CFStringRef(ref)), nil
} else if typeID == C.CFDictionaryGetTypeID() {
return ConvertCFDictionary(C.CFDictionaryRef(ref))
} else if typeID == C.CFArrayGetTypeID() {
arr := CFArrayToArray(C.CFArrayRef(ref))
results := make([]interface{}, 0, len(arr))
for _, ref := range arr {
v, err := Convert(ref)
if err != nil {
return nil, err
}
results = append(results, v)
}
return results, nil
} else if typeID == C.CFDataGetTypeID() {
b, err := CFDataToBytes(C.CFDataRef(ref))
if err != nil {
return nil, err
}
return b, nil
} else if typeID == C.CFNumberGetTypeID() {
return CFNumberToInterface(C.CFNumberRef(ref)), nil
} else if typeID == C.CFBooleanGetTypeID() {
if C.CFBooleanGetValue(C.CFBooleanRef(ref)) != 0 {
return true, nil
}
return false, nil
}
return nil, fmt.Errorf("Invalid type: %s", CFTypeDescription(ref))
}
// ConvertCFDictionary converts a CFDictionary to map (deep).
func ConvertCFDictionary(d C.CFDictionaryRef) (map[interface{}]interface{}, error) {
m := CFDictionaryToMap(d)
result := make(map[interface{}]interface{})
for k, v := range m {
gk, err := Convert(k)
if err != nil {
return nil, err
}
gv, err := Convert(v)
if err != nil {
return nil, err
}
result[gk] = gv
}
return result, nil
}
// CFNumberToInterface converts the CFNumberRef to the most appropriate numeric
// type.
// This code is from github.com/kballard/go-osx-plist.
func CFNumberToInterface(cfNumber C.CFNumberRef) interface{} {
typ := C.CFNumberGetType(cfNumber)
switch typ {
case C.kCFNumberSInt8Type:
var sint C.SInt8
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint)) //nolint
return int8(sint)
case C.kCFNumberSInt16Type:
var sint C.SInt16
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint)) //nolint
return int16(sint)
case C.kCFNumberSInt32Type:
var sint C.SInt32
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint)) //nolint
return int32(sint)
case C.kCFNumberSInt64Type:
var sint C.SInt64
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&sint)) //nolint
return int64(sint)
case C.kCFNumberFloat32Type:
var float C.Float32
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&float)) //nolint
return float32(float)
case C.kCFNumberFloat64Type:
var float C.Float64
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&float)) //nolint
return float64(float)
case C.kCFNumberCharType:
var char C.char
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&char)) //nolint
return byte(char)
case C.kCFNumberShortType:
var short C.short
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&short)) //nolint
return int16(short)
case C.kCFNumberIntType:
var i C.int
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&i)) //nolint
return int32(i)
case C.kCFNumberLongType:
var long C.long
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&long)) //nolint
return int(long)
case C.kCFNumberLongLongType:
// This is the only type that may actually overflow us
var longlong C.longlong
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&longlong)) //nolint
return int64(longlong)
case C.kCFNumberFloatType:
var float C.float
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&float)) //nolint
return float32(float)
case C.kCFNumberDoubleType:
var double C.double
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&double)) //nolint
return float64(double)
case C.kCFNumberCFIndexType:
// CFIndex is a long
var index C.CFIndex
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&index)) //nolint
return int(index)
case C.kCFNumberNSIntegerType:
// We don't have a definition of NSInteger, but we know it's either an int or a long
var nsInt C.long
C.CFNumberGetValue(cfNumber, typ, unsafe.Pointer(&nsInt)) //nolint
return int(nsInt)
}
panic("Unknown CFNumber type")
}
+69
View File
@@ -0,0 +1,69 @@
//go:build darwin || ios
// +build darwin ios
package keychain
/*
#cgo LDFLAGS: -framework CoreFoundation
#include <CoreFoundation/CoreFoundation.h>
*/
import "C"
import (
"math"
"time"
)
const nsPerSec = 1000 * 1000 * 1000
// absoluteTimeIntervalSince1970() returns the number of seconds from
// the Unix epoch (1970-01-01T00:00:00+00:00) to the Core Foundation
// absolute reference date (2001-01-01T00:00:00+00:00). It should be
// exactly 978307200.
func absoluteTimeIntervalSince1970() int64 {
return int64(C.kCFAbsoluteTimeIntervalSince1970)
}
func unixToAbsoluteTime(s int64, ns int64) C.CFAbsoluteTime {
// Subtract as int64s first before converting to floating
// point to minimize precision loss (assuming the given time
// isn't much earlier than the Core Foundation absolute
// reference date).
abs := s - absoluteTimeIntervalSince1970()
return C.CFAbsoluteTime(abs) + C.CFTimeInterval(ns)/nsPerSec
}
func absoluteTimeToUnix(abs C.CFAbsoluteTime) (int64, int64) {
i, frac := math.Modf(float64(abs))
return int64(i) + absoluteTimeIntervalSince1970(), int64(frac * nsPerSec)
}
// TimeToCFDate will convert the given time.Time to a CFDateRef, which
// must be released with Release(ref).
func TimeToCFDate(t time.Time) C.CFDateRef {
s := t.Unix()
ns := int64(t.Nanosecond())
abs := unixToAbsoluteTime(s, ns)
return C.CFDateCreate(C.kCFAllocatorDefault, abs)
}
// CFDateToTime will convert the given CFDateRef to a time.Time.
func CFDateToTime(d C.CFDateRef) time.Time {
abs := C.CFDateGetAbsoluteTime(d)
s, ns := absoluteTimeToUnix(abs)
return time.Unix(s, ns)
}
// Wrappers around C functions for testing.
func cfDateToAbsoluteTime(d C.CFDateRef) C.CFAbsoluteTime {
return C.CFDateGetAbsoluteTime(d)
}
func absoluteTimeToCFDate(abs C.CFAbsoluteTime) C.CFDateRef {
return C.CFDateCreate(C.kCFAllocatorDefault, abs)
}
func releaseCFDate(d C.CFDateRef) {
Release(C.CFTypeRef(d))
}
+23
View File
@@ -0,0 +1,23 @@
//go:build darwin && ios
// +build darwin,ios
package keychain
/*
#cgo LDFLAGS: -framework CoreFoundation -framework Security
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
*/
import "C"
var AccessibleKey = attrKey(C.CFTypeRef(C.kSecAttrAccessible))
var accessibleTypeRef = map[Accessible]C.CFTypeRef{
AccessibleWhenUnlocked: C.CFTypeRef(C.kSecAttrAccessibleWhenUnlocked),
AccessibleAfterFirstUnlock: C.CFTypeRef(C.kSecAttrAccessibleAfterFirstUnlock),
AccessibleAlways: C.CFTypeRef(C.kSecAttrAccessibleAlways),
AccessibleWhenPasscodeSetThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly),
AccessibleWhenUnlockedThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleWhenUnlockedThisDeviceOnly),
AccessibleAfterFirstUnlockThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly),
AccessibleAccessibleAlwaysThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleAlwaysThisDeviceOnly),
}
+653
View File
@@ -0,0 +1,653 @@
//go:build darwin
// +build darwin
package keychain
// See https://developer.apple.com/library/ios/documentation/Security/Reference/keychainservices/index.html for the APIs used below.
// Also see https://developer.apple.com/library/ios/documentation/Security/Conceptual/keychainServConcepts/01introduction/introduction.html .
/*
#cgo LDFLAGS: -framework CoreFoundation -framework Security
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
*/
import "C"
import (
"fmt"
"time"
)
// Error defines keychain errors
type Error int
var (
// ErrorUnimplemented corresponds to errSecUnimplemented result code
ErrorUnimplemented = Error(C.errSecUnimplemented)
// ErrorParam corresponds to errSecParam result code
ErrorParam = Error(C.errSecParam)
// ErrorAllocate corresponds to errSecAllocate result code
ErrorAllocate = Error(C.errSecAllocate)
// ErrorNotAvailable corresponds to errSecNotAvailable result code
ErrorNotAvailable = Error(C.errSecNotAvailable)
// ErrorAuthFailed corresponds to errSecAuthFailed result code
ErrorAuthFailed = Error(C.errSecAuthFailed)
// ErrorDuplicateItem corresponds to errSecDuplicateItem result code
ErrorDuplicateItem = Error(C.errSecDuplicateItem)
// ErrorItemNotFound corresponds to errSecItemNotFound result code
ErrorItemNotFound = Error(C.errSecItemNotFound)
// ErrorInteractionNotAllowed corresponds to errSecInteractionNotAllowed result code
ErrorInteractionNotAllowed = Error(C.errSecInteractionNotAllowed)
// ErrorDecode corresponds to errSecDecode result code
ErrorDecode = Error(C.errSecDecode)
// ErrorNoSuchKeychain corresponds to errSecNoSuchKeychain result code
ErrorNoSuchKeychain = Error(C.errSecNoSuchKeychain)
// ErrorNoAccessForItem corresponds to errSecNoAccessForItem result code
ErrorNoAccessForItem = Error(C.errSecNoAccessForItem)
// ErrorReadOnly corresponds to errSecReadOnly result code
ErrorReadOnly = Error(C.errSecReadOnly)
// ErrorInvalidKeychain corresponds to errSecInvalidKeychain result code
ErrorInvalidKeychain = Error(C.errSecInvalidKeychain)
// ErrorDuplicateKeyChain corresponds to errSecDuplicateKeychain result code
ErrorDuplicateKeyChain = Error(C.errSecDuplicateKeychain)
// ErrorWrongVersion corresponds to errSecWrongSecVersion result code
ErrorWrongVersion = Error(C.errSecWrongSecVersion)
// ErrorReadonlyAttribute corresponds to errSecReadOnlyAttr result code
ErrorReadonlyAttribute = Error(C.errSecReadOnlyAttr)
// ErrorInvalidSearchRef corresponds to errSecInvalidSearchRef result code
ErrorInvalidSearchRef = Error(C.errSecInvalidSearchRef)
// ErrorInvalidItemRef corresponds to errSecInvalidItemRef result code
ErrorInvalidItemRef = Error(C.errSecInvalidItemRef)
// ErrorDataNotAvailable corresponds to errSecDataNotAvailable result code
ErrorDataNotAvailable = Error(C.errSecDataNotAvailable)
// ErrorDataNotModifiable corresponds to errSecDataNotModifiable result code
ErrorDataNotModifiable = Error(C.errSecDataNotModifiable)
// ErrorInvalidOwnerEdit corresponds to errSecInvalidOwnerEdit result code
ErrorInvalidOwnerEdit = Error(C.errSecInvalidOwnerEdit)
// ErrorUserCanceled corresponds to errSecUserCanceled result code
ErrorUserCanceled = Error(C.errSecUserCanceled)
)
func checkError(errCode C.OSStatus) error {
if errCode == C.errSecSuccess {
return nil
}
return Error(errCode)
}
func (k Error) Error() (msg string) {
// SecCopyErrorMessageString is only available on OSX, so derive manually.
// Messages derived from `$ security error $errcode`.
switch k {
case ErrorUnimplemented:
msg = "Function or operation not implemented."
case ErrorParam:
msg = "One or more parameters passed to the function were not valid."
case ErrorAllocate:
msg = "Failed to allocate memory."
case ErrorNotAvailable:
msg = "No keychain is available. You may need to restart your computer."
case ErrorAuthFailed:
msg = "The user name or passphrase you entered is not correct."
case ErrorDuplicateItem:
msg = "The specified item already exists in the keychain."
case ErrorItemNotFound:
msg = "The specified item could not be found in the keychain."
case ErrorInteractionNotAllowed:
msg = "User interaction is not allowed."
case ErrorDecode:
msg = "Unable to decode the provided data."
case ErrorNoSuchKeychain:
msg = "The specified keychain could not be found."
case ErrorNoAccessForItem:
msg = "The specified item has no access control."
case ErrorReadOnly:
msg = "Read-only error."
case ErrorReadonlyAttribute:
msg = "The attribute is read-only."
case ErrorInvalidKeychain:
msg = "The keychain is not valid."
case ErrorDuplicateKeyChain:
msg = "A keychain with the same name already exists."
case ErrorWrongVersion:
msg = "The version is incorrect."
case ErrorInvalidItemRef:
msg = "The item reference is invalid."
case ErrorInvalidSearchRef:
msg = "The search reference is invalid."
case ErrorDataNotAvailable:
msg = "The data is not available."
case ErrorDataNotModifiable:
msg = "The data is not modifiable."
case ErrorInvalidOwnerEdit:
msg = "An invalid attempt to change the owner of an item."
case ErrorUserCanceled:
msg = "User canceled the operation."
default:
msg = "Keychain Error."
}
return fmt.Sprintf("%s (%d)", msg, k)
}
// SecClass is the items class code
type SecClass int
// Keychain Item Classes
var (
/*
kSecClassGenericPassword item attributes:
kSecAttrAccess (OS X only)
kSecAttrAccessGroup (iOS; also OS X if kSecAttrSynchronizable specified)
kSecAttrAccessible (iOS; also OS X if kSecAttrSynchronizable specified)
kSecAttrAccount
kSecAttrService
*/
SecClassGenericPassword SecClass = 1
SecClassInternetPassword SecClass = 2
)
// SecClassKey is the key type for SecClass
var SecClassKey = attrKey(C.CFTypeRef(C.kSecClass))
var secClassTypeRef = map[SecClass]C.CFTypeRef{
SecClassGenericPassword: C.CFTypeRef(C.kSecClassGenericPassword),
SecClassInternetPassword: C.CFTypeRef(C.kSecClassInternetPassword),
}
var (
// ServiceKey is for kSecAttrService
ServiceKey = attrKey(C.CFTypeRef(C.kSecAttrService))
// ServerKey is for kSecAttrServer
ServerKey = attrKey(C.CFTypeRef(C.kSecAttrServer))
// ProtocolKey is for kSecAttrProtocol
ProtocolKey = attrKey(C.CFTypeRef(C.kSecAttrProtocol))
// AuthenticationTypeKey is for kSecAttrAuthenticationType
AuthenticationTypeKey = attrKey(C.CFTypeRef(C.kSecAttrAuthenticationType))
// PortKey is for kSecAttrPort
PortKey = attrKey(C.CFTypeRef(C.kSecAttrPort))
// PathKey is for kSecAttrPath
PathKey = attrKey(C.CFTypeRef(C.kSecAttrPath))
// LabelKey is for kSecAttrLabel
LabelKey = attrKey(C.CFTypeRef(C.kSecAttrLabel))
// AccountKey is for kSecAttrAccount
AccountKey = attrKey(C.CFTypeRef(C.kSecAttrAccount))
// AccessGroupKey is for kSecAttrAccessGroup
AccessGroupKey = attrKey(C.CFTypeRef(C.kSecAttrAccessGroup))
// DataKey is for kSecValueData
DataKey = attrKey(C.CFTypeRef(C.kSecValueData))
// DescriptionKey is for kSecAttrDescription
DescriptionKey = attrKey(C.CFTypeRef(C.kSecAttrDescription))
// CommentKey is for kSecAttrComment
CommentKey = attrKey(C.CFTypeRef(C.kSecAttrComment))
// CreationDateKey is for kSecAttrCreationDate
CreationDateKey = attrKey(C.CFTypeRef(C.kSecAttrCreationDate))
// ModificationDateKey is for kSecAttrModificationDate
ModificationDateKey = attrKey(C.CFTypeRef(C.kSecAttrModificationDate))
)
// Synchronizable is the items synchronizable status
type Synchronizable int
const (
// SynchronizableDefault is the default setting
SynchronizableDefault Synchronizable = 0
// SynchronizableAny is for kSecAttrSynchronizableAny
SynchronizableAny = 1
// SynchronizableYes enables synchronization
SynchronizableYes = 2
// SynchronizableNo disables synchronization
SynchronizableNo = 3
)
// SynchronizableKey is the key type for Synchronizable
var SynchronizableKey = attrKey(C.CFTypeRef(C.kSecAttrSynchronizable))
var syncTypeRef = map[Synchronizable]C.CFTypeRef{
SynchronizableAny: C.CFTypeRef(C.kSecAttrSynchronizableAny),
SynchronizableYes: C.CFTypeRef(C.kCFBooleanTrue),
SynchronizableNo: C.CFTypeRef(C.kCFBooleanFalse),
}
// Accessible is the items accessibility
type Accessible int
const (
// AccessibleDefault is the default
AccessibleDefault Accessible = 0
// AccessibleWhenUnlocked is when unlocked
AccessibleWhenUnlocked = 1
// AccessibleAfterFirstUnlock is after first unlock
AccessibleAfterFirstUnlock = 2
// AccessibleAlways is always
AccessibleAlways = 3
// AccessibleWhenPasscodeSetThisDeviceOnly is when passcode is set
AccessibleWhenPasscodeSetThisDeviceOnly = 4
// AccessibleWhenUnlockedThisDeviceOnly is when unlocked for this device only
AccessibleWhenUnlockedThisDeviceOnly = 5
// AccessibleAfterFirstUnlockThisDeviceOnly is after first unlock for this device only
AccessibleAfterFirstUnlockThisDeviceOnly = 6
// AccessibleAccessibleAlwaysThisDeviceOnly is always for this device only
AccessibleAccessibleAlwaysThisDeviceOnly = 7
)
// MatchLimit is whether to limit results on query
type MatchLimit int
const (
// MatchLimitDefault is the default
MatchLimitDefault MatchLimit = 0
// MatchLimitOne limits to one result
MatchLimitOne = 1
// MatchLimitAll is no limit
MatchLimitAll = 2
)
// MatchLimitKey is key type for MatchLimit
var MatchLimitKey = attrKey(C.CFTypeRef(C.kSecMatchLimit))
var matchTypeRef = map[MatchLimit]C.CFTypeRef{
MatchLimitOne: C.CFTypeRef(C.kSecMatchLimitOne),
MatchLimitAll: C.CFTypeRef(C.kSecMatchLimitAll),
}
// ReturnAttributesKey is key type for kSecReturnAttributes
var ReturnAttributesKey = attrKey(C.CFTypeRef(C.kSecReturnAttributes))
// ReturnDataKey is key type for kSecReturnData
var ReturnDataKey = attrKey(C.CFTypeRef(C.kSecReturnData))
// ReturnRefKey is key type for kSecReturnRef
var ReturnRefKey = attrKey(C.CFTypeRef(C.kSecReturnRef))
// Item for adding, querying or deleting.
type Item struct {
// Values can be string, []byte, Convertable or CFTypeRef (constant).
attr map[string]interface{}
}
// SetSecClass sets the security class
func (k *Item) SetSecClass(sc SecClass) {
k.attr[SecClassKey] = secClassTypeRef[sc]
}
// SetInt32 sets an int32 attribute for a string key
func (k *Item) SetInt32(key string, v int32) {
if v != 0 {
k.attr[key] = v
} else {
delete(k.attr, key)
}
}
// SetString sets a string attibute for a string key
func (k *Item) SetString(key string, s string) {
if s != "" {
k.attr[key] = s
} else {
delete(k.attr, key)
}
}
// SetService sets the service attribute (for generic application items)
func (k *Item) SetService(s string) {
k.SetString(ServiceKey, s)
}
// SetServer sets the server attribute (for internet password items)
func (k *Item) SetServer(s string) {
k.SetString(ServerKey, s)
}
// SetProtocol sets the protocol attribute (for internet password items)
// Example values are: "htps", "http", "smb "
func (k *Item) SetProtocol(s string) {
k.SetString(ProtocolKey, s)
}
// SetAuthenticationType sets the authentication type attribute (for internet password items)
func (k *Item) SetAuthenticationType(s string) {
k.SetString(AuthenticationTypeKey, s)
}
// SetPort sets the port attribute (for internet password items)
func (k *Item) SetPort(v int32) {
k.SetInt32(PortKey, v)
}
// SetPath sets the path attribute (for internet password items)
func (k *Item) SetPath(s string) {
k.SetString(PathKey, s)
}
// SetAccount sets the account attribute
func (k *Item) SetAccount(a string) {
k.SetString(AccountKey, a)
}
// SetLabel sets the label attribute
func (k *Item) SetLabel(l string) {
k.SetString(LabelKey, l)
}
// SetDescription sets the description attribute
func (k *Item) SetDescription(s string) {
k.SetString(DescriptionKey, s)
}
// SetComment sets the comment attribute
func (k *Item) SetComment(s string) {
k.SetString(CommentKey, s)
}
// SetData sets the data attribute
func (k *Item) SetData(b []byte) {
if b != nil {
k.attr[DataKey] = b
} else {
delete(k.attr, DataKey)
}
}
// SetAccessGroup sets the access group attribute
func (k *Item) SetAccessGroup(ag string) {
k.SetString(AccessGroupKey, ag)
}
// SetSynchronizable sets the synchronizable attribute
func (k *Item) SetSynchronizable(sync Synchronizable) {
if sync != SynchronizableDefault {
k.attr[SynchronizableKey] = syncTypeRef[sync]
} else {
delete(k.attr, SynchronizableKey)
}
}
// SetAccessible sets the accessible attribute
func (k *Item) SetAccessible(accessible Accessible) {
if accessible != AccessibleDefault {
k.attr[AccessibleKey] = accessibleTypeRef[accessible]
} else {
delete(k.attr, AccessibleKey)
}
}
// SetMatchLimit sets the match limit
func (k *Item) SetMatchLimit(matchLimit MatchLimit) {
if matchLimit != MatchLimitDefault {
k.attr[MatchLimitKey] = matchTypeRef[matchLimit]
} else {
delete(k.attr, MatchLimitKey)
}
}
// SetReturnAttributes sets the return value type on query
func (k *Item) SetReturnAttributes(b bool) {
k.attr[ReturnAttributesKey] = b
}
// SetReturnData enables returning data on query
func (k *Item) SetReturnData(b bool) {
k.attr[ReturnDataKey] = b
}
// SetReturnRef enables returning references on query
func (k *Item) SetReturnRef(b bool) {
k.attr[ReturnRefKey] = b
}
// NewItem is a new empty keychain item
func NewItem() Item {
return Item{make(map[string]interface{})}
}
// NewGenericPassword creates a generic password item with the default keychain. This is a convenience method.
func NewGenericPassword(service string, account string, label string, data []byte, accessGroup string) Item {
item := NewItem()
item.SetSecClass(SecClassGenericPassword)
item.SetService(service)
item.SetAccount(account)
item.SetLabel(label)
item.SetData(data)
item.SetAccessGroup(accessGroup)
return item
}
// AddItem adds a Item to a Keychain
func AddItem(item Item) error {
cfDict, err := ConvertMapToCFDictionary(item.attr)
if err != nil {
return err
}
defer Release(C.CFTypeRef(cfDict))
errCode := C.SecItemAdd(cfDict, nil)
err = checkError(errCode)
return err
}
// UpdateItem updates the queryItem with the parameters from updateItem
func UpdateItem(queryItem Item, updateItem Item) error {
cfDict, err := ConvertMapToCFDictionary(queryItem.attr)
if err != nil {
return err
}
defer Release(C.CFTypeRef(cfDict))
cfDictUpdate, err := ConvertMapToCFDictionary(updateItem.attr)
if err != nil {
return err
}
defer Release(C.CFTypeRef(cfDictUpdate))
errCode := C.SecItemUpdate(cfDict, cfDictUpdate)
err = checkError(errCode)
return err
}
// QueryResult stores all possible results from queries.
// Not all fields are applicable all the time. Results depend on query.
type QueryResult struct {
// For generic application items
Service string
// For internet password items
Server string
Protocol string
AuthenticationType string
Port int32
Path string
Account string
AccessGroup string
Label string
Description string
Comment string
Data []byte
CreationDate time.Time
ModificationDate time.Time
}
// QueryItemRef returns query result as CFTypeRef. You must release it when you are done.
func QueryItemRef(item Item) (C.CFTypeRef, error) {
cfDict, err := ConvertMapToCFDictionary(item.attr)
if err != nil {
return 0, err
}
defer Release(C.CFTypeRef(cfDict))
var resultsRef C.CFTypeRef
errCode := C.SecItemCopyMatching(cfDict, &resultsRef) //nolint
if Error(errCode) == ErrorItemNotFound {
return 0, nil
}
err = checkError(errCode)
if err != nil {
return 0, err
}
return resultsRef, nil
}
// QueryItem returns a list of query results.
func QueryItem(item Item) ([]QueryResult, error) {
resultsRef, err := QueryItemRef(item)
if err != nil {
return nil, err
}
if resultsRef == 0 {
return nil, nil
}
defer Release(resultsRef)
results := make([]QueryResult, 0, 1)
typeID := C.CFGetTypeID(resultsRef)
if typeID == C.CFArrayGetTypeID() {
arr := CFArrayToArray(C.CFArrayRef(resultsRef))
for _, ref := range arr {
elementTypeID := C.CFGetTypeID(ref)
if elementTypeID == C.CFDictionaryGetTypeID() {
item, err := convertResult(C.CFDictionaryRef(ref))
if err != nil {
return nil, err
}
results = append(results, *item)
} else {
return nil, fmt.Errorf("invalid result type (If you SetReturnRef(true) you should use QueryItemRef directly)")
}
}
} else if typeID == C.CFDictionaryGetTypeID() {
item, err := convertResult(C.CFDictionaryRef(resultsRef))
if err != nil {
return nil, err
}
results = append(results, *item)
} else if typeID == C.CFDataGetTypeID() {
b, err := CFDataToBytes(C.CFDataRef(resultsRef))
if err != nil {
return nil, err
}
item := QueryResult{Data: b}
results = append(results, item)
} else {
return nil, fmt.Errorf("Invalid result type: %s", CFTypeDescription(resultsRef))
}
return results, nil
}
func attrKey(ref C.CFTypeRef) string {
return CFStringToString(C.CFStringRef(ref))
}
func convertResult(d C.CFDictionaryRef) (*QueryResult, error) {
m := CFDictionaryToMap(d)
result := QueryResult{}
for k, v := range m {
switch attrKey(k) {
case ServiceKey:
result.Service = CFStringToString(C.CFStringRef(v))
case ServerKey:
result.Server = CFStringToString(C.CFStringRef(v))
case ProtocolKey:
result.Protocol = CFStringToString(C.CFStringRef(v))
case AuthenticationTypeKey:
result.AuthenticationType = CFStringToString(C.CFStringRef(v))
case PortKey:
val := CFNumberToInterface(C.CFNumberRef(v))
result.Port = val.(int32)
case PathKey:
result.Path = CFStringToString(C.CFStringRef(v))
case AccountKey:
result.Account = CFStringToString(C.CFStringRef(v))
case AccessGroupKey:
result.AccessGroup = CFStringToString(C.CFStringRef(v))
case LabelKey:
result.Label = CFStringToString(C.CFStringRef(v))
case DescriptionKey:
result.Description = CFStringToString(C.CFStringRef(v))
case CommentKey:
result.Comment = CFStringToString(C.CFStringRef(v))
case DataKey:
b, err := CFDataToBytes(C.CFDataRef(v))
if err != nil {
return nil, err
}
result.Data = b
case CreationDateKey:
result.CreationDate = CFDateToTime(C.CFDateRef(v))
case ModificationDateKey:
result.ModificationDate = CFDateToTime(C.CFDateRef(v))
// default:
// fmt.Printf("Unhandled key in conversion: %v = %v\n", cfTypeValue(k), cfTypeValue(v))
}
}
return &result, nil
}
// DeleteGenericPasswordItem removes a generic password item.
func DeleteGenericPasswordItem(service string, account string) error {
item := NewItem()
item.SetSecClass(SecClassGenericPassword)
item.SetService(service)
item.SetAccount(account)
return DeleteItem(item)
}
// DeleteItem removes a Item
func DeleteItem(item Item) error {
cfDict, err := ConvertMapToCFDictionary(item.attr)
if err != nil {
return err
}
defer Release(C.CFTypeRef(cfDict))
errCode := C.SecItemDelete(cfDict)
return checkError(errCode)
}
// GetAccountsForService is deprecated
func GetAccountsForService(service string) ([]string, error) {
return GetGenericPasswordAccounts(service)
}
// GetGenericPasswordAccounts returns generic password accounts for service. This is a convenience method.
func GetGenericPasswordAccounts(service string) ([]string, error) {
query := NewItem()
query.SetSecClass(SecClassGenericPassword)
query.SetService(service)
query.SetMatchLimit(MatchLimitAll)
query.SetReturnAttributes(true)
results, err := QueryItem(query)
if err != nil {
return nil, err
}
accounts := make([]string, 0, len(results))
for _, r := range results {
accounts = append(accounts, r.Account)
}
return accounts, nil
}
// GetGenericPassword returns password data for service and account. This is a convenience method.
// If item is not found returns nil, nil.
func GetGenericPassword(service string, account string, label string, accessGroup string) ([]byte, error) {
query := NewItem()
query.SetSecClass(SecClassGenericPassword)
query.SetService(service)
query.SetAccount(account)
query.SetLabel(label)
query.SetAccessGroup(accessGroup)
query.SetMatchLimit(MatchLimitOne)
query.SetReturnData(true)
results, err := QueryItem(query)
if err != nil {
return nil, err
}
if len(results) > 1 {
return nil, fmt.Errorf("Too many results")
}
if len(results) == 1 {
return results[0].Data, nil
}
return nil, nil
}
+25
View File
@@ -0,0 +1,25 @@
//go:build darwin && !ios
// +build darwin,!ios
package keychain
/*
#cgo LDFLAGS: -framework CoreFoundation -framework Security
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
*/
import "C"
// AccessibleKey is key for kSecAttrAccessible
var AccessibleKey = attrKey(C.CFTypeRef(C.kSecAttrAccessible))
var accessibleTypeRef = map[Accessible]C.CFTypeRef{
AccessibleWhenUnlocked: C.CFTypeRef(C.kSecAttrAccessibleWhenUnlocked),
AccessibleAfterFirstUnlock: C.CFTypeRef(C.kSecAttrAccessibleAfterFirstUnlock),
AccessibleAlways: C.CFTypeRef(C.kSecAttrAccessibleAlways),
AccessibleWhenUnlockedThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleWhenUnlockedThisDeviceOnly),
AccessibleAfterFirstUnlockThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly),
AccessibleAccessibleAlwaysThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleAlwaysThisDeviceOnly),
// Only available in 10.10
//AccessibleWhenPasscodeSetThisDeviceOnly: C.CFTypeRef(C.kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly),
}
+31
View File
@@ -0,0 +1,31 @@
package keychain
import (
"crypto/rand"
"encoding/base32"
"strings"
)
var randRead = rand.Read
// RandomID returns random ID (base32) string with prefix, using 256 bits as
// recommended by tptacek: https://gist.github.com/tqbf/be58d2d39690c3b366ad
func RandomID(prefix string) (string, error) {
buf, err := RandBytes(32)
if err != nil {
return "", err
}
str := base32.StdEncoding.EncodeToString(buf)
str = strings.ReplaceAll(str, "=", "")
str = prefix + str
return str, nil
}
// RandBytes returns random bytes of length
func RandBytes(length int) ([]byte, error) {
buf := make([]byte, length)
if _, err := randRead(buf); err != nil {
return nil, err
}
return buf, nil
}
-102
View File
@@ -1,102 +0,0 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package execabs is a drop-in replacement for os/exec
// that requires PATH lookups to find absolute paths.
// That is, execabs.Command("cmd") runs the same PATH lookup
// as exec.Command("cmd"), but if the result is a path
// which is relative, the Run and Start methods will report
// an error instead of running the executable.
//
// See https://blog.golang.org/path-security for more information
// about when it may be necessary or appropriate to use this package.
package execabs
import (
"context"
"fmt"
"os/exec"
"path/filepath"
"reflect"
"unsafe"
)
// ErrNotFound is the error resulting if a path search failed to find an executable file.
// It is an alias for exec.ErrNotFound.
var ErrNotFound = exec.ErrNotFound
// Cmd represents an external command being prepared or run.
// It is an alias for exec.Cmd.
type Cmd = exec.Cmd
// Error is returned by LookPath when it fails to classify a file as an executable.
// It is an alias for exec.Error.
type Error = exec.Error
// An ExitError reports an unsuccessful exit by a command.
// It is an alias for exec.ExitError.
type ExitError = exec.ExitError
func relError(file, path string) error {
return fmt.Errorf("%s resolves to executable in current directory (.%c%s)", file, filepath.Separator, path)
}
// LookPath searches for an executable named file in the directories
// named by the PATH environment variable. If file contains a slash,
// it is tried directly and the PATH is not consulted. The result will be
// an absolute path.
//
// LookPath differs from exec.LookPath in its handling of PATH lookups,
// which are used for file names without slashes. If exec.LookPath's
// PATH lookup would have returned an executable from the current directory,
// LookPath instead returns an error.
func LookPath(file string) (string, error) {
path, err := exec.LookPath(file)
if err != nil && !isGo119ErrDot(err) {
return "", err
}
if filepath.Base(file) == file && !filepath.IsAbs(path) {
return "", relError(file, path)
}
return path, nil
}
func fixCmd(name string, cmd *exec.Cmd) {
if filepath.Base(name) == name && !filepath.IsAbs(cmd.Path) {
// exec.Command was called with a bare binary name and
// exec.LookPath returned a path which is not absolute.
// Set cmd.lookPathErr and clear cmd.Path so that it
// cannot be run.
lookPathErr := (*error)(unsafe.Pointer(reflect.ValueOf(cmd).Elem().FieldByName("lookPathErr").Addr().Pointer()))
if *lookPathErr == nil {
*lookPathErr = relError(name, cmd.Path)
}
cmd.Path = ""
}
}
// CommandContext is like Command but includes a context.
//
// The provided context is used to kill the process (by calling os.Process.Kill)
// if the context becomes done before the command completes on its own.
func CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd {
cmd := exec.CommandContext(ctx, name, arg...)
fixCmd(name, cmd)
return cmd
}
// Command returns the Cmd struct to execute the named program with the given arguments.
// See exec.Command for most details.
//
// Command differs from exec.Command in its handling of PATH lookups,
// which are used when the program name contains no slashes.
// If exec.Command would have returned an exec.Cmd configured to run an
// executable from the current directory, Command instead
// returns an exec.Cmd that will return an error from Start or Run.
func Command(name string, arg ...string) *exec.Cmd {
cmd := exec.Command(name, arg...)
fixCmd(name, cmd)
return cmd
}
-12
View File
@@ -1,12 +0,0 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !go1.19
// +build !go1.19
package execabs
func isGo119ErrDot(err error) bool {
return false
}
-15
View File
@@ -1,15 +0,0 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.19
// +build go1.19
package execabs
import "strings"
func isGo119ErrDot(err error) bool {
// TODO: return errors.Is(err, exec.ErrDot)
return strings.Contains(err.Error(), "current directory")
}
-30
View File
@@ -1,30 +0,0 @@
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package unsafeheader contains header declarations for the Go runtime's
// slice and string implementations.
//
// This package allows x/sys to use types equivalent to
// reflect.SliceHeader and reflect.StringHeader without introducing
// a dependency on the (relatively heavy) "reflect" package.
package unsafeheader
import (
"unsafe"
)
// Slice is the runtime representation of a slice.
// It cannot be used safely or portably and its representation may change in a later release.
type Slice struct {
Data unsafe.Pointer
Len int
Cap int
}
// String is the runtime representation of a string.
// It cannot be used safely or portably and its representation may change in a later release.
type String struct {
Data unsafe.Pointer
Len int
}
+1 -2
View File
@@ -2,8 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build windows && go1.9 //go:build windows
// +build windows,go1.9
package windows package windows
-9
View File
@@ -1,9 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !go1.12
// +build !go1.12
// This file is here to allow bodyless functions with go:linkname for Go 1.11
// and earlier (see https://golang.org/issue/23311).
+10 -7
View File
@@ -37,14 +37,17 @@ func (token Token) Environ(inheritExisting bool) (env []string, err error) {
return nil, err return nil, err
} }
defer DestroyEnvironmentBlock(block) defer DestroyEnvironmentBlock(block)
blockp := uintptr(unsafe.Pointer(block)) size := unsafe.Sizeof(*block)
for { for *block != 0 {
entry := UTF16PtrToString((*uint16)(unsafe.Pointer(blockp))) // find NUL terminator
if len(entry) == 0 { end := unsafe.Pointer(block)
break for *(*uint16)(end) != 0 {
end = unsafe.Add(end, size)
} }
env = append(env, entry)
blockp += 2 * (uintptr(len(entry)) + 1) entry := unsafe.Slice(block, (uintptr(end)-uintptr(unsafe.Pointer(block)))/size)
env = append(env, UTF16ToString(entry))
block = (*uint16)(unsafe.Add(end, size))
} }
return env, nil return env, nil
} }
-1
View File
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build windows //go:build windows
// +build windows
package windows package windows
+81 -11
View File
@@ -22,7 +22,7 @@ import (
// but only if there is space or tab inside s. // but only if there is space or tab inside s.
func EscapeArg(s string) string { func EscapeArg(s string) string {
if len(s) == 0 { if len(s) == 0 {
return "\"\"" return `""`
} }
n := len(s) n := len(s)
hasSpace := false hasSpace := false
@@ -35,7 +35,7 @@ func EscapeArg(s string) string {
} }
} }
if hasSpace { if hasSpace {
n += 2 n += 2 // Reserve space for quotes.
} }
if n == len(s) { if n == len(s) {
return s return s
@@ -82,36 +82,106 @@ func EscapeArg(s string) string {
// in CreateProcess's CommandLine argument, CreateService/ChangeServiceConfig's BinaryPathName argument, // in CreateProcess's CommandLine argument, CreateService/ChangeServiceConfig's BinaryPathName argument,
// or any program that uses CommandLineToArgv. // or any program that uses CommandLineToArgv.
func ComposeCommandLine(args []string) string { func ComposeCommandLine(args []string) string {
var commandLine string if len(args) == 0 {
for i := range args { return ""
if i > 0 {
commandLine += " "
} }
commandLine += EscapeArg(args[i])
// Per https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw:
// “This function accepts command lines that contain a program name; the
// program name can be enclosed in quotation marks or not.”
//
// Unfortunately, it provides no means of escaping interior quotation marks
// within that program name, and we have no way to report them here.
prog := args[0]
mustQuote := len(prog) == 0
for i := 0; i < len(prog); i++ {
c := prog[i]
if c <= ' ' || (c == '"' && i == 0) {
// Force quotes for not only the ASCII space and tab as described in the
// MSDN article, but also ASCII control characters.
// The documentation for CommandLineToArgvW doesn't say what happens when
// the first argument is not a valid program name, but it empirically
// seems to drop unquoted control characters.
mustQuote = true
break
} }
return commandLine }
var commandLine []byte
if mustQuote {
commandLine = make([]byte, 0, len(prog)+2)
commandLine = append(commandLine, '"')
for i := 0; i < len(prog); i++ {
c := prog[i]
if c == '"' {
// This quote would interfere with our surrounding quotes.
// We have no way to report an error, so just strip out
// the offending character instead.
continue
}
commandLine = append(commandLine, c)
}
commandLine = append(commandLine, '"')
} else {
if len(args) == 1 {
// args[0] is a valid command line representing itself.
// No need to allocate a new slice or string for it.
return prog
}
commandLine = []byte(prog)
}
for _, arg := range args[1:] {
commandLine = append(commandLine, ' ')
// TODO(bcmills): since we're already appending to a slice, it would be nice
// to avoid the intermediate allocations of EscapeArg.
// Perhaps we can factor out an appendEscapedArg function.
commandLine = append(commandLine, EscapeArg(arg)...)
}
return string(commandLine)
} }
// DecomposeCommandLine breaks apart its argument command line into unescaped parts using CommandLineToArgv, // DecomposeCommandLine breaks apart its argument command line into unescaped parts using CommandLineToArgv,
// as gathered from GetCommandLine, QUERY_SERVICE_CONFIG's BinaryPathName argument, or elsewhere that // as gathered from GetCommandLine, QUERY_SERVICE_CONFIG's BinaryPathName argument, or elsewhere that
// command lines are passed around. // command lines are passed around.
// DecomposeCommandLine returns an error if commandLine contains NUL.
func DecomposeCommandLine(commandLine string) ([]string, error) { func DecomposeCommandLine(commandLine string) ([]string, error) {
if len(commandLine) == 0 { if len(commandLine) == 0 {
return []string{}, nil return []string{}, nil
} }
utf16CommandLine, err := UTF16FromString(commandLine)
if err != nil {
return nil, errorspkg.New("string with NUL passed to DecomposeCommandLine")
}
var argc int32 var argc int32
argv, err := CommandLineToArgv(StringToUTF16Ptr(commandLine), &argc) argv, err := commandLineToArgv(&utf16CommandLine[0], &argc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer LocalFree(Handle(unsafe.Pointer(argv))) defer LocalFree(Handle(unsafe.Pointer(argv)))
var args []string var args []string
for _, v := range (*argv)[:argc] { for _, p := range unsafe.Slice(argv, argc) {
args = append(args, UTF16ToString((*v)[:])) args = append(args, UTF16PtrToString(p))
} }
return args, nil return args, nil
} }
// CommandLineToArgv parses a Unicode command line string and sets
// argc to the number of parsed arguments.
//
// The returned memory should be freed using a single call to LocalFree.
//
// Note that although the return type of CommandLineToArgv indicates 8192
// entries of up to 8192 characters each, the actual count of parsed arguments
// may exceed 8192, and the documentation for CommandLineToArgvW does not mention
// any bound on the lengths of the individual argument strings.
// (See https://go.dev/issue/63236.)
func CommandLineToArgv(cmd *uint16, argc *int32) (argv *[8192]*[8192]uint16, err error) {
argp, err := commandLineToArgv(cmd, argc)
argv = (*[8192]*[8192]uint16)(unsafe.Pointer(argp))
return argv, err
}
func CloseOnExec(fd Handle) { func CloseOnExec(fd Handle) {
SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0) SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0)
} }
-1
View File
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build generate //go:build generate
// +build generate
package windows package windows
-1
View File
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build windows && race //go:build windows && race
// +build windows,race
package windows package windows
-1
View File
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build windows && !race //go:build windows && !race
// +build windows,!race
package windows package windows
+6 -15
View File
@@ -7,8 +7,6 @@ package windows
import ( import (
"syscall" "syscall"
"unsafe" "unsafe"
"golang.org/x/sys/internal/unsafeheader"
) )
const ( const (
@@ -1341,21 +1339,14 @@ func (selfRelativeSD *SECURITY_DESCRIPTOR) copySelfRelativeSecurityDescriptor()
sdLen = min sdLen = min
} }
var src []byte src := unsafe.Slice((*byte)(unsafe.Pointer(selfRelativeSD)), sdLen)
h := (*unsafeheader.Slice)(unsafe.Pointer(&src)) // SECURITY_DESCRIPTOR has pointers in it, which means checkptr expects for it to
h.Data = unsafe.Pointer(selfRelativeSD) // be aligned properly. When we're copying a Windows-allocated struct to a
h.Len = sdLen // Go-allocated one, make sure that the Go allocation is aligned to the
h.Cap = sdLen // pointer size.
const psize = int(unsafe.Sizeof(uintptr(0))) const psize = int(unsafe.Sizeof(uintptr(0)))
var dst []byte
h = (*unsafeheader.Slice)(unsafe.Pointer(&dst))
alloc := make([]uintptr, (sdLen+psize-1)/psize) alloc := make([]uintptr, (sdLen+psize-1)/psize)
h.Data = (*unsafeheader.Slice)(unsafe.Pointer(&alloc)).Data dst := unsafe.Slice((*byte)(unsafe.Pointer(&alloc[0])), sdLen)
h.Len = sdLen
h.Cap = sdLen
copy(dst, src) copy(dst, src)
return (*SECURITY_DESCRIPTOR)(unsafe.Pointer(&dst[0])) return (*SECURITY_DESCRIPTOR)(unsafe.Pointer(&dst[0]))
} }
+11 -1
View File
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build windows //go:build windows
// +build windows
package windows package windows
@@ -141,6 +140,12 @@ const (
SERVICE_DYNAMIC_INFORMATION_LEVEL_START_REASON = 1 SERVICE_DYNAMIC_INFORMATION_LEVEL_START_REASON = 1
) )
type ENUM_SERVICE_STATUS struct {
ServiceName *uint16
DisplayName *uint16
ServiceStatus SERVICE_STATUS
}
type SERVICE_STATUS struct { type SERVICE_STATUS struct {
ServiceType uint32 ServiceType uint32
CurrentState uint32 CurrentState uint32
@@ -212,6 +217,10 @@ type SERVICE_FAILURE_ACTIONS struct {
Actions *SC_ACTION Actions *SC_ACTION
} }
type SERVICE_FAILURE_ACTIONS_FLAG struct {
FailureActionsOnNonCrashFailures int32
}
type SC_ACTION struct { type SC_ACTION struct {
Type uint32 Type uint32
Delay uint32 Delay uint32
@@ -245,3 +254,4 @@ type QUERY_SERVICE_LOCK_STATUS struct {
//sys UnsubscribeServiceChangeNotifications(subscription uintptr) = sechost.UnsubscribeServiceChangeNotifications? //sys UnsubscribeServiceChangeNotifications(subscription uintptr) = sechost.UnsubscribeServiceChangeNotifications?
//sys RegisterServiceCtrlHandlerEx(serviceName *uint16, handlerProc uintptr, context uintptr) (handle Handle, err error) = advapi32.RegisterServiceCtrlHandlerExW //sys RegisterServiceCtrlHandlerEx(serviceName *uint16, handlerProc uintptr, context uintptr) (handle Handle, err error) = advapi32.RegisterServiceCtrlHandlerExW
//sys QueryServiceDynamicInformation(service Handle, infoLevel uint32, dynamicInfo unsafe.Pointer) (err error) = advapi32.QueryServiceDynamicInformation? //sys QueryServiceDynamicInformation(service Handle, infoLevel uint32, dynamicInfo unsafe.Pointer) (err error) = advapi32.QueryServiceDynamicInformation?
//sys EnumDependentServices(service Handle, activityState uint32, services *ENUM_SERVICE_STATUS, buffSize uint32, bytesNeeded *uint32, servicesReturned *uint32) (err error) = advapi32.EnumDependentServicesW
-1
View File
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build windows //go:build windows
// +build windows
package windows package windows
+1 -10
View File
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build windows //go:build windows
// +build windows
// Package windows contains an interface to the low-level operating system // Package windows contains an interface to the low-level operating system
// primitives. OS details vary depending on the underlying system, and // primitives. OS details vary depending on the underlying system, and
@@ -30,8 +29,6 @@ import (
"strings" "strings"
"syscall" "syscall"
"unsafe" "unsafe"
"golang.org/x/sys/internal/unsafeheader"
) )
// ByteSliceFromString returns a NUL-terminated slice of bytes // ByteSliceFromString returns a NUL-terminated slice of bytes
@@ -83,13 +80,7 @@ func BytePtrToString(p *byte) string {
ptr = unsafe.Pointer(uintptr(ptr) + 1) ptr = unsafe.Pointer(uintptr(ptr) + 1)
} }
var s []byte return string(unsafe.Slice(p, n))
h := (*unsafeheader.Slice)(unsafe.Pointer(&s))
h.Data = unsafe.Pointer(p)
h.Len = n
h.Cap = n
return string(s)
} }
// Single-word zero for use when we need a valid pointer to 0 bytes. // Single-word zero for use when we need a valid pointer to 0 bytes.
+163 -49
View File
@@ -10,14 +10,11 @@ import (
errorspkg "errors" errorspkg "errors"
"fmt" "fmt"
"runtime" "runtime"
"strings"
"sync" "sync"
"syscall" "syscall"
"time" "time"
"unicode/utf16" "unicode/utf16"
"unsafe" "unsafe"
"golang.org/x/sys/internal/unsafeheader"
) )
type Handle uintptr type Handle uintptr
@@ -87,22 +84,13 @@ func StringToUTF16(s string) []uint16 {
// s, with a terminating NUL added. If s contains a NUL byte at any // s, with a terminating NUL added. If s contains a NUL byte at any
// location, it returns (nil, syscall.EINVAL). // location, it returns (nil, syscall.EINVAL).
func UTF16FromString(s string) ([]uint16, error) { func UTF16FromString(s string) ([]uint16, error) {
if strings.IndexByte(s, 0) != -1 { return syscall.UTF16FromString(s)
return nil, syscall.EINVAL
}
return utf16.Encode([]rune(s + "\x00")), nil
} }
// UTF16ToString returns the UTF-8 encoding of the UTF-16 sequence s, // UTF16ToString returns the UTF-8 encoding of the UTF-16 sequence s,
// with a terminating NUL and any bytes after the NUL removed. // with a terminating NUL and any bytes after the NUL removed.
func UTF16ToString(s []uint16) string { func UTF16ToString(s []uint16) string {
for i, v := range s { return syscall.UTF16ToString(s)
if v == 0 {
s = s[:i]
break
}
}
return string(utf16.Decode(s))
} }
// StringToUTF16Ptr is deprecated. Use UTF16PtrFromString instead. // StringToUTF16Ptr is deprecated. Use UTF16PtrFromString instead.
@@ -137,28 +125,21 @@ func UTF16PtrToString(p *uint16) string {
for ptr := unsafe.Pointer(p); *(*uint16)(ptr) != 0; n++ { for ptr := unsafe.Pointer(p); *(*uint16)(ptr) != 0; n++ {
ptr = unsafe.Pointer(uintptr(ptr) + unsafe.Sizeof(*p)) ptr = unsafe.Pointer(uintptr(ptr) + unsafe.Sizeof(*p))
} }
return UTF16ToString(unsafe.Slice(p, n))
var s []uint16
h := (*unsafeheader.Slice)(unsafe.Pointer(&s))
h.Data = unsafe.Pointer(p)
h.Len = n
h.Cap = n
return string(utf16.Decode(s))
} }
func Getpagesize() int { return 4096 } func Getpagesize() int { return 4096 }
// NewCallback converts a Go function to a function pointer conforming to the stdcall calling convention. // NewCallback converts a Go function to a function pointer conforming to the stdcall calling convention.
// This is useful when interoperating with Windows code requiring callbacks. // This is useful when interoperating with Windows code requiring callbacks.
// The argument is expected to be a function with with one uintptr-sized result. The function must not have arguments with size larger than the size of uintptr. // The argument is expected to be a function with one uintptr-sized result. The function must not have arguments with size larger than the size of uintptr.
func NewCallback(fn interface{}) uintptr { func NewCallback(fn interface{}) uintptr {
return syscall.NewCallback(fn) return syscall.NewCallback(fn)
} }
// NewCallbackCDecl converts a Go function to a function pointer conforming to the cdecl calling convention. // NewCallbackCDecl converts a Go function to a function pointer conforming to the cdecl calling convention.
// This is useful when interoperating with Windows code requiring callbacks. // This is useful when interoperating with Windows code requiring callbacks.
// The argument is expected to be a function with with one uintptr-sized result. The function must not have arguments with size larger than the size of uintptr. // The argument is expected to be a function with one uintptr-sized result. The function must not have arguments with size larger than the size of uintptr.
func NewCallbackCDecl(fn interface{}) uintptr { func NewCallbackCDecl(fn interface{}) uintptr {
return syscall.NewCallbackCDecl(fn) return syscall.NewCallbackCDecl(fn)
} }
@@ -173,6 +154,8 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys GetModuleFileName(module Handle, filename *uint16, size uint32) (n uint32, err error) = kernel32.GetModuleFileNameW //sys GetModuleFileName(module Handle, filename *uint16, size uint32) (n uint32, err error) = kernel32.GetModuleFileNameW
//sys GetModuleHandleEx(flags uint32, moduleName *uint16, module *Handle) (err error) = kernel32.GetModuleHandleExW //sys GetModuleHandleEx(flags uint32, moduleName *uint16, module *Handle) (err error) = kernel32.GetModuleHandleExW
//sys SetDefaultDllDirectories(directoryFlags uint32) (err error) //sys SetDefaultDllDirectories(directoryFlags uint32) (err error)
//sys AddDllDirectory(path *uint16) (cookie uintptr, err error) = kernel32.AddDllDirectory
//sys RemoveDllDirectory(cookie uintptr) (err error) = kernel32.RemoveDllDirectory
//sys SetDllDirectory(path string) (err error) = kernel32.SetDllDirectoryW //sys SetDllDirectory(path string) (err error) = kernel32.SetDllDirectoryW
//sys GetVersion() (ver uint32, err error) //sys GetVersion() (ver uint32, err error)
//sys FormatMessage(flags uint32, msgsrc uintptr, msgid uint32, langid uint32, buf []uint16, args *byte) (n uint32, err error) = FormatMessageW //sys FormatMessage(flags uint32, msgsrc uintptr, msgid uint32, langid uint32, buf []uint16, args *byte) (n uint32, err error) = FormatMessageW
@@ -182,6 +165,7 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys CreateFile(name *uint16, access uint32, mode uint32, sa *SecurityAttributes, createmode uint32, attrs uint32, templatefile Handle) (handle Handle, err error) [failretval==InvalidHandle] = CreateFileW //sys CreateFile(name *uint16, access uint32, mode uint32, sa *SecurityAttributes, createmode uint32, attrs uint32, templatefile Handle) (handle Handle, err error) [failretval==InvalidHandle] = CreateFileW
//sys CreateNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *SecurityAttributes) (handle Handle, err error) [failretval==InvalidHandle] = CreateNamedPipeW //sys CreateNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *SecurityAttributes) (handle Handle, err error) [failretval==InvalidHandle] = CreateNamedPipeW
//sys ConnectNamedPipe(pipe Handle, overlapped *Overlapped) (err error) //sys ConnectNamedPipe(pipe Handle, overlapped *Overlapped) (err error)
//sys DisconnectNamedPipe(pipe Handle) (err error)
//sys GetNamedPipeInfo(pipe Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) //sys GetNamedPipeInfo(pipe Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error)
//sys GetNamedPipeHandleState(pipe Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW //sys GetNamedPipeHandleState(pipe Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
//sys SetNamedPipeHandleState(pipe Handle, state *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32) (err error) = SetNamedPipeHandleState //sys SetNamedPipeHandleState(pipe Handle, state *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32) (err error) = SetNamedPipeHandleState
@@ -210,6 +194,7 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys GetComputerName(buf *uint16, n *uint32) (err error) = GetComputerNameW //sys GetComputerName(buf *uint16, n *uint32) (err error) = GetComputerNameW
//sys GetComputerNameEx(nametype uint32, buf *uint16, n *uint32) (err error) = GetComputerNameExW //sys GetComputerNameEx(nametype uint32, buf *uint16, n *uint32) (err error) = GetComputerNameExW
//sys SetEndOfFile(handle Handle) (err error) //sys SetEndOfFile(handle Handle) (err error)
//sys SetFileValidData(handle Handle, validDataLength int64) (err error)
//sys GetSystemTimeAsFileTime(time *Filetime) //sys GetSystemTimeAsFileTime(time *Filetime)
//sys GetSystemTimePreciseAsFileTime(time *Filetime) //sys GetSystemTimePreciseAsFileTime(time *Filetime)
//sys GetTimeZoneInformation(tzi *Timezoneinformation) (rc uint32, err error) [failretval==0xffffffff] //sys GetTimeZoneInformation(tzi *Timezoneinformation) (rc uint32, err error) [failretval==0xffffffff]
@@ -232,7 +217,7 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys shGetKnownFolderPath(id *KNOWNFOLDERID, flags uint32, token Token, path **uint16) (ret error) = shell32.SHGetKnownFolderPath //sys shGetKnownFolderPath(id *KNOWNFOLDERID, flags uint32, token Token, path **uint16) (ret error) = shell32.SHGetKnownFolderPath
//sys TerminateProcess(handle Handle, exitcode uint32) (err error) //sys TerminateProcess(handle Handle, exitcode uint32) (err error)
//sys GetExitCodeProcess(handle Handle, exitcode *uint32) (err error) //sys GetExitCodeProcess(handle Handle, exitcode *uint32) (err error)
//sys GetStartupInfo(startupInfo *StartupInfo) (err error) = GetStartupInfoW //sys getStartupInfo(startupInfo *StartupInfo) = GetStartupInfoW
//sys GetProcessTimes(handle Handle, creationTime *Filetime, exitTime *Filetime, kernelTime *Filetime, userTime *Filetime) (err error) //sys GetProcessTimes(handle Handle, creationTime *Filetime, exitTime *Filetime, kernelTime *Filetime, userTime *Filetime) (err error)
//sys DuplicateHandle(hSourceProcessHandle Handle, hSourceHandle Handle, hTargetProcessHandle Handle, lpTargetHandle *Handle, dwDesiredAccess uint32, bInheritHandle bool, dwOptions uint32) (err error) //sys DuplicateHandle(hSourceProcessHandle Handle, hSourceHandle Handle, hTargetProcessHandle Handle, lpTargetHandle *Handle, dwDesiredAccess uint32, bInheritHandle bool, dwOptions uint32) (err error)
//sys WaitForSingleObject(handle Handle, waitMilliseconds uint32) (event uint32, err error) [failretval==0xffffffff] //sys WaitForSingleObject(handle Handle, waitMilliseconds uint32) (event uint32, err error) [failretval==0xffffffff]
@@ -251,12 +236,13 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys CreateEnvironmentBlock(block **uint16, token Token, inheritExisting bool) (err error) = userenv.CreateEnvironmentBlock //sys CreateEnvironmentBlock(block **uint16, token Token, inheritExisting bool) (err error) = userenv.CreateEnvironmentBlock
//sys DestroyEnvironmentBlock(block *uint16) (err error) = userenv.DestroyEnvironmentBlock //sys DestroyEnvironmentBlock(block *uint16) (err error) = userenv.DestroyEnvironmentBlock
//sys getTickCount64() (ms uint64) = kernel32.GetTickCount64 //sys getTickCount64() (ms uint64) = kernel32.GetTickCount64
//sys GetFileTime(handle Handle, ctime *Filetime, atime *Filetime, wtime *Filetime) (err error)
//sys SetFileTime(handle Handle, ctime *Filetime, atime *Filetime, wtime *Filetime) (err error) //sys SetFileTime(handle Handle, ctime *Filetime, atime *Filetime, wtime *Filetime) (err error)
//sys GetFileAttributes(name *uint16) (attrs uint32, err error) [failretval==INVALID_FILE_ATTRIBUTES] = kernel32.GetFileAttributesW //sys GetFileAttributes(name *uint16) (attrs uint32, err error) [failretval==INVALID_FILE_ATTRIBUTES] = kernel32.GetFileAttributesW
//sys SetFileAttributes(name *uint16, attrs uint32) (err error) = kernel32.SetFileAttributesW //sys SetFileAttributes(name *uint16, attrs uint32) (err error) = kernel32.SetFileAttributesW
//sys GetFileAttributesEx(name *uint16, level uint32, info *byte) (err error) = kernel32.GetFileAttributesExW //sys GetFileAttributesEx(name *uint16, level uint32, info *byte) (err error) = kernel32.GetFileAttributesExW
//sys GetCommandLine() (cmd *uint16) = kernel32.GetCommandLineW //sys GetCommandLine() (cmd *uint16) = kernel32.GetCommandLineW
//sys CommandLineToArgv(cmd *uint16, argc *int32) (argv *[8192]*[8192]uint16, err error) [failretval==nil] = shell32.CommandLineToArgvW //sys commandLineToArgv(cmd *uint16, argc *int32) (argv **uint16, err error) [failretval==nil] = shell32.CommandLineToArgvW
//sys LocalFree(hmem Handle) (handle Handle, err error) [failretval!=0] //sys LocalFree(hmem Handle) (handle Handle, err error) [failretval!=0]
//sys LocalAlloc(flags uint32, length uint32) (ptr uintptr, err error) //sys LocalAlloc(flags uint32, length uint32) (ptr uintptr, err error)
//sys SetHandleInformation(handle Handle, mask uint32, flags uint32) (err error) //sys SetHandleInformation(handle Handle, mask uint32, flags uint32) (err error)
@@ -315,12 +301,15 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys RegNotifyChangeKeyValue(key Handle, watchSubtree bool, notifyFilter uint32, event Handle, asynchronous bool) (regerrno error) = advapi32.RegNotifyChangeKeyValue //sys RegNotifyChangeKeyValue(key Handle, watchSubtree bool, notifyFilter uint32, event Handle, asynchronous bool) (regerrno error) = advapi32.RegNotifyChangeKeyValue
//sys GetCurrentProcessId() (pid uint32) = kernel32.GetCurrentProcessId //sys GetCurrentProcessId() (pid uint32) = kernel32.GetCurrentProcessId
//sys ProcessIdToSessionId(pid uint32, sessionid *uint32) (err error) = kernel32.ProcessIdToSessionId //sys ProcessIdToSessionId(pid uint32, sessionid *uint32) (err error) = kernel32.ProcessIdToSessionId
//sys ClosePseudoConsole(console Handle) = kernel32.ClosePseudoConsole
//sys createPseudoConsole(size uint32, in Handle, out Handle, flags uint32, pconsole *Handle) (hr error) = kernel32.CreatePseudoConsole
//sys GetConsoleMode(console Handle, mode *uint32) (err error) = kernel32.GetConsoleMode //sys GetConsoleMode(console Handle, mode *uint32) (err error) = kernel32.GetConsoleMode
//sys SetConsoleMode(console Handle, mode uint32) (err error) = kernel32.SetConsoleMode //sys SetConsoleMode(console Handle, mode uint32) (err error) = kernel32.SetConsoleMode
//sys GetConsoleScreenBufferInfo(console Handle, info *ConsoleScreenBufferInfo) (err error) = kernel32.GetConsoleScreenBufferInfo //sys GetConsoleScreenBufferInfo(console Handle, info *ConsoleScreenBufferInfo) (err error) = kernel32.GetConsoleScreenBufferInfo
//sys setConsoleCursorPosition(console Handle, position uint32) (err error) = kernel32.SetConsoleCursorPosition //sys setConsoleCursorPosition(console Handle, position uint32) (err error) = kernel32.SetConsoleCursorPosition
//sys WriteConsole(console Handle, buf *uint16, towrite uint32, written *uint32, reserved *byte) (err error) = kernel32.WriteConsoleW //sys WriteConsole(console Handle, buf *uint16, towrite uint32, written *uint32, reserved *byte) (err error) = kernel32.WriteConsoleW
//sys ReadConsole(console Handle, buf *uint16, toread uint32, read *uint32, inputControl *byte) (err error) = kernel32.ReadConsoleW //sys ReadConsole(console Handle, buf *uint16, toread uint32, read *uint32, inputControl *byte) (err error) = kernel32.ReadConsoleW
//sys resizePseudoConsole(pconsole Handle, size uint32) (hr error) = kernel32.ResizePseudoConsole
//sys CreateToolhelp32Snapshot(flags uint32, processId uint32) (handle Handle, err error) [failretval==InvalidHandle] = kernel32.CreateToolhelp32Snapshot //sys CreateToolhelp32Snapshot(flags uint32, processId uint32) (handle Handle, err error) [failretval==InvalidHandle] = kernel32.CreateToolhelp32Snapshot
//sys Module32First(snapshot Handle, moduleEntry *ModuleEntry32) (err error) = kernel32.Module32FirstW //sys Module32First(snapshot Handle, moduleEntry *ModuleEntry32) (err error) = kernel32.Module32FirstW
//sys Module32Next(snapshot Handle, moduleEntry *ModuleEntry32) (err error) = kernel32.Module32NextW //sys Module32Next(snapshot Handle, moduleEntry *ModuleEntry32) (err error) = kernel32.Module32NextW
@@ -360,10 +349,31 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys SetProcessPriorityBoost(process Handle, disable bool) (err error) = kernel32.SetProcessPriorityBoost //sys SetProcessPriorityBoost(process Handle, disable bool) (err error) = kernel32.SetProcessPriorityBoost
//sys GetProcessWorkingSetSizeEx(hProcess Handle, lpMinimumWorkingSetSize *uintptr, lpMaximumWorkingSetSize *uintptr, flags *uint32) //sys GetProcessWorkingSetSizeEx(hProcess Handle, lpMinimumWorkingSetSize *uintptr, lpMaximumWorkingSetSize *uintptr, flags *uint32)
//sys SetProcessWorkingSetSizeEx(hProcess Handle, dwMinimumWorkingSetSize uintptr, dwMaximumWorkingSetSize uintptr, flags uint32) (err error) //sys SetProcessWorkingSetSizeEx(hProcess Handle, dwMinimumWorkingSetSize uintptr, dwMaximumWorkingSetSize uintptr, flags uint32) (err error)
//sys ClearCommBreak(handle Handle) (err error)
//sys ClearCommError(handle Handle, lpErrors *uint32, lpStat *ComStat) (err error)
//sys EscapeCommFunction(handle Handle, dwFunc uint32) (err error)
//sys GetCommState(handle Handle, lpDCB *DCB) (err error)
//sys GetCommModemStatus(handle Handle, lpModemStat *uint32) (err error)
//sys GetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) //sys GetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error)
//sys PurgeComm(handle Handle, dwFlags uint32) (err error)
//sys SetCommBreak(handle Handle) (err error)
//sys SetCommMask(handle Handle, dwEvtMask uint32) (err error)
//sys SetCommState(handle Handle, lpDCB *DCB) (err error)
//sys SetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) //sys SetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error)
//sys SetupComm(handle Handle, dwInQueue uint32, dwOutQueue uint32) (err error)
//sys WaitCommEvent(handle Handle, lpEvtMask *uint32, lpOverlapped *Overlapped) (err error)
//sys GetActiveProcessorCount(groupNumber uint16) (ret uint32) //sys GetActiveProcessorCount(groupNumber uint16) (ret uint32)
//sys GetMaximumProcessorCount(groupNumber uint16) (ret uint32) //sys GetMaximumProcessorCount(groupNumber uint16) (ret uint32)
//sys EnumWindows(enumFunc uintptr, param unsafe.Pointer) (err error) = user32.EnumWindows
//sys EnumChildWindows(hwnd HWND, enumFunc uintptr, param unsafe.Pointer) = user32.EnumChildWindows
//sys GetClassName(hwnd HWND, className *uint16, maxCount int32) (copied int32, err error) = user32.GetClassNameW
//sys GetDesktopWindow() (hwnd HWND) = user32.GetDesktopWindow
//sys GetForegroundWindow() (hwnd HWND) = user32.GetForegroundWindow
//sys IsWindow(hwnd HWND) (isWindow bool) = user32.IsWindow
//sys IsWindowUnicode(hwnd HWND) (isUnicode bool) = user32.IsWindowUnicode
//sys IsWindowVisible(hwnd HWND) (isVisible bool) = user32.IsWindowVisible
//sys GetGUIThreadInfo(thread uint32, info *GUIThreadInfo) (err error) = user32.GetGUIThreadInfo
//sys GetLargePageMinimum() (size uintptr)
// Volume Management Functions // Volume Management Functions
//sys DefineDosDevice(flags uint32, deviceName *uint16, targetPath *uint16) (err error) = DefineDosDeviceW //sys DefineDosDevice(flags uint32, deviceName *uint16, targetPath *uint16) (err error) = DefineDosDeviceW
@@ -411,7 +421,7 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys VerQueryValue(block unsafe.Pointer, subBlock string, pointerToBufferPointer unsafe.Pointer, bufSize *uint32) (err error) = version.VerQueryValueW //sys VerQueryValue(block unsafe.Pointer, subBlock string, pointerToBufferPointer unsafe.Pointer, bufSize *uint32) (err error) = version.VerQueryValueW
// Process Status API (PSAPI) // Process Status API (PSAPI)
//sys EnumProcesses(processIds []uint32, bytesReturned *uint32) (err error) = psapi.EnumProcesses //sys enumProcesses(processIds *uint32, nSize uint32, bytesReturned *uint32) (err error) = psapi.EnumProcesses
//sys EnumProcessModules(process Handle, module *Handle, cb uint32, cbNeeded *uint32) (err error) = psapi.EnumProcessModules //sys EnumProcessModules(process Handle, module *Handle, cb uint32, cbNeeded *uint32) (err error) = psapi.EnumProcessModules
//sys EnumProcessModulesEx(process Handle, module *Handle, cb uint32, cbNeeded *uint32, filterFlag uint32) (err error) = psapi.EnumProcessModulesEx //sys EnumProcessModulesEx(process Handle, module *Handle, cb uint32, cbNeeded *uint32, filterFlag uint32) (err error) = psapi.EnumProcessModulesEx
//sys GetModuleInformation(process Handle, module Handle, modinfo *ModuleInfo, cb uint32) (err error) = psapi.GetModuleInformation //sys GetModuleInformation(process Handle, module Handle, modinfo *ModuleInfo, cb uint32) (err error) = psapi.GetModuleInformation
@@ -439,6 +449,14 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys RtlAddFunctionTable(functionTable *RUNTIME_FUNCTION, entryCount uint32, baseAddress uintptr) (ret bool) = ntdll.RtlAddFunctionTable //sys RtlAddFunctionTable(functionTable *RUNTIME_FUNCTION, entryCount uint32, baseAddress uintptr) (ret bool) = ntdll.RtlAddFunctionTable
//sys RtlDeleteFunctionTable(functionTable *RUNTIME_FUNCTION) (ret bool) = ntdll.RtlDeleteFunctionTable //sys RtlDeleteFunctionTable(functionTable *RUNTIME_FUNCTION) (ret bool) = ntdll.RtlDeleteFunctionTable
// Desktop Window Manager API (Dwmapi)
//sys DwmGetWindowAttribute(hwnd HWND, attribute uint32, value unsafe.Pointer, size uint32) (ret error) = dwmapi.DwmGetWindowAttribute
//sys DwmSetWindowAttribute(hwnd HWND, attribute uint32, value unsafe.Pointer, size uint32) (ret error) = dwmapi.DwmSetWindowAttribute
// Windows Multimedia API
//sys TimeBeginPeriod (period uint32) (err error) [failretval != 0] = winmm.timeBeginPeriod
//sys TimeEndPeriod (period uint32) (err error) [failretval != 0] = winmm.timeEndPeriod
// syscall interface implementation for other packages // syscall interface implementation for other packages
// GetCurrentProcess returns the handle for the current process. // GetCurrentProcess returns the handle for the current process.
@@ -748,7 +766,7 @@ func Utimes(path string, tv []Timeval) (err error) {
if e != nil { if e != nil {
return e return e
} }
defer Close(h) defer CloseHandle(h)
a := NsecToFiletime(tv[0].Nanoseconds()) a := NsecToFiletime(tv[0].Nanoseconds())
w := NsecToFiletime(tv[1].Nanoseconds()) w := NsecToFiletime(tv[1].Nanoseconds())
return SetFileTime(h, nil, &a, &w) return SetFileTime(h, nil, &a, &w)
@@ -768,7 +786,7 @@ func UtimesNano(path string, ts []Timespec) (err error) {
if e != nil { if e != nil {
return e return e
} }
defer Close(h) defer CloseHandle(h)
a := NsecToFiletime(TimespecToNsec(ts[0])) a := NsecToFiletime(TimespecToNsec(ts[0]))
w := NsecToFiletime(TimespecToNsec(ts[1])) w := NsecToFiletime(TimespecToNsec(ts[1]))
return SetFileTime(h, nil, &a, &w) return SetFileTime(h, nil, &a, &w)
@@ -826,6 +844,9 @@ const socket_error = uintptr(^uint32(0))
//sys WSAStartup(verreq uint32, data *WSAData) (sockerr error) = ws2_32.WSAStartup //sys WSAStartup(verreq uint32, data *WSAData) (sockerr error) = ws2_32.WSAStartup
//sys WSACleanup() (err error) [failretval==socket_error] = ws2_32.WSACleanup //sys WSACleanup() (err error) [failretval==socket_error] = ws2_32.WSACleanup
//sys WSAIoctl(s Handle, iocc uint32, inbuf *byte, cbif uint32, outbuf *byte, cbob uint32, cbbr *uint32, overlapped *Overlapped, completionRoutine uintptr) (err error) [failretval==socket_error] = ws2_32.WSAIoctl //sys WSAIoctl(s Handle, iocc uint32, inbuf *byte, cbif uint32, outbuf *byte, cbob uint32, cbbr *uint32, overlapped *Overlapped, completionRoutine uintptr) (err error) [failretval==socket_error] = ws2_32.WSAIoctl
//sys WSALookupServiceBegin(querySet *WSAQUERYSET, flags uint32, handle *Handle) (err error) [failretval==socket_error] = ws2_32.WSALookupServiceBeginW
//sys WSALookupServiceNext(handle Handle, flags uint32, size *int32, querySet *WSAQUERYSET) (err error) [failretval==socket_error] = ws2_32.WSALookupServiceNextW
//sys WSALookupServiceEnd(handle Handle) (err error) [failretval==socket_error] = ws2_32.WSALookupServiceEnd
//sys socket(af int32, typ int32, protocol int32) (handle Handle, err error) [failretval==InvalidHandle] = ws2_32.socket //sys socket(af int32, typ int32, protocol int32) (handle Handle, err error) [failretval==InvalidHandle] = ws2_32.socket
//sys sendto(s Handle, buf []byte, flags int32, to unsafe.Pointer, tolen int32) (err error) [failretval==socket_error] = ws2_32.sendto //sys sendto(s Handle, buf []byte, flags int32, to unsafe.Pointer, tolen int32) (err error) [failretval==socket_error] = ws2_32.sendto
//sys recvfrom(s Handle, buf []byte, flags int32, from *RawSockaddrAny, fromlen *int32) (n int32, err error) [failretval==-1] = ws2_32.recvfrom //sys recvfrom(s Handle, buf []byte, flags int32, from *RawSockaddrAny, fromlen *int32) (n int32, err error) [failretval==-1] = ws2_32.recvfrom
@@ -963,7 +984,8 @@ func (sa *SockaddrUnix) sockaddr() (unsafe.Pointer, int32, error) {
if n > 0 { if n > 0 {
sl += int32(n) + 1 sl += int32(n) + 1
} }
if sa.raw.Path[0] == '@' { if sa.raw.Path[0] == '@' || (sa.raw.Path[0] == 0 && sl > 3) {
// Check sl > 3 so we don't change unnamed socket behavior.
sa.raw.Path[0] = 0 sa.raw.Path[0] = 0
// Don't count trailing NUL for abstract address. // Don't count trailing NUL for abstract address.
sl-- sl--
@@ -1021,8 +1043,7 @@ func (rsa *RawSockaddrAny) Sockaddr() (Sockaddr, error) {
for n < len(pp.Path) && pp.Path[n] != 0 { for n < len(pp.Path) && pp.Path[n] != 0 {
n++ n++
} }
bytes := (*[len(pp.Path)]byte)(unsafe.Pointer(&pp.Path[0]))[0:n] sa.Name = string(unsafe.Slice((*byte)(unsafe.Pointer(&pp.Path[0])), n))
sa.Name = string(bytes)
return sa, nil return sa, nil
case AF_INET: case AF_INET:
@@ -1108,10 +1129,14 @@ func Shutdown(fd Handle, how int) (err error) {
} }
func WSASendto(s Handle, bufs *WSABuf, bufcnt uint32, sent *uint32, flags uint32, to Sockaddr, overlapped *Overlapped, croutine *byte) (err error) { func WSASendto(s Handle, bufs *WSABuf, bufcnt uint32, sent *uint32, flags uint32, to Sockaddr, overlapped *Overlapped, croutine *byte) (err error) {
rsa, l, err := to.sockaddr() var rsa unsafe.Pointer
var l int32
if to != nil {
rsa, l, err = to.sockaddr()
if err != nil { if err != nil {
return err return err
} }
}
return WSASendTo(s, bufs, bufcnt, sent, flags, (*RawSockaddrAny)(unsafe.Pointer(rsa)), l, overlapped, croutine) return WSASendTo(s, bufs, bufcnt, sent, flags, (*RawSockaddrAny)(unsafe.Pointer(rsa)), l, overlapped, croutine)
} }
@@ -1350,6 +1375,17 @@ func SetsockoptIPv6Mreq(fd Handle, level, opt int, mreq *IPv6Mreq) (err error) {
return syscall.EWINDOWS return syscall.EWINDOWS
} }
func EnumProcesses(processIds []uint32, bytesReturned *uint32) error {
// EnumProcesses syscall expects the size parameter to be in bytes, but the code generated with mksyscall uses
// the length of the processIds slice instead. Hence, this wrapper function is added to fix the discrepancy.
var p *uint32
if len(processIds) > 0 {
p = &processIds[0]
}
size := uint32(len(processIds) * 4)
return enumProcesses(p, size, bytesReturned)
}
func Getpid() (pid int) { return int(GetCurrentProcessId()) } func Getpid() (pid int) { return int(GetCurrentProcessId()) }
func FindFirstFile(name *uint16, data *Win32finddata) (handle Handle, err error) { func FindFirstFile(name *uint16, data *Win32finddata) (handle Handle, err error) {
@@ -1609,6 +1645,11 @@ func SetConsoleCursorPosition(console Handle, position Coord) error {
return setConsoleCursorPosition(console, *((*uint32)(unsafe.Pointer(&position)))) return setConsoleCursorPosition(console, *((*uint32)(unsafe.Pointer(&position))))
} }
func GetStartupInfo(startupInfo *StartupInfo) error {
getStartupInfo(startupInfo)
return nil
}
func (s NTStatus) Errno() syscall.Errno { func (s NTStatus) Errno() syscall.Errno {
return rtlNtStatusToDosErrorNoTeb(s) return rtlNtStatusToDosErrorNoTeb(s)
} }
@@ -1643,12 +1684,8 @@ func NewNTUnicodeString(s string) (*NTUnicodeString, error) {
// Slice returns a uint16 slice that aliases the data in the NTUnicodeString. // Slice returns a uint16 slice that aliases the data in the NTUnicodeString.
func (s *NTUnicodeString) Slice() []uint16 { func (s *NTUnicodeString) Slice() []uint16 {
var slice []uint16 slice := unsafe.Slice(s.Buffer, s.MaximumLength)
hdr := (*unsafeheader.Slice)(unsafe.Pointer(&slice)) return slice[:s.Length]
hdr.Data = unsafe.Pointer(s.Buffer)
hdr.Len = int(s.Length)
hdr.Cap = int(s.MaximumLength)
return slice
} }
func (s *NTUnicodeString) String() string { func (s *NTUnicodeString) String() string {
@@ -1671,12 +1708,8 @@ func NewNTString(s string) (*NTString, error) {
// Slice returns a byte slice that aliases the data in the NTString. // Slice returns a byte slice that aliases the data in the NTString.
func (s *NTString) Slice() []byte { func (s *NTString) Slice() []byte {
var slice []byte slice := unsafe.Slice(s.Buffer, s.MaximumLength)
hdr := (*unsafeheader.Slice)(unsafe.Pointer(&slice)) return slice[:s.Length]
hdr.Data = unsafe.Pointer(s.Buffer)
hdr.Len = int(s.Length)
hdr.Cap = int(s.MaximumLength)
return slice
} }
func (s *NTString) String() string { func (s *NTString) String() string {
@@ -1728,10 +1761,7 @@ func LoadResourceData(module, resInfo Handle) (data []byte, err error) {
if err != nil { if err != nil {
return return
} }
h := (*unsafeheader.Slice)(unsafe.Pointer(&data)) data = unsafe.Slice((*byte)(unsafe.Pointer(ptr)), size)
h.Data = unsafe.Pointer(ptr)
h.Len = int(size)
h.Cap = int(size)
return return
} }
@@ -1802,3 +1832,87 @@ type PSAPI_WORKING_SET_EX_INFORMATION struct {
// A PSAPI_WORKING_SET_EX_BLOCK union that indicates the attributes of the page at VirtualAddress. // A PSAPI_WORKING_SET_EX_BLOCK union that indicates the attributes of the page at VirtualAddress.
VirtualAttributes PSAPI_WORKING_SET_EX_BLOCK VirtualAttributes PSAPI_WORKING_SET_EX_BLOCK
} }
// CreatePseudoConsole creates a windows pseudo console.
func CreatePseudoConsole(size Coord, in Handle, out Handle, flags uint32, pconsole *Handle) error {
// We need this wrapper to manually cast Coord to uint32. The autogenerated wrappers only
// accept arguments that can be casted to uintptr, and Coord can't.
return createPseudoConsole(*((*uint32)(unsafe.Pointer(&size))), in, out, flags, pconsole)
}
// ResizePseudoConsole resizes the internal buffers of the pseudo console to the width and height specified in `size`.
func ResizePseudoConsole(pconsole Handle, size Coord) error {
// We need this wrapper to manually cast Coord to uint32. The autogenerated wrappers only
// accept arguments that can be casted to uintptr, and Coord can't.
return resizePseudoConsole(pconsole, *((*uint32)(unsafe.Pointer(&size))))
}
// DCB constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-dcb.
const (
CBR_110 = 110
CBR_300 = 300
CBR_600 = 600
CBR_1200 = 1200
CBR_2400 = 2400
CBR_4800 = 4800
CBR_9600 = 9600
CBR_14400 = 14400
CBR_19200 = 19200
CBR_38400 = 38400
CBR_57600 = 57600
CBR_115200 = 115200
CBR_128000 = 128000
CBR_256000 = 256000
DTR_CONTROL_DISABLE = 0x00000000
DTR_CONTROL_ENABLE = 0x00000010
DTR_CONTROL_HANDSHAKE = 0x00000020
RTS_CONTROL_DISABLE = 0x00000000
RTS_CONTROL_ENABLE = 0x00001000
RTS_CONTROL_HANDSHAKE = 0x00002000
RTS_CONTROL_TOGGLE = 0x00003000
NOPARITY = 0
ODDPARITY = 1
EVENPARITY = 2
MARKPARITY = 3
SPACEPARITY = 4
ONESTOPBIT = 0
ONE5STOPBITS = 1
TWOSTOPBITS = 2
)
// EscapeCommFunction constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-escapecommfunction.
const (
SETXOFF = 1
SETXON = 2
SETRTS = 3
CLRRTS = 4
SETDTR = 5
CLRDTR = 6
SETBREAK = 8
CLRBREAK = 9
)
// PurgeComm constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-purgecomm.
const (
PURGE_TXABORT = 0x0001
PURGE_RXABORT = 0x0002
PURGE_TXCLEAR = 0x0004
PURGE_RXCLEAR = 0x0008
)
// SetCommMask constants. See https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setcommmask.
const (
EV_RXCHAR = 0x0001
EV_RXFLAG = 0x0002
EV_TXEMPTY = 0x0004
EV_CTS = 0x0008
EV_DSR = 0x0010
EV_RLSD = 0x0020
EV_BREAK = 0x0040
EV_ERR = 0x0080
EV_RING = 0x0100
)
+194 -3
View File
@@ -247,6 +247,7 @@ const (
PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY = 0x00020007 PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY = 0x00020007
PROC_THREAD_ATTRIBUTE_UMS_THREAD = 0x00030006 PROC_THREAD_ATTRIBUTE_UMS_THREAD = 0x00030006
PROC_THREAD_ATTRIBUTE_PROTECTION_LEVEL = 0x0002000b PROC_THREAD_ATTRIBUTE_PROTECTION_LEVEL = 0x0002000b
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = 0x00020016
) )
const ( const (
@@ -1094,6 +1095,32 @@ const (
SOMAXCONN = 0x7fffffff SOMAXCONN = 0x7fffffff
TCP_NODELAY = 1 TCP_NODELAY = 1
TCP_EXPEDITED_1122 = 2
TCP_KEEPALIVE = 3
TCP_MAXSEG = 4
TCP_MAXRT = 5
TCP_STDURG = 6
TCP_NOURG = 7
TCP_ATMARK = 8
TCP_NOSYNRETRIES = 9
TCP_TIMESTAMPS = 10
TCP_OFFLOAD_PREFERENCE = 11
TCP_CONGESTION_ALGORITHM = 12
TCP_DELAY_FIN_ACK = 13
TCP_MAXRTMS = 14
TCP_FASTOPEN = 15
TCP_KEEPCNT = 16
TCP_KEEPIDLE = TCP_KEEPALIVE
TCP_KEEPINTVL = 17
TCP_FAIL_CONNECT_ON_ICMP_ERROR = 18
TCP_ICMP_ERROR_INFO = 19
UDP_NOCHECKSUM = 1
UDP_SEND_MSG_SIZE = 2
UDP_RECV_MAX_COALESCED_SIZE = 3
UDP_CHECKSUM_COVERAGE = 20
UDP_COALESCED_INFO = 3
SHUT_RD = 0 SHUT_RD = 0
SHUT_WR = 1 SHUT_WR = 1
@@ -1243,6 +1270,51 @@ const (
DnsSectionAdditional = 0x0003 DnsSectionAdditional = 0x0003
) )
const (
// flags of WSALookupService
LUP_DEEP = 0x0001
LUP_CONTAINERS = 0x0002
LUP_NOCONTAINERS = 0x0004
LUP_NEAREST = 0x0008
LUP_RETURN_NAME = 0x0010
LUP_RETURN_TYPE = 0x0020
LUP_RETURN_VERSION = 0x0040
LUP_RETURN_COMMENT = 0x0080
LUP_RETURN_ADDR = 0x0100
LUP_RETURN_BLOB = 0x0200
LUP_RETURN_ALIASES = 0x0400
LUP_RETURN_QUERY_STRING = 0x0800
LUP_RETURN_ALL = 0x0FF0
LUP_RES_SERVICE = 0x8000
LUP_FLUSHCACHE = 0x1000
LUP_FLUSHPREVIOUS = 0x2000
LUP_NON_AUTHORITATIVE = 0x4000
LUP_SECURE = 0x8000
LUP_RETURN_PREFERRED_NAMES = 0x10000
LUP_DNS_ONLY = 0x20000
LUP_ADDRCONFIG = 0x100000
LUP_DUAL_ADDR = 0x200000
LUP_FILESERVER = 0x400000
LUP_DISABLE_IDN_ENCODING = 0x00800000
LUP_API_ANSI = 0x01000000
LUP_RESOLUTION_HANDLE = 0x80000000
)
const (
// values of WSAQUERYSET's namespace
NS_ALL = 0
NS_DNS = 12
NS_NLA = 15
NS_BTH = 16
NS_EMAIL = 37
NS_PNRPNAME = 38
NS_PNRPCLOUD = 39
)
type DNSSRVData struct { type DNSSRVData struct {
Target *uint16 Target *uint16
Priority uint16 Priority uint16
@@ -2094,6 +2166,12 @@ const (
ENABLE_LVB_GRID_WORLDWIDE = 0x10 ENABLE_LVB_GRID_WORLDWIDE = 0x10
) )
// Pseudo console related constants used for the flags parameter to
// CreatePseudoConsole. See: https://learn.microsoft.com/en-us/windows/console/createpseudoconsole
const (
PSEUDOCONSOLE_INHERIT_CURSOR = 0x1
)
type Coord struct { type Coord struct {
X int16 X int16
Y int16 Y int16
@@ -2175,19 +2253,23 @@ type JOBOBJECT_BASIC_UI_RESTRICTIONS struct {
} }
const ( const (
// JobObjectInformationClass // JobObjectInformationClass for QueryInformationJobObject and SetInformationJobObject
JobObjectAssociateCompletionPortInformation = 7 JobObjectAssociateCompletionPortInformation = 7
JobObjectBasicAccountingInformation = 1
JobObjectBasicAndIoAccountingInformation = 8
JobObjectBasicLimitInformation = 2 JobObjectBasicLimitInformation = 2
JobObjectBasicProcessIdList = 3
JobObjectBasicUIRestrictions = 4 JobObjectBasicUIRestrictions = 4
JobObjectCpuRateControlInformation = 15 JobObjectCpuRateControlInformation = 15
JobObjectEndOfJobTimeInformation = 6 JobObjectEndOfJobTimeInformation = 6
JobObjectExtendedLimitInformation = 9 JobObjectExtendedLimitInformation = 9
JobObjectGroupInformation = 11 JobObjectGroupInformation = 11
JobObjectGroupInformationEx = 14 JobObjectGroupInformationEx = 14
JobObjectLimitViolationInformation2 = 35 JobObjectLimitViolationInformation = 13
JobObjectLimitViolationInformation2 = 34
JobObjectNetRateControlInformation = 32 JobObjectNetRateControlInformation = 32
JobObjectNotificationLimitInformation = 12 JobObjectNotificationLimitInformation = 12
JobObjectNotificationLimitInformation2 = 34 JobObjectNotificationLimitInformation2 = 33
JobObjectSecurityLimitInformation = 5 JobObjectSecurityLimitInformation = 5
) )
@@ -3213,3 +3295,112 @@ type ModuleInfo struct {
} }
const ALL_PROCESSOR_GROUPS = 0xFFFF const ALL_PROCESSOR_GROUPS = 0xFFFF
type Rect struct {
Left int32
Top int32
Right int32
Bottom int32
}
type GUIThreadInfo struct {
Size uint32
Flags uint32
Active HWND
Focus HWND
Capture HWND
MenuOwner HWND
MoveSize HWND
CaretHandle HWND
CaretRect Rect
}
const (
DWMWA_NCRENDERING_ENABLED = 1
DWMWA_NCRENDERING_POLICY = 2
DWMWA_TRANSITIONS_FORCEDISABLED = 3
DWMWA_ALLOW_NCPAINT = 4
DWMWA_CAPTION_BUTTON_BOUNDS = 5
DWMWA_NONCLIENT_RTL_LAYOUT = 6
DWMWA_FORCE_ICONIC_REPRESENTATION = 7
DWMWA_FLIP3D_POLICY = 8
DWMWA_EXTENDED_FRAME_BOUNDS = 9
DWMWA_HAS_ICONIC_BITMAP = 10
DWMWA_DISALLOW_PEEK = 11
DWMWA_EXCLUDED_FROM_PEEK = 12
DWMWA_CLOAK = 13
DWMWA_CLOAKED = 14
DWMWA_FREEZE_REPRESENTATION = 15
DWMWA_PASSIVE_UPDATE_MODE = 16
DWMWA_USE_HOSTBACKDROPBRUSH = 17
DWMWA_USE_IMMERSIVE_DARK_MODE = 20
DWMWA_WINDOW_CORNER_PREFERENCE = 33
DWMWA_BORDER_COLOR = 34
DWMWA_CAPTION_COLOR = 35
DWMWA_TEXT_COLOR = 36
DWMWA_VISIBLE_FRAME_BORDER_THICKNESS = 37
)
type WSAQUERYSET struct {
Size uint32
ServiceInstanceName *uint16
ServiceClassId *GUID
Version *WSAVersion
Comment *uint16
NameSpace uint32
NSProviderId *GUID
Context *uint16
NumberOfProtocols uint32
AfpProtocols *AFProtocols
QueryString *uint16
NumberOfCsAddrs uint32
SaBuffer *CSAddrInfo
OutputFlags uint32
Blob *BLOB
}
type WSAVersion struct {
Version uint32
EnumerationOfComparison int32
}
type AFProtocols struct {
AddressFamily int32
Protocol int32
}
type CSAddrInfo struct {
LocalAddr SocketAddress
RemoteAddr SocketAddress
SocketType int32
Protocol int32
}
type BLOB struct {
Size uint32
BlobData *byte
}
type ComStat struct {
Flags uint32
CBInQue uint32
CBOutQue uint32
}
type DCB struct {
DCBlength uint32
BaudRate uint32
Flags uint32
wReserved uint16
XonLim uint16
XoffLim uint16
ByteSize uint8
Parity uint8
StopBits uint8
XonChar byte
XoffChar byte
ErrorChar byte
EofChar byte
EvtChar byte
wReserved1 uint16
}
+325 -13
View File
@@ -40,6 +40,7 @@ var (
modadvapi32 = NewLazySystemDLL("advapi32.dll") modadvapi32 = NewLazySystemDLL("advapi32.dll")
modcrypt32 = NewLazySystemDLL("crypt32.dll") modcrypt32 = NewLazySystemDLL("crypt32.dll")
moddnsapi = NewLazySystemDLL("dnsapi.dll") moddnsapi = NewLazySystemDLL("dnsapi.dll")
moddwmapi = NewLazySystemDLL("dwmapi.dll")
modiphlpapi = NewLazySystemDLL("iphlpapi.dll") modiphlpapi = NewLazySystemDLL("iphlpapi.dll")
modkernel32 = NewLazySystemDLL("kernel32.dll") modkernel32 = NewLazySystemDLL("kernel32.dll")
modmswsock = NewLazySystemDLL("mswsock.dll") modmswsock = NewLazySystemDLL("mswsock.dll")
@@ -54,6 +55,7 @@ var (
moduser32 = NewLazySystemDLL("user32.dll") moduser32 = NewLazySystemDLL("user32.dll")
moduserenv = NewLazySystemDLL("userenv.dll") moduserenv = NewLazySystemDLL("userenv.dll")
modversion = NewLazySystemDLL("version.dll") modversion = NewLazySystemDLL("version.dll")
modwinmm = NewLazySystemDLL("winmm.dll")
modwintrust = NewLazySystemDLL("wintrust.dll") modwintrust = NewLazySystemDLL("wintrust.dll")
modws2_32 = NewLazySystemDLL("ws2_32.dll") modws2_32 = NewLazySystemDLL("ws2_32.dll")
modwtsapi32 = NewLazySystemDLL("wtsapi32.dll") modwtsapi32 = NewLazySystemDLL("wtsapi32.dll")
@@ -85,6 +87,7 @@ var (
procDeleteService = modadvapi32.NewProc("DeleteService") procDeleteService = modadvapi32.NewProc("DeleteService")
procDeregisterEventSource = modadvapi32.NewProc("DeregisterEventSource") procDeregisterEventSource = modadvapi32.NewProc("DeregisterEventSource")
procDuplicateTokenEx = modadvapi32.NewProc("DuplicateTokenEx") procDuplicateTokenEx = modadvapi32.NewProc("DuplicateTokenEx")
procEnumDependentServicesW = modadvapi32.NewProc("EnumDependentServicesW")
procEnumServicesStatusExW = modadvapi32.NewProc("EnumServicesStatusExW") procEnumServicesStatusExW = modadvapi32.NewProc("EnumServicesStatusExW")
procEqualSid = modadvapi32.NewProc("EqualSid") procEqualSid = modadvapi32.NewProc("EqualSid")
procFreeSid = modadvapi32.NewProc("FreeSid") procFreeSid = modadvapi32.NewProc("FreeSid")
@@ -175,14 +178,20 @@ var (
procDnsNameCompare_W = moddnsapi.NewProc("DnsNameCompare_W") procDnsNameCompare_W = moddnsapi.NewProc("DnsNameCompare_W")
procDnsQuery_W = moddnsapi.NewProc("DnsQuery_W") procDnsQuery_W = moddnsapi.NewProc("DnsQuery_W")
procDnsRecordListFree = moddnsapi.NewProc("DnsRecordListFree") procDnsRecordListFree = moddnsapi.NewProc("DnsRecordListFree")
procDwmGetWindowAttribute = moddwmapi.NewProc("DwmGetWindowAttribute")
procDwmSetWindowAttribute = moddwmapi.NewProc("DwmSetWindowAttribute")
procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses") procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses")
procGetAdaptersInfo = modiphlpapi.NewProc("GetAdaptersInfo") procGetAdaptersInfo = modiphlpapi.NewProc("GetAdaptersInfo")
procGetBestInterfaceEx = modiphlpapi.NewProc("GetBestInterfaceEx") procGetBestInterfaceEx = modiphlpapi.NewProc("GetBestInterfaceEx")
procGetIfEntry = modiphlpapi.NewProc("GetIfEntry") procGetIfEntry = modiphlpapi.NewProc("GetIfEntry")
procAddDllDirectory = modkernel32.NewProc("AddDllDirectory")
procAssignProcessToJobObject = modkernel32.NewProc("AssignProcessToJobObject") procAssignProcessToJobObject = modkernel32.NewProc("AssignProcessToJobObject")
procCancelIo = modkernel32.NewProc("CancelIo") procCancelIo = modkernel32.NewProc("CancelIo")
procCancelIoEx = modkernel32.NewProc("CancelIoEx") procCancelIoEx = modkernel32.NewProc("CancelIoEx")
procClearCommBreak = modkernel32.NewProc("ClearCommBreak")
procClearCommError = modkernel32.NewProc("ClearCommError")
procCloseHandle = modkernel32.NewProc("CloseHandle") procCloseHandle = modkernel32.NewProc("CloseHandle")
procClosePseudoConsole = modkernel32.NewProc("ClosePseudoConsole")
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe") procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
procCreateDirectoryW = modkernel32.NewProc("CreateDirectoryW") procCreateDirectoryW = modkernel32.NewProc("CreateDirectoryW")
procCreateEventExW = modkernel32.NewProc("CreateEventExW") procCreateEventExW = modkernel32.NewProc("CreateEventExW")
@@ -197,6 +206,7 @@ var (
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW") procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
procCreatePipe = modkernel32.NewProc("CreatePipe") procCreatePipe = modkernel32.NewProc("CreatePipe")
procCreateProcessW = modkernel32.NewProc("CreateProcessW") procCreateProcessW = modkernel32.NewProc("CreateProcessW")
procCreatePseudoConsole = modkernel32.NewProc("CreatePseudoConsole")
procCreateSymbolicLinkW = modkernel32.NewProc("CreateSymbolicLinkW") procCreateSymbolicLinkW = modkernel32.NewProc("CreateSymbolicLinkW")
procCreateToolhelp32Snapshot = modkernel32.NewProc("CreateToolhelp32Snapshot") procCreateToolhelp32Snapshot = modkernel32.NewProc("CreateToolhelp32Snapshot")
procDefineDosDeviceW = modkernel32.NewProc("DefineDosDeviceW") procDefineDosDeviceW = modkernel32.NewProc("DefineDosDeviceW")
@@ -204,7 +214,9 @@ var (
procDeleteProcThreadAttributeList = modkernel32.NewProc("DeleteProcThreadAttributeList") procDeleteProcThreadAttributeList = modkernel32.NewProc("DeleteProcThreadAttributeList")
procDeleteVolumeMountPointW = modkernel32.NewProc("DeleteVolumeMountPointW") procDeleteVolumeMountPointW = modkernel32.NewProc("DeleteVolumeMountPointW")
procDeviceIoControl = modkernel32.NewProc("DeviceIoControl") procDeviceIoControl = modkernel32.NewProc("DeviceIoControl")
procDisconnectNamedPipe = modkernel32.NewProc("DisconnectNamedPipe")
procDuplicateHandle = modkernel32.NewProc("DuplicateHandle") procDuplicateHandle = modkernel32.NewProc("DuplicateHandle")
procEscapeCommFunction = modkernel32.NewProc("EscapeCommFunction")
procExitProcess = modkernel32.NewProc("ExitProcess") procExitProcess = modkernel32.NewProc("ExitProcess")
procExpandEnvironmentStringsW = modkernel32.NewProc("ExpandEnvironmentStringsW") procExpandEnvironmentStringsW = modkernel32.NewProc("ExpandEnvironmentStringsW")
procFindClose = modkernel32.NewProc("FindClose") procFindClose = modkernel32.NewProc("FindClose")
@@ -228,6 +240,8 @@ var (
procGenerateConsoleCtrlEvent = modkernel32.NewProc("GenerateConsoleCtrlEvent") procGenerateConsoleCtrlEvent = modkernel32.NewProc("GenerateConsoleCtrlEvent")
procGetACP = modkernel32.NewProc("GetACP") procGetACP = modkernel32.NewProc("GetACP")
procGetActiveProcessorCount = modkernel32.NewProc("GetActiveProcessorCount") procGetActiveProcessorCount = modkernel32.NewProc("GetActiveProcessorCount")
procGetCommModemStatus = modkernel32.NewProc("GetCommModemStatus")
procGetCommState = modkernel32.NewProc("GetCommState")
procGetCommTimeouts = modkernel32.NewProc("GetCommTimeouts") procGetCommTimeouts = modkernel32.NewProc("GetCommTimeouts")
procGetCommandLineW = modkernel32.NewProc("GetCommandLineW") procGetCommandLineW = modkernel32.NewProc("GetCommandLineW")
procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW") procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW")
@@ -246,9 +260,11 @@ var (
procGetFileAttributesW = modkernel32.NewProc("GetFileAttributesW") procGetFileAttributesW = modkernel32.NewProc("GetFileAttributesW")
procGetFileInformationByHandle = modkernel32.NewProc("GetFileInformationByHandle") procGetFileInformationByHandle = modkernel32.NewProc("GetFileInformationByHandle")
procGetFileInformationByHandleEx = modkernel32.NewProc("GetFileInformationByHandleEx") procGetFileInformationByHandleEx = modkernel32.NewProc("GetFileInformationByHandleEx")
procGetFileTime = modkernel32.NewProc("GetFileTime")
procGetFileType = modkernel32.NewProc("GetFileType") procGetFileType = modkernel32.NewProc("GetFileType")
procGetFinalPathNameByHandleW = modkernel32.NewProc("GetFinalPathNameByHandleW") procGetFinalPathNameByHandleW = modkernel32.NewProc("GetFinalPathNameByHandleW")
procGetFullPathNameW = modkernel32.NewProc("GetFullPathNameW") procGetFullPathNameW = modkernel32.NewProc("GetFullPathNameW")
procGetLargePageMinimum = modkernel32.NewProc("GetLargePageMinimum")
procGetLastError = modkernel32.NewProc("GetLastError") procGetLastError = modkernel32.NewProc("GetLastError")
procGetLogicalDriveStringsW = modkernel32.NewProc("GetLogicalDriveStringsW") procGetLogicalDriveStringsW = modkernel32.NewProc("GetLogicalDriveStringsW")
procGetLogicalDrives = modkernel32.NewProc("GetLogicalDrives") procGetLogicalDrives = modkernel32.NewProc("GetLogicalDrives")
@@ -312,6 +328,7 @@ var (
procProcess32NextW = modkernel32.NewProc("Process32NextW") procProcess32NextW = modkernel32.NewProc("Process32NextW")
procProcessIdToSessionId = modkernel32.NewProc("ProcessIdToSessionId") procProcessIdToSessionId = modkernel32.NewProc("ProcessIdToSessionId")
procPulseEvent = modkernel32.NewProc("PulseEvent") procPulseEvent = modkernel32.NewProc("PulseEvent")
procPurgeComm = modkernel32.NewProc("PurgeComm")
procQueryDosDeviceW = modkernel32.NewProc("QueryDosDeviceW") procQueryDosDeviceW = modkernel32.NewProc("QueryDosDeviceW")
procQueryFullProcessImageNameW = modkernel32.NewProc("QueryFullProcessImageNameW") procQueryFullProcessImageNameW = modkernel32.NewProc("QueryFullProcessImageNameW")
procQueryInformationJobObject = modkernel32.NewProc("QueryInformationJobObject") procQueryInformationJobObject = modkernel32.NewProc("QueryInformationJobObject")
@@ -321,8 +338,13 @@ var (
procReadProcessMemory = modkernel32.NewProc("ReadProcessMemory") procReadProcessMemory = modkernel32.NewProc("ReadProcessMemory")
procReleaseMutex = modkernel32.NewProc("ReleaseMutex") procReleaseMutex = modkernel32.NewProc("ReleaseMutex")
procRemoveDirectoryW = modkernel32.NewProc("RemoveDirectoryW") procRemoveDirectoryW = modkernel32.NewProc("RemoveDirectoryW")
procRemoveDllDirectory = modkernel32.NewProc("RemoveDllDirectory")
procResetEvent = modkernel32.NewProc("ResetEvent") procResetEvent = modkernel32.NewProc("ResetEvent")
procResizePseudoConsole = modkernel32.NewProc("ResizePseudoConsole")
procResumeThread = modkernel32.NewProc("ResumeThread") procResumeThread = modkernel32.NewProc("ResumeThread")
procSetCommBreak = modkernel32.NewProc("SetCommBreak")
procSetCommMask = modkernel32.NewProc("SetCommMask")
procSetCommState = modkernel32.NewProc("SetCommState")
procSetCommTimeouts = modkernel32.NewProc("SetCommTimeouts") procSetCommTimeouts = modkernel32.NewProc("SetCommTimeouts")
procSetConsoleCursorPosition = modkernel32.NewProc("SetConsoleCursorPosition") procSetConsoleCursorPosition = modkernel32.NewProc("SetConsoleCursorPosition")
procSetConsoleMode = modkernel32.NewProc("SetConsoleMode") procSetConsoleMode = modkernel32.NewProc("SetConsoleMode")
@@ -338,6 +360,7 @@ var (
procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle") procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle")
procSetFilePointer = modkernel32.NewProc("SetFilePointer") procSetFilePointer = modkernel32.NewProc("SetFilePointer")
procSetFileTime = modkernel32.NewProc("SetFileTime") procSetFileTime = modkernel32.NewProc("SetFileTime")
procSetFileValidData = modkernel32.NewProc("SetFileValidData")
procSetHandleInformation = modkernel32.NewProc("SetHandleInformation") procSetHandleInformation = modkernel32.NewProc("SetHandleInformation")
procSetInformationJobObject = modkernel32.NewProc("SetInformationJobObject") procSetInformationJobObject = modkernel32.NewProc("SetInformationJobObject")
procSetNamedPipeHandleState = modkernel32.NewProc("SetNamedPipeHandleState") procSetNamedPipeHandleState = modkernel32.NewProc("SetNamedPipeHandleState")
@@ -348,6 +371,7 @@ var (
procSetStdHandle = modkernel32.NewProc("SetStdHandle") procSetStdHandle = modkernel32.NewProc("SetStdHandle")
procSetVolumeLabelW = modkernel32.NewProc("SetVolumeLabelW") procSetVolumeLabelW = modkernel32.NewProc("SetVolumeLabelW")
procSetVolumeMountPointW = modkernel32.NewProc("SetVolumeMountPointW") procSetVolumeMountPointW = modkernel32.NewProc("SetVolumeMountPointW")
procSetupComm = modkernel32.NewProc("SetupComm")
procSizeofResource = modkernel32.NewProc("SizeofResource") procSizeofResource = modkernel32.NewProc("SizeofResource")
procSleepEx = modkernel32.NewProc("SleepEx") procSleepEx = modkernel32.NewProc("SleepEx")
procTerminateJobObject = modkernel32.NewProc("TerminateJobObject") procTerminateJobObject = modkernel32.NewProc("TerminateJobObject")
@@ -366,6 +390,7 @@ var (
procVirtualQueryEx = modkernel32.NewProc("VirtualQueryEx") procVirtualQueryEx = modkernel32.NewProc("VirtualQueryEx")
procVirtualUnlock = modkernel32.NewProc("VirtualUnlock") procVirtualUnlock = modkernel32.NewProc("VirtualUnlock")
procWTSGetActiveConsoleSessionId = modkernel32.NewProc("WTSGetActiveConsoleSessionId") procWTSGetActiveConsoleSessionId = modkernel32.NewProc("WTSGetActiveConsoleSessionId")
procWaitCommEvent = modkernel32.NewProc("WaitCommEvent")
procWaitForMultipleObjects = modkernel32.NewProc("WaitForMultipleObjects") procWaitForMultipleObjects = modkernel32.NewProc("WaitForMultipleObjects")
procWaitForSingleObject = modkernel32.NewProc("WaitForSingleObject") procWaitForSingleObject = modkernel32.NewProc("WaitForSingleObject")
procWriteConsoleW = modkernel32.NewProc("WriteConsoleW") procWriteConsoleW = modkernel32.NewProc("WriteConsoleW")
@@ -444,9 +469,18 @@ var (
procCommandLineToArgvW = modshell32.NewProc("CommandLineToArgvW") procCommandLineToArgvW = modshell32.NewProc("CommandLineToArgvW")
procSHGetKnownFolderPath = modshell32.NewProc("SHGetKnownFolderPath") procSHGetKnownFolderPath = modshell32.NewProc("SHGetKnownFolderPath")
procShellExecuteW = modshell32.NewProc("ShellExecuteW") procShellExecuteW = modshell32.NewProc("ShellExecuteW")
procEnumChildWindows = moduser32.NewProc("EnumChildWindows")
procEnumWindows = moduser32.NewProc("EnumWindows")
procExitWindowsEx = moduser32.NewProc("ExitWindowsEx") procExitWindowsEx = moduser32.NewProc("ExitWindowsEx")
procGetClassNameW = moduser32.NewProc("GetClassNameW")
procGetDesktopWindow = moduser32.NewProc("GetDesktopWindow")
procGetForegroundWindow = moduser32.NewProc("GetForegroundWindow")
procGetGUIThreadInfo = moduser32.NewProc("GetGUIThreadInfo")
procGetShellWindow = moduser32.NewProc("GetShellWindow") procGetShellWindow = moduser32.NewProc("GetShellWindow")
procGetWindowThreadProcessId = moduser32.NewProc("GetWindowThreadProcessId") procGetWindowThreadProcessId = moduser32.NewProc("GetWindowThreadProcessId")
procIsWindow = moduser32.NewProc("IsWindow")
procIsWindowUnicode = moduser32.NewProc("IsWindowUnicode")
procIsWindowVisible = moduser32.NewProc("IsWindowVisible")
procMessageBoxW = moduser32.NewProc("MessageBoxW") procMessageBoxW = moduser32.NewProc("MessageBoxW")
procCreateEnvironmentBlock = moduserenv.NewProc("CreateEnvironmentBlock") procCreateEnvironmentBlock = moduserenv.NewProc("CreateEnvironmentBlock")
procDestroyEnvironmentBlock = moduserenv.NewProc("DestroyEnvironmentBlock") procDestroyEnvironmentBlock = moduserenv.NewProc("DestroyEnvironmentBlock")
@@ -454,6 +488,8 @@ var (
procGetFileVersionInfoSizeW = modversion.NewProc("GetFileVersionInfoSizeW") procGetFileVersionInfoSizeW = modversion.NewProc("GetFileVersionInfoSizeW")
procGetFileVersionInfoW = modversion.NewProc("GetFileVersionInfoW") procGetFileVersionInfoW = modversion.NewProc("GetFileVersionInfoW")
procVerQueryValueW = modversion.NewProc("VerQueryValueW") procVerQueryValueW = modversion.NewProc("VerQueryValueW")
proctimeBeginPeriod = modwinmm.NewProc("timeBeginPeriod")
proctimeEndPeriod = modwinmm.NewProc("timeEndPeriod")
procWinVerifyTrustEx = modwintrust.NewProc("WinVerifyTrustEx") procWinVerifyTrustEx = modwintrust.NewProc("WinVerifyTrustEx")
procFreeAddrInfoW = modws2_32.NewProc("FreeAddrInfoW") procFreeAddrInfoW = modws2_32.NewProc("FreeAddrInfoW")
procGetAddrInfoW = modws2_32.NewProc("GetAddrInfoW") procGetAddrInfoW = modws2_32.NewProc("GetAddrInfoW")
@@ -461,6 +497,9 @@ var (
procWSAEnumProtocolsW = modws2_32.NewProc("WSAEnumProtocolsW") procWSAEnumProtocolsW = modws2_32.NewProc("WSAEnumProtocolsW")
procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult") procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult")
procWSAIoctl = modws2_32.NewProc("WSAIoctl") procWSAIoctl = modws2_32.NewProc("WSAIoctl")
procWSALookupServiceBeginW = modws2_32.NewProc("WSALookupServiceBeginW")
procWSALookupServiceEnd = modws2_32.NewProc("WSALookupServiceEnd")
procWSALookupServiceNextW = modws2_32.NewProc("WSALookupServiceNextW")
procWSARecv = modws2_32.NewProc("WSARecv") procWSARecv = modws2_32.NewProc("WSARecv")
procWSARecvFrom = modws2_32.NewProc("WSARecvFrom") procWSARecvFrom = modws2_32.NewProc("WSARecvFrom")
procWSASend = modws2_32.NewProc("WSASend") procWSASend = modws2_32.NewProc("WSASend")
@@ -718,6 +757,14 @@ func DuplicateTokenEx(existingToken Token, desiredAccess uint32, tokenAttributes
return return
} }
func EnumDependentServices(service Handle, activityState uint32, services *ENUM_SERVICE_STATUS, buffSize uint32, bytesNeeded *uint32, servicesReturned *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procEnumDependentServicesW.Addr(), 6, uintptr(service), uintptr(activityState), uintptr(unsafe.Pointer(services)), uintptr(buffSize), uintptr(unsafe.Pointer(bytesNeeded)), uintptr(unsafe.Pointer(servicesReturned)))
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func EnumServicesStatusEx(mgr Handle, infoLevel uint32, serviceType uint32, serviceState uint32, services *byte, bufSize uint32, bytesNeeded *uint32, servicesReturned *uint32, resumeHandle *uint32, groupName *uint16) (err error) { func EnumServicesStatusEx(mgr Handle, infoLevel uint32, serviceType uint32, serviceState uint32, services *byte, bufSize uint32, bytesNeeded *uint32, servicesReturned *uint32, resumeHandle *uint32, groupName *uint16) (err error) {
r1, _, e1 := syscall.Syscall12(procEnumServicesStatusExW.Addr(), 10, uintptr(mgr), uintptr(infoLevel), uintptr(serviceType), uintptr(serviceState), uintptr(unsafe.Pointer(services)), uintptr(bufSize), uintptr(unsafe.Pointer(bytesNeeded)), uintptr(unsafe.Pointer(servicesReturned)), uintptr(unsafe.Pointer(resumeHandle)), uintptr(unsafe.Pointer(groupName)), 0, 0) r1, _, e1 := syscall.Syscall12(procEnumServicesStatusExW.Addr(), 10, uintptr(mgr), uintptr(infoLevel), uintptr(serviceType), uintptr(serviceState), uintptr(unsafe.Pointer(services)), uintptr(bufSize), uintptr(unsafe.Pointer(bytesNeeded)), uintptr(unsafe.Pointer(servicesReturned)), uintptr(unsafe.Pointer(resumeHandle)), uintptr(unsafe.Pointer(groupName)), 0, 0)
if r1 == 0 { if r1 == 0 {
@@ -1525,6 +1572,22 @@ func DnsRecordListFree(rl *DNSRecord, freetype uint32) {
return return
} }
func DwmGetWindowAttribute(hwnd HWND, attribute uint32, value unsafe.Pointer, size uint32) (ret error) {
r0, _, _ := syscall.Syscall6(procDwmGetWindowAttribute.Addr(), 4, uintptr(hwnd), uintptr(attribute), uintptr(value), uintptr(size), 0, 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
func DwmSetWindowAttribute(hwnd HWND, attribute uint32, value unsafe.Pointer, size uint32) (ret error) {
r0, _, _ := syscall.Syscall6(procDwmSetWindowAttribute.Addr(), 4, uintptr(hwnd), uintptr(attribute), uintptr(value), uintptr(size), 0, 0)
if r0 != 0 {
ret = syscall.Errno(r0)
}
return
}
func GetAdaptersAddresses(family uint32, flags uint32, reserved uintptr, adapterAddresses *IpAdapterAddresses, sizePointer *uint32) (errcode error) { func GetAdaptersAddresses(family uint32, flags uint32, reserved uintptr, adapterAddresses *IpAdapterAddresses, sizePointer *uint32) (errcode error) {
r0, _, _ := syscall.Syscall6(procGetAdaptersAddresses.Addr(), 5, uintptr(family), uintptr(flags), uintptr(reserved), uintptr(unsafe.Pointer(adapterAddresses)), uintptr(unsafe.Pointer(sizePointer)), 0) r0, _, _ := syscall.Syscall6(procGetAdaptersAddresses.Addr(), 5, uintptr(family), uintptr(flags), uintptr(reserved), uintptr(unsafe.Pointer(adapterAddresses)), uintptr(unsafe.Pointer(sizePointer)), 0)
if r0 != 0 { if r0 != 0 {
@@ -1557,6 +1620,15 @@ func GetIfEntry(pIfRow *MibIfRow) (errcode error) {
return return
} }
func AddDllDirectory(path *uint16) (cookie uintptr, err error) {
r0, _, e1 := syscall.Syscall(procAddDllDirectory.Addr(), 1, uintptr(unsafe.Pointer(path)), 0, 0)
cookie = uintptr(r0)
if cookie == 0 {
err = errnoErr(e1)
}
return
}
func AssignProcessToJobObject(job Handle, process Handle) (err error) { func AssignProcessToJobObject(job Handle, process Handle) (err error) {
r1, _, e1 := syscall.Syscall(procAssignProcessToJobObject.Addr(), 2, uintptr(job), uintptr(process), 0) r1, _, e1 := syscall.Syscall(procAssignProcessToJobObject.Addr(), 2, uintptr(job), uintptr(process), 0)
if r1 == 0 { if r1 == 0 {
@@ -1581,6 +1653,22 @@ func CancelIoEx(s Handle, o *Overlapped) (err error) {
return return
} }
func ClearCommBreak(handle Handle) (err error) {
r1, _, e1 := syscall.Syscall(procClearCommBreak.Addr(), 1, uintptr(handle), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func ClearCommError(handle Handle, lpErrors *uint32, lpStat *ComStat) (err error) {
r1, _, e1 := syscall.Syscall(procClearCommError.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(lpErrors)), uintptr(unsafe.Pointer(lpStat)))
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func CloseHandle(handle Handle) (err error) { func CloseHandle(handle Handle) (err error) {
r1, _, e1 := syscall.Syscall(procCloseHandle.Addr(), 1, uintptr(handle), 0, 0) r1, _, e1 := syscall.Syscall(procCloseHandle.Addr(), 1, uintptr(handle), 0, 0)
if r1 == 0 { if r1 == 0 {
@@ -1589,6 +1677,11 @@ func CloseHandle(handle Handle) (err error) {
return return
} }
func ClosePseudoConsole(console Handle) {
syscall.Syscall(procClosePseudoConsole.Addr(), 1, uintptr(console), 0, 0)
return
}
func ConnectNamedPipe(pipe Handle, overlapped *Overlapped) (err error) { func ConnectNamedPipe(pipe Handle, overlapped *Overlapped) (err error) {
r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(overlapped)), 0) r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(overlapped)), 0)
if r1 == 0 { if r1 == 0 {
@@ -1718,6 +1811,14 @@ func CreateProcess(appName *uint16, commandLine *uint16, procSecurity *SecurityA
return return
} }
func createPseudoConsole(size uint32, in Handle, out Handle, flags uint32, pconsole *Handle) (hr error) {
r0, _, _ := syscall.Syscall6(procCreatePseudoConsole.Addr(), 5, uintptr(size), uintptr(in), uintptr(out), uintptr(flags), uintptr(unsafe.Pointer(pconsole)), 0)
if r0 != 0 {
hr = syscall.Errno(r0)
}
return
}
func CreateSymbolicLink(symlinkfilename *uint16, targetfilename *uint16, flags uint32) (err error) { func CreateSymbolicLink(symlinkfilename *uint16, targetfilename *uint16, flags uint32) (err error) {
r1, _, e1 := syscall.Syscall(procCreateSymbolicLinkW.Addr(), 3, uintptr(unsafe.Pointer(symlinkfilename)), uintptr(unsafe.Pointer(targetfilename)), uintptr(flags)) r1, _, e1 := syscall.Syscall(procCreateSymbolicLinkW.Addr(), 3, uintptr(unsafe.Pointer(symlinkfilename)), uintptr(unsafe.Pointer(targetfilename)), uintptr(flags))
if r1&0xff == 0 { if r1&0xff == 0 {
@@ -1772,6 +1873,14 @@ func DeviceIoControl(handle Handle, ioControlCode uint32, inBuffer *byte, inBuff
return return
} }
func DisconnectNamedPipe(pipe Handle) (err error) {
r1, _, e1 := syscall.Syscall(procDisconnectNamedPipe.Addr(), 1, uintptr(pipe), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func DuplicateHandle(hSourceProcessHandle Handle, hSourceHandle Handle, hTargetProcessHandle Handle, lpTargetHandle *Handle, dwDesiredAccess uint32, bInheritHandle bool, dwOptions uint32) (err error) { func DuplicateHandle(hSourceProcessHandle Handle, hSourceHandle Handle, hTargetProcessHandle Handle, lpTargetHandle *Handle, dwDesiredAccess uint32, bInheritHandle bool, dwOptions uint32) (err error) {
var _p0 uint32 var _p0 uint32
if bInheritHandle { if bInheritHandle {
@@ -1784,6 +1893,14 @@ func DuplicateHandle(hSourceProcessHandle Handle, hSourceHandle Handle, hTargetP
return return
} }
func EscapeCommFunction(handle Handle, dwFunc uint32) (err error) {
r1, _, e1 := syscall.Syscall(procEscapeCommFunction.Addr(), 2, uintptr(handle), uintptr(dwFunc), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func ExitProcess(exitcode uint32) { func ExitProcess(exitcode uint32) {
syscall.Syscall(procExitProcess.Addr(), 1, uintptr(exitcode), 0, 0) syscall.Syscall(procExitProcess.Addr(), 1, uintptr(exitcode), 0, 0)
return return
@@ -1985,6 +2102,22 @@ func GetActiveProcessorCount(groupNumber uint16) (ret uint32) {
return return
} }
func GetCommModemStatus(handle Handle, lpModemStat *uint32) (err error) {
r1, _, e1 := syscall.Syscall(procGetCommModemStatus.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(lpModemStat)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func GetCommState(handle Handle, lpDCB *DCB) (err error) {
r1, _, e1 := syscall.Syscall(procGetCommState.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(lpDCB)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func GetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) { func GetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) {
r1, _, e1 := syscall.Syscall(procGetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0) r1, _, e1 := syscall.Syscall(procGetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0)
if r1 == 0 { if r1 == 0 {
@@ -2125,6 +2258,14 @@ func GetFileInformationByHandleEx(handle Handle, class uint32, outBuffer *byte,
return return
} }
func GetFileTime(handle Handle, ctime *Filetime, atime *Filetime, wtime *Filetime) (err error) {
r1, _, e1 := syscall.Syscall6(procGetFileTime.Addr(), 4, uintptr(handle), uintptr(unsafe.Pointer(ctime)), uintptr(unsafe.Pointer(atime)), uintptr(unsafe.Pointer(wtime)), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func GetFileType(filehandle Handle) (n uint32, err error) { func GetFileType(filehandle Handle) (n uint32, err error) {
r0, _, e1 := syscall.Syscall(procGetFileType.Addr(), 1, uintptr(filehandle), 0, 0) r0, _, e1 := syscall.Syscall(procGetFileType.Addr(), 1, uintptr(filehandle), 0, 0)
n = uint32(r0) n = uint32(r0)
@@ -2152,6 +2293,12 @@ func GetFullPathName(path *uint16, buflen uint32, buf *uint16, fname **uint16) (
return return
} }
func GetLargePageMinimum() (size uintptr) {
r0, _, _ := syscall.Syscall(procGetLargePageMinimum.Addr(), 0, 0, 0, 0)
size = uintptr(r0)
return
}
func GetLastError() (lasterr error) { func GetLastError() (lasterr error) {
r0, _, _ := syscall.Syscall(procGetLastError.Addr(), 0, 0, 0, 0) r0, _, _ := syscall.Syscall(procGetLastError.Addr(), 0, 0, 0, 0)
if r0 != 0 { if r0 != 0 {
@@ -2320,11 +2467,8 @@ func GetShortPathName(longpath *uint16, shortpath *uint16, buflen uint32) (n uin
return return
} }
func GetStartupInfo(startupInfo *StartupInfo) (err error) { func getStartupInfo(startupInfo *StartupInfo) {
r1, _, e1 := syscall.Syscall(procGetStartupInfoW.Addr(), 1, uintptr(unsafe.Pointer(startupInfo)), 0, 0) syscall.Syscall(procGetStartupInfoW.Addr(), 1, uintptr(unsafe.Pointer(startupInfo)), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return return
} }
@@ -2726,6 +2870,14 @@ func PulseEvent(event Handle) (err error) {
return return
} }
func PurgeComm(handle Handle, dwFlags uint32) (err error) {
r1, _, e1 := syscall.Syscall(procPurgeComm.Addr(), 2, uintptr(handle), uintptr(dwFlags), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func QueryDosDevice(deviceName *uint16, targetPath *uint16, max uint32) (n uint32, err error) { func QueryDosDevice(deviceName *uint16, targetPath *uint16, max uint32) (n uint32, err error) {
r0, _, e1 := syscall.Syscall(procQueryDosDeviceW.Addr(), 3, uintptr(unsafe.Pointer(deviceName)), uintptr(unsafe.Pointer(targetPath)), uintptr(max)) r0, _, e1 := syscall.Syscall(procQueryDosDeviceW.Addr(), 3, uintptr(unsafe.Pointer(deviceName)), uintptr(unsafe.Pointer(targetPath)), uintptr(max))
n = uint32(r0) n = uint32(r0)
@@ -2807,6 +2959,14 @@ func RemoveDirectory(path *uint16) (err error) {
return return
} }
func RemoveDllDirectory(cookie uintptr) (err error) {
r1, _, e1 := syscall.Syscall(procRemoveDllDirectory.Addr(), 1, uintptr(cookie), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func ResetEvent(event Handle) (err error) { func ResetEvent(event Handle) (err error) {
r1, _, e1 := syscall.Syscall(procResetEvent.Addr(), 1, uintptr(event), 0, 0) r1, _, e1 := syscall.Syscall(procResetEvent.Addr(), 1, uintptr(event), 0, 0)
if r1 == 0 { if r1 == 0 {
@@ -2815,6 +2975,14 @@ func ResetEvent(event Handle) (err error) {
return return
} }
func resizePseudoConsole(pconsole Handle, size uint32) (hr error) {
r0, _, _ := syscall.Syscall(procResizePseudoConsole.Addr(), 2, uintptr(pconsole), uintptr(size), 0)
if r0 != 0 {
hr = syscall.Errno(r0)
}
return
}
func ResumeThread(thread Handle) (ret uint32, err error) { func ResumeThread(thread Handle) (ret uint32, err error) {
r0, _, e1 := syscall.Syscall(procResumeThread.Addr(), 1, uintptr(thread), 0, 0) r0, _, e1 := syscall.Syscall(procResumeThread.Addr(), 1, uintptr(thread), 0, 0)
ret = uint32(r0) ret = uint32(r0)
@@ -2824,6 +2992,30 @@ func ResumeThread(thread Handle) (ret uint32, err error) {
return return
} }
func SetCommBreak(handle Handle) (err error) {
r1, _, e1 := syscall.Syscall(procSetCommBreak.Addr(), 1, uintptr(handle), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func SetCommMask(handle Handle, dwEvtMask uint32) (err error) {
r1, _, e1 := syscall.Syscall(procSetCommMask.Addr(), 2, uintptr(handle), uintptr(dwEvtMask), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func SetCommState(handle Handle, lpDCB *DCB) (err error) {
r1, _, e1 := syscall.Syscall(procSetCommState.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(lpDCB)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func SetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) { func SetCommTimeouts(handle Handle, timeouts *CommTimeouts) (err error) {
r1, _, e1 := syscall.Syscall(procSetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0) r1, _, e1 := syscall.Syscall(procSetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0)
if r1 == 0 { if r1 == 0 {
@@ -2952,6 +3144,14 @@ func SetFileTime(handle Handle, ctime *Filetime, atime *Filetime, wtime *Filetim
return return
} }
func SetFileValidData(handle Handle, validDataLength int64) (err error) {
r1, _, e1 := syscall.Syscall(procSetFileValidData.Addr(), 2, uintptr(handle), uintptr(validDataLength), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func SetHandleInformation(handle Handle, mask uint32, flags uint32) (err error) { func SetHandleInformation(handle Handle, mask uint32, flags uint32) (err error) {
r1, _, e1 := syscall.Syscall(procSetHandleInformation.Addr(), 3, uintptr(handle), uintptr(mask), uintptr(flags)) r1, _, e1 := syscall.Syscall(procSetHandleInformation.Addr(), 3, uintptr(handle), uintptr(mask), uintptr(flags))
if r1 == 0 { if r1 == 0 {
@@ -3037,6 +3237,14 @@ func SetVolumeMountPoint(volumeMountPoint *uint16, volumeName *uint16) (err erro
return return
} }
func SetupComm(handle Handle, dwInQueue uint32, dwOutQueue uint32) (err error) {
r1, _, e1 := syscall.Syscall(procSetupComm.Addr(), 3, uintptr(handle), uintptr(dwInQueue), uintptr(dwOutQueue))
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func SizeofResource(module Handle, resInfo Handle) (size uint32, err error) { func SizeofResource(module Handle, resInfo Handle) (size uint32, err error) {
r0, _, e1 := syscall.Syscall(procSizeofResource.Addr(), 2, uintptr(module), uintptr(resInfo), 0) r0, _, e1 := syscall.Syscall(procSizeofResource.Addr(), 2, uintptr(module), uintptr(resInfo), 0)
size = uint32(r0) size = uint32(r0)
@@ -3183,6 +3391,14 @@ func WTSGetActiveConsoleSessionId() (sessionID uint32) {
return return
} }
func WaitCommEvent(handle Handle, lpEvtMask *uint32, lpOverlapped *Overlapped) (err error) {
r1, _, e1 := syscall.Syscall(procWaitCommEvent.Addr(), 3, uintptr(handle), uintptr(unsafe.Pointer(lpEvtMask)), uintptr(unsafe.Pointer(lpOverlapped)))
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func waitForMultipleObjects(count uint32, handles uintptr, waitAll bool, waitMilliseconds uint32) (event uint32, err error) { func waitForMultipleObjects(count uint32, handles uintptr, waitAll bool, waitMilliseconds uint32) (event uint32, err error) {
var _p0 uint32 var _p0 uint32
if waitAll { if waitAll {
@@ -3469,12 +3685,8 @@ func EnumProcessModulesEx(process Handle, module *Handle, cb uint32, cbNeeded *u
return return
} }
func EnumProcesses(processIds []uint32, bytesReturned *uint32) (err error) { func enumProcesses(processIds *uint32, nSize uint32, bytesReturned *uint32) (err error) {
var _p0 *uint32 r1, _, e1 := syscall.Syscall(procEnumProcesses.Addr(), 3, uintptr(unsafe.Pointer(processIds)), uintptr(nSize), uintptr(unsafe.Pointer(bytesReturned)))
if len(processIds) > 0 {
_p0 = &processIds[0]
}
r1, _, e1 := syscall.Syscall(procEnumProcesses.Addr(), 3, uintptr(unsafe.Pointer(_p0)), uintptr(len(processIds)), uintptr(unsafe.Pointer(bytesReturned)))
if r1 == 0 { if r1 == 0 {
err = errnoErr(e1) err = errnoErr(e1)
} }
@@ -3777,9 +3989,9 @@ func setupUninstallOEMInf(infFileName *uint16, flags SUOI, reserved uintptr) (er
return return
} }
func CommandLineToArgv(cmd *uint16, argc *int32) (argv *[8192]*[8192]uint16, err error) { func commandLineToArgv(cmd *uint16, argc *int32) (argv **uint16, err error) {
r0, _, e1 := syscall.Syscall(procCommandLineToArgvW.Addr(), 2, uintptr(unsafe.Pointer(cmd)), uintptr(unsafe.Pointer(argc)), 0) r0, _, e1 := syscall.Syscall(procCommandLineToArgvW.Addr(), 2, uintptr(unsafe.Pointer(cmd)), uintptr(unsafe.Pointer(argc)), 0)
argv = (*[8192]*[8192]uint16)(unsafe.Pointer(r0)) argv = (**uint16)(unsafe.Pointer(r0))
if argv == nil { if argv == nil {
err = errnoErr(e1) err = errnoErr(e1)
} }
@@ -3802,6 +4014,19 @@ func ShellExecute(hwnd Handle, verb *uint16, file *uint16, args *uint16, cwd *ui
return return
} }
func EnumChildWindows(hwnd HWND, enumFunc uintptr, param unsafe.Pointer) {
syscall.Syscall(procEnumChildWindows.Addr(), 3, uintptr(hwnd), uintptr(enumFunc), uintptr(param))
return
}
func EnumWindows(enumFunc uintptr, param unsafe.Pointer) (err error) {
r1, _, e1 := syscall.Syscall(procEnumWindows.Addr(), 2, uintptr(enumFunc), uintptr(param), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func ExitWindowsEx(flags uint32, reason uint32) (err error) { func ExitWindowsEx(flags uint32, reason uint32) (err error) {
r1, _, e1 := syscall.Syscall(procExitWindowsEx.Addr(), 2, uintptr(flags), uintptr(reason), 0) r1, _, e1 := syscall.Syscall(procExitWindowsEx.Addr(), 2, uintptr(flags), uintptr(reason), 0)
if r1 == 0 { if r1 == 0 {
@@ -3810,6 +4035,35 @@ func ExitWindowsEx(flags uint32, reason uint32) (err error) {
return return
} }
func GetClassName(hwnd HWND, className *uint16, maxCount int32) (copied int32, err error) {
r0, _, e1 := syscall.Syscall(procGetClassNameW.Addr(), 3, uintptr(hwnd), uintptr(unsafe.Pointer(className)), uintptr(maxCount))
copied = int32(r0)
if copied == 0 {
err = errnoErr(e1)
}
return
}
func GetDesktopWindow() (hwnd HWND) {
r0, _, _ := syscall.Syscall(procGetDesktopWindow.Addr(), 0, 0, 0, 0)
hwnd = HWND(r0)
return
}
func GetForegroundWindow() (hwnd HWND) {
r0, _, _ := syscall.Syscall(procGetForegroundWindow.Addr(), 0, 0, 0, 0)
hwnd = HWND(r0)
return
}
func GetGUIThreadInfo(thread uint32, info *GUIThreadInfo) (err error) {
r1, _, e1 := syscall.Syscall(procGetGUIThreadInfo.Addr(), 2, uintptr(thread), uintptr(unsafe.Pointer(info)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func GetShellWindow() (shellWindow HWND) { func GetShellWindow() (shellWindow HWND) {
r0, _, _ := syscall.Syscall(procGetShellWindow.Addr(), 0, 0, 0, 0) r0, _, _ := syscall.Syscall(procGetShellWindow.Addr(), 0, 0, 0, 0)
shellWindow = HWND(r0) shellWindow = HWND(r0)
@@ -3825,6 +4079,24 @@ func GetWindowThreadProcessId(hwnd HWND, pid *uint32) (tid uint32, err error) {
return return
} }
func IsWindow(hwnd HWND) (isWindow bool) {
r0, _, _ := syscall.Syscall(procIsWindow.Addr(), 1, uintptr(hwnd), 0, 0)
isWindow = r0 != 0
return
}
func IsWindowUnicode(hwnd HWND) (isUnicode bool) {
r0, _, _ := syscall.Syscall(procIsWindowUnicode.Addr(), 1, uintptr(hwnd), 0, 0)
isUnicode = r0 != 0
return
}
func IsWindowVisible(hwnd HWND) (isVisible bool) {
r0, _, _ := syscall.Syscall(procIsWindowVisible.Addr(), 1, uintptr(hwnd), 0, 0)
isVisible = r0 != 0
return
}
func MessageBox(hwnd HWND, text *uint16, caption *uint16, boxtype uint32) (ret int32, err error) { func MessageBox(hwnd HWND, text *uint16, caption *uint16, boxtype uint32) (ret int32, err error) {
r0, _, e1 := syscall.Syscall6(procMessageBoxW.Addr(), 4, uintptr(hwnd), uintptr(unsafe.Pointer(text)), uintptr(unsafe.Pointer(caption)), uintptr(boxtype), 0, 0) r0, _, e1 := syscall.Syscall6(procMessageBoxW.Addr(), 4, uintptr(hwnd), uintptr(unsafe.Pointer(text)), uintptr(unsafe.Pointer(caption)), uintptr(boxtype), 0, 0)
ret = int32(r0) ret = int32(r0)
@@ -3914,6 +4186,22 @@ func _VerQueryValue(block unsafe.Pointer, subBlock *uint16, pointerToBufferPoint
return return
} }
func TimeBeginPeriod(period uint32) (err error) {
r1, _, e1 := syscall.Syscall(proctimeBeginPeriod.Addr(), 1, uintptr(period), 0, 0)
if r1 != 0 {
err = errnoErr(e1)
}
return
}
func TimeEndPeriod(period uint32) (err error) {
r1, _, e1 := syscall.Syscall(proctimeEndPeriod.Addr(), 1, uintptr(period), 0, 0)
if r1 != 0 {
err = errnoErr(e1)
}
return
}
func WinVerifyTrustEx(hwnd HWND, actionId *GUID, data *WinTrustData) (ret error) { func WinVerifyTrustEx(hwnd HWND, actionId *GUID, data *WinTrustData) (ret error) {
r0, _, _ := syscall.Syscall(procWinVerifyTrustEx.Addr(), 3, uintptr(hwnd), uintptr(unsafe.Pointer(actionId)), uintptr(unsafe.Pointer(data))) r0, _, _ := syscall.Syscall(procWinVerifyTrustEx.Addr(), 3, uintptr(hwnd), uintptr(unsafe.Pointer(actionId)), uintptr(unsafe.Pointer(data)))
if r0 != 0 { if r0 != 0 {
@@ -3972,6 +4260,30 @@ func WSAIoctl(s Handle, iocc uint32, inbuf *byte, cbif uint32, outbuf *byte, cbo
return return
} }
func WSALookupServiceBegin(querySet *WSAQUERYSET, flags uint32, handle *Handle) (err error) {
r1, _, e1 := syscall.Syscall(procWSALookupServiceBeginW.Addr(), 3, uintptr(unsafe.Pointer(querySet)), uintptr(flags), uintptr(unsafe.Pointer(handle)))
if r1 == socket_error {
err = errnoErr(e1)
}
return
}
func WSALookupServiceEnd(handle Handle) (err error) {
r1, _, e1 := syscall.Syscall(procWSALookupServiceEnd.Addr(), 1, uintptr(handle), 0, 0)
if r1 == socket_error {
err = errnoErr(e1)
}
return
}
func WSALookupServiceNext(handle Handle, flags uint32, size *int32, querySet *WSAQUERYSET) (err error) {
r1, _, e1 := syscall.Syscall6(procWSALookupServiceNextW.Addr(), 4, uintptr(handle), uintptr(flags), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(querySet)), 0, 0)
if r1 == socket_error {
err = errnoErr(e1)
}
return
}
func WSARecv(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, overlapped *Overlapped, croutine *byte) (err error) { func WSARecv(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, overlapped *Overlapped, croutine *byte) (err error) {
r1, _, e1 := syscall.Syscall9(procWSARecv.Addr(), 7, uintptr(s), uintptr(unsafe.Pointer(bufs)), uintptr(bufcnt), uintptr(unsafe.Pointer(recvd)), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(croutine)), 0, 0) r1, _, e1 := syscall.Syscall9(procWSARecv.Addr(), 7, uintptr(s), uintptr(unsafe.Pointer(bufs)), uintptr(bufcnt), uintptr(unsafe.Pointer(recvd)), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(croutine)), 0, 0)
if r1 == socket_error { if r1 == socket_error {
+7 -6
View File
@@ -1,8 +1,9 @@
# github.com/danieljoos/wincred v1.1.2 # github.com/danieljoos/wincred v1.2.2
## explicit; go 1.13 ## explicit; go 1.18
github.com/danieljoos/wincred github.com/danieljoos/wincred
# golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 # github.com/keybase/go-keychain v0.0.1
## explicit; go 1.17 ## explicit; go 1.21
golang.org/x/sys/execabs github.com/keybase/go-keychain
golang.org/x/sys/internal/unsafeheader # golang.org/x/sys v0.20.0
## explicit; go 1.18
golang.org/x/sys/windows golang.org/x/sys/windows
@@ -1,3 +1,5 @@
//go:build windows
package main package main
import ( import (
@@ -1,3 +1,5 @@
//go:build windows
package wincred package wincred
import ( import (
@@ -111,15 +113,15 @@ func exactMatch(serverURL, target url.URL) bool {
} }
func approximateMatch(serverURL, target url.URL) bool { func approximateMatch(serverURL, target url.URL) bool {
//if scheme is missing assume it is the same as target // if scheme is missing assume it is the same as target
if serverURL.Scheme == "" { if serverURL.Scheme == "" {
serverURL.Scheme = target.Scheme serverURL.Scheme = target.Scheme
} }
//if port is missing assume it is the same as target // if port is missing assume it is the same as target
if serverURL.Port() == "" && target.Port() != "" { if serverURL.Port() == "" && target.Port() != "" {
serverURL.Host = serverURL.Host + ":" + target.Port() serverURL.Host = serverURL.Host + ":" + target.Port()
} }
//if path is missing assume it is the same as target // if path is missing assume it is the same as target
if serverURL.Path == "" { if serverURL.Path == "" {
serverURL.Path = target.Path serverURL.Path = target.Path
} }
+291
View File
@@ -0,0 +1,291 @@
//go:build windows
package wincred
import (
"fmt"
"strings"
"testing"
"github.com/docker/docker-credential-helpers/credentials"
)
func TestWinCredHelper(t *testing.T) {
creds := &credentials.Credentials{
ServerURL: "https://foobar.docker.io:2376/v1",
Username: "foobar",
Secret: "foobarbaz",
}
creds1 := &credentials.Credentials{
ServerURL: "https://foobar.docker.io:2376/v2",
Username: "foobarbaz",
Secret: "foobar",
}
helper := Wincred{}
// check for and remove remaining credentials from previous fail tests
oldauths, err := helper.List()
if err != nil {
t.Fatal(err)
}
for k, v := range oldauths {
if strings.Compare(k, creds.ServerURL) == 0 && strings.Compare(v, creds.Username) == 0 {
if err := helper.Delete(creds.ServerURL); err != nil {
t.Fatal(err)
}
} else if strings.Compare(k, creds1.ServerURL) == 0 && strings.Compare(v, creds1.Username) == 0 {
if err := helper.Delete(creds1.ServerURL); err != nil {
t.Fatal(err)
}
}
}
// recount for credentials
oldauths, err = helper.List()
if err != nil {
t.Fatal(err)
}
if err := helper.Add(creds); err != nil {
t.Fatal(err)
}
username, secret, err := helper.Get(creds.ServerURL)
if err != nil {
t.Fatal(err)
}
if username != "foobar" {
t.Fatalf("expected %s, got %s\n", "foobar", username)
}
if secret != "foobarbaz" {
t.Fatalf("expected %s, got %s\n", "foobarbaz", secret)
}
auths, err := helper.List()
if err != nil || len(auths)-len(oldauths) != 1 {
t.Fatal(err)
}
helper.Add(creds1)
defer helper.Delete(creds1.ServerURL)
newauths, err := helper.List()
if err != nil {
t.Fatal(err)
}
if len(newauths)-len(auths) != 1 {
if err == nil {
t.Fatalf("Error: len(newauths): %d, len(auths): %d", len(newauths), len(auths))
}
t.Fatalf("Error: len(newauths): %d, len(auths): %d\n Error= %v", len(newauths), len(auths), err)
}
if err := helper.Delete(creds.ServerURL); err != nil {
t.Fatal(err)
}
}
// TestWinCredHelperRetrieveAliases verifies that secrets can be accessed
// through variations on the URL
func TestWinCredHelperRetrieveAliases(t *testing.T) {
tests := []struct {
doc string
storeURL string
readURL string
}{
{
doc: "stored with port, retrieved without",
storeURL: "https://foobar.docker.io:2376",
readURL: "https://foobar.docker.io",
},
{
doc: "stored as https, retrieved without scheme",
storeURL: "https://foobar.docker.io",
readURL: "foobar.docker.io",
},
{
doc: "stored with path, retrieved without",
storeURL: "https://foobar.docker.io/one/two",
readURL: "https://foobar.docker.io",
},
}
helper := Wincred{}
t.Cleanup(func() {
for _, tc := range tests {
if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) {
t.Errorf("cleanup: failed to delete '%s': %v", tc.storeURL, err)
}
}
})
// Clean store before testing.
for _, tc := range tests {
if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) {
t.Errorf("prepare: failed to delete '%s': %v", tc.storeURL, err)
}
}
for _, tc := range tests {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
c := &credentials.Credentials{ServerURL: tc.storeURL, Username: "hello", Secret: "world"}
if err := helper.Add(c); err != nil {
t.Fatalf("Error: failed to store secret for URL %q: %s", tc.storeURL, err)
}
if _, _, err := helper.Get(tc.readURL); err != nil {
t.Errorf("Error: failed to read secret for URL %q using %q", tc.storeURL, tc.readURL)
}
if err := helper.Delete(tc.storeURL); err != nil {
t.Error(err)
}
})
}
}
// TestWinCredHelperRetrieveStrict verifies that only matching secrets are
// returned.
func TestWinCredHelperRetrieveStrict(t *testing.T) {
tests := []struct {
doc string
storeURL string
readURL string
}{
{
doc: "stored as https, retrieved using http",
storeURL: "https://foobar.docker.io:2376",
readURL: "http://foobar.docker.io:2376",
},
{
doc: "stored as http, retrieved using https",
storeURL: "http://foobar.docker.io:2376",
readURL: "https://foobar.docker.io:2376",
},
{
// stored as http, retrieved without a scheme specified (hence, using the default https://)
doc: "stored as http, retrieved without scheme",
storeURL: "http://foobar.docker.io",
readURL: "foobar.docker.io:5678",
},
{
doc: "non-matching ports",
storeURL: "https://foobar.docker.io:1234",
readURL: "https://foobar.docker.io:5678",
},
// TODO: is this desired behavior? The other way round does work
// {
// doc: "non-matching ports (stored without port)",
// storeURL: "https://foobar.docker.io",
// readURL: "https://foobar.docker.io:5678",
// },
{
doc: "non-matching paths",
storeURL: "https://foobar.docker.io:1234/one/two",
readURL: "https://foobar.docker.io:1234/five/six",
},
}
helper := Wincred{}
t.Cleanup(func() {
for _, tc := range tests {
if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) {
t.Errorf("cleanup: failed to delete '%s': %v", tc.storeURL, err)
}
}
})
// Clean store before testing.
for _, tc := range tests {
if err := helper.Delete(tc.storeURL); err != nil && !credentials.IsErrCredentialsNotFound(err) {
t.Errorf("prepare: failed to delete '%s': %v", tc.storeURL, err)
}
}
for _, tc := range tests {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
c := &credentials.Credentials{ServerURL: tc.storeURL, Username: "hello", Secret: "world"}
if err := helper.Add(c); err != nil {
t.Fatalf("Error: failed to store secret for URL %q: %s", tc.storeURL, err)
}
if _, _, err := helper.Get(tc.readURL); err == nil {
t.Errorf("Error: managed to read secret for URL %q using %q, but should not be able to", tc.storeURL, tc.readURL)
}
if err := helper.Delete(tc.storeURL); err != nil {
t.Error(err)
}
})
}
}
// TestWinCredHelperStoreRetrieve verifies that secrets stored in the
// the keychain can be read back using the URL that was used to store them.
func TestWinCredHelperStoreRetrieve(t *testing.T) {
tests := []struct {
url string
}{
{url: "foobar.docker.io"},
{url: "foobar.docker.io:2376"},
{url: "//foobar.docker.io:2376"},
{url: "https://foobar.docker.io:2376"},
{url: "http://foobar.docker.io:2376"},
{url: "https://foobar.docker.io:2376/some/path"},
{url: "https://foobar.docker.io:2376/some/other/path"},
{url: "https://foobar.docker.io:2376/some/other/path?foo=bar"},
}
helper := Wincred{}
t.Cleanup(func() {
for _, tc := range tests {
if err := helper.Delete(tc.url); err != nil && !credentials.IsErrCredentialsNotFound(err) {
t.Errorf("cleanup: failed to delete '%s': %v", tc.url, err)
}
}
})
// Clean store before testing.
for _, tc := range tests {
if err := helper.Delete(tc.url); err != nil && !credentials.IsErrCredentialsNotFound(err) {
t.Errorf("prepare: failed to delete '%s': %v", tc.url, err)
}
}
// Note that we don't delete between individual tests here, to verify that
// subsequent stores/overwrites don't affect storing / retrieving secrets.
for i, tc := range tests {
tc := tc
t.Run(tc.url, func(t *testing.T) {
c := &credentials.Credentials{
ServerURL: tc.url,
Username: fmt.Sprintf("user-%d", i),
Secret: fmt.Sprintf("secret-%d", i),
}
if err := helper.Add(c); err != nil {
t.Fatalf("Error: failed to store secret for URL: %s: %s", tc.url, err)
}
user, secret, err := helper.Get(tc.url)
if err != nil {
t.Fatalf("Error: failed to read secret for URL %q: %s", tc.url, err)
}
if user != c.Username {
t.Errorf("Error: expected username %s, got username %s for URL: %s", c.Username, user, tc.url)
}
if secret != c.Secret {
t.Errorf("Error: expected secret %s, got secret %s for URL: %s", c.Secret, secret, tc.url)
}
})
}
}
func TestMissingCredentials(t *testing.T) {
helper := Wincred{}
_, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd")
if !credentials.IsErrCredentialsNotFound(err) {
t.Fatalf("expected ErrCredentialsNotFound, got %v", err)
}
}
-244
View File
@@ -1,244 +0,0 @@
package wincred
import (
"fmt"
"strings"
"testing"
"github.com/docker/docker-credential-helpers/credentials"
)
func TestWinCredHelper(t *testing.T) {
creds := &credentials.Credentials{
ServerURL: "https://foobar.docker.io:2376/v1",
Username: "foobar",
Secret: "foobarbaz",
}
creds1 := &credentials.Credentials{
ServerURL: "https://foobar.docker.io:2376/v2",
Username: "foobarbaz",
Secret: "foobar",
}
helper := Wincred{}
// check for and remove remaining credentials from previous fail tests
oldauths, err := helper.List()
if err != nil {
t.Fatal(err)
}
for k, v := range oldauths {
if strings.Compare(k, creds.ServerURL) == 0 && strings.Compare(v, creds.Username) == 0 {
if err := helper.Delete(creds.ServerURL); err != nil {
t.Fatal(err)
}
} else if strings.Compare(k, creds1.ServerURL) == 0 && strings.Compare(v, creds1.Username) == 0 {
if err := helper.Delete(creds1.ServerURL); err != nil {
t.Fatal(err)
}
}
}
// recount for credentials
oldauths, err = helper.List()
if err != nil {
t.Fatal(err)
}
if err := helper.Add(creds); err != nil {
t.Fatal(err)
}
username, secret, err := helper.Get(creds.ServerURL)
if err != nil {
t.Fatal(err)
}
if username != "foobar" {
t.Fatalf("expected %s, got %s\n", "foobar", username)
}
if secret != "foobarbaz" {
t.Fatalf("expected %s, got %s\n", "foobarbaz", secret)
}
auths, err := helper.List()
if err != nil || len(auths)-len(oldauths) != 1 {
t.Fatal(err)
}
helper.Add(creds1)
defer helper.Delete(creds1.ServerURL)
newauths, err := helper.List()
if err != nil {
t.Fatal(err)
}
if len(newauths)-len(auths) != 1 {
if err == nil {
t.Fatalf("Error: len(newauths): %d, len(auths): %d", len(newauths), len(auths))
}
t.Fatalf("Error: len(newauths): %d, len(auths): %d\n Error= %v", len(newauths), len(auths), err)
}
if err := helper.Delete(creds.ServerURL); err != nil {
t.Fatal(err)
}
}
// TestWinCredHelperRetrieveAliases verifies that secrets can be accessed
// through variations on the URL
func TestWinCredHelperRetrieveAliases(t *testing.T) {
tests := []struct {
storeURL string
readURL string
}{
// stored with port, retrieved without
{"https://foobar.docker.io:2376", "https://foobar.docker.io"},
// stored as https, retrieved without scheme
{"https://foobar.docker.io", "foobar.docker.io"},
// stored with path, retrieved without
{"https://foobar.docker.io/one/two", "https://foobar.docker.io"},
}
helper := Wincred{}
defer func() {
for _, te := range tests {
helper.Delete(te.storeURL)
}
}()
// Clean store before testing.
for _, te := range tests {
helper.Delete(te.storeURL)
}
for _, te := range tests {
c := &credentials.Credentials{ServerURL: te.storeURL, Username: "hello", Secret: "world"}
if err := helper.Add(c); err != nil {
t.Errorf("Error: failed to store secret for URL %q: %s", te.storeURL, err)
continue
}
if _, _, err := helper.Get(te.readURL); err != nil {
t.Errorf("Error: failed to read secret for URL %q using %q", te.storeURL, te.readURL)
}
helper.Delete(te.storeURL)
}
}
// TestWinCredHelperRetrieveStrict verifies that only matching secrets are
// returned.
func TestWinCredHelperRetrieveStrict(t *testing.T) {
tests := []struct {
storeURL string
readURL string
}{
// stored as https, retrieved using http
{"https://foobar.docker.io:2376", "http://foobar.docker.io:2376"},
// stored as http, retrieved using https
{"http://foobar.docker.io:2376", "https://foobar.docker.io:2376"},
// same: stored as http, retrieved without a scheme specified (hence, using the default https://)
{"http://foobar.docker.io", "foobar.docker.io:5678"},
// non-matching ports
{"https://foobar.docker.io:1234", "https://foobar.docker.io:5678"},
// non-matching ports TODO is this desired behavior? The other way round does work
//{"https://foobar.docker.io", "https://foobar.docker.io:5678"},
// non-matching paths
{"https://foobar.docker.io:1234/one/two", "https://foobar.docker.io:1234/five/six"},
}
helper := Wincred{}
defer func() {
for _, te := range tests {
helper.Delete(te.storeURL)
}
}()
// Clean store before testing.
for _, te := range tests {
helper.Delete(te.storeURL)
}
for _, te := range tests {
c := &credentials.Credentials{ServerURL: te.storeURL, Username: "hello", Secret: "world"}
if err := helper.Add(c); err != nil {
t.Errorf("Error: failed to store secret for URL %q: %s", te.storeURL, err)
continue
}
if _, _, err := helper.Get(te.readURL); err == nil {
t.Errorf("Error: managed to read secret for URL %q using %q, but should not be able to", te.storeURL, te.readURL)
}
helper.Delete(te.storeURL)
}
}
// TestWinCredHelperStoreRetrieve verifies that secrets stored in the
// the keychain can be read back using the URL that was used to store them.
func TestWinCredHelperStoreRetrieve(t *testing.T) {
tests := []struct {
url string
}{
{url: "foobar.docker.io"},
{url: "foobar.docker.io:2376"},
{url: "//foobar.docker.io:2376"},
{url: "https://foobar.docker.io:2376"},
{url: "http://foobar.docker.io:2376"},
{url: "https://foobar.docker.io:2376/some/path"},
{url: "https://foobar.docker.io:2376/some/other/path"},
{url: "https://foobar.docker.io:2376/some/other/path?foo=bar"},
}
helper := Wincred{}
defer func() {
for _, te := range tests {
helper.Delete(te.url)
}
}()
// Clean store before testing.
for _, te := range tests {
helper.Delete(te.url)
}
// Note that we don't delete between individual tests here, to verify that
// subsequent stores/overwrites don't affect storing / retrieving secrets.
for i, te := range tests {
c := &credentials.Credentials{
ServerURL: te.url,
Username: fmt.Sprintf("user-%d", i),
Secret: fmt.Sprintf("secret-%d", i),
}
if err := helper.Add(c); err != nil {
t.Errorf("Error: failed to store secret for URL: %s: %s", te.url, err)
continue
}
user, secret, err := helper.Get(te.url)
if err != nil {
t.Errorf("Error: failed to read secret for URL %q: %s", te.url, err)
continue
}
if user != c.Username {
t.Errorf("Error: expected username %s, got username %s for URL: %s", c.Username, user, te.url)
}
if secret != c.Secret {
t.Errorf("Error: expected secret %s, got secret %s for URL: %s", c.Secret, secret, te.url)
}
}
}
func TestMissingCredentials(t *testing.T) {
helper := Wincred{}
_, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd")
if !credentials.IsErrCredentialsNotFound(err) {
t.Fatalf("expected ErrCredentialsNotFound, got %v", err)
}
}