1
0
mirror of https://github.com/docker/docker-credential-helpers.git synced 2026-06-28 07:11:36 +05:30

Compare commits

..

272 Commits

Author SHA1 Message Date
Sebastiaan van Stijn a25b89fc7b Merge pull request #437 from docker/dependabot/github_actions/codecov/codecov-action-6.0.1
build(deps): bump codecov/codecov-action from 6.0.0 to 6.0.1
2026-05-20 17:35:25 +02:00
dependabot[bot] 308a004bb3 build(deps): bump codecov/codecov-action from 6.0.0 to 6.0.1
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 6.0.0 to 6.0.1.
- [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/57e3a136b779b570ffcdbf80b3bdc90e7fab3de2...e79a6962e0d4c0c17b229090214935d2e33f8354)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-20 15:13:47 +00:00
Austin Vazquez 312e321de7 Merge pull request #431 from thaJeztah/bump_go_1.26.3
update to go1.26.3
2026-05-07 18:09:57 -07:00
Sebastiaan van Stijn 6fd0550317 Merge pull request #430 from docker/dependabot/github_actions/crazy-max/dot-github/dot-github/workflows/zizmor.yml-1.7.1
build(deps): bump crazy-max/.github/.github/workflows/zizmor.yml from 1.6.0 to 1.7.1
2026-05-08 00:36:39 +02:00
Sebastiaan van Stijn d930f0631a update to go1.26.3
- https://github.com/golang/go/issues?q=milestone%3AGo1.26.3+label%3ACherryPickApproved
- full diff: https://github.com/golang/go/compare/go1.26.2...go1.26.3

This release include 11 security fixes:

- cmd/go: malicious module proxy can bypass checksum database

    A malicious module proxy could exploit a flaw in the go command's
    validation of module checksums to bypass checksum database validation.

    This vulnerability affects any user using an untrusted module proxy
    (GOMODPROXY) or checksum database (GOSUMDB).

    A malicious module proxy can serve altered versions of the Go toolchain.
    When selecting a different version of the Go toolchain than the
    currently installed toolchain (due to the GOTOOLCHAIN environment variable,
    or a go.work or go.mod with a toolchain line), the go command will download
    and execute a toolchain provided by the module proxy. A malicious module
    proxy can bypass checksum database validation for this downloaded
    toolchain.

    Since this vulnerability affects the security of toolchain downloads,
    setting GOTOOLCHAIN to a fixed version is not sufficient. You must upgrade
    your base Go toolchain.

    The go tool always validates the hash of a toolchain before executing it,
    so fixed versions will refuse to execute any cached, altered versions of the
    toolchain.

    The go tool trusts go.sum files to contain accurate hashes of the current
    module's dependencies. A malicious proxy exploiting this vulnerability to
    serve an altered module will have caused an incorrect hash to be recorded
    in the go.sum. Users who have configured a non-trusted GOPROXY can determine
    if they have been affected by running "rm go.sum ; go mod tidy ; go mod verify",
    which will revalidate all dependencies of the current module.

    The specific flaw in more detail:

    The go command consults the checksum database to validate downloaded modules,
    when a module is not listed in the go.sum file. It verifies that the module hash
    reported by the checksum database matches the hash of the downloaded module.
    If, however, the checksum database returns a successful response that contains
    no entry for the module, the go command incorrectly permitted validation to succeed.

    A module proxy may mirror or proxy the checksum database, in which case the go
    command will not connect to the checksum database directly. Checksums reported
    by the checksum database are cryptographically signed, so a malicious proxy
    cannot alter the reported checksum for a module. However, a proxy which returns
    an empty checksum response, or a checksum response for an unrelated module,
    could cause the go command to proceed as if a downloaded module has been validated.

    The go command now properly checks checksum database responses to ensure
    that the expected module signature is present, not just that if a signature is
    present it matches the expectation.

    Thanks to Mundur (https://github.com/M0nd0R) for reporting this issue.

    This is CVE-2026-42501 and Go issue https://go.dev/issue/79070.

- net/http/httputil: ReverseProxy forwards queries with more than urlmaxqueryparams parameters

    When used with a Rewrite function, or a Director function which parses query parameters,
    ReverseProxy sanitizes the forwarded request to remove query parameters which are not
    parsed by url.ParseQuery. ReverseProxy did not take ParseQuery's limit on the total number
    of query parameters (controlled by GODEBUG=urlmaxqueryparams=N) into account.
    This could permit ReverseProxy to forward a request containing a query parameter
    that was not visible to the Rewrite function.

    For example, the query "a1=x&a2=x&...&a10000=x&hidden=y" could forward the parameter
    "hidden=y" while hiding it from the proxy's Rewrite function.

    ReverseProxy now avoids forwarding parameters that exceed the ParseQuery limit.

    This is CVE-2026-39825 and Go issue https://go.dev/issue/78948.

- net: panic in Dial and LookupPort when handling NUL byte on Windows

    The Dial and LookupPort functions would panic on Windows when provided
    with an input containing a NUL (0). These functions now return an error
    rather than panicking.

    This is CVE-2026-39836 and Go issue https://go.dev/issue/79006.

- net/mail: quadratic string concatenation in consumePhrase

    Pathological inputs could cause DoS through consumePhrase
    when parsing an email address according to RFC 5322.

    This is CVE-2026-42499 and Go issue https://go.dev/issue/78987.

- net/mail: quadratic string concatentation in consumeComment

    Well-crafted inputs reaching ParseAddress, ParseAddressList,
    and ParseDate were able to trigger excessive CPU exhaustion
    and memory allocations.

    This is CVE-2026-39820 and Go issue https://go.dev/issue/78566.

- cmd/go: "go bug" follows symlinks in predictable temporary filenames

    The "go bug" command wrote to two files with predictable names in
    the system temporary directory (for example, "/tmp").

    An attacker with access to the temporary directory could create a
    symlink in one of these names, causing "go bug" to overwrite the
    target of the symlink.

    The "go bug" command now uses os.MkdirTemp to create a safe
    working directory.

    Thanks to Harshit Gupta (Mr HAX) for reporting this issue.

    This is CVE-2026-39819 and Go issue https://go.dev/issue/78584.

- cmd/go: "go tool pack" does not sanitize output paths

    The "go tool pack" subcommand is a minimal version of the Unix ar utility.
    It is used by the compiler as an internal tool with known-good inputs.

    The "pack" subcommand did not sanitize output filenames.
    When invoked to extract a malicious archive file, it could write
    files to arbitrary locations on the filesystem.

    The "pack" subcommand now refuses to extract files with names
    containing any directory components.

    Thanks to Harshit Gupta (Mr HAX) for reporting this issue.

    This is CVE-2026-39817 and Go issue https://go.dev/issue/78778.

- net/http: infinite loop in HTTP/2 transport when given bad SETTINGS_MAX_FRAME_SIZE

    When processing HTTP/2 SETTINGS frames, transport will enter an infinite loop of
    writing CONTINUATION frames if it receives a SETTINGS_MAX_FRAME_SIZE with a
    value of 0.

    This allows potential DoS against a client by a malicious server. HTTP/2
    transport now properly checks that the received SETTINGS_MAX_FRAME_SIZE is
    valid.

    Thanks to Marwan Atia (marwansamir688@gmail.com) for reporting this issue.

    This is CVE-2026-33814 and Go issue https://go.dev/issue/78476.

- html/template: escaper bypass leads to XSS

    If a trusted template author were to write a
    tag containing an empty type attribute or a type
    attribute with an ASCII whitespace, the execution of
    the template would incorrectly escape any data passed
    into the block.

    Thanks to Mundur (https://github.com/M0nd0R) for reporting this issue.

    This is CVE-2026-39826 and Go issue https://go.dev/issue/78981.

- net: crash when handling long CNAME response

    When using LookupCNAME with the cgo DNS resolver,
    a very long CNAME response could trigger a double-free of C memory
    and a crash. The double-free has been fixed.

    Thanks to hamayanhamayan for reporting this issue.

    This is CVE-2026-33811 and Go issue https://go.dev/issue/78803.

- html/template: bypass of meta content URL escaping causes XSS

    CVE-2026-27142 fixed a vulnerability in which URLs were not
    correctly escaped inside of a tag's attribute.
    If the URL content were to insert ASCII whitespaces around the
    = rune inside of the attribute, the escaper would
    fail to similarly escape it, leading to XSS.

    Dynamic inputs to a tag's attribute are now
    whitespace sanitized prior to escaping.

    Thanks to Samy Ghannad for reporting this issue.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-05-08 00:35:00 +02:00
dependabot[bot] 777084b6aa build(deps): bump crazy-max/.github/.github/workflows/zizmor.yml
Bumps [crazy-max/.github/.github/workflows/zizmor.yml](https://github.com/crazy-max/.github) from 1.6.0 to 1.7.1.
- [Release notes](https://github.com/crazy-max/.github/releases)
- [Commits](https://github.com/crazy-max/.github/compare/d89fe92d808a15e2b2ed5cdb62db7c172c31410d...64a0bfaf6e6bb1c448d6e4c42b11034ee7094f16)

---
updated-dependencies:
- dependency-name: crazy-max/.github/.github/workflows/zizmor.yml
  dependency-version: 1.7.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-23 09:43:31 +00:00
Paweł Gronowski ca5e83c83a Merge pull request #228 from thaJeztah/carry_openbsd_secretservice
secretservice: allow building on openbsd
2026-04-20 13:02:07 +02:00
Paweł Gronowski 7cd63786cf Merge pull request #428 from thaJeztah/wincred_cleanups
wincred: minor cleanups
2026-04-20 13:01:57 +02:00
Rafael Ávila de Espíndola 5fcff353e7 secretservice: allow building on openbsd
There is nothing linux specific about secretservice. I was able to
build it on openbsd with this change.

Signed-off-by: Rafael Ávila de Espíndola <rafael@espindo.la>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-20 12:56:55 +02:00
Sebastiaan van Stijn 390d43e7e4 wincred: minor cleanups
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-20 11:48:16 +02:00
Paweł Gronowski ed71c9e95d Merge pull request #425 from thaJeztah/bump_xx
Dockerfile: update xx to v1.9.0
2026-04-17 17:47:17 +02:00
Paweł Gronowski 8af787bb3e Merge pull request #424 from thaJeztah/bump_go
update to Go 1.25.9
2026-04-17 17:47:10 +02:00
Paweł Gronowski 8b34acde14 Merge pull request #426 from thaJeztah/bump_golangci
Dockerfile: update golangci-lint to v2.11
2026-04-17 17:47:07 +02:00
Sebastiaan van Stijn 815dddf301 Dockerfile: update xx to v1.9.0
full diff: https://github.com/tonistiigi/xx/compare/v1.7.0...v1.9.0

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-17 15:52:06 +02:00
Sebastiaan van Stijn 1be1c1e190 update to Go 1.25.9
go1.25.9 (released 2026-04-07) includes security fixes to the go command,
the compiler, and the archive/tar, crypto/tls, crypto/x509, html/template,
and os packages, as well as bug fixes to the go command, the compiler, and
the runtime. See the Go 1.25.9 milestone on our issue tracker for details.

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

From the security announce:

We have just released Go versions 1.26.2 and 1.25.9, minor point releases.

These releases include 10 security fixes following the security policy:

- os: Root.Chmod can follow symlinks out of the root on Linux

  On Linux, if the target of Root.Chmod is replaced with a symlink while
  the chmod operation is in progress, Chmod could operate on the target
  of the symlink, even when the target lies outside the root.

  The Linux fchmodat syscall silently ignores the AT_SYMLINK_NOFOLLOW flag,
  which Root.Chmod uses to avoid symlink traversal. Root.Chmod checks its
  target before acting and returns an error if the target is a symlink
  lying outside the root, so the impact is limited to cases where the
  target is replaced with a symlink between the check and operation.

  On Linux, Root.Chmod now uses the fchmodat2 syscall when available, and
  an workaround using /proc/self/fd otherwise.

  Thanks to Uuganbayar Lkhamsuren for reporting this issue.

  This is CVE-2026-32282 and Go issue https://go.dev/issue/78293.

- html/template: JS template literal context incorrectly tracked

  Context was not properly tracked across template branches for JS template
  literals, leading to possibly incorrect escaping of content when branches were
  used.

  Additionally template actions within JS template literals did not properly
  track
  the brace depth, leading to incorrect escaping being applied.

  These issues could cause actions within JS template literals to be incorrectly
  or improperly escaped, leading to XSS vulnerabilities.

  This only affects templates that use template actions within JS template
  literals.

  This is CVE-2026-32289 and Go issue https://go.dev/issue/78331.

- crypto/x509: excluded DNS constraints not properly applied to wildcard domains

  When verifying a certificate chain containing excluded DNS constraints, these
  constraints are not correctly applied to wildcard DNS SANs which use a
  different
  case than the constraint.

  For example, if a certificate contains the DNS name "*.example.com" and the
  excluded DNS name "EXAMPLE.COM", the constraint will not be applied.

  This only affects validation of otherwise trusted certificate chains, issued
  by
  a root CA in the VerifyOptions.Roots CertPool, or in the system certificate
  pool.

  This issue only affects Go 1.26.

  Thank you to Riyas from Saintgits College of Engineering, k1rnt, @1seal for
  reporting this issue.

  This is CVE-2026-33810 and Go issue https://go.dev/issue/78332.

- cmd/compile: no-op interface conversion bypasses overlap checking

  Previously, the compiler failed to unwrap pointers contained within
  a no-op interface conversion leading to an incorrect determination
  of a non-overlapping move.

  To prevent unsafe move operations, the compiler will now unwrap all
  such conversions before considering a move non-overlapping.

  Thank you to Jakub Ciolek - https://ciolek.dev/ for reporting this issue.

  This is CVE-2026-27144 and Go issue https://go.dev/issue/78371.

- cmd/compile: possible memory corruption after bound check elimination

  Previously, slices and arrays accessed using induction variables
  were sometimes incorrectly proved in-bound. If the induction variable
  used for indexing were to overflow or underflow, it could allow access
  to memory beyond the scope of the original slice or array.

  To prevent this behavior, the compiler ensures that any mutated induction
  variable that overflows/underflows with respect to its loop condition
  is not used for bound check elimination.

  Thank you to Jakub Ciolek - https://ciolek.dev/ for reporting this issue.

  This is CVE-2026-27143 and Go issue https://go.dev/issue/78333.

- archive/tar: unbounded allocation when parsing old format GNU sparse map

  tar.Reader could allocate an unbounded amount of memory when reading
  a maliciously-crafted archive containing a large number of sparse
  regions encoded in the "old GNU sparse map" format.

  We now limit both the number of old GNU sparse map extension blocks,
  and the total number of sparse file entries, regardless of encoding.

  Thanks to Colin Walters (wal...@verbum.org) who initially reported this issue.
  Thanks also to Uuganbayar Lkhamsuren (https://github.com/uug4na) and Jakub
  Ciolek
  who additionally reported this issue.

  This is CVE-2026-32288 and Go issue https://go.dev/issue/78301.

- crypto/tls: multiple key update handshake messages can cause connection to
  deadlock

  If one side of the TLS connection sends multiple key update messages
  post-handshake in a single record, the connection can deadlock, causing
  uncontrolled consumption of resources. This can lead to a denial of service.

  This only affects TLS 1.3.

  Thank you to Jakub Ciolek - https://ciolek.dev/ for reporting this issue.

  This is CVE-2026-32283 and Go issue https://go.dev/issue/78334.

- cmd/go: trust layer bypass when using cgo and SWIG

  A well-crafted SWIG source file could take advantage
  of a file-naming convention used inside the trust
  boundary of the cgo compiler. Doing so could result
  in arbitrary code execution during build time.

  SWIG files are disallowed from using this convention.

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

  This is CVE-2026-27140 and Go issue https://go.dev/issue/78335.

- crypto/x509: unexpected work during chain building

  During chain building, the amount of work that is done is not correctly
  limited
  when a large number of intermediate certificates are passed in
  VerifyOptions.Intermediates, which can lead to a denial of service. This
  affects
  both direct users of crypto/x509 and users of crypto/tls.

  Thank you to Jakub Ciolek - https://ciolek.dev/ for reporting this issue.

  This is CVE-2026-32280 and Go issue https://go.dev/issue/78282.

- crypto/x509: inefficient policy validation

  Validating certificate chains which use policies is unexpectedly inefficient
  when certificates in the chain contain a very large number of policy mappings,
  possibly causing denial of service.

  This only affects validation of otherwise trusted certificate chains, issued
  by
  a root CA in the VerifyOptions.Roots CertPool, or in the system certificate
  pool.

  Thank you to Jakub Ciolek - https://ciolek.dev/ for reporting this issue.

  This is CVE-2026-32281 and Go issue https://go.dev/issue/78281.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-17 15:51:51 +02:00
Sebastiaan van Stijn 73bc2ff95d Dockerfile: update golangci-lint to v2.11
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-17 15:51:34 +02:00
Paweł Gronowski ae9f7a436a Merge pull request #412 from thaJeztah/dockerfile_docs
Dockerfile: document build-args
2026-04-17 15:41:51 +02:00
Sebastiaan van Stijn 27baf766de Merge pull request #423 from crazy-max/zizmor
ci: zizmor workflow
2026-04-16 16:05:09 +02:00
CrazyMax 9264cc84b4 fix zizmor findings
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2026-04-15 12:21:16 +02:00
CrazyMax 28f86c4a07 ci: zizmor workflow
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2026-04-15 12:21:07 +02:00
Sebastiaan van Stijn 6fe9815c68 Dockerfile: document build-args
```
docker buildx build --quiet --call=outline .

BUILD ARG            VALUE                                         DESCRIPTION
GO_VERSION           1.25.8                                        sets the version of the golang base image to use.
BASE_DEBIAN_DISTRO   bookworm                                      sets the golang base image debian variant to use.
XX_VERSION           1.7.0                                         sets the version of the xx utility to use.
OSXCROSS_VERSION     11.3-r8-debian                                sets the MacOSX cross toolchain to use.
PACKAGE              github.com/docker/docker-credential-helpers   sets the package name to print in the "--version" output.
```

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-04-15 11:52:15 +02:00
Sebastiaan van Stijn 16a8c2ce61 Merge pull request #421 from docker/dependabot/github_actions/actions/upload-artifact-7.0.1
build(deps): bump actions/upload-artifact from 7.0.0 to 7.0.1
2026-04-15 11:49:37 +02:00
Sebastiaan van Stijn 164ec8c494 Merge pull request #422 from docker/dependabot/github_actions/actions/setup-go-6.4.0
build(deps): bump actions/setup-go from 6.3.0 to 6.4.0
2026-04-15 11:48:45 +02:00
dependabot[bot] defdb5e2f5 build(deps): bump actions/setup-go from 6.3.0 to 6.4.0
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6.3.0 to 6.4.0.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/4b73464bb391d4059bd26b0524d20df3927bd417...4a3601121dd01d1626a1e23e37211e3254c1c06c)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-15 09:43:51 +00:00
dependabot[bot] 28e11f3745 build(deps): bump actions/upload-artifact from 7.0.0 to 7.0.1
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/bbbca2ddaa5d8feaa63e36b76fdaad77386f024f...043fb46d1a93c77aae656e7c1c64a875d1fc6a0a)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-15 09:43:47 +00:00
Sebastiaan van Stijn e3d1da694f Merge pull request #419 from docker/dependabot/github_actions/actions/github-script-9
build(deps): bump actions/github-script from 8 to 9
2026-04-14 23:26:08 +02:00
Sebastiaan van Stijn 98a1b9ce53 Merge pull request #413 from docker/dependabot/github_actions/docker/bake-action-7
build(deps): bump docker/bake-action from 6 to 7
2026-04-14 23:25:31 +02:00
Sebastiaan van Stijn ca21698edc Merge pull request #418 from docker/dependabot/github_actions/codecov/codecov-action-6
build(deps): bump codecov/codecov-action from 5 to 6
2026-04-14 23:17:01 +02:00
dependabot[bot] 300c1b491f build(deps): bump docker/bake-action from 6 to 7
Bumps [docker/bake-action](https://github.com/docker/bake-action) from 6 to 7.
- [Release notes](https://github.com/docker/bake-action/releases)
- [Commits](https://github.com/docker/bake-action/compare/v6...v7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-14 21:16:09 +00:00
Sebastiaan van Stijn 14b6d371c9 Merge pull request #420 from docker/dependabot/github_actions/softprops/action-gh-release-3.0.0
build(deps): bump softprops/action-gh-release from 2.5.0 to 3.0.0
2026-04-14 23:13:27 +02:00
dependabot[bot] bfd43cacbb build(deps): bump codecov/codecov-action from 5 to 6
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5 to 6.
- [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/v5...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-14 21:12:32 +00:00
dependabot[bot] 7b43509f5b build(deps): bump actions/github-script from 8 to 9
Bumps [actions/github-script](https://github.com/actions/github-script) from 8 to 9.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v8...v9)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-version: '9'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-14 21:12:31 +00:00
Sebastiaan van Stijn 5ad51ee4a4 Merge pull request #417 from thaJeztah/pin_actions
ci: pin actions by sha
2026-04-14 23:11:29 +02:00
dependabot[bot] 5b6a1880fa build(deps): bump softprops/action-gh-release from 2.5.0 to 3.0.0
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.5.0 to 3.0.0.
- [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/a06a81a03ee405af7f2048a818ed3f03bbf83c7b...b4309332981a82ec1c5618f44dd2e27cc8bfbfda)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-13 10:33:46 +00:00
Sebastiaan van Stijn 29038b4df4 ci: pin actions by sha
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-25 20:24:38 +01:00
Sebastiaan van Stijn 955f6c518d Merge pull request #414 from thaJeztah/update_go1.25.8
update to go1.25.8
2026-03-10 17:30:11 +01:00
Sebastiaan van Stijn 0202e5a960 update to go1.25.8
go1.25.8 (released 2026-03-05) includes security fixes to the html/template,
net/url, and os packages, as well as bug fixes to the go command, the compiler,
and the os package. See the Go 1.25.8 milestone on our issue tracker for details.

- 1.25.8 https://github.com/golang/go/issues?q=milestone%3AGo1.25.8+label%3ACherryPickApproved
- diff: https://github.com/golang/go/compare/go1.25.7...go1.25.8
- 1.26.1 https://github.com/golang/go/issues?q=milestone%3AGo1.26.1+label%3ACherryPickApproved
- diff: https://github.com/golang/go/compare/go1.26.0...go1.26.1

---

We have just released Go versions 1.26.1 and 1.25.8, minor point releases.

These releases include 5 security fixes following the security policy:

crypto/x509: incorrect enforcement of email constraints

- When verifying a certificate chain which contains a certificate containing
  multiple email address constraints (composed of the full email address) which
  share common local portions (the portion of the address before the '@'
  character) but different domain portions (the portion of the address after the
  '@' character), these constraints will not be properly applied, and only the
  last constraint will be considered.

  This can allow certificates in the chain containing email addresses which are
  either not permitted or excluded by the relevant constraints to be returned by
  calls to Certificate.Verify. Since the name constraint checks happen after chain
  building is complete, this only applies to certificate chains which chain to
  trusted roots (root certificates either in VerifyOptions.Roots or in the system
  root certificate pool), requiring a trusted CA to issue certificates containing
  either not permitted or excluded email addresses.

  This issue only affects Go 1.26.

  Thanks to Jakub Ciolek for reporting this issue.

  This is CVE-2026-27137 and Go issue https://go.dev/issue/77952.

- crypto/x509: panic in name constraint checking for malformed certificates

  Certificate verification can panic when a certificate in the chain has an empty
  DNS name and another certificate in the chain has excluded name constraints.
  This can crash programs that are either directly verifying X.509 certificate
  chains, or those that use TLS.

  Since the name constraint checks happen after chain building is complete, this
  only applies to certificate chains which chain to trusted roots (root
  certificates either in VerifyOptions.Roots or in the system root certificate
  pool), requiring a trusted CA to issue certificates containing malformed DNS
  names.

  This issue only affects Go 1.26.

  Thanks to Jakub Ciolek for reporting this issue.

  This is CVE-2026-27138 and Go issue https://go.dev/issue/77953.

- html/template: URLs in meta content attribute actions are not escaped

  Actions which insert URLs into the content attribute of HTML meta tags are not
  escaped. This can allow XSS if the meta tag also has an http-equiv attribute
  with the value "refresh".

  A new GODEBUG setting has been added, htmlmetacontenturlescape, which can be
  used to disable escaping URLs in actions in the meta content attribute which
  follow "url=" by setting htmlmetacontenturlescape=0.

  This is CVE-2026-27142 and Go issue https://go.dev/issue/77954.

- net/url: reject IPv6 literal not at start of host

  The Go standard library function net/url.Parse insufficiently
  validated the host/authority component and accepted some invalid URLs
  by effectively treating garbage before an IP-literal as ignorable.
  The function should have rejected this as invalid.

  To prevent this behavior, net/url.Parse now rejects IPv6 literals
  that do not appear at the start of the host subcomponent of a URL.

  Thanks to Masaki Hara (https://github.com/qnighy) of Wantedly.

  This is CVE-2026-25679 and Go issue https://go.dev/issue/77578.

- os: FileInfo can escape from a Root

  On Unix platforms, when listing the contents of a directory using
  File.ReadDir or File.Readdir the returned FileInfo could reference
  a file outside of the Root in which the File was opened.

  The contents of the FileInfo were populated using the lstat system
  call, which takes the path to the file as a parameter. If a component
  of the full path of the file described by the FileInfo is replaced with
  a symbolic link, the target of the lstat can be directed to another
  location on the filesystem.

  The impact of this escape is limited to reading metadata provided by
  lstat from arbitrary locations on the filesystem. This could be used
  to probe for the presence or absence of files as well as gleaning
  metadata like file sizes, but does not permit reading or writing files
  outside the root.

  The FileInfo is now populated using fstatat.

  Thank you to Miloslav Trmač of Red Hat for reporting this issue.

  This is CVE-2026-27139 and Go issue https://go.dev/issue/77827.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-03-06 13:58:18 +01:00
CrazyMax 4e2b0ff14f Merge pull request #408 from docker/dependabot/github_actions/actions/upload-artifact-7
build(deps): bump actions/upload-artifact from 6 to 7
2026-03-05 11:15:50 +01:00
CrazyMax 8fe8d458f7 Merge pull request #409 from docker/dependabot/github_actions/crazy-max/ghaction-import-gpg-7
build(deps): bump crazy-max/ghaction-import-gpg from 6 to 7
2026-03-05 11:15:27 +01:00
CrazyMax af758c414c Merge pull request #411 from docker/dependabot/github_actions/docker/setup-buildx-action-4
build(deps): bump docker/setup-buildx-action from 3 to 4
2026-03-05 11:15:06 +01:00
dependabot[bot] dc6f4f5cb9 build(deps): bump docker/setup-buildx-action from 3 to 4
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-05 09:43:23 +00:00
CrazyMax 4e68cd824e Merge pull request #410 from docker/dependabot/github_actions/docker/setup-qemu-action-4
build(deps): bump docker/setup-qemu-action from 3 to 4
2026-03-04 11:02:41 +01:00
dependabot[bot] d520877610 build(deps): bump docker/setup-qemu-action from 3 to 4
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-04 09:43:23 +00:00
dependabot[bot] f0e4adbf36 build(deps): bump crazy-max/ghaction-import-gpg from 6 to 7
Bumps [crazy-max/ghaction-import-gpg](https://github.com/crazy-max/ghaction-import-gpg) from 6 to 7.
- [Release notes](https://github.com/crazy-max/ghaction-import-gpg/releases)
- [Commits](https://github.com/crazy-max/ghaction-import-gpg/compare/v6...v7)

---
updated-dependencies:
- dependency-name: crazy-max/ghaction-import-gpg
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-03 09:43:35 +00:00
dependabot[bot] bf6137df6b build(deps): bump actions/upload-artifact from 6 to 7
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-27 09:43:12 +00:00
Sebastiaan van Stijn 2b4e08bca3 Merge pull request #407 from thaJeztah/bump_go1.25.7
update to go1.25.7
2026-02-05 13:23:39 +01:00
Sebastiaan van Stijn 62deeb49c1 update to go1.25.7
go1.25.7 (released 2026-02-04) includes security fixes to the go command
and the crypto/tls package, as well as bug fixes to the compiler and the
crypto/x509 package. See the Go 1.25.7 milestone on our issue tracker for
details:
https://github.com/golang/go/issues?q=milestone%3AGo1.25.7+label%3ACherryPickApproved

full diff: https://github.com/golang/go/compare/go1.25.6...go1.25.7

From the security mailing list:

> Hello gophers,
>
> We have just released Go versions 1.25.7 and 1.24.13, minor point releases.
>
> These releases include 2 security fixes following the security policy:
>
> - cmd/cgo: remove user-content from doc strings in cgo ASTs
>
>   A discrepancy between how Go and C/C++ comments
>   were parsed allowed for code smuggling into the
>   resulting cgo binary.
>
>   To prevent this behavior, the cgo compiler
>   will no longer parse user-provided doc
>   comments.
>
>   Thank you to RyotaK (https://ryotak.net) of
>   GMO Flatt Security Inc. for reporting this issue.
>
>   This is CVE-2025-61732 and https://go.dev/issue/76697.
>
> - crypto/tls: unexpected session resumption when using Config.GetConfigForClient
>
>   Config.GetConfigForClient is documented to use the original Config's session
>   ticket keys unless explicitly overridden. This can cause unexpected behavior if
>   the returned Config modifies authentication parameters, like ClientCAs: a
>   connection initially established with the parent (or a sibling) Config can be
>   resumed, bypassing the modified authentication requirements.
>
>   If ClientAuth is VerifyClientCertIfGiven or RequireAndVerifyClientCert (on the
>   server) or InsecureSkipVerify is false (on the client), crypto/tls now checks
>   that the root of the previously-verified chain is still in ClientCAs/RootCAs
>   when resuming a connection.
>
>   Go 1.26 Release Candidate 2, Go 1.25.6, and Go 1.24.12 had fixed a similar issue
>   related to session ticket keys being implicitly shared by Config.Clone. Since
>   this fix is broader, the Config.Clone behavior change has been reverted.
>
>   Note that VerifyPeerCertificate still behaves as documented: it does not apply
>   to resumed connections. Applications that use Config.GetConfigForClient or
>   Config.Clone and do not wish to blindly resume connections established with the
>   original Config must use VerifyConnection instead (or SetSessionTicketKeys or
>   SessionTicketsDisabled).
>
>   Thanks to Coia Prant (github.com/rbqvq) for reporting this issue.
>
>   This updates CVE-2025-68121 and Go issue https://go.dev/issue/77217.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-02-05 13:13:02 +01:00
Paweł Gronowski 6ca9924445 Merge pull request #406 from thaJeztah/bump_go
update to go1.25.6
2026-01-19 13:51:23 +00:00
Sebastiaan van Stijn 806dc5f678 update to go1.25.6
This releases includes 6 security fixes following the security policy:

- archive/zip: denial of service when parsing arbitrary ZIP archives

    archive/zip used a super-linear file name indexing algorithm that is invoked the first time a file in an archive is opened. This can lead to a denial of service when consuming a maliciously constructed ZIP archive.

    Thanks to Thanks to Jakub Ciolek for reporting this issue.

    This is CVE-2025-61728 and Go issue https://go.dev/issue/77102.

- net/http: memory exhaustion in Request.ParseForm

    When parsing a URL-encoded form net/http may allocate an unexpected amount of
    memory when provided a large number of key-value pairs. This can result in a
    denial of service due to memory exhaustion.

    Thanks to jub0bs for reporting this issue.

    This is CVE-2025-61726 and Go issue https://go.dev/issue/77101.

- crypto/tls: Config.Clone copies automatically generated session ticket keys, session resumption does not account for the expiration of full certificate chain

    The Config.Clone methods allows cloning a Config which has already been passed
    to a TLS function, allowing it to be mutated and reused.

    If Config.SessionTicketKey has not been set, and Config.SetSessionTicketKeys has
    not been called, crypto/tls will generate random session ticket keys and
    automatically rotate them. Config.Clone would copy these automatically generated
    keys into the returned Config, meaning that the two Configs would share session
    ticket keys, allowing sessions created using one Config could be used to resume
    sessions with the other Config. This can allow clients to resume sessions even
    though the Config may be configured such that they should not be able to do so.

    Config.Clone no longer copies the automatically generated session ticket keys.
    Config.Clone still copies keys which are explicitly provided, either by setting
    Config.SessionTicketKey or by calling Config.SetSessionTicketKeys.

    This issue was discoverd by the Go Security team while investigating another
    issue reported by Coia Prant (github.com/rbqvq).

    Additionally, on the server side only the expiration of the leaf certificate, if
    one was provided during the initial handshake, was checked when considering if a
    session could be resumed. This allowed sessions to be resumed if an intermediate
    or root certificate in the chain had expired.

    Session resumption now takes into account of the full chain when determining if
    the session can be resumed.

    Thanks to Coia Prant (github.com/rbqvq) for reporting this issue.

    This is CVE-2025-68121 and Go issue https://go.dev/issue/77113.

- cmd/go: bypass of flag sanitization can lead to arbitrary code execution

    Usage of 'CgoPkgConfig' allowed execution of the pkg-config
    binary with flags that are not explicitly safe-listed.

    To prevent this behavior, compiler flags resulting from usage
    of 'CgoPkgConfig' are sanitized prior to invoking pkg-config.

    Thank you to RyotaK (https://ryotak.net) of GMO Flatt Security Inc.
    for reporting this issue.

    This is CVE-2025-61731 and go.dev/issue/77100.

- cmd/go: unexpected code execution when invoking toolchain

    The Go toolchain supports multiple VCS which are used retrieving modules and
    embedding build information into binaries.

    On systems with Mercurial installed (hg) downloading modules (e.g. via go get or
    go mod download) from non-standard sources (e.g. custom domains) can cause
    unexpected code execution due to how external VCS commands are constructed.

    On systems with Git installed, downloading and building modules with malicious
    version strings could allow an attacker to write to arbitrary files on the
    system the user has access to. This can only be triggered by explicitly
    providing the malicious version strings to the toolchain, and does not affect
    usage of @latest or bare module paths.

    The toolchain now uses safer VCS options to prevent misinterpretation of
    untrusted inputs. In addition, the toolchain now disallows module version
    strings prefixed with a "-" or "/" character.

    Thanks to splitline (@splitline) from DEVCORE Research Team for reporting this
    issue.

    This is CVE-2025-68119 and Go issue https://go.dev/issue/77099.

- crypto/tls: handshake messages may be processed at the incorrect encryption level

    During the TLS 1.3 handshake if multiple messages are sent in records that span
    encryption level boundaries (for instance the Client Hello and Encrypted
    Extensions messages), the subsequent messages may be processed before the
    encryption level changes. This can cause some minor information disclosure if a
    network-local attacker can inject messages during the handshake.

    Thanks to Coia Prant (github.com/rbqvq) for reporting this issue.

    This is CVE-2025-61730 and Go issue https://go.dev/issue/76443

View the release notes for more information:
https://go.dev/doc/devel/release#go1.25.6

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-19 14:21:10 +01:00
Sebastiaan van Stijn a7b23cd2b5 Merge pull request #405 from thaJeztah/gha_perms
gha: set default permissions, add guardrail timeouts, and update branch name (master -> main)
2026-01-09 11:41:43 +01:00
Sebastiaan van Stijn 178a3a4e57 gha: update master branch to main
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-09 09:43:50 +01:00
Sebastiaan van Stijn f5fd80af0f gha: add guardrails timeouts to jobs
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-09 09:35:19 +01:00
Sebastiaan van Stijn ae163ade7b gha: set "read" permissions as default
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-09 09:28:23 +01:00
Sebastiaan van Stijn b871f76540 Merge pull request #404 from thaJeztah/rm_noninteractive
Dockerfile: remove redundant DEBIAN_FRONTEND=noninteractive
2026-01-08 17:44:32 +01:00
Sebastiaan van Stijn 50c1460bf5 Dockerfile: remove redundant DEBIAN_FRONTEND=noninteractive
This should no longer be needed for current versions of Debian
and Ubuntu.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-08 13:06:40 +01:00
Sebastiaan van Stijn aecf6e5780 Merge pull request #402 from thaJeztah/bump_golangci_lint
Dockerfile: update golangci-lint to v2.8
2026-01-08 13:06:09 +01:00
Sebastiaan van Stijn ecf6c1ccc7 Merge pull request #399 from ameya-keskar/bump_go_1.25.5
update to go1.25.5
2026-01-08 12:40:22 +01:00
Ameya Keskar b844409a12 update to go1.25.5
- Update Go version to v1.25.5 in build workflow
- Update GO_VERSION to 1.25.5 in Dockerfile
- Update GO_VERSION to 1.25.5

Signed-off-by: Ameya Keskar <55844298+ameya-keskar@users.noreply.github.com>
2026-01-08 11:59:16 +01:00
Sebastiaan van Stijn 9df2c7782a Merge pull request #401 from thaJeztah/bump_ubuntu
gha: update some actions to ubuntu 24.04
2026-01-08 11:57:23 +01:00
Sebastiaan van Stijn 7a15b77bcb Dockerfile: update golangci-lint to v2.8
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-08 11:55:17 +01:00
Sebastiaan van Stijn 81f7ebebfd gha: update some actions to ubuntu 24.04
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2026-01-08 11:49:17 +01:00
Sebastiaan van Stijn 3f97cf3ce3 Merge pull request #398 from docker/dependabot/github_actions/actions/upload-artifact-6
build(deps): bump actions/upload-artifact from 4 to 6
2026-01-08 11:28:12 +01:00
Sebastiaan van Stijn 8b5e6dffc6 Merge pull request #397 from docker/dependabot/github_actions/softprops/action-gh-release-2.5.0
build(deps): bump softprops/action-gh-release from 2.4.1 to 2.5.0
2026-01-08 11:27:39 +01:00
Sebastiaan van Stijn 4741f33d28 Merge pull request #395 from docker/dependabot/github_actions/actions/checkout-6
build(deps): bump actions/checkout from 5 to 6
2026-01-08 11:25:55 +01:00
dependabot[bot] 78303955b8 build(deps): bump actions/upload-artifact from 4 to 6
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 09:09:36 +00:00
dependabot[bot] 9b0c242b5c build(deps): bump softprops/action-gh-release from 2.4.1 to 2.5.0
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.4.1 to 2.5.0.
- [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/6da8fa9354ddfdc4aeace5fc48d7f679b5214090...a06a81a03ee405af7f2048a818ed3f03bbf83c7b)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-02 09:09:45 +00:00
dependabot[bot] 057ed818a9 build(deps): bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-21 09:09:36 +00:00
Sebastiaan van Stijn b7a754b9ff Merge pull request #392 from thaJeztah/bump_go_1.25.2
update to go1.25.2
2025-10-13 14:20:23 +02:00
Sebastiaan van Stijn 62777f0887 Merge pull request #391 from docker/dependabot/github_actions/softprops/action-gh-release-2.4.1
build(deps): bump softprops/action-gh-release from 2.3.3 to 2.4.1
2025-10-13 13:24:04 +02:00
Sebastiaan van Stijn 9d04e49561 update to go1.25.2
This minor release includes 10 security fixes following the security policy:

- net/mail: excessive CPU consumption in ParseAddress

    The ParseAddress function constructed domain-literal address components through repeated string concatenation. When parsing large domain-literal components, this could cause excessive CPU consumption.

    Thanks to Philippe Antoine (Catena cyber) for reporting this issue.

    This is CVE-2025-61725 and Go issue https://go.dev/issue/75680.

- crypto/x509: quadratic complexity when checking name constraints

    Due to the design of the name constraint checking algorithm, the processing time
    of some inputs scales non-linearly with respect to the size of the certificate.

    This affects programs which validate arbitrary certificate chains.

    Thanks to Jakub Ciolek for reporting this issue.

    This is CVE-2025-58187 and Go issue https://go.dev/issue/75681.

- crypto/tls: ALPN negotiation errors can contain arbitrary text

    The crypto/tls conn.Handshake method returns an error on the server-side when
    ALPN negotation fails which can contain arbitrary attacker controlled
    information provided by the client-side of the connection which is not escaped.

    This affects programs which log these errors without any additional form of
    sanitization, and may allow injection of attacker controlled information into
    logs.

    Thanks to National Cyber Security Centre Finland for reporting this issue.

    This is CVE-2025-58189 and Go issue https://go.dev/issue/75652.

- encoding/pem: quadratic complexity when parsing some invalid inputs

    Due to the design of the PEM parsing function, the processing time for some
    inputs scales non-linearly with respect to the size of the input.

    This affects programs which parse untrusted PEM inputs.

    Thanks to Jakub Ciolek for reporting this issue.

    This is CVE-2025-61723 and Go issue https://go.dev/issue/75676.

- net/url: insufficient validation of bracketed IPv6 hostnames

    The Parse function permitted values other than IPv6 addresses to be included in square brackets within the host component of a URL. RFC 3986 permits IPv6 addresses to be included within the host component, enclosed within square brackets. For example: "http://[::1]/". IPv4 addresses and hostnames must not appear within square brackets. Parse did not enforce this requirement.

    Thanks to Enze Wang, Jingcheng Yang and Zehui Miao of Tsinghua University for reporting this issue.

    This is CVE-2025-47912 and Go issue https://go.dev/issue/75678.

- encoding/asn1: pre-allocating memory when parsing DER payload can cause memory exhaustion

    When parsing DER payloads, memories were being allocated prior to fully validating the payloads.
    This permits an attacker to craft a big empty DER payload to cause memory exhaustion in functions such as asn1.Unmarshal, x509.ParseCertificateRequest, and ocsp.ParseResponse.

    Thanks to Jakub Ciolek for reporting this issue.

    This is CVE-2025-58185 and Go issue https://go.dev/issue/75671.

- net/http: lack of limit when parsing cookies can cause memory exhaustion

    Despite HTTP headers having a default limit of 1 MB, the number of cookies that can be parsed did not have a limit.
    By sending a lot of very small cookies such as "a=;", an attacker can make an HTTP server allocate a large amount of structs, causing large memory consumption.

    net/http now limits the number of cookies accepted to 3000, which can be adjusted using the httpcookiemaxnum GODEBUG option.

    Thanks to jub0bs for reporting this issue.

    This is CVE-2025-58186 and Go issue https://go.dev/issue/75672.

- crypto/x509: panic when validating certificates with DSA public keys

    Validating certificate chains which contain DSA public keys can cause programs
    to panic, due to a interface cast that assumes they implement the Equal method.

    This affects programs which validate arbitrary certificate chains.

    Thanks to Jakub Ciolek for reporting this issue.

    This is CVE-2025-58188 and Go issue https://go.dev/issue/75675.

- archive/tar: unbounded allocation when parsing GNU sparse map

    tar.Reader did not set a maximum size on the number of sparse region data blocks in GNU tar pax 1.0 sparse files. A maliciously-crafted archive containing a large number of sparse regions could cause a Reader to read an unbounded amount of data from the archive into memory. When reading from a compressed source, a small compressed input could result in large allocations.

    Thanks to Harshit Gupta (Mr HAX) - https://www.linkedin.com/in/iam-harshit-gupta/ for reporting this issue.

    This is CVE-2025-58183 and Go issue https://go.dev/issue/75677.

- net/textproto: excessive CPU consumption in Reader.ReadResponse

    The Reader.ReadResponse function constructed a response string through
    repeated string concatenation of lines. When the number of lines in a response is large,
    this could cause excessive CPU consumption.

    Thanks to Jakub Ciolek for reporting this issue.

    This is CVE-2025-61724 and Go issue https://go.dev/issue/75716.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-13 13:22:38 +02:00
dependabot[bot] bc131d729d build(deps): bump softprops/action-gh-release from 2.3.3 to 2.4.1
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.3 to 2.4.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/6cbd405e2c4e67a21c47fa9e383d020e4e28b836...6da8fa9354ddfdc4aeace5fc48d7f679b5214090)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-13 09:27:58 +00:00
Sebastiaan van Stijn 84c3413e0e Merge pull request #387 from thaJeztah/bump_go1.25
update to go1.25.1
2025-10-02 21:54:13 +02:00
Sebastiaan van Stijn fcb0b664b5 update to go1.25.1
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-02 21:49:20 +02:00
Sebastiaan van Stijn cf4e41fbb0 Merge pull request #388 from thaJeztah/bump_wincred
vendor: github.com/danieljoos/wincred v1.2.3
2025-10-02 21:48:59 +02:00
Sebastiaan van Stijn 53f7bdc3fa vendor: github.com/danieljoos/wincred v1.2.3
fix unsafe uintptr usage to be GC-safe on go1.25

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

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-02 21:43:59 +02:00
Sebastiaan van Stijn d4602cd917 Merge pull request #249 from crazy-max/upd-dockerfile
Dockerfile: merge build stages
2025-10-01 16:45:53 +02:00
CrazyMax ae84c25786 Dockerfile: merge build stages
Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com>
2025-10-01 16:34:48 +02:00
Sebastiaan van Stijn 2adf3cf9aa Merge pull request #383 from thaJeztah/bump_go_deps
update to go1.24.7, xx v1.7.0
2025-10-01 16:29:36 +02:00
Sebastiaan van Stijn 1fdce4c733 Dockerfile: update xx to v1.7.0
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-01 16:24:12 +02:00
Sebastiaan van Stijn 962a779645 update to go1.24.7
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-01 16:23:59 +02:00
Sebastiaan van Stijn ec5efac3ca Merge pull request #386 from thaJeztah/bump_golangci_lint
Dockerfile: update golangci-lint to v2.5
2025-10-01 16:20:44 +02:00
Sebastiaan van Stijn 8154b98959 Merge pull request #385 from thaJeztah/bump_deb
deb: Dockerfile: update to debian bookworm, ubuntu jammy (22.04)
2025-10-01 16:20:25 +02:00
CrazyMax d075f3cecc Merge pull request #379 from docker/dependabot/github_actions/softprops/action-gh-release-2.3.3
build(deps): bump softprops/action-gh-release from 2.3.2 to 2.3.3
2025-10-01 16:15:13 +02:00
Sebastiaan van Stijn fdddb02817 deb: Dockerfile: use ubuntu:jammy (22.04 LTS)
ubuntu 20.04 reached end of standard support;
https://ubuntu.com/blog/ubuntu-20-04-lts-end-of-life-standard-support-is-coming-to-an-end-heres-how-to-prepare

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-01 15:56:28 +02:00
Sebastiaan van Stijn c07513a69d deb: Dockerfile: update to golang bookworm
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-01 15:56:28 +02:00
Sebastiaan van Stijn 4142982fb8 Dockerfile: update golangci-lint to v2.5
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-01 15:55:19 +02:00
Sebastiaan van Stijn 860f1459e3 pass: fix QF1001 (staticcheck)
pass/pass_test.go:86:6: QF1001: could apply De Morgan's law (staticcheck)
            if !(strings.HasSuffix(server, "2376/v1") || strings.HasSuffix(server, "2375/v1")) {
               ^
    pass/pass_test.go:89:6: QF1001: could apply De Morgan's law (staticcheck)
            if !(username == "foo" || username == "bar") {
               ^

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-01 15:55:19 +02:00
Sebastiaan van Stijn d378d46316 Merge pull request #384 from thaJeztah/bump_distros
gha: add macos-15-intel, remove macos-13 (deprecated)
2025-10-01 15:53:03 +02:00
Sebastiaan van Stijn 4c97a761df Merge pull request #378 from docker/dependabot/github_actions/actions/github-script-8
build(deps): bump actions/github-script from 7 to 8
2025-10-01 15:22:51 +02:00
Sebastiaan van Stijn b61abf1cb8 Merge pull request #377 from docker/dependabot/github_actions/actions/setup-go-6
build(deps): bump actions/setup-go from 5 to 6
2025-10-01 15:22:19 +02:00
Sebastiaan van Stijn 85841ea0ce Merge pull request #376 from docker/dependabot/github_actions/actions/checkout-5
build(deps): bump actions/checkout from 4 to 5
2025-10-01 15:21:38 +02:00
Sebastiaan van Stijn c32e697324 gha: add macos-15-intel, remove macos-13 (deprecated)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-01 15:11:20 +02:00
dependabot[bot] d770c60191 build(deps): bump softprops/action-gh-release from 2.3.2 to 2.3.3
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.3.2 to 2.3.3.
- [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/72f2c25fcb47643c292f7107632f7a47c1df5cd8...6cbd405e2c4e67a21c47fa9e383d020e4e28b836)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 09:15:45 +00:00
dependabot[bot] 5095e43ecf build(deps): bump actions/github-script from 7 to 8
Bumps [actions/github-script](https://github.com/actions/github-script) from 7 to 8.
- [Release notes](https://github.com/actions/github-script/releases)
- [Commits](https://github.com/actions/github-script/compare/v7...v8)

---
updated-dependencies:
- dependency-name: actions/github-script
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-05 09:08:21 +00:00
dependabot[bot] 00313838c6 build(deps): bump actions/setup-go from 5 to 6
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 5 to 6.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-04 13:01:52 +00:00
dependabot[bot] bcf656656f build(deps): bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 18:09:48 +00:00
Sebastiaan van Stijn fd27520bbd Merge pull request #375 from austinvazquez/update-golang-1.23.12
update to go1.23.12
2025-08-11 16:01:13 +02:00
Austin Vazquez 4849c2328b update to go1.23.12
Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
2025-08-08 10:54:56 -05:00
Austin Vazquez 2e8005f3a7 Merge pull request #373 from docker/dependabot/github_actions/softprops/action-gh-release-2.3.2
build(deps): bump softprops/action-gh-release from 2.2.1 to 2.3.2
2025-08-08 08:38:17 -07:00
dependabot[bot] 5d4d5150ae build(deps): bump softprops/action-gh-release from 2.2.1 to 2.3.2
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 2.2.1 to 2.3.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/c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda...72f2c25fcb47643c292f7107632f7a47c1df5cd8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-11 09:52:55 +00:00
Albin Kerouanton f9d3010165 Merge pull request #367 from akerouanton/osxkeychain-set-atyp
osxkeychain: store: add atyp attribute
2025-03-14 12:52:36 +01:00
Albin Kerouanton e7bd3957ae osxkeychain: store: add atyp attribute
Prior to v0.9.0, the osxkeychain creds helper was adding the `atyp`
attribute (ie. authentication type) to its credentials. It was also
specifying this attribute when querying the keychain for credentials.

Since v0.9.0, we don't set this attribute anymore. So, if a credential
is stored with v0.9.0+ and then queried with a v0.8.2 helper, the
atyp attribute will be missing and the credential won't be found.

Signed-off-by: Albin Kerouanton <albinker@gmail.com>
2025-03-14 12:45:31 +01:00
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
75 changed files with 4001 additions and 1581 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.
+12
View File
@@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: "github-actions"
open-pull-requests-limit: 10
directory: "/"
schedule:
interval: "daily"
cooldown:
default-days: 2
labels:
- "dependencies"
- "bot"
+59 -40
View File
@@ -4,22 +4,26 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
on:
workflow_dispatch:
push:
branches:
- 'master'
- 'main'
tags:
- 'v*'
pull_request:
env:
DESTDIR: ./bin
GO_VERSION: 1.18.5
GO_VERSION: 1.26.3
jobs:
validate:
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
timeout-minutes: 30 # guardrails timeout for the whole job
strategy:
fail-fast: false
matrix:
@@ -29,10 +33,10 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
-
name: Run
run: |
@@ -40,33 +44,41 @@ jobs:
test:
runs-on: ${{ matrix.os }}
timeout-minutes: 30 # guardrails timeout for the whole job
strategy:
fail-fast: false
matrix:
os:
- ubuntu-20.04
- macOS-11
- ubuntu-24.04
- ubuntu-22.04
- macOS-15-intel
- macOS-15
- macOS-14
- windows-2022
steps:
-
name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
-
name: Set up Go
uses: actions/setup-go@v3
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version: ${{ env.GO_VERSION }}
cache: true
-
name: Install deps
if: ${{ matrix.os == 'ubuntu-20.04' }}
name: Install deps (ubuntu)
if: startsWith(matrix.os, 'ubuntu-')
run: |
sudo apt-get update
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
if: ${{ matrix.os == 'ubuntu-20.04' }}
uses: actions/github-script@v6
if: ${{ !startsWith(matrix.os, 'windows-') }}
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
id: gpg
with:
script: |
@@ -82,39 +94,40 @@ jobs:
core.setOutput('passphrase', fs.readFileSync('.github/workflows/fixtures/7D851EB72D73BDA0.pass', {encoding: 'utf8'}));
-
name: Import GPG key
if: ${{ matrix.os == 'ubuntu-20.04' }}
uses: crazy-max/ghaction-import-gpg@v5
if: ${{ !startsWith(matrix.os, 'windows-') }}
uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
with:
gpg_private_key: ${{ steps.gpg.outputs.key }}
passphrase: ${{ steps.gpg.outputs.passphrase }}
trust_level: 5
-
name: Init pass
if: ${{ !startsWith(matrix.os, 'windows-') }}
run: |
pass init 7D851EB72D73BDA0
shell: bash
-
name: Test
run: |
if [ "${{ matrix.os }}" = "ubuntu-20.04" ]; then
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
make test COVERAGEDIR=${{ env.DESTDIR }}
shell: bash
-
name: Upload coverage
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
with:
file: ./coverage.txt
files: ${{ env.DESTDIR }}/coverage.txt
token: ${{ secrets.CODECOV_TOKEN }}
test-sandboxed:
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
timeout-minutes: 30 # guardrails timeout for the whole job
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
-
name: Test
uses: docker/bake-action@v2
uses: docker/bake-action@a66e1c87e2eca0503c343edf1d208c716d54b8a8 # v7.1.0
with:
targets: test
set: |
@@ -122,24 +135,29 @@ jobs:
*.cache-to=type=gha,scope=test,mode=max
-
name: Upload coverage
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
with:
file: ${{ env.DESTDIR }}//coverage.txt
files: ${{ env.DESTDIR }}//coverage.txt
token: ${{ secrets.CODECOV_TOKEN }}
build:
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
timeout-minutes: 30 # guardrails timeout for the whole job
permissions:
# required to create GitHub release
contents: write
steps:
-
name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
-
name: Build
run: |
@@ -157,7 +175,7 @@ jobs:
find ${{ env.DESTDIR }} -type f -exec file -e ascii -e text -- {} +
-
name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: docker-credential-helpers
path: ${{ env.DESTDIR }}/*
@@ -165,7 +183,7 @@ jobs:
-
name: GitHub Release
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -173,11 +191,12 @@ jobs:
files: ${{ env.DESTDIR }}/*
build-deb:
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
timeout-minutes: 30 # guardrails timeout for the whole job
steps:
-
name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
-
+28
View File
@@ -0,0 +1,28 @@
name: zizmor
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
on:
workflow_dispatch:
push:
branches:
- 'main'
tags:
- 'v*'
pull_request:
jobs:
run:
uses: crazy-max/.github/.github/workflows/zizmor.yml@64a0bfaf6e6bb1c448d6e4c42b11034ee7094f16 # v1.7.1
permissions:
contents: read
security-events: write
with:
min-severity: medium
min-confidence: medium
persona: pedantic
+12 -24
View File
@@ -1,35 +1,23 @@
version: "2"
run:
timeout: 10m
modules-download-mode: vendor
linters:
default: none
enable:
- gofmt
- govet
- deadcode
- depguard
- goimports
- ineffassign
- misspell
- unused
- varcheck
- revive
- staticcheck
- typecheck
- structcheck
disable-all: true
- unused
settings:
revive:
rules:
- name: package-comments # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#package-comments
disabled: true
linters-settings:
depguard:
list-type: blacklist
include-go-root: true
packages:
# The io/ioutil package has been deprecated.
# https://go.dev/doc/go1.16#ioutil
- io/ioutil
issues:
exclude-rules:
- linters:
- revive
text: "stutters"
formatters:
enable:
- gofmt
- goimports
+49 -50
View File
@@ -1,11 +1,28 @@
# syntax=docker/dockerfile:1
ARG GO_VERSION=1.18.5
ARG XX_VERSION=1.1.2
ARG OSXCROSS_VERSION=11.3-r7-debian
ARG GOLANGCI_LINT_VERSION=v1.47.3
ARG DEBIAN_FRONTEND=noninteractive
# GO_VERSION sets the version of the golang base image to use.
# It must be a valid tag in the docker.io/library/golang image repository.
ARG GO_VERSION=1.26.3
# BASE_DEBIAN_DISTRO sets the golang base image debian variant to use.
# It must be a valid variant in the docker.io/library/golang image repository.
ARG BASE_DEBIAN_DISTRO=bookworm
# XX_VERSION sets the version of the xx utility to use.
# It must be a valid tag in the docker.io/tonistiigi/xx image repository.
ARG XX_VERSION=1.9.0
# OSXCROSS_VERSION sets the MacOSX cross toolchain to use.
# It must be a valid tag in the docker.io/crazymax/osxcross image repository.
ARG OSXCROSS_VERSION=11.3-r8-debian
# GOLANGCI_LINT_VERSION sets the version of the golangci-lint image to use.
# It must be a valid tag in the docker.io/golangci/golangci-lint image repository.
ARG GOLANGCI_LINT_VERSION=v2.11
# PACKAGE sets the package name to print in the "--version" output.
# It sets the "github.com/docker/docker-credential-helpers/credentials.Package
# variable at compile time.
ARG PACKAGE=github.com/docker/docker-credential-helpers
# xx is a helper for cross-compilation
@@ -14,9 +31,8 @@ FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
# osxcross contains the MacOSX cross toolchain for xx
FROM crazymax/osxcross:${OSXCROSS_VERSION} AS osxcross
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-bullseye AS gobase
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-${BASE_DEBIAN_DISTRO} AS gobase
COPY --from=xx / /
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
ENV GOFLAGS="-mod=vendor"
ENV CGO_ENABLED="1"
@@ -38,7 +54,8 @@ FROM scratch AS vendor-update
COPY --from=vendored /out /
FROM vendored AS vendor-validate
RUN --mount=type=bind,target=.,rw <<EOT
RUN --mount=target=/context \
--mount=target=.,type=tmpfs <<EOT
set -e
rsync -a /context/. .
git add -A
@@ -53,8 +70,7 @@ EOT
FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION} AS golangci-lint
FROM gobase AS lint
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=. \
--mount=type=cache,target=/root/.cache \
--mount=from=golangci-lint,source=/usr/bin/golangci-lint,target=/usr/bin/golangci-lint \
@@ -62,11 +78,9 @@ RUN --mount=type=bind,target=. \
FROM gobase AS base
ARG TARGETPLATFORM
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
ARG DEBIAN_FRONTEND
RUN xx-apt-get install -y dbus-x11 gnome-keyring gpg-agent gpgconf libsecret-1-dev pass
RUN --mount=type=bind,target=. \
--mount=type=cache,target=/root/.cache \
@@ -85,8 +99,8 @@ RUN --mount=type=bind,target=. \
gpg -k
mkdir /out
xx-go test -short -v -coverprofile=/out/coverage.txt -covermode=atomic ./...
xx-go tool cover -func=/out/coverage.txt
xx-go --wrap
make test COVERAGEDIR=/out
EOT
FROM scratch AS test-coverage
@@ -96,21 +110,7 @@ FROM gobase AS version
RUN --mount=target=. \
echo -n "$(./hack/git-meta version)" | tee /tmp/.version ; echo -n "$(./hack/git-meta revision)" | tee /tmp/.revision
FROM base AS build-linux
ARG PACKAGE
RUN --mount=type=bind,target=. \
--mount=type=cache,target=/root/.cache \
--mount=type=cache,target=/go/pkg/mod \
--mount=type=bind,source=/tmp/.version,target=/tmp/.version,from=version \
--mount=type=bind,source=/tmp/.revision,target=/tmp/.revision,from=version <<EOT
set -ex
xx-go --wrap
make build-pass build-secretservice PACKAGE=$PACKAGE VERSION=$(cat /tmp/.version) REVISION=$(cat /tmp/.revision) DESTDIR=/out
xx-verify /out/docker-credential-pass
xx-verify /out/docker-credential-secretservice
EOT
FROM base AS build-darwin
FROM base AS build
ARG PACKAGE
RUN --mount=type=bind,target=. \
--mount=type=cache,target=/root/.cache \
@@ -119,29 +119,28 @@ RUN --mount=type=bind,target=. \
--mount=type=bind,source=/tmp/.version,target=/tmp/.version,from=version \
--mount=type=bind,source=/tmp/.revision,target=/tmp/.revision,from=version <<EOT
set -ex
export MACOSX_VERSION_MIN=$(make print-MACOSX_DEPLOYMENT_TARGET)
xx-go --wrap
go install std
make build-osxkeychain build-pass PACKAGE=$PACKAGE VERSION=$(cat /tmp/.version) REVISION=$(cat /tmp/.revision) DESTDIR=/out
xx-verify /out/docker-credential-osxkeychain
xx-verify /out/docker-credential-pass
case "$(xx-info os)" in
linux)
make build-pass build-secretservice PACKAGE=$PACKAGE VERSION=$(cat /tmp/.version) REVISION=$(cat /tmp/.revision) DESTDIR=/out
xx-verify /out/docker-credential-pass
xx-verify /out/docker-credential-secretservice
;;
darwin)
go install std
make build-osxkeychain build-pass PACKAGE=$PACKAGE VERSION=$(cat /tmp/.version) REVISION=$(cat /tmp/.revision) DESTDIR=/out
xx-verify /out/docker-credential-osxkeychain
xx-verify /out/docker-credential-pass
;;
windows)
make build-wincred PACKAGE=$PACKAGE VERSION=$(cat /tmp/.version) REVISION=$(cat /tmp/.revision) DESTDIR=/out
mv /out/docker-credential-wincred /out/docker-credential-wincred.exe
xx-verify /out/docker-credential-wincred.exe
;;
esac
EOT
FROM base AS build-windows
ARG PACKAGE
RUN --mount=type=bind,target=. \
--mount=type=cache,target=/root/.cache \
--mount=type=cache,target=/go/pkg/mod \
--mount=type=bind,source=/tmp/.version,target=/tmp/.version,from=version \
--mount=type=bind,source=/tmp/.revision,target=/tmp/.revision,from=version <<EOT
set -ex
xx-go --wrap
make build-wincred PACKAGE=$PACKAGE VERSION=$(cat /tmp/.version) REVISION=$(cat /tmp/.revision) DESTDIR=/out
mv /out/docker-credential-wincred /out/docker-credential-wincred.exe
xx-verify /out/docker-credential-wincred.exe
EOT
FROM build-$TARGETOS AS build
FROM scratch AS binaries
COPY --from=build /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
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
all: cross
@@ -35,8 +57,10 @@ release: # create release
./hack/release
.PHONY: test
test: # tests all packages except vendor
go test -v `go list ./... | grep -v /vendor/`
test:
mkdir -p $(COVERAGEDIR)
go test -short -v -coverprofile=$(COVERAGEDIR)/coverage.txt -covermode=atomic ./...
go tool cover -func=$(COVERAGEDIR)/coverage.txt
.PHONY: lint
lint:
@@ -72,3 +96,6 @@ vendor:
rm -rf ./vendor
cp -R "$($@_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)
[![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)
[![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:
```shell
# install emulators
$ docker run --privileged --rm tonistiigi/binfmt --install all
# create builder
$ docker buildx create --use
@@ -31,20 +34,20 @@ $ docker buildx bake
Or if the toolchain is already installed on your machine:
1 - Download the source.
1. Download the source.
```shell
$ git clone https://github.com/docker/docker-credential-helpers.git
$ 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
$ 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
$ 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) {
return credentials.NewErrCredentialsMissingServerURL()
}
if credentials.IsCredentialsMissingUsernameMessage(msg) {
return credentials.NewErrCredentialsMissingUsername()
}
return nil
}
// Store uses an external program to save credentials.
func Store(program ProgramFunc, creds *credentials.Credentials) error {
cmd := program("store")
cmd := program(credentials.ActionStore)
buffer := new(bytes.Buffer)
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()
if err != nil {
t := strings.TrimSpace(string(out))
if isValidErr := isValidCredsMessage(t); isValidErr != nil {
if isValidErr := isValidCredsMessage(string(out)); isValidErr != nil {
err = isValidErr
}
return fmt.Errorf("error storing credentials - err: %v, out: `%s`", err, t)
return fmt.Errorf("error storing credentials - err: %v, out: `%s`", err, strings.TrimSpace(string(out)))
}
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.
func Get(program ProgramFunc, serverURL string) (*credentials.Credentials, error) {
cmd := program("get")
cmd := program(credentials.ActionGet)
cmd.Input(strings.NewReader(serverURL))
out, err := cmd.Output()
if err != nil {
t := strings.TrimSpace(string(out))
if credentials.IsErrCredentialsNotFoundMessage(t) {
if credentials.IsErrCredentialsNotFoundMessage(string(out)) {
return nil, credentials.NewErrCredentialsNotFound()
}
if isValidErr := isValidCredsMessage(t); isValidErr != nil {
if isValidErr := isValidCredsMessage(string(out)); isValidErr != nil {
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{
@@ -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.
func Erase(program ProgramFunc, serverURL string) error {
cmd := program("erase")
cmd := program(credentials.ActionErase)
cmd.Input(strings.NewReader(serverURL))
out, err := cmd.Output()
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.
func List(program ProgramFunc) (map[string]string, error) {
cmd := program("list")
cmd := program(credentials.ActionList)
cmd.Input(strings.NewReader("unused"))
out, err := cmd.Output()
if err != nil {
+35 -28
View File
@@ -11,24 +11,24 @@ import (
)
const (
validServerAddress = "https://index.docker.io/v1"
validServerAddress = "https://registry.example.com/v1"
validUsername = "linus"
validServerAddress2 = "https://example.com:5002"
invalidServerAddress = "https://foobar.example.com"
missingCredsAddress = "https://missing.docker.io/v1"
missingCredsAddress = "https://missing.example.com/v1"
)
var errProgramExited = fmt.Errorf("exited 1")
// 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.
type mockProgram struct {
arg string
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.
func (m *mockProgram) Output() ([]byte, error) {
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
}
// 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) {
m.input = in
}
@@ -92,16 +92,16 @@ func mockProgramFn(args ...string) Program {
}
func ExampleStore() {
p := NewShellProgramFunc("docker-credential-secretservice")
p := NewShellProgramFunc("docker-credential-pass")
c := &credentials.Credentials{
ServerURL: "https://example.com",
Username: "calavera",
ServerURL: "https://registry.example.com",
Username: "exampleuser",
Secret: "my super secret token",
}
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 {
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 {
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() {
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 {
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) {
@@ -152,10 +152,10 @@ func TestGet(t *testing.T) {
}
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 {
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
err string
}{
{missingCredsAddress, credentials.NewErrCredentialsNotFound().Error()},
{invalidServerAddress, "error getting credentials - err: exited 1, out: `program failed`"},
{"", fmt.Sprintf("error getting credentials - err: %s, out: `%s`",
missingServerURLErr.Error(), missingServerURLErr.Error())},
{
serverURL: missingCredsAddress,
err: credentials.NewErrCredentialsNotFound().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 {
@@ -177,26 +184,26 @@ func TestGet(t *testing.T) {
t.Fatalf("Expected error for server %s, got nil", v.serverURL)
}
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() {
p := NewShellProgramFunc("docker-credential-secretservice")
p := NewShellProgramFunc("docker-credential-pass")
if err := Erase(p, "https://example.com"); err != nil {
fmt.Println(err)
if err := Erase(p, "https://registry.example.com"); err != nil {
_, _ = fmt.Println(err)
}
}
func TestErase(t *testing.T) {
if err := Erase(mockProgramFn, validServerAddress); err != nil {
t.Fatal(err)
t.Error(err)
}
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 {
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
import (
"fmt"
"io"
"os"
exec "golang.org/x/sys/execabs"
"os/exec"
)
// 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.
type ProgramFunc func(args ...string) Program
// NewShellProgramFunc creates programs that are executed in a Shell.
func NewShellProgramFunc(name 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 {
// NewShellProgramFunc creates a [ProgramFunc] to run command in a [Shell].
func NewShellProgramFunc(command string) ProgramFunc {
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 {
programCmd := exec.Command(commandName, args...)
programCmd.Env = os.Environ()
// NewShellProgramFuncWithEnv creates a [ProgramFunc] tu run command
// in a [Shell] with the given environment variables.
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 {
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
return programCmd
ec.Stderr = os.Stderr
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 {
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) {
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) {
s.cmd.Stdin = in
}
+46 -23
View File
@@ -10,6 +10,20 @@ import (
"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.
type Credentials struct {
ServerURL string
@@ -43,42 +57,52 @@ func SetCredsLabel(label string) {
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.
// It uses os.Args[1] as the key for the action.
// 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.
func Serve(helper Helper) {
var err error
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 {
err = HandleCommand(helper, os.Args[1], os.Stdin, os.Stdout)
switch os.Args[1] {
case "--version", "-v":
_ = PrintVersion(os.Stdout)
os.Exit(0)
case "--help", "-h":
_, _ = fmt.Fprintln(os.Stdout, usage())
os.Exit(0)
}
if err != nil {
fmt.Fprintf(os.Stdout, "%v\n", err)
if err := HandleCommand(helper, os.Args[1], os.Stdin, os.Stdout); err != nil {
_, _ = fmt.Fprintln(os.Stdout, err)
os.Exit(1)
}
}
// HandleCommand uses a helper and a key to run a credential action.
func HandleCommand(helper Helper, key string, in io.Reader, out io.Writer) error {
switch key {
case "store":
func usage() string {
return fmt.Sprintf("Usage: %s <store|get|erase|list|version>", Name)
}
// 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)
case "get":
case ActionGet:
return Get(helper, in, out)
case "erase":
case ActionErase:
return Erase(helper, in)
case "list":
case ActionList:
return List(helper, out)
case "version":
case ActionVersion:
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.
@@ -132,18 +156,17 @@ func Get(helper Helper, reader io.Reader, writer io.Writer) error {
return err
}
resp := Credentials{
buffer.Reset()
err = json.NewEncoder(buffer).Encode(Credentials{
ServerURL: serverURL,
Username: username,
Secret: secret,
}
buffer.Reset()
if err := json.NewEncoder(buffer).Encode(resp); err != nil {
})
if err != nil {
return err
}
fmt.Fprint(writer, buffer.String())
_, _ = fmt.Fprint(writer, buffer.String())
return nil
}
@@ -181,6 +204,6 @@ func List(helper Helper, writer io.Writer) error {
// PrintVersion outputs the current version.
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
}
+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) {
//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
}
func TestStore(t *testing.T) {
serverURL := "https://index.docker.io/v1/"
const serverURL = "https://registry.example.com/v1/"
creds := &Credentials{
ServerURL: serverURL,
Username: "foo",
@@ -65,11 +65,11 @@ func TestStore(t *testing.T) {
}
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" {
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()
if err := Store(h, in); IsCredentialsMissingServerURL(err) == false {
t.Fatal(err)
if err := Store(h, in); !IsCredentialsMissingServerURL(err) {
t.Error(err)
}
}
func TestStoreMissingUsername(t *testing.T) {
creds := &Credentials{
ServerURL: "https://index.docker.io/v1/",
ServerURL: "https://registry.example.com/v1/",
Username: "",
Secret: "bar",
}
@@ -108,13 +108,13 @@ func TestStoreMissingUsername(t *testing.T) {
h := newMemoryStore()
if err := Store(h, in); IsCredentialsMissingUsername(err) == false {
t.Fatal(err)
if err := Store(h, in); !IsCredentialsMissingUsername(err) {
t.Error(err)
}
}
func TestGet(t *testing.T) {
serverURL := "https://index.docker.io/v1/"
const serverURL = "https://registry.example.com/v1/"
creds := &Credentials{
ServerURL: serverURL,
Username: "foo",
@@ -147,16 +147,16 @@ func TestGet(t *testing.T) {
}
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" {
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) {
serverURL := "https://index.docker.io/v1/"
const serverURL = "https://registry.example.com/v1/"
creds := &Credentials{
ServerURL: serverURL,
Username: "foo",
@@ -176,13 +176,13 @@ func TestGetMissingServerURL(t *testing.T) {
buf := strings.NewReader("")
w := new(bytes.Buffer)
if err := Get(h, buf, w); IsCredentialsMissingServerURL(err) == false {
t.Fatal(err)
if err := Get(h, buf, w); !IsCredentialsMissingServerURL(err) {
t.Error(err)
}
}
func TestErase(t *testing.T) {
serverURL := "https://index.docker.io/v1/"
const serverURL = "https://registry.example.com/v1/"
creds := &Credentials{
ServerURL: serverURL,
Username: "foo",
@@ -206,12 +206,12 @@ func TestErase(t *testing.T) {
w := new(bytes.Buffer)
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) {
serverURL := "https://index.docker.io/v1/"
const serverURL = "https://registry.example.com/v1/"
creds := &Credentials{
ServerURL: serverURL,
Username: "foo",
@@ -229,21 +229,21 @@ func TestEraseMissingServerURL(t *testing.T) {
}
buf := strings.NewReader("")
if err := Erase(h, buf); IsCredentialsMissingServerURL(err) == false {
t.Fatal(err)
if err := Erase(h, buf); !IsCredentialsMissingServerURL(err) {
t.Error(err)
}
}
func TestList(t *testing.T) {
//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
// 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
out := new(bytes.Buffer)
h := newMemoryStore()
if err := List(h, out); err != nil {
t.Fatal(err)
}
//testing that there is an output
// testing that there is an output
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
import (
"errors"
"strings"
)
const (
// ErrCredentialsNotFound standardizes the not found error, so every helper returns
// the same message and docker can handle it properly.
@@ -21,6 +26,11 @@ func (errCredentialsNotFound) Error() string {
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
// for when the credentials are not in the store.
func NewErrCredentialsNotFound() error {
@@ -30,8 +40,8 @@ func NewErrCredentialsNotFound() error {
// IsErrCredentialsNotFound returns true if the error
// was caused by not having a set of credentials in a store.
func IsErrCredentialsNotFound(err error) bool {
_, ok := err.(errCredentialsNotFound)
return ok
var target errCredentialsNotFound
return errors.As(err, &target)
}
// IsErrCredentialsNotFoundMessage returns true if the error
@@ -40,7 +50,7 @@ func IsErrCredentialsNotFound(err error) bool {
// This function helps to check messages returned by an
// external program via its standard output.
func IsErrCredentialsNotFoundMessage(err string) bool {
return err == errCredentialsNotFoundMessage
return strings.TrimSpace(err) == errCredentialsNotFoundMessage
}
// errCredentialsMissingServerURL represents an error raised
@@ -53,6 +63,12 @@ func (errCredentialsMissingServerURL) Error() string {
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
// when the credentials object has no username or when no
// username is provided to a credentials operation requiring
@@ -63,6 +79,12 @@ func (errCredentialsMissingUsername) Error() string {
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
// errCredentialsMissingServerURL.
func NewErrCredentialsMissingServerURL() error {
@@ -78,25 +100,25 @@ func NewErrCredentialsMissingUsername() error {
// IsCredentialsMissingServerURL returns true if the error
// was an errCredentialsMissingServerURL.
func IsCredentialsMissingServerURL(err error) bool {
_, ok := err.(errCredentialsMissingServerURL)
return ok
var target errCredentialsMissingServerURL
return errors.As(err, &target)
}
// IsCredentialsMissingServerURLMessage checks for an
// errCredentialsMissingServerURL in the error message.
func IsCredentialsMissingServerURLMessage(err string) bool {
return err == errCredentialsMissingServerURLMessage
return strings.TrimSpace(err) == errCredentialsMissingServerURLMessage
}
// IsCredentialsMissingUsername returns true if the error
// was an errCredentialsMissingUsername.
func IsCredentialsMissingUsername(err error) bool {
_, ok := err.(errCredentialsMissingUsername)
return ok
var target errCredentialsMissingUsername
return errors.As(err, &target)
}
// IsCredentialsMissingUsernameMessage checks for an
// errCredentialsMissingUsername in the error message.
func IsCredentialsMissingUsernameMessage(err string) bool {
return err == errCredentialsMissingUsernameMessage
return strings.TrimSpace(err) == errCredentialsMissingUsernameMessage
}
+14 -7
View File
@@ -1,12 +1,19 @@
ARG GO_VERSION=1.18.5
ARG DISTRO=ubuntu
ARG SUITE=focal
# syntax=docker/dockerfile:1
FROM golang:${GO_VERSION}-buster AS golang
# GO_VERSION sets the version of the golang base image to use.
# It must be a valid tag in the docker.io/library/golang image repository.
ARG GO_VERSION=1.26.3
# BASE_DEBIAN_DISTRO sets the golang base image debian variant to use.
# It must be a valid variant in the docker.io/library/golang image repository.
ARG BASE_DEBIAN_DISTRO=bookworm
ARG DISTRO=ubuntu
ARG SUITE=jammy
FROM golang:${GO_VERSION}-${BASE_DEBIAN_DISTRO} AS gobase
FROM ${DISTRO}:${SUITE}
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -yy debhelper dh-make libsecret-1-dev
RUN mkdir -p /build
@@ -15,7 +22,7 @@ ENV GOPROXY=https://proxy.golang.org|direct
ENV GO111MODULE=off
ENV GOPATH=/build
ENV PATH=$PATH:/usr/local/go/bin:$GOPATH/bin
COPY --from=golang /usr/local/go /usr/local/go
COPY --from=gobase /usr/local/go /usr/local/go
COPY Makefile .
COPY credentials credentials
+3 -2
View File
@@ -1,5 +1,5 @@
variable "GO_VERSION" {
default = "1.18.5"
default = null
}
# Defines the output folder
@@ -62,7 +62,8 @@ target "binaries" {
"linux/arm/v6",
"linux/ppc64le",
"linux/s390x",
"windows/amd64"
"windows/amd64",
"windows/arm64"
]
}
+10 -3
View File
@@ -1,8 +1,15 @@
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 (
github.com/danieljoos/wincred v1.1.2
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64
github.com/danieljoos/wincred v1.2.3
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.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ=
github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -1,3 +1,5 @@
//go:build darwin && cgo
package main
import (
+182
View File
@@ -0,0 +1,182 @@
//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))
// Prior to v0.9, the credential helper was searching for credentials with
// the "dflt" authentication type (see [1]). Since v0.9.0, Get doesn't use
// that attribute anymore, and v0.9.0 - v0.9.2 were not setting it here
// either.
//
// In order to keep compatibility with older versions, we need to store
// credentials with this attribute set. This way, credentials stored with
// newer versions can be retrieved by older versions.
//
// [1]: https://github.com/docker/docker-credential-helpers/blob/v0.8.2/osxkeychain/osxkeychain.c#L66
item.SetAuthenticationType("dflt")
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/exec"
"path"
"path/filepath"
"strings"
"sync"
@@ -28,11 +29,12 @@ type Pass struct{}
// 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
// backwards incompatible, we assume that all Pass instances share the same configuration
// initializationMutex is held while initializing so that only one 'pass'
// round-tripping is done to check pass is functioning.
var initializationMutex sync.Mutex
var passInitialized bool
var (
// initializationMutex is held while initializing so that only one 'pass'
// round-tripping is done to check pass is functioning.
initializationMutex sync.Mutex
passInitialized bool
)
// CheckInitialized checks whether the password helper can be used. It
// 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")
}
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))
return err
}
@@ -97,17 +98,17 @@ func (p Pass) Delete(serverURL string) error {
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))
return err
}
func getPassDir() string {
passDir := "$HOME/.password-store"
if envDir := os.Getenv("PASSWORD_STORE_DIR"); envDir != "" {
passDir = envDir
if passDir := os.Getenv("PASSWORD_STORE_DIR"); passDir != "" {
return passDir
}
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.
@@ -140,23 +141,14 @@ func (p Pass) Get(serverURL string) (string, string, error) {
return "", "", errors.New("missing server url")
}
encoded := base64.URLEncoding.EncodeToString([]byte(serverURL))
if _, err := os.Stat(path.Join(getPassDir(), PASS_FOLDER, encoded)); err != nil {
if os.IsNotExist(err) {
return "", "", credentials.NewErrCredentialsNotFound()
}
return "", "", err
}
encoded := encodeServerURL(serverURL)
usernames, err := listPassDir(encoded)
if err != nil {
return "", "", err
}
if len(usernames) < 1 {
return "", "", fmt.Errorf("no usernames for %s", serverURL)
return "", "", credentials.NewErrCredentialsNotFound()
}
actual := strings.TrimSuffix(usernames[0].Name(), ".gpg")
@@ -178,7 +170,7 @@ func (p Pass) List() (map[string]string, error) {
continue
}
serverURL, err := base64.URLEncoding.DecodeString(server.Name())
serverURL, err := decodeServerURL(server.Name())
if err != nil {
return nil, err
}
@@ -189,11 +181,27 @@ func (p Pass) List() (map[string]string, error) {
}
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
}
// 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
import (
"os"
"path"
"strings"
"testing"
@@ -8,75 +12,184 @@ import (
)
func TestPassHelper(t *testing.T) {
helper := Pass{}
creds := &credentials.Credentials{
ServerURL: "https://foobar.docker.io:2376/v1",
ServerURL: "https://foobar.example.com:2376/v1",
Username: "nothing",
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"
helper.Add(creds)
u, s, err := helper.Get(creds.ServerURL)
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()
if err != nil {
t.Fatal(err)
t.Error(err)
}
for server, username := range credsList {
if !(strings.Contains(server, "2376") ||
strings.Contains(server, "9999")) {
t.Fatalf("invalid url: %s", creds.ServerURL)
if !strings.HasSuffix(server, "2376/v1") && !strings.HasSuffix(server, "2375/v1") {
t.Errorf("invalid url: %s", server)
}
if username != "nothing" {
t.Fatalf("invalid username: %v", username)
if username != "foo" && username != "bar" {
t.Errorf("invalid username: %v", username)
}
u, s, err := helper.Get(server)
if err != nil {
t.Fatal(err)
t.Error(err)
}
if u != username {
t.Fatalf("invalid username %s", u)
t.Errorf("invalid username %s", u)
}
if s != "isthebestmeshuggahalbum" {
t.Fatalf("invalid secret: %s", s)
t.Errorf("invalid secret: %s", s)
}
err = helper.Delete(server)
if err != nil {
t.Fatal(err)
if err := helper.Delete(server); err != nil {
t.Error(err)
}
_, _, err = helper.Get(server)
if !credentials.IsErrCredentialsNotFound(err) {
t.Fatalf("expected credentials not found, actual: %v", err)
if _, _, err := helper.Get(server); !credentials.IsErrCredentialsNotFound(err) {
t.Errorf("expected credentials not found, actual: %v", err)
}
}
credsList, err = helper.List()
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 {
t.Fatal("didn't delete all creds?")
creds := []*credentials.Credentials{
{
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) {
helper := Pass{}
_, _, err := helper.Get("garbage")
if !credentials.IsErrCredentialsNotFound(err) {
t.Fatalf("expected credentials not found, actual: %v", err)
if _, _, err := helper.Get("garbage"); !credentials.IsErrCredentialsNotFound(err) {
t.Errorf("expected credentials not found, actual: %v", err)
}
}
+53 -26
View File
@@ -13,34 +13,61 @@ func TestHelperParseURL(t *testing.T) {
expectedURL string
err error
}{
{url: "foobar.docker.io", expectedURL: "//foobar.docker.io"},
{url: "foobar.docker.io:2376", expectedURL: "//foobar.docker.io:2376"},
{url: "//foobar.docker.io:2376", expectedURL: "//foobar.docker.io:2376"},
{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: "https://foobar.docker.io:2376/some/other/path?foo=bar", expectedURL: "https://foobar.docker.io:2376/some/other/path"},
{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",
expectedURL: "//foobar.example.com",
},
{
url: "foobar.example.com:2376",
expectedURL: "//foobar.example.com:2376",
},
{
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 {
u, err := Parse(te.url)
for _, tc := range tests {
tc := tc
t.Run(tc.url, func(t *testing.T) {
u, err := Parse(tc.url)
if te.err == nil && err != nil {
t.Errorf("Error: failed to parse URL %q: %s", te.url, err)
continue
}
if te.err != nil && err == nil {
t.Errorf("Error: expected error %q, got none when parsing URL %q", te.err, te.url)
continue
}
if te.err != nil && err.Error() != te.err.Error() {
t.Errorf("Error: expected error %q, got %q when parsing URL %q", te.err, err, te.url)
continue
}
if u != nil && u.String() != te.expectedURL {
t.Errorf("Error: expected URL: %q, but got %q for URL: %q", te.expectedURL, u.String(), te.url)
}
if tc.err == nil && err != nil {
t.Fatalf("Error: failed to parse URL %q: %s", tc.url, err)
}
if tc.err != nil && err == nil {
t.Fatalf("Error: expected error %q, got none when parsing URL %q", tc.err, tc.url)
}
if tc.err != nil && err.Error() != tc.err.Error() {
t.Fatalf("Error: expected error %q, got %q when parsing URL %q", tc.err, err, tc.url)
}
if u != nil && u.String() != tc.expectedURL {
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
import (
@@ -1,6 +1,6 @@
#include <string.h>
#include <stdlib.h>
#include "secretservice_linux.h"
#include "secretservice.h"
const SecretSchema *docker_get_schema(void)
{
@@ -17,11 +17,11 @@ const SecretSchema *docker_get_schema(void)
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;
secret_password_store_sync (DOCKER_SCHEMA, SECRET_COLLECTION_DEFAULT,
server, secret, NULL, &err,
displaylabel, secret, NULL, &err,
"label", label,
"server", server,
"username", username,
@@ -83,6 +83,9 @@ GError *get(char *server, char **username, char **secret) {
}
g_free(value);
secretValue = secret_item_get_secret(l->data);
if (secretValue == NULL) {
continue;
}
if (secret != NULL) {
*secret = strdup(secret_value_get(secretValue, &length));
secret_value_unref(secretValue);
@@ -1,12 +1,15 @@
//go:build (linux || openbsd) && cgo
package secretservice
/*
#cgo pkg-config: libsecret-1
#include "secretservice_linux.h"
#include "secretservice.h"
#include <stdlib.h>
*/
import "C"
import (
"errors"
"unsafe"
@@ -30,8 +33,10 @@ func (h Secretservice) Add(creds *credentials.Credentials) error {
defer C.free(unsafe.Pointer(username))
secret := C.CString(creds.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)
errMsg := (*C.char)(unsafe.Pointer(err.message))
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()
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 *get(char *server, char **username, char **secret);
GError *list(char *label, char *** paths, char *** accts, unsigned int *list_l);
@@ -1,3 +1,5 @@
//go:build (linux || openbsd) && cgo
package secretservice
import (
@@ -11,7 +13,7 @@ func TestSecretServiceHelper(t *testing.T) {
t.Skip("test requires gnome-keyring but travis CI doesn't have it")
creds := &credentials.Credentials{
ServerURL: "https://foobar.docker.io:2376/v1",
ServerURL: "https://foobar.example.com:2376/v1",
Username: "foobar",
Secret: "foobarbaz",
}
@@ -28,7 +30,6 @@ func TestSecretServiceHelper(t *testing.T) {
// remove them as they probably come from a previous failed test
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)
}
+1 -1
View File
@@ -4,7 +4,7 @@ wincred
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)
[![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)
[![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)
+7 -13
View File
@@ -31,13 +31,13 @@ func utf16FromString(str string) []uint16 {
// goBytes copies the given C byte array to a Go byte array (see `C.GoBytes`).
// This function avoids having cgo as dependency.
func goBytes(src uintptr, len uint32) []byte {
if src == uintptr(0) {
func goBytes(src *byte, len uint32) []byte {
if src == nil || len == 0 {
return []byte{}
}
rv := make([]byte, len)
copy(rv, *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
Data: src,
Data: uintptr(unsafe.Pointer(src)),
Len: int(len),
Cap: int(len),
})))
@@ -59,7 +59,7 @@ func sysToCredential(cred *sysCREDENTIAL) (result *Credential) {
result.CredentialBlob = goBytes(cred.CredentialBlob, cred.CredentialBlobSize)
result.Attributes = make([]CredentialAttribute, cred.AttributeCount)
attrSlice := *(*[]sysCREDENTIAL_ATTRIBUTE)(unsafe.Pointer(&reflect.SliceHeader{
Data: cred.Attributes,
Data: uintptr(unsafe.Pointer(cred.Attributes)),
Len: int(cred.AttributeCount),
Cap: int(cred.AttributeCount),
}))
@@ -85,17 +85,13 @@ func sysFromCredential(cred *Credential) (result *sysCREDENTIAL) {
result.LastWritten = syscall.NsecToFiletime(cred.LastWritten.UnixNano())
result.CredentialBlobSize = uint32(len(cred.CredentialBlob))
if len(cred.CredentialBlob) > 0 {
result.CredentialBlob = uintptr(unsafe.Pointer(&cred.CredentialBlob[0]))
} else {
result.CredentialBlob = 0
result.CredentialBlob = &cred.CredentialBlob[0]
}
result.Persist = uint32(cred.Persist)
result.AttributeCount = uint32(len(cred.Attributes))
attributes := make([]sysCREDENTIAL_ATTRIBUTE, len(cred.Attributes))
if len(attributes) > 0 {
result.Attributes = uintptr(unsafe.Pointer(&attributes[0]))
} else {
result.Attributes = 0
result.Attributes = &attributes[0]
}
for i := range cred.Attributes {
inAttr := &cred.Attributes[i]
@@ -104,9 +100,7 @@ func sysFromCredential(cred *Credential) (result *sysCREDENTIAL) {
outAttr.Flags = 0
outAttr.ValueSize = uint32(len(inAttr.Value))
if len(inAttr.Value) > 0 {
outAttr.Value = uintptr(unsafe.Pointer(&inAttr.Value[0]))
} else {
outAttr.Value = 0
outAttr.Value = &inAttr.Value[0]
}
}
result.TargetAlias, _ = syscall.UTF16PtrFromString(cred.TargetAlias)
+23 -15
View File
@@ -1,21 +1,24 @@
//go:build windows
// +build windows
package wincred
import (
"reflect"
"runtime"
"syscall"
"unsafe"
syscall "golang.org/x/sys/windows"
"golang.org/x/sys/windows"
)
var (
modadvapi32 = syscall.NewLazyDLL("advapi32.dll")
procCredRead proc = modadvapi32.NewProc("CredReadW")
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
procCredRead = modadvapi32.NewProc("CredReadW")
procCredWrite proc = modadvapi32.NewProc("CredWriteW")
procCredDelete proc = modadvapi32.NewProc("CredDeleteW")
procCredFree proc = modadvapi32.NewProc("CredFree")
procCredEnumerate proc = modadvapi32.NewProc("CredEnumerateW")
procCredEnumerate = modadvapi32.NewProc("CredEnumerateW")
)
// Interface for syscall.Proc: helps testing
@@ -29,12 +32,12 @@ type sysCREDENTIAL struct {
Type uint32
TargetName *uint16
Comment *uint16
LastWritten syscall.Filetime
LastWritten windows.Filetime
CredentialBlobSize uint32
CredentialBlob uintptr
CredentialBlob *byte
Persist uint32
AttributeCount uint32
Attributes uintptr
Attributes *sysCREDENTIAL_ATTRIBUTE
TargetAlias *uint16
UserName *uint16
}
@@ -44,7 +47,7 @@ type sysCREDENTIAL_ATTRIBUTE struct {
Keyword *uint16
Flags uint32
ValueSize uint32
Value uintptr
Value *byte
}
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentialw
@@ -59,15 +62,17 @@ const (
sysCRED_TYPE_DOMAIN_EXTENDED sysCRED_TYPE = 0x6
// https://docs.microsoft.com/en-us/windows/desktop/Debug/system-error-codes
sysERROR_NOT_FOUND = syscall.Errno(1168)
sysERROR_INVALID_PARAMETER = syscall.Errno(87)
sysERROR_NOT_FOUND = windows.Errno(1168)
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
func sysCredRead(targetName string, typ sysCRED_TYPE) (*Credential, error) {
var pcred *sysCREDENTIAL
targetNamePtr, _ := syscall.UTF16PtrFromString(targetName)
ret, _, err := procCredRead.Call(
targetNamePtr, _ := windows.UTF16PtrFromString(targetName)
ret, _, err := syscall.SyscallN(
procCredRead.Addr(),
uintptr(unsafe.Pointer(targetNamePtr)),
uintptr(typ),
0,
@@ -89,6 +94,8 @@ func sysCredWrite(cred *Credential, typ sysCRED_TYPE) error {
uintptr(unsafe.Pointer(ncred)),
0,
)
// Make sure everything reachable from ncred stays alive through the call.
runtime.KeepAlive(ncred)
if ret == 0 {
return err
}
@@ -98,7 +105,7 @@ func sysCredWrite(cred *Credential, typ sysCRED_TYPE) error {
// https://docs.microsoft.com/en-us/windows/desktop/api/wincred/nf-wincred-creddeletew
func sysCredDelete(cred *Credential, typ sysCRED_TYPE) error {
targetNamePtr, _ := syscall.UTF16PtrFromString(cred.TargetName)
targetNamePtr, _ := windows.UTF16PtrFromString(cred.TargetName)
ret, _, err := procCredDelete.Call(
uintptr(unsafe.Pointer(targetNamePtr)),
uintptr(typ),
@@ -117,9 +124,10 @@ func sysCredEnumerate(filter string, all bool) ([]*Credential, error) {
var pcreds uintptr
var filterPtr *uint16
if !all {
filterPtr, _ = syscall.UTF16PtrFromString(filter)
filterPtr, _ = windows.UTF16PtrFromString(filter)
}
ret, _, err := procCredEnumerate.Call(
ret, _, err := syscall.SyscallN(
procCredEnumerate.Addr(),
uintptr(unsafe.Pointer(filterPtr)),
0,
uintptr(unsafe.Pointer(&count)),
+2
View File
@@ -1,3 +1,4 @@
//go:build !windows
// +build !windows
package wincred
@@ -17,6 +18,7 @@ const (
sysERROR_NOT_FOUND = syscall.Errno(1)
sysERROR_INVALID_PARAMETER = syscall.Errno(1)
sysERROR_BAD_USERNAME = syscall.Errno(1)
)
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.
// For example when trying to create a new generic credential with an empty target name.
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.
+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
// license that can be found in the LICENSE file.
//go:build windows && go1.9
// +build windows,go1.9
//go:build 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
}
defer DestroyEnvironmentBlock(block)
blockp := uintptr(unsafe.Pointer(block))
for {
entry := UTF16PtrToString((*uint16)(unsafe.Pointer(blockp)))
if len(entry) == 0 {
break
size := unsafe.Sizeof(*block)
for *block != 0 {
// find NUL terminator
end := unsafe.Pointer(block)
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
}
-1
View File
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build windows
// +build windows
package windows
+82 -12
View File
@@ -22,7 +22,7 @@ import (
// but only if there is space or tab inside s.
func EscapeArg(s string) string {
if len(s) == 0 {
return "\"\""
return `""`
}
n := len(s)
hasSpace := false
@@ -35,7 +35,7 @@ func EscapeArg(s string) string {
}
}
if hasSpace {
n += 2
n += 2 // Reserve space for quotes.
}
if n == len(s) {
return s
@@ -82,36 +82,106 @@ func EscapeArg(s string) string {
// in CreateProcess's CommandLine argument, CreateService/ChangeServiceConfig's BinaryPathName argument,
// or any program that uses CommandLineToArgv.
func ComposeCommandLine(args []string) string {
var commandLine string
for i := range args {
if i > 0 {
commandLine += " "
}
commandLine += EscapeArg(args[i])
if len(args) == 0 {
return ""
}
return commandLine
// 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
}
}
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,
// as gathered from GetCommandLine, QUERY_SERVICE_CONFIG's BinaryPathName argument, or elsewhere that
// command lines are passed around.
// DecomposeCommandLine returns an error if commandLine contains NUL.
func DecomposeCommandLine(commandLine string) ([]string, error) {
if len(commandLine) == 0 {
return []string{}, nil
}
utf16CommandLine, err := UTF16FromString(commandLine)
if err != nil {
return nil, errorspkg.New("string with NUL passed to DecomposeCommandLine")
}
var argc int32
argv, err := CommandLineToArgv(StringToUTF16Ptr(commandLine), &argc)
argv, err := commandLineToArgv(&utf16CommandLine[0], &argc)
if err != nil {
return nil, err
}
defer LocalFree(Handle(unsafe.Pointer(argv)))
var args []string
for _, v := range (*argv)[:argc] {
args = append(args, UTF16ToString((*v)[:]))
for _, p := range unsafe.Slice(argv, argc) {
args = append(args, UTF16PtrToString(p))
}
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) {
SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0)
}
-1
View File
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build generate
// +build generate
package windows
-1
View File
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build windows && race
// +build windows,race
package windows
-1
View File
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build windows && !race
// +build windows,!race
package windows
+6 -15
View File
@@ -7,8 +7,6 @@ package windows
import (
"syscall"
"unsafe"
"golang.org/x/sys/internal/unsafeheader"
)
const (
@@ -1341,21 +1339,14 @@ func (selfRelativeSD *SECURITY_DESCRIPTOR) copySelfRelativeSecurityDescriptor()
sdLen = min
}
var src []byte
h := (*unsafeheader.Slice)(unsafe.Pointer(&src))
h.Data = unsafe.Pointer(selfRelativeSD)
h.Len = sdLen
h.Cap = sdLen
src := unsafe.Slice((*byte)(unsafe.Pointer(selfRelativeSD)), sdLen)
// SECURITY_DESCRIPTOR has pointers in it, which means checkptr expects for it to
// be aligned properly. When we're copying a Windows-allocated struct to a
// Go-allocated one, make sure that the Go allocation is aligned to the
// pointer size.
const psize = int(unsafe.Sizeof(uintptr(0)))
var dst []byte
h = (*unsafeheader.Slice)(unsafe.Pointer(&dst))
alloc := make([]uintptr, (sdLen+psize-1)/psize)
h.Data = (*unsafeheader.Slice)(unsafe.Pointer(&alloc)).Data
h.Len = sdLen
h.Cap = sdLen
dst := unsafe.Slice((*byte)(unsafe.Pointer(&alloc[0])), sdLen)
copy(dst, src)
return (*SECURITY_DESCRIPTOR)(unsafe.Pointer(&dst[0]))
}
+11 -1
View File
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build windows
// +build windows
package windows
@@ -141,6 +140,12 @@ const (
SERVICE_DYNAMIC_INFORMATION_LEVEL_START_REASON = 1
)
type ENUM_SERVICE_STATUS struct {
ServiceName *uint16
DisplayName *uint16
ServiceStatus SERVICE_STATUS
}
type SERVICE_STATUS struct {
ServiceType uint32
CurrentState uint32
@@ -212,6 +217,10 @@ type SERVICE_FAILURE_ACTIONS struct {
Actions *SC_ACTION
}
type SERVICE_FAILURE_ACTIONS_FLAG struct {
FailureActionsOnNonCrashFailures int32
}
type SC_ACTION struct {
Type uint32
Delay uint32
@@ -245,3 +254,4 @@ type QUERY_SERVICE_LOCK_STATUS struct {
//sys UnsubscribeServiceChangeNotifications(subscription uintptr) = sechost.UnsubscribeServiceChangeNotifications?
//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 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.
//go:build windows
// +build windows
package windows
+1 -10
View File
@@ -3,7 +3,6 @@
// license that can be found in the LICENSE file.
//go:build windows
// +build windows
// Package windows contains an interface to the low-level operating system
// primitives. OS details vary depending on the underlying system, and
@@ -30,8 +29,6 @@ import (
"strings"
"syscall"
"unsafe"
"golang.org/x/sys/internal/unsafeheader"
)
// ByteSliceFromString returns a NUL-terminated slice of bytes
@@ -83,13 +80,7 @@ func BytePtrToString(p *byte) string {
ptr = unsafe.Pointer(uintptr(ptr) + 1)
}
var s []byte
h := (*unsafeheader.Slice)(unsafe.Pointer(&s))
h.Data = unsafe.Pointer(p)
h.Len = n
h.Cap = n
return string(s)
return string(unsafe.Slice(p, n))
}
// Single-word zero for use when we need a valid pointer to 0 bytes.
+165 -51
View File
@@ -10,14 +10,11 @@ import (
errorspkg "errors"
"fmt"
"runtime"
"strings"
"sync"
"syscall"
"time"
"unicode/utf16"
"unsafe"
"golang.org/x/sys/internal/unsafeheader"
)
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
// location, it returns (nil, syscall.EINVAL).
func UTF16FromString(s string) ([]uint16, error) {
if strings.IndexByte(s, 0) != -1 {
return nil, syscall.EINVAL
}
return utf16.Encode([]rune(s + "\x00")), nil
return syscall.UTF16FromString(s)
}
// UTF16ToString returns the UTF-8 encoding of the UTF-16 sequence s,
// with a terminating NUL and any bytes after the NUL removed.
func UTF16ToString(s []uint16) string {
for i, v := range s {
if v == 0 {
s = s[:i]
break
}
}
return string(utf16.Decode(s))
return syscall.UTF16ToString(s)
}
// StringToUTF16Ptr is deprecated. Use UTF16PtrFromString instead.
@@ -137,28 +125,21 @@ func UTF16PtrToString(p *uint16) string {
for ptr := unsafe.Pointer(p); *(*uint16)(ptr) != 0; n++ {
ptr = unsafe.Pointer(uintptr(ptr) + unsafe.Sizeof(*p))
}
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))
return UTF16ToString(unsafe.Slice(p, n))
}
func Getpagesize() int { return 4096 }
// 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.
// 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 {
return syscall.NewCallback(fn)
}
// 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.
// 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 {
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 GetModuleHandleEx(flags uint32, moduleName *uint16, module *Handle) (err error) = kernel32.GetModuleHandleExW
//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 GetVersion() (ver uint32, err error)
//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 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 DisconnectNamedPipe(pipe Handle) (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 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 GetComputerNameEx(nametype uint32, buf *uint16, n *uint32) (err error) = GetComputerNameExW
//sys SetEndOfFile(handle Handle) (err error)
//sys SetFileValidData(handle Handle, validDataLength int64) (err error)
//sys GetSystemTimeAsFileTime(time *Filetime)
//sys GetSystemTimePreciseAsFileTime(time *Filetime)
//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 TerminateProcess(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 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]
@@ -251,12 +236,13 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys CreateEnvironmentBlock(block **uint16, token Token, inheritExisting bool) (err error) = userenv.CreateEnvironmentBlock
//sys DestroyEnvironmentBlock(block *uint16) (err error) = userenv.DestroyEnvironmentBlock
//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 GetFileAttributes(name *uint16) (attrs uint32, err error) [failretval==INVALID_FILE_ATTRIBUTES] = kernel32.GetFileAttributesW
//sys SetFileAttributes(name *uint16, attrs uint32) (err error) = kernel32.SetFileAttributesW
//sys GetFileAttributesEx(name *uint16, level uint32, info *byte) (err error) = kernel32.GetFileAttributesExW
//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 LocalAlloc(flags uint32, length uint32) (ptr uintptr, 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 GetCurrentProcessId() (pid uint32) = kernel32.GetCurrentProcessId
//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 SetConsoleMode(console Handle, mode uint32) (err error) = kernel32.SetConsoleMode
//sys GetConsoleScreenBufferInfo(console Handle, info *ConsoleScreenBufferInfo) (err error) = kernel32.GetConsoleScreenBufferInfo
//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 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 Module32First(snapshot Handle, moduleEntry *ModuleEntry32) (err error) = kernel32.Module32FirstW
//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 GetProcessWorkingSetSizeEx(hProcess Handle, lpMinimumWorkingSetSize *uintptr, lpMaximumWorkingSetSize *uintptr, flags *uint32)
//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 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 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 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
//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
// 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 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
@@ -439,6 +449,14 @@ func NewCallbackCDecl(fn interface{}) uintptr {
//sys RtlAddFunctionTable(functionTable *RUNTIME_FUNCTION, entryCount uint32, baseAddress uintptr) (ret bool) = ntdll.RtlAddFunctionTable
//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
// GetCurrentProcess returns the handle for the current process.
@@ -748,7 +766,7 @@ func Utimes(path string, tv []Timeval) (err error) {
if e != nil {
return e
}
defer Close(h)
defer CloseHandle(h)
a := NsecToFiletime(tv[0].Nanoseconds())
w := NsecToFiletime(tv[1].Nanoseconds())
return SetFileTime(h, nil, &a, &w)
@@ -768,7 +786,7 @@ func UtimesNano(path string, ts []Timespec) (err error) {
if e != nil {
return e
}
defer Close(h)
defer CloseHandle(h)
a := NsecToFiletime(TimespecToNsec(ts[0]))
w := NsecToFiletime(TimespecToNsec(ts[1]))
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 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 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 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
@@ -963,7 +984,8 @@ func (sa *SockaddrUnix) sockaddr() (unsafe.Pointer, int32, error) {
if n > 0 {
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
// Don't count trailing NUL for abstract address.
sl--
@@ -1021,8 +1043,7 @@ func (rsa *RawSockaddrAny) Sockaddr() (Sockaddr, error) {
for n < len(pp.Path) && pp.Path[n] != 0 {
n++
}
bytes := (*[len(pp.Path)]byte)(unsafe.Pointer(&pp.Path[0]))[0:n]
sa.Name = string(bytes)
sa.Name = string(unsafe.Slice((*byte)(unsafe.Pointer(&pp.Path[0])), n))
return sa, nil
case AF_INET:
@@ -1108,9 +1129,13 @@ 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) {
rsa, l, err := to.sockaddr()
if err != nil {
return err
var rsa unsafe.Pointer
var l int32
if to != nil {
rsa, l, err = to.sockaddr()
if err != nil {
return err
}
}
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
}
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 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))))
}
func GetStartupInfo(startupInfo *StartupInfo) error {
getStartupInfo(startupInfo)
return nil
}
func (s NTStatus) Errno() syscall.Errno {
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.
func (s *NTUnicodeString) Slice() []uint16 {
var slice []uint16
hdr := (*unsafeheader.Slice)(unsafe.Pointer(&slice))
hdr.Data = unsafe.Pointer(s.Buffer)
hdr.Len = int(s.Length)
hdr.Cap = int(s.MaximumLength)
return slice
slice := unsafe.Slice(s.Buffer, s.MaximumLength)
return slice[:s.Length]
}
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.
func (s *NTString) Slice() []byte {
var slice []byte
hdr := (*unsafeheader.Slice)(unsafe.Pointer(&slice))
hdr.Data = unsafe.Pointer(s.Buffer)
hdr.Len = int(s.Length)
hdr.Cap = int(s.MaximumLength)
return slice
slice := unsafe.Slice(s.Buffer, s.MaximumLength)
return slice[:s.Length]
}
func (s *NTString) String() string {
@@ -1728,10 +1761,7 @@ func LoadResourceData(module, resInfo Handle) (data []byte, err error) {
if err != nil {
return
}
h := (*unsafeheader.Slice)(unsafe.Pointer(&data))
h.Data = unsafe.Pointer(ptr)
h.Len = int(size)
h.Cap = int(size)
data = unsafe.Slice((*byte)(unsafe.Pointer(ptr)), size)
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.
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
)
+195 -4
View File
@@ -247,6 +247,7 @@ const (
PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY = 0x00020007
PROC_THREAD_ATTRIBUTE_UMS_THREAD = 0x00030006
PROC_THREAD_ATTRIBUTE_PROTECTION_LEVEL = 0x0002000b
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = 0x00020016
)
const (
@@ -1093,7 +1094,33 @@ const (
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_WR = 1
@@ -1243,6 +1270,51 @@ const (
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 {
Target *uint16
Priority uint16
@@ -2094,6 +2166,12 @@ const (
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 {
X int16
Y int16
@@ -2175,19 +2253,23 @@ type JOBOBJECT_BASIC_UI_RESTRICTIONS struct {
}
const (
// JobObjectInformationClass
// JobObjectInformationClass for QueryInformationJobObject and SetInformationJobObject
JobObjectAssociateCompletionPortInformation = 7
JobObjectBasicAccountingInformation = 1
JobObjectBasicAndIoAccountingInformation = 8
JobObjectBasicLimitInformation = 2
JobObjectBasicProcessIdList = 3
JobObjectBasicUIRestrictions = 4
JobObjectCpuRateControlInformation = 15
JobObjectEndOfJobTimeInformation = 6
JobObjectExtendedLimitInformation = 9
JobObjectGroupInformation = 11
JobObjectGroupInformationEx = 14
JobObjectLimitViolationInformation2 = 35
JobObjectLimitViolationInformation = 13
JobObjectLimitViolationInformation2 = 34
JobObjectNetRateControlInformation = 32
JobObjectNotificationLimitInformation = 12
JobObjectNotificationLimitInformation2 = 34
JobObjectNotificationLimitInformation2 = 33
JobObjectSecurityLimitInformation = 5
)
@@ -3213,3 +3295,112 @@ type ModuleInfo struct {
}
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")
modcrypt32 = NewLazySystemDLL("crypt32.dll")
moddnsapi = NewLazySystemDLL("dnsapi.dll")
moddwmapi = NewLazySystemDLL("dwmapi.dll")
modiphlpapi = NewLazySystemDLL("iphlpapi.dll")
modkernel32 = NewLazySystemDLL("kernel32.dll")
modmswsock = NewLazySystemDLL("mswsock.dll")
@@ -54,6 +55,7 @@ var (
moduser32 = NewLazySystemDLL("user32.dll")
moduserenv = NewLazySystemDLL("userenv.dll")
modversion = NewLazySystemDLL("version.dll")
modwinmm = NewLazySystemDLL("winmm.dll")
modwintrust = NewLazySystemDLL("wintrust.dll")
modws2_32 = NewLazySystemDLL("ws2_32.dll")
modwtsapi32 = NewLazySystemDLL("wtsapi32.dll")
@@ -85,6 +87,7 @@ var (
procDeleteService = modadvapi32.NewProc("DeleteService")
procDeregisterEventSource = modadvapi32.NewProc("DeregisterEventSource")
procDuplicateTokenEx = modadvapi32.NewProc("DuplicateTokenEx")
procEnumDependentServicesW = modadvapi32.NewProc("EnumDependentServicesW")
procEnumServicesStatusExW = modadvapi32.NewProc("EnumServicesStatusExW")
procEqualSid = modadvapi32.NewProc("EqualSid")
procFreeSid = modadvapi32.NewProc("FreeSid")
@@ -175,14 +178,20 @@ var (
procDnsNameCompare_W = moddnsapi.NewProc("DnsNameCompare_W")
procDnsQuery_W = moddnsapi.NewProc("DnsQuery_W")
procDnsRecordListFree = moddnsapi.NewProc("DnsRecordListFree")
procDwmGetWindowAttribute = moddwmapi.NewProc("DwmGetWindowAttribute")
procDwmSetWindowAttribute = moddwmapi.NewProc("DwmSetWindowAttribute")
procGetAdaptersAddresses = modiphlpapi.NewProc("GetAdaptersAddresses")
procGetAdaptersInfo = modiphlpapi.NewProc("GetAdaptersInfo")
procGetBestInterfaceEx = modiphlpapi.NewProc("GetBestInterfaceEx")
procGetIfEntry = modiphlpapi.NewProc("GetIfEntry")
procAddDllDirectory = modkernel32.NewProc("AddDllDirectory")
procAssignProcessToJobObject = modkernel32.NewProc("AssignProcessToJobObject")
procCancelIo = modkernel32.NewProc("CancelIo")
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
procClearCommBreak = modkernel32.NewProc("ClearCommBreak")
procClearCommError = modkernel32.NewProc("ClearCommError")
procCloseHandle = modkernel32.NewProc("CloseHandle")
procClosePseudoConsole = modkernel32.NewProc("ClosePseudoConsole")
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
procCreateDirectoryW = modkernel32.NewProc("CreateDirectoryW")
procCreateEventExW = modkernel32.NewProc("CreateEventExW")
@@ -197,6 +206,7 @@ var (
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
procCreatePipe = modkernel32.NewProc("CreatePipe")
procCreateProcessW = modkernel32.NewProc("CreateProcessW")
procCreatePseudoConsole = modkernel32.NewProc("CreatePseudoConsole")
procCreateSymbolicLinkW = modkernel32.NewProc("CreateSymbolicLinkW")
procCreateToolhelp32Snapshot = modkernel32.NewProc("CreateToolhelp32Snapshot")
procDefineDosDeviceW = modkernel32.NewProc("DefineDosDeviceW")
@@ -204,7 +214,9 @@ var (
procDeleteProcThreadAttributeList = modkernel32.NewProc("DeleteProcThreadAttributeList")
procDeleteVolumeMountPointW = modkernel32.NewProc("DeleteVolumeMountPointW")
procDeviceIoControl = modkernel32.NewProc("DeviceIoControl")
procDisconnectNamedPipe = modkernel32.NewProc("DisconnectNamedPipe")
procDuplicateHandle = modkernel32.NewProc("DuplicateHandle")
procEscapeCommFunction = modkernel32.NewProc("EscapeCommFunction")
procExitProcess = modkernel32.NewProc("ExitProcess")
procExpandEnvironmentStringsW = modkernel32.NewProc("ExpandEnvironmentStringsW")
procFindClose = modkernel32.NewProc("FindClose")
@@ -228,6 +240,8 @@ var (
procGenerateConsoleCtrlEvent = modkernel32.NewProc("GenerateConsoleCtrlEvent")
procGetACP = modkernel32.NewProc("GetACP")
procGetActiveProcessorCount = modkernel32.NewProc("GetActiveProcessorCount")
procGetCommModemStatus = modkernel32.NewProc("GetCommModemStatus")
procGetCommState = modkernel32.NewProc("GetCommState")
procGetCommTimeouts = modkernel32.NewProc("GetCommTimeouts")
procGetCommandLineW = modkernel32.NewProc("GetCommandLineW")
procGetComputerNameExW = modkernel32.NewProc("GetComputerNameExW")
@@ -246,9 +260,11 @@ var (
procGetFileAttributesW = modkernel32.NewProc("GetFileAttributesW")
procGetFileInformationByHandle = modkernel32.NewProc("GetFileInformationByHandle")
procGetFileInformationByHandleEx = modkernel32.NewProc("GetFileInformationByHandleEx")
procGetFileTime = modkernel32.NewProc("GetFileTime")
procGetFileType = modkernel32.NewProc("GetFileType")
procGetFinalPathNameByHandleW = modkernel32.NewProc("GetFinalPathNameByHandleW")
procGetFullPathNameW = modkernel32.NewProc("GetFullPathNameW")
procGetLargePageMinimum = modkernel32.NewProc("GetLargePageMinimum")
procGetLastError = modkernel32.NewProc("GetLastError")
procGetLogicalDriveStringsW = modkernel32.NewProc("GetLogicalDriveStringsW")
procGetLogicalDrives = modkernel32.NewProc("GetLogicalDrives")
@@ -312,6 +328,7 @@ var (
procProcess32NextW = modkernel32.NewProc("Process32NextW")
procProcessIdToSessionId = modkernel32.NewProc("ProcessIdToSessionId")
procPulseEvent = modkernel32.NewProc("PulseEvent")
procPurgeComm = modkernel32.NewProc("PurgeComm")
procQueryDosDeviceW = modkernel32.NewProc("QueryDosDeviceW")
procQueryFullProcessImageNameW = modkernel32.NewProc("QueryFullProcessImageNameW")
procQueryInformationJobObject = modkernel32.NewProc("QueryInformationJobObject")
@@ -321,8 +338,13 @@ var (
procReadProcessMemory = modkernel32.NewProc("ReadProcessMemory")
procReleaseMutex = modkernel32.NewProc("ReleaseMutex")
procRemoveDirectoryW = modkernel32.NewProc("RemoveDirectoryW")
procRemoveDllDirectory = modkernel32.NewProc("RemoveDllDirectory")
procResetEvent = modkernel32.NewProc("ResetEvent")
procResizePseudoConsole = modkernel32.NewProc("ResizePseudoConsole")
procResumeThread = modkernel32.NewProc("ResumeThread")
procSetCommBreak = modkernel32.NewProc("SetCommBreak")
procSetCommMask = modkernel32.NewProc("SetCommMask")
procSetCommState = modkernel32.NewProc("SetCommState")
procSetCommTimeouts = modkernel32.NewProc("SetCommTimeouts")
procSetConsoleCursorPosition = modkernel32.NewProc("SetConsoleCursorPosition")
procSetConsoleMode = modkernel32.NewProc("SetConsoleMode")
@@ -338,6 +360,7 @@ var (
procSetFileInformationByHandle = modkernel32.NewProc("SetFileInformationByHandle")
procSetFilePointer = modkernel32.NewProc("SetFilePointer")
procSetFileTime = modkernel32.NewProc("SetFileTime")
procSetFileValidData = modkernel32.NewProc("SetFileValidData")
procSetHandleInformation = modkernel32.NewProc("SetHandleInformation")
procSetInformationJobObject = modkernel32.NewProc("SetInformationJobObject")
procSetNamedPipeHandleState = modkernel32.NewProc("SetNamedPipeHandleState")
@@ -348,6 +371,7 @@ var (
procSetStdHandle = modkernel32.NewProc("SetStdHandle")
procSetVolumeLabelW = modkernel32.NewProc("SetVolumeLabelW")
procSetVolumeMountPointW = modkernel32.NewProc("SetVolumeMountPointW")
procSetupComm = modkernel32.NewProc("SetupComm")
procSizeofResource = modkernel32.NewProc("SizeofResource")
procSleepEx = modkernel32.NewProc("SleepEx")
procTerminateJobObject = modkernel32.NewProc("TerminateJobObject")
@@ -366,6 +390,7 @@ var (
procVirtualQueryEx = modkernel32.NewProc("VirtualQueryEx")
procVirtualUnlock = modkernel32.NewProc("VirtualUnlock")
procWTSGetActiveConsoleSessionId = modkernel32.NewProc("WTSGetActiveConsoleSessionId")
procWaitCommEvent = modkernel32.NewProc("WaitCommEvent")
procWaitForMultipleObjects = modkernel32.NewProc("WaitForMultipleObjects")
procWaitForSingleObject = modkernel32.NewProc("WaitForSingleObject")
procWriteConsoleW = modkernel32.NewProc("WriteConsoleW")
@@ -444,9 +469,18 @@ var (
procCommandLineToArgvW = modshell32.NewProc("CommandLineToArgvW")
procSHGetKnownFolderPath = modshell32.NewProc("SHGetKnownFolderPath")
procShellExecuteW = modshell32.NewProc("ShellExecuteW")
procEnumChildWindows = moduser32.NewProc("EnumChildWindows")
procEnumWindows = moduser32.NewProc("EnumWindows")
procExitWindowsEx = moduser32.NewProc("ExitWindowsEx")
procGetClassNameW = moduser32.NewProc("GetClassNameW")
procGetDesktopWindow = moduser32.NewProc("GetDesktopWindow")
procGetForegroundWindow = moduser32.NewProc("GetForegroundWindow")
procGetGUIThreadInfo = moduser32.NewProc("GetGUIThreadInfo")
procGetShellWindow = moduser32.NewProc("GetShellWindow")
procGetWindowThreadProcessId = moduser32.NewProc("GetWindowThreadProcessId")
procIsWindow = moduser32.NewProc("IsWindow")
procIsWindowUnicode = moduser32.NewProc("IsWindowUnicode")
procIsWindowVisible = moduser32.NewProc("IsWindowVisible")
procMessageBoxW = moduser32.NewProc("MessageBoxW")
procCreateEnvironmentBlock = moduserenv.NewProc("CreateEnvironmentBlock")
procDestroyEnvironmentBlock = moduserenv.NewProc("DestroyEnvironmentBlock")
@@ -454,6 +488,8 @@ var (
procGetFileVersionInfoSizeW = modversion.NewProc("GetFileVersionInfoSizeW")
procGetFileVersionInfoW = modversion.NewProc("GetFileVersionInfoW")
procVerQueryValueW = modversion.NewProc("VerQueryValueW")
proctimeBeginPeriod = modwinmm.NewProc("timeBeginPeriod")
proctimeEndPeriod = modwinmm.NewProc("timeEndPeriod")
procWinVerifyTrustEx = modwintrust.NewProc("WinVerifyTrustEx")
procFreeAddrInfoW = modws2_32.NewProc("FreeAddrInfoW")
procGetAddrInfoW = modws2_32.NewProc("GetAddrInfoW")
@@ -461,6 +497,9 @@ var (
procWSAEnumProtocolsW = modws2_32.NewProc("WSAEnumProtocolsW")
procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult")
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")
procWSARecvFrom = modws2_32.NewProc("WSARecvFrom")
procWSASend = modws2_32.NewProc("WSASend")
@@ -718,6 +757,14 @@ func DuplicateTokenEx(existingToken Token, desiredAccess uint32, tokenAttributes
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) {
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 {
@@ -1525,6 +1572,22 @@ func DnsRecordListFree(rl *DNSRecord, freetype uint32) {
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) {
r0, _, _ := syscall.Syscall6(procGetAdaptersAddresses.Addr(), 5, uintptr(family), uintptr(flags), uintptr(reserved), uintptr(unsafe.Pointer(adapterAddresses)), uintptr(unsafe.Pointer(sizePointer)), 0)
if r0 != 0 {
@@ -1557,6 +1620,15 @@ func GetIfEntry(pIfRow *MibIfRow) (errcode error) {
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) {
r1, _, e1 := syscall.Syscall(procAssignProcessToJobObject.Addr(), 2, uintptr(job), uintptr(process), 0)
if r1 == 0 {
@@ -1581,6 +1653,22 @@ func CancelIoEx(s Handle, o *Overlapped) (err error) {
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) {
r1, _, e1 := syscall.Syscall(procCloseHandle.Addr(), 1, uintptr(handle), 0, 0)
if r1 == 0 {
@@ -1589,6 +1677,11 @@ func CloseHandle(handle Handle) (err error) {
return
}
func ClosePseudoConsole(console Handle) {
syscall.Syscall(procClosePseudoConsole.Addr(), 1, uintptr(console), 0, 0)
return
}
func ConnectNamedPipe(pipe Handle, overlapped *Overlapped) (err error) {
r1, _, e1 := syscall.Syscall(procConnectNamedPipe.Addr(), 2, uintptr(pipe), uintptr(unsafe.Pointer(overlapped)), 0)
if r1 == 0 {
@@ -1718,6 +1811,14 @@ func CreateProcess(appName *uint16, commandLine *uint16, procSecurity *SecurityA
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) {
r1, _, e1 := syscall.Syscall(procCreateSymbolicLinkW.Addr(), 3, uintptr(unsafe.Pointer(symlinkfilename)), uintptr(unsafe.Pointer(targetfilename)), uintptr(flags))
if r1&0xff == 0 {
@@ -1772,6 +1873,14 @@ func DeviceIoControl(handle Handle, ioControlCode uint32, inBuffer *byte, inBuff
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) {
var _p0 uint32
if bInheritHandle {
@@ -1784,6 +1893,14 @@ func DuplicateHandle(hSourceProcessHandle Handle, hSourceHandle Handle, hTargetP
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) {
syscall.Syscall(procExitProcess.Addr(), 1, uintptr(exitcode), 0, 0)
return
@@ -1985,6 +2102,22 @@ func GetActiveProcessorCount(groupNumber uint16) (ret uint32) {
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) {
r1, _, e1 := syscall.Syscall(procGetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0)
if r1 == 0 {
@@ -2125,6 +2258,14 @@ func GetFileInformationByHandleEx(handle Handle, class uint32, outBuffer *byte,
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) {
r0, _, e1 := syscall.Syscall(procGetFileType.Addr(), 1, uintptr(filehandle), 0, 0)
n = uint32(r0)
@@ -2152,6 +2293,12 @@ func GetFullPathName(path *uint16, buflen uint32, buf *uint16, fname **uint16) (
return
}
func GetLargePageMinimum() (size uintptr) {
r0, _, _ := syscall.Syscall(procGetLargePageMinimum.Addr(), 0, 0, 0, 0)
size = uintptr(r0)
return
}
func GetLastError() (lasterr error) {
r0, _, _ := syscall.Syscall(procGetLastError.Addr(), 0, 0, 0, 0)
if r0 != 0 {
@@ -2320,11 +2467,8 @@ func GetShortPathName(longpath *uint16, shortpath *uint16, buflen uint32) (n uin
return
}
func GetStartupInfo(startupInfo *StartupInfo) (err error) {
r1, _, e1 := syscall.Syscall(procGetStartupInfoW.Addr(), 1, uintptr(unsafe.Pointer(startupInfo)), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
func getStartupInfo(startupInfo *StartupInfo) {
syscall.Syscall(procGetStartupInfoW.Addr(), 1, uintptr(unsafe.Pointer(startupInfo)), 0, 0)
return
}
@@ -2726,6 +2870,14 @@ func PulseEvent(event Handle) (err error) {
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) {
r0, _, e1 := syscall.Syscall(procQueryDosDeviceW.Addr(), 3, uintptr(unsafe.Pointer(deviceName)), uintptr(unsafe.Pointer(targetPath)), uintptr(max))
n = uint32(r0)
@@ -2807,6 +2959,14 @@ func RemoveDirectory(path *uint16) (err error) {
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) {
r1, _, e1 := syscall.Syscall(procResetEvent.Addr(), 1, uintptr(event), 0, 0)
if r1 == 0 {
@@ -2815,6 +2975,14 @@ func ResetEvent(event Handle) (err error) {
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) {
r0, _, e1 := syscall.Syscall(procResumeThread.Addr(), 1, uintptr(thread), 0, 0)
ret = uint32(r0)
@@ -2824,6 +2992,30 @@ func ResumeThread(thread Handle) (ret uint32, err error) {
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) {
r1, _, e1 := syscall.Syscall(procSetCommTimeouts.Addr(), 2, uintptr(handle), uintptr(unsafe.Pointer(timeouts)), 0)
if r1 == 0 {
@@ -2952,6 +3144,14 @@ func SetFileTime(handle Handle, ctime *Filetime, atime *Filetime, wtime *Filetim
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) {
r1, _, e1 := syscall.Syscall(procSetHandleInformation.Addr(), 3, uintptr(handle), uintptr(mask), uintptr(flags))
if r1 == 0 {
@@ -3037,6 +3237,14 @@ func SetVolumeMountPoint(volumeMountPoint *uint16, volumeName *uint16) (err erro
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) {
r0, _, e1 := syscall.Syscall(procSizeofResource.Addr(), 2, uintptr(module), uintptr(resInfo), 0)
size = uint32(r0)
@@ -3183,6 +3391,14 @@ func WTSGetActiveConsoleSessionId() (sessionID uint32) {
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) {
var _p0 uint32
if waitAll {
@@ -3469,12 +3685,8 @@ func EnumProcessModulesEx(process Handle, module *Handle, cb uint32, cbNeeded *u
return
}
func EnumProcesses(processIds []uint32, bytesReturned *uint32) (err error) {
var _p0 *uint32
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)))
func enumProcesses(processIds *uint32, nSize uint32, bytesReturned *uint32) (err error) {
r1, _, e1 := syscall.Syscall(procEnumProcesses.Addr(), 3, uintptr(unsafe.Pointer(processIds)), uintptr(nSize), uintptr(unsafe.Pointer(bytesReturned)))
if r1 == 0 {
err = errnoErr(e1)
}
@@ -3777,9 +3989,9 @@ func setupUninstallOEMInf(infFileName *uint16, flags SUOI, reserved uintptr) (er
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)
argv = (*[8192]*[8192]uint16)(unsafe.Pointer(r0))
argv = (**uint16)(unsafe.Pointer(r0))
if argv == nil {
err = errnoErr(e1)
}
@@ -3802,6 +4014,19 @@ func ShellExecute(hwnd Handle, verb *uint16, file *uint16, args *uint16, cwd *ui
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) {
r1, _, e1 := syscall.Syscall(procExitWindowsEx.Addr(), 2, uintptr(flags), uintptr(reason), 0)
if r1 == 0 {
@@ -3810,6 +4035,35 @@ func ExitWindowsEx(flags uint32, reason uint32) (err error) {
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) {
r0, _, _ := syscall.Syscall(procGetShellWindow.Addr(), 0, 0, 0, 0)
shellWindow = HWND(r0)
@@ -3825,6 +4079,24 @@ func GetWindowThreadProcessId(hwnd HWND, pid *uint32) (tid uint32, err error) {
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) {
r0, _, e1 := syscall.Syscall6(procMessageBoxW.Addr(), 4, uintptr(hwnd), uintptr(unsafe.Pointer(text)), uintptr(unsafe.Pointer(caption)), uintptr(boxtype), 0, 0)
ret = int32(r0)
@@ -3914,6 +4186,22 @@ func _VerQueryValue(block unsafe.Pointer, subBlock *uint16, pointerToBufferPoint
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) {
r0, _, _ := syscall.Syscall(procWinVerifyTrustEx.Addr(), 3, uintptr(hwnd), uintptr(unsafe.Pointer(actionId)), uintptr(unsafe.Pointer(data)))
if r0 != 0 {
@@ -3972,6 +4260,30 @@ func WSAIoctl(s Handle, iocc uint32, inbuf *byte, cbif uint32, outbuf *byte, cbo
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) {
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 {
+7 -6
View File
@@ -1,8 +1,9 @@
# github.com/danieljoos/wincred v1.1.2
## explicit; go 1.13
# github.com/danieljoos/wincred v1.2.3
## explicit; go 1.18
github.com/danieljoos/wincred
# golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64
## explicit; go 1.17
golang.org/x/sys/execabs
golang.org/x/sys/internal/unsafeheader
# github.com/keybase/go-keychain v0.0.1
## explicit; go 1.21
github.com/keybase/go-keychain
# golang.org/x/sys v0.20.0
## explicit; go 1.18
golang.org/x/sys/windows
@@ -1,3 +1,5 @@
//go:build windows
package main
import (
@@ -1,9 +1,10 @@
//go:build windows
package wincred
import (
"bytes"
"net/url"
"strings"
winc "github.com/danieljoos/wincred"
"github.com/docker/docker-credential-helpers/credentials"
@@ -52,9 +53,7 @@ func (h Wincred) Get(serverURL string) (string, string, error) {
}
for _, attr := range g.Attributes {
if strings.Compare(attr.Keyword, "label") == 0 &&
bytes.Compare(attr.Value, []byte(credentials.CredsLabel)) == 0 {
if attr.Keyword == "label" && bytes.Equal(attr.Value, []byte(credentials.CredsLabel)) {
return g.UserName, string(g.CredentialBlob), nil
}
}
@@ -73,11 +72,10 @@ func getTarget(serverURL string) (string, error) {
}
var targets []string
for i := range creds {
attrs := creds[i].Attributes
for _, attr := range attrs {
for _, cred := range creds {
for _, attr := range cred.Attributes {
if attr.Keyword == "label" && bytes.Equal(attr.Value, []byte(credentials.CredsLabel)) {
targets = append(targets, creds[i].TargetName)
targets = append(targets, cred.TargetName)
}
}
}
@@ -111,15 +109,15 @@ func exactMatch(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 == "" {
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() != "" {
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 == "" {
serverURL.Path = target.Path
}
@@ -134,16 +132,14 @@ func (h Wincred) List() (map[string]string, error) {
}
resp := make(map[string]string)
for i := range creds {
attrs := creds[i].Attributes
for _, attr := range attrs {
if strings.Compare(attr.Keyword, "label") == 0 &&
bytes.Compare(attr.Value, []byte(credentials.CredsLabel)) == 0 {
resp[creds[i].TargetName] = creds[i].UserName
for _, cred := range creds {
for _, attr := range cred.Attributes {
if attr.Keyword == "label" && bytes.Equal(attr.Value, []byte(credentials.CredsLabel)) {
resp[cred.TargetName] = cred.UserName
break
}
}
}
return resp, nil
+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)
}
}