mirror of
https://github.com/docker/docker-credential-helpers.git
synced 2026-06-28 15:21:29 +05:30
Compare commits
159 Commits
v0.8.0
..
e3d1da694f
| Author | SHA1 | Date | |
|---|---|---|---|
| e3d1da694f | |||
| 98a1b9ce53 | |||
| ca21698edc | |||
| 300c1b491f | |||
| 14b6d371c9 | |||
| bfd43cacbb | |||
| 7b43509f5b | |||
| 5ad51ee4a4 | |||
| 5b6a1880fa | |||
| 29038b4df4 | |||
| 955f6c518d | |||
| 0202e5a960 | |||
| 4e2b0ff14f | |||
| 8fe8d458f7 | |||
| af758c414c | |||
| dc6f4f5cb9 | |||
| 4e68cd824e | |||
| d520877610 | |||
| f0e4adbf36 | |||
| bf6137df6b | |||
| 2b4e08bca3 | |||
| 62deeb49c1 | |||
| 6ca9924445 | |||
| 806dc5f678 | |||
| a7b23cd2b5 | |||
| 178a3a4e57 | |||
| f5fd80af0f | |||
| ae163ade7b | |||
| b871f76540 | |||
| 50c1460bf5 | |||
| aecf6e5780 | |||
| ecf6c1ccc7 | |||
| b844409a12 | |||
| 9df2c7782a | |||
| 7a15b77bcb | |||
| 81f7ebebfd | |||
| 3f97cf3ce3 | |||
| 8b5e6dffc6 | |||
| 4741f33d28 | |||
| 78303955b8 | |||
| 9b0c242b5c | |||
| 057ed818a9 | |||
| b7a754b9ff | |||
| 62777f0887 | |||
| 9d04e49561 | |||
| bc131d729d | |||
| 84c3413e0e | |||
| fcb0b664b5 | |||
| cf4e41fbb0 | |||
| 53f7bdc3fa | |||
| d4602cd917 | |||
| ae84c25786 | |||
| 2adf3cf9aa | |||
| 1fdce4c733 | |||
| 962a779645 | |||
| ec5efac3ca | |||
| 8154b98959 | |||
| d075f3cecc | |||
| fdddb02817 | |||
| c07513a69d | |||
| 4142982fb8 | |||
| 860f1459e3 | |||
| d378d46316 | |||
| 4c97a761df | |||
| b61abf1cb8 | |||
| 85841ea0ce | |||
| c32e697324 | |||
| d770c60191 | |||
| 5095e43ecf | |||
| 00313838c6 | |||
| bcf656656f | |||
| fd27520bbd | |||
| 4849c2328b | |||
| 2e8005f3a7 | |||
| 5d4d5150ae | |||
| f9d3010165 | |||
| e7bd3957ae | |||
| cfd6d21216 | |||
| ab29a6c87b | |||
| 576efaa084 | |||
| 9d6cdddf25 | |||
| d8e34f8743 | |||
| b1d5bf0326 | |||
| 50b162c340 | |||
| 833d2c334f | |||
| 9651bf7802 | |||
| 26274da6cf | |||
| f4cdabf916 | |||
| c7514a0999 | |||
| 36a3c50452 | |||
| 4e957ecd1b | |||
| f7f8554b4c | |||
| 1a77fa667f | |||
| 8a779f2b11 | |||
| a767624e34 | |||
| a5569fbfff | |||
| 99bf48e5f8 | |||
| 1041211a6e | |||
| 8c804df56c | |||
| a7974e91c5 | |||
| ffe5a9835c | |||
| e79a8203ca | |||
| 4cdcdc29eb | |||
| 8438667191 | |||
| fc66c3f02c | |||
| 7810dc4db9 | |||
| 28e893e56d | |||
| 1161e9c157 | |||
| a17e9a013b | |||
| fe0e8e3a01 | |||
| c2ca986943 | |||
| 407e50d36e | |||
| 10845d8f94 | |||
| 43ae7f3412 | |||
| 1d9eaaa4ef | |||
| 7346714456 | |||
| ab3fc5283d | |||
| 713df50a2d | |||
| 7e7c5576ba | |||
| ad253f54a5 | |||
| fa991bcbeb | |||
| db1da9da5d | |||
| c23b2d6e4f | |||
| 13e62f3bbe | |||
| 2d241f3602 | |||
| 58c87f0952 | |||
| dbb72e35c3 | |||
| 6e5b45e59f | |||
| 2ed5a274b6 | |||
| 7dbcc1c472 | |||
| 6b9df3ebb5 | |||
| dc10c50685 | |||
| 896eb37d47 | |||
| a14669f4ff | |||
| 74840b3740 | |||
| d3ef442f59 | |||
| f64d6b131b | |||
| 1bb9aa3210 | |||
| 73b9e5d51f | |||
| 0c43fede6d | |||
| a941c5247f | |||
| 097f945536 | |||
| 9272dcb90a | |||
| ecacf8cdcf | |||
| 5be670a285 | |||
| 73aa8c0daa | |||
| c23afb6c37 | |||
| d622133060 | |||
| 12500fb753 | |||
| bf726a0656 | |||
| d9632f6a08 | |||
| 292722b503 | |||
| 979dcc4762 | |||
| f411a65c31 | |||
| 9629bd77ad | |||
| f642c26173 | |||
| 8fc330691f | |||
| 6a3e64c0b4 | |||
| 218f1787ac |
@@ -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)**
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
open-pull-requests-limit: 10
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "bot"
|
||||
+47
-30
@@ -1,5 +1,14 @@
|
||||
name: build
|
||||
|
||||
# Default to 'contents: read', which grants actions to read commits.
|
||||
#
|
||||
# If any permission is set, any permission not included in the list is
|
||||
# implicitly set to "none".
|
||||
#
|
||||
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -8,18 +17,19 @@ on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'main'
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
DESTDIR: ./bin
|
||||
GO_VERSION: 1.20.6
|
||||
GO_VERSION: 1.25.8
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30 # guardrails timeout for the whole job
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -29,10 +39,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,24 +50,26 @@ jobs:
|
||||
|
||||
test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 30 # guardrails timeout for the whole job
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-24.04
|
||||
- ubuntu-22.04
|
||||
- ubuntu-20.04
|
||||
- macOS-11
|
||||
- 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@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
cache: true
|
||||
-
|
||||
name: Install deps (ubuntu)
|
||||
if: startsWith(matrix.os, 'ubuntu-')
|
||||
@@ -72,7 +84,7 @@ jobs:
|
||||
-
|
||||
name: GPG conf
|
||||
if: ${{ !startsWith(matrix.os, 'windows-') }}
|
||||
uses: actions/github-script@v6
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
id: gpg
|
||||
with:
|
||||
script: |
|
||||
@@ -89,7 +101,7 @@ jobs:
|
||||
-
|
||||
name: Import GPG key
|
||||
if: ${{ !startsWith(matrix.os, 'windows-') }}
|
||||
uses: crazy-max/ghaction-import-gpg@v5
|
||||
uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
|
||||
with:
|
||||
gpg_private_key: ${{ steps.gpg.outputs.key }}
|
||||
passphrase: ${{ steps.gpg.outputs.passphrase }}
|
||||
@@ -107,22 +119,21 @@ jobs:
|
||||
shell: bash
|
||||
-
|
||||
name: Upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||
with:
|
||||
file: ${{ env.DESTDIR }}/coverage.txt
|
||||
files: ${{ env.DESTDIR }}/coverage.txt
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
test-sandboxed:
|
||||
runs-on: ubuntu-22.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: |
|
||||
@@ -130,24 +141,29 @@ jobs:
|
||||
*.cache-to=type=gha,scope=test,mode=max
|
||||
-
|
||||
name: Upload coverage
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
|
||||
with:
|
||||
file: ${{ env.DESTDIR }}//coverage.txt
|
||||
files: ${{ env.DESTDIR }}//coverage.txt
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-22.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: |
|
||||
@@ -165,7 +181,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@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: docker-credential-helpers
|
||||
path: ${{ env.DESTDIR }}/*
|
||||
@@ -173,7 +189,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:
|
||||
@@ -181,11 +197,12 @@ jobs:
|
||||
files: ${{ env.DESTDIR }}/*
|
||||
|
||||
build-deb:
|
||||
runs-on: ubuntu-22.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
|
||||
-
|
||||
|
||||
+12
-21
@@ -1,32 +1,23 @@
|
||||
version: "2"
|
||||
run:
|
||||
timeout: 10m
|
||||
modules-download-mode: vendor
|
||||
|
||||
linters:
|
||||
default: none
|
||||
enable:
|
||||
- gofmt
|
||||
- govet
|
||||
- depguard
|
||||
- goimports
|
||||
- ineffassign
|
||||
- misspell
|
||||
- unused
|
||||
- revive
|
||||
- staticcheck
|
||||
- typecheck
|
||||
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
|
||||
|
||||
+21
-40
@@ -1,10 +1,11 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.20.6
|
||||
ARG XX_VERSION=1.2.1
|
||||
ARG OSXCROSS_VERSION=11.3-r7-debian
|
||||
ARG GOLANGCI_LINT_VERSION=v1.51.1
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG GO_VERSION=1.25.8
|
||||
ARG DEBIAN_VERSION=bookworm
|
||||
|
||||
ARG XX_VERSION=1.7.0
|
||||
ARG OSXCROSS_VERSION=11.3-r8-debian
|
||||
ARG GOLANGCI_LINT_VERSION=v2.8
|
||||
|
||||
ARG PACKAGE=github.com/docker/docker-credential-helpers
|
||||
|
||||
@@ -14,9 +15,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}-${DEBIAN_VERSION} 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"
|
||||
@@ -54,8 +54,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 \
|
||||
@@ -63,11 +62,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 \
|
||||
@@ -97,21 +94,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 \
|
||||
@@ -122,28 +105,26 @@ RUN --mount=type=bind,target=. \
|
||||
set -ex
|
||||
export MACOSX_VERSION_MIN=$(make print-MACOSX_DEPLOYMENT_TARGET)
|
||||
xx-go --wrap
|
||||
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
|
||||
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
|
||||
;;
|
||||
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 build-$TARGETOS AS build
|
||||
|
||||
FROM scratch AS binaries
|
||||
COPY --from=build /out /
|
||||
|
||||
|
||||
@@ -12,7 +12,22 @@ 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 = -mmacosx-version-min=$(MACOSX_DEPLOYMENT_TARGET)
|
||||
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
|
||||
|
||||
@@ -34,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/
|
||||
|
||||
+5
-12
@@ -16,11 +16,9 @@ func isValidCredsMessage(msg string) error {
|
||||
if credentials.IsCredentialsMissingServerURLMessage(msg) {
|
||||
return credentials.NewErrCredentialsMissingServerURL()
|
||||
}
|
||||
|
||||
if credentials.IsCredentialsMissingUsernameMessage(msg) {
|
||||
return credentials.NewErrCredentialsMissingUsername()
|
||||
}
|
||||
|
||||
return 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
|
||||
@@ -55,17 +50,15 @@ func Get(program ProgramFunc, serverURL string) (*credentials.Credentials, error
|
||||
|
||||
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{
|
||||
|
||||
+16
-13
@@ -15,27 +15,30 @@ 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...)
|
||||
// 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.Environ(), 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.
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package credentials
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// ErrCredentialsNotFound standardizes the not found error, so every helper returns
|
||||
@@ -47,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
|
||||
@@ -104,7 +107,7 @@ func IsCredentialsMissingServerURL(err error) bool {
|
||||
// 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
|
||||
@@ -117,5 +120,5 @@ func IsCredentialsMissingUsername(err error) bool {
|
||||
// IsCredentialsMissingUsernameMessage checks for an
|
||||
// errCredentialsMissingUsername in the error message.
|
||||
func IsCredentialsMissingUsernameMessage(err string) bool {
|
||||
return err == errCredentialsMissingUsernameMessage
|
||||
return strings.TrimSpace(err) == errCredentialsMissingUsernameMessage
|
||||
}
|
||||
|
||||
+3
-5
@@ -1,14 +1,12 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.20.6
|
||||
ARG GO_VERSION=1.25.8
|
||||
ARG DISTRO=ubuntu
|
||||
ARG SUITE=focal
|
||||
ARG SUITE=jammy
|
||||
|
||||
FROM golang:${GO_VERSION}-bullseye AS golang
|
||||
FROM golang:${GO_VERSION}-bookworm AS golang
|
||||
|
||||
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
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
variable "GO_VERSION" {
|
||||
default = "1.20.6"
|
||||
default = null
|
||||
}
|
||||
|
||||
# Defines the output folder
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
module github.com/docker/docker-credential-helpers
|
||||
|
||||
go 1.19
|
||||
go 1.21
|
||||
|
||||
require github.com/danieljoos/wincred v1.2.0
|
||||
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 golang.org/x/sys v0.8.0 // indirect
|
||||
require (
|
||||
github.com/danieljoos/wincred v1.2.3
|
||||
github.com/keybase/go-keychain v0.0.1
|
||||
)
|
||||
|
||||
require golang.org/x/sys v0.20.0 // indirect
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE=
|
||||
github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec=
|
||||
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/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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,227 +0,0 @@
|
||||
#include "osxkeychain.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]);
|
||||
}
|
||||
}
|
||||
+94
-101
@@ -3,30 +3,33 @@
|
||||
package osxkeychain
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Security -framework Foundation
|
||||
#cgo LDFLAGS: -framework Security -framework CoreFoundation
|
||||
|
||||
#include "osxkeychain.h"
|
||||
#include <stdlib.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <Security/Security.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"unsafe"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
"github.com/docker/docker-credential-helpers/registryurl"
|
||||
"github.com/keybase/go-keychain"
|
||||
)
|
||||
|
||||
// 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."
|
||||
// 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`)
|
||||
@@ -38,152 +41,142 @@ type Osxkeychain struct{}
|
||||
func (h Osxkeychain) Add(creds *credentials.Credentials) error {
|
||||
_ = h.Delete(creds.ServerURL) // ignore errors as existing credential may not exist.
|
||||
|
||||
s, err := splitServer(creds.ServerURL)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
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
|
||||
return keychain.AddItem(item)
|
||||
}
|
||||
|
||||
// Delete removes credentials from the keychain.
|
||||
func (h Osxkeychain) Delete(serverURL string) error {
|
||||
s, err := splitServer(serverURL)
|
||||
if err != nil {
|
||||
item := keychain.NewItem()
|
||||
item.SetSecClass(keychain.SecClassInternetPassword)
|
||||
if err := splitServer(serverURL, item); err != nil {
|
||||
return err
|
||||
}
|
||||
defer freeServer(s)
|
||||
|
||||
if errMsg := C.keychain_delete(s); errMsg != nil {
|
||||
defer C.free(unsafe.Pointer(errMsg))
|
||||
switch goMsg := C.GoString(errMsg); goMsg {
|
||||
if err := keychain.DeleteItem(item); err != nil {
|
||||
switch err.Error() {
|
||||
case errCredentialsNotFound:
|
||||
return credentials.NewErrCredentialsNotFound()
|
||||
case errInteractionNotAllowed:
|
||||
return ErrInteractionNotAllowed
|
||||
default:
|
||||
return errors.New(goMsg)
|
||||
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) {
|
||||
s, err := splitServer(serverURL)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
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))
|
||||
switch goMsg := C.GoString(errMsg); goMsg {
|
||||
res, err := keychain.QueryItem(item)
|
||||
if err != nil {
|
||||
switch err.Error() {
|
||||
case errCredentialsNotFound:
|
||||
return "", "", credentials.NewErrCredentialsNotFound()
|
||||
case errInteractionNotAllowed:
|
||||
return "", "", ErrInteractionNotAllowed
|
||||
default:
|
||||
return "", "", errors.New(goMsg)
|
||||
return "", "", err
|
||||
}
|
||||
} else if len(res) == 0 {
|
||||
return "", "", credentials.NewErrCredentialsNotFound()
|
||||
}
|
||||
|
||||
user := C.GoStringN(username, C.int(usernameLen))
|
||||
pass := C.GoStringN(secret, C.int(secretLen))
|
||||
return user, pass, nil
|
||||
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) {
|
||||
credsLabelC := C.CString(credentials.CredsLabel)
|
||||
defer C.free(unsafe.Pointer(credsLabelC))
|
||||
item := keychain.NewItem()
|
||||
item.SetSecClass(keychain.SecClassInternetPassword)
|
||||
item.SetMatchLimit(keychain.MatchLimitAll)
|
||||
item.SetReturnAttributes(true)
|
||||
item.SetLabel(credentials.CredsLabel)
|
||||
|
||||
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))
|
||||
switch goMsg := C.GoString(errMsg); goMsg {
|
||||
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, errors.New(goMsg)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
for _, r := range res {
|
||||
proto := "http"
|
||||
if r.Protocol == kSecProtocolTypeHTTPS {
|
||||
proto = "https"
|
||||
}
|
||||
resp[C.GoString(pathTmp[i])] = C.GoString(acctTmp[i])
|
||||
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
|
||||
}
|
||||
|
||||
func splitServer(serverURL string) (*C.struct_Server, error) {
|
||||
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 nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
proto := C.kSecProtocolTypeHTTPS
|
||||
item.SetProtocol(kSecProtocolTypeHTTPS)
|
||||
if u.Scheme == "http" {
|
||||
proto = C.kSecProtocolTypeHTTP
|
||||
item.SetProtocol(kSecProtocolTypeHTTP)
|
||||
}
|
||||
var port int
|
||||
p := u.Port()
|
||||
if p != "" {
|
||||
port, err = strconv.Atoi(p)
|
||||
item.SetServer(u.Hostname())
|
||||
if p := u.Port(); p != "" {
|
||||
port, err := strconv.Atoi(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
item.SetPort(int32(port))
|
||||
}
|
||||
|
||||
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))
|
||||
item.SetPath(u.Path)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -15,11 +15,6 @@ func TestOSXKeychainHelper(t *testing.T) {
|
||||
Username: "foobar",
|
||||
Secret: "foobarbaz",
|
||||
}
|
||||
creds1 := &credentials.Credentials{
|
||||
ServerURL: "https://foobar.example.com:2376/v2",
|
||||
Username: "foobarbaz",
|
||||
Secret: "foobar",
|
||||
}
|
||||
helper := Osxkeychain{}
|
||||
if err := helper.Add(creds); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -43,19 +38,49 @@ func TestOSXKeychainHelper(t *testing.T) {
|
||||
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 _, 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
|
||||
@@ -107,7 +132,7 @@ func TestOSXKeychainHelperRetrieveAliases(t *testing.T) {
|
||||
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)
|
||||
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)
|
||||
@@ -116,6 +141,39 @@ func TestOSXKeychainHelperRetrieveAliases(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
+23
-17
@@ -87,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
|
||||
}
|
||||
@@ -99,7 +98,7 @@ 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
|
||||
}
|
||||
@@ -142,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")
|
||||
@@ -180,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
|
||||
}
|
||||
@@ -191,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
|
||||
}
|
||||
|
||||
+73
-2
@@ -3,6 +3,8 @@
|
||||
package pass
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -81,10 +83,10 @@ func TestPassHelperList(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
for server, username := range credsList {
|
||||
if !(strings.HasSuffix(server, "2376/v1") || strings.HasSuffix(server, "2375/v1")) {
|
||||
if !strings.HasSuffix(server, "2376/v1") && !strings.HasSuffix(server, "2375/v1") {
|
||||
t.Errorf("invalid url: %s", server)
|
||||
}
|
||||
if !(username == "foo" || username == "bar") {
|
||||
if username != "foo" && username != "bar" {
|
||||
t.Errorf("invalid username: %v", username)
|
||||
}
|
||||
|
||||
@@ -116,6 +118,75 @@ func TestPassHelperList(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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{}
|
||||
if _, _, err := helper.Get("garbage"); !credentials.IsErrCredentialsNotFound(err) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -33,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);
|
||||
|
||||
+7
-13
@@ -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)
|
||||
|
||||
+7
-3
@@ -5,6 +5,7 @@ package wincred
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
@@ -33,10 +34,10 @@ type sysCREDENTIAL struct {
|
||||
Comment *uint16
|
||||
LastWritten windows.Filetime
|
||||
CredentialBlobSize uint32
|
||||
CredentialBlob uintptr
|
||||
CredentialBlob *byte
|
||||
Persist uint32
|
||||
AttributeCount uint32
|
||||
Attributes uintptr
|
||||
Attributes *sysCREDENTIAL_ATTRIBUTE
|
||||
TargetAlias *uint16
|
||||
UserName *uint16
|
||||
}
|
||||
@@ -46,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
|
||||
@@ -63,6 +64,7 @@ const (
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/Debug/system-error-codes
|
||||
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
|
||||
@@ -92,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
|
||||
}
|
||||
|
||||
+2
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,13 @@
|
||||
linters-settings:
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- ifElseChain
|
||||
- elseif
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- gofmt
|
||||
- gocritic
|
||||
- unconvert
|
||||
- revive
|
||||
- govet
|
||||
+22
@@ -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
@@ -0,0 +1,126 @@
|
||||
# Go Keychain
|
||||
|
||||
[](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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
-30
@@ -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
@@ -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
@@ -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
@@ -37,14 +37,17 @@ func (token Token) Environ(inheritExisting bool) (env []string, err error) {
|
||||
return nil, err
|
||||
}
|
||||
defer DestroyEnvironmentBlock(block)
|
||||
blockp := unsafe.Pointer(block)
|
||||
for {
|
||||
entry := UTF16PtrToString((*uint16)(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 = unsafe.Add(blockp, 2*(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
@@ -3,7 +3,6 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
|
||||
+77
-12
@@ -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,20 +82,68 @@ 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 += " "
|
||||
if len(args) == 0 {
|
||||
return ""
|
||||
}
|
||||
commandLine += EscapeArg(args[i])
|
||||
|
||||
// Per https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-commandlinetoargvw:
|
||||
// “This function accepts command lines that contain a program name; the
|
||||
// program name can be enclosed in quotation marks or not.”
|
||||
//
|
||||
// Unfortunately, it provides no means of escaping interior quotation marks
|
||||
// within that program name, and we have no way to report them here.
|
||||
prog := args[0]
|
||||
mustQuote := len(prog) == 0
|
||||
for i := 0; i < len(prog); i++ {
|
||||
c := prog[i]
|
||||
if c <= ' ' || (c == '"' && i == 0) {
|
||||
// Force quotes for not only the ASCII space and tab as described in the
|
||||
// MSDN article, but also ASCII control characters.
|
||||
// The documentation for CommandLineToArgvW doesn't say what happens when
|
||||
// the first argument is not a valid program name, but it empirically
|
||||
// seems to drop unquoted control characters.
|
||||
mustQuote = true
|
||||
break
|
||||
}
|
||||
return commandLine
|
||||
}
|
||||
var commandLine []byte
|
||||
if mustQuote {
|
||||
commandLine = make([]byte, 0, len(prog)+2)
|
||||
commandLine = append(commandLine, '"')
|
||||
for i := 0; i < len(prog); i++ {
|
||||
c := prog[i]
|
||||
if c == '"' {
|
||||
// This quote would interfere with our surrounding quotes.
|
||||
// We have no way to report an error, so just strip out
|
||||
// the offending character instead.
|
||||
continue
|
||||
}
|
||||
commandLine = append(commandLine, c)
|
||||
}
|
||||
commandLine = append(commandLine, '"')
|
||||
} else {
|
||||
if len(args) == 1 {
|
||||
// args[0] is a valid command line representing itself.
|
||||
// No need to allocate a new slice or string for it.
|
||||
return prog
|
||||
}
|
||||
commandLine = []byte(prog)
|
||||
}
|
||||
|
||||
for _, arg := range args[1:] {
|
||||
commandLine = append(commandLine, ' ')
|
||||
// TODO(bcmills): since we're already appending to a slice, it would be nice
|
||||
// to avoid the intermediate allocations of EscapeArg.
|
||||
// Perhaps we can factor out an appendEscapedArg function.
|
||||
commandLine = append(commandLine, EscapeArg(arg)...)
|
||||
}
|
||||
return string(commandLine)
|
||||
}
|
||||
|
||||
// DecomposeCommandLine breaks apart its argument command line into unescaped parts using CommandLineToArgv,
|
||||
// as gathered from GetCommandLine, QUERY_SERVICE_CONFIG's BinaryPathName argument, or elsewhere that
|
||||
// command lines are passed around.
|
||||
// DecomposeCommandLine returns error if commandLine contains NUL.
|
||||
// DecomposeCommandLine returns an error if commandLine contains NUL.
|
||||
func DecomposeCommandLine(commandLine string) ([]string, error) {
|
||||
if len(commandLine) == 0 {
|
||||
return []string{}, nil
|
||||
@@ -105,18 +153,35 @@ func DecomposeCommandLine(commandLine string) ([]string, error) {
|
||||
return nil, errorspkg.New("string with NUL passed to DecomposeCommandLine")
|
||||
}
|
||||
var argc int32
|
||||
argv, err := CommandLineToArgv(&utf16CommandLine[0], &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
@@ -3,7 +3,6 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build generate
|
||||
// +build generate
|
||||
|
||||
package windows
|
||||
|
||||
|
||||
-1
@@ -3,7 +3,6 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows && race
|
||||
// +build windows,race
|
||||
|
||||
package windows
|
||||
|
||||
|
||||
-1
@@ -3,7 +3,6 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows && !race
|
||||
// +build windows,!race
|
||||
|
||||
package windows
|
||||
|
||||
|
||||
+6
-15
@@ -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]))
|
||||
}
|
||||
|
||||
+4
-1
@@ -3,7 +3,6 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
@@ -218,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
|
||||
|
||||
-1
@@ -3,7 +3,6 @@
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package windows
|
||||
|
||||
|
||||
-1
@@ -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
|
||||
|
||||
+136
-26
@@ -15,8 +15,6 @@ import (
|
||||
"time"
|
||||
"unicode/utf16"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/internal/unsafeheader"
|
||||
)
|
||||
|
||||
type Handle uintptr
|
||||
@@ -127,22 +125,21 @@ func UTF16PtrToString(p *uint16) string {
|
||||
for ptr := unsafe.Pointer(p); *(*uint16)(ptr) != 0; n++ {
|
||||
ptr = unsafe.Pointer(uintptr(ptr) + unsafe.Sizeof(*p))
|
||||
}
|
||||
|
||||
return string(utf16.Decode(unsafe.Slice(p, n)))
|
||||
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)
|
||||
}
|
||||
@@ -157,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
|
||||
@@ -166,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
|
||||
@@ -194,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]
|
||||
@@ -216,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]
|
||||
@@ -235,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)
|
||||
@@ -299,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
|
||||
@@ -344,8 +349,19 @@ 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
|
||||
@@ -405,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
|
||||
@@ -437,6 +453,10 @@ func NewCallbackCDecl(fn interface{}) uintptr {
|
||||
//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.
|
||||
@@ -964,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--
|
||||
@@ -1354,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) {
|
||||
@@ -1613,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)
|
||||
}
|
||||
@@ -1647,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 {
|
||||
@@ -1675,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 {
|
||||
@@ -1732,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
|
||||
}
|
||||
|
||||
@@ -1806,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
|
||||
)
|
||||
|
||||
+57
@@ -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 (
|
||||
@@ -1094,6 +1095,32 @@ const (
|
||||
SOMAXCONN = 0x7fffffff
|
||||
|
||||
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
|
||||
@@ -2139,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
|
||||
@@ -3347,3 +3380,27 @@ 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
|
||||
}
|
||||
|
||||
+194
-13
@@ -55,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")
|
||||
@@ -183,10 +184,14 @@ var (
|
||||
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")
|
||||
@@ -201,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")
|
||||
@@ -208,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")
|
||||
@@ -232,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")
|
||||
@@ -250,6 +260,7 @@ 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")
|
||||
@@ -317,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")
|
||||
@@ -326,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")
|
||||
@@ -343,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")
|
||||
@@ -353,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")
|
||||
@@ -371,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")
|
||||
@@ -468,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")
|
||||
@@ -1598,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 {
|
||||
@@ -1622,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 {
|
||||
@@ -1630,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 {
|
||||
@@ -1759,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 {
|
||||
@@ -1813,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 {
|
||||
@@ -1825,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
|
||||
@@ -2026,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 {
|
||||
@@ -2166,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)
|
||||
@@ -2367,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
|
||||
}
|
||||
|
||||
@@ -2773,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)
|
||||
@@ -2854,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 {
|
||||
@@ -2862,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)
|
||||
@@ -2871,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 {
|
||||
@@ -2999,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 {
|
||||
@@ -3084,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)
|
||||
@@ -3230,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 {
|
||||
@@ -3516,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)
|
||||
}
|
||||
@@ -3824,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)
|
||||
}
|
||||
@@ -4021,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 {
|
||||
|
||||
Vendored
+6
-4
@@ -1,7 +1,9 @@
|
||||
# github.com/danieljoos/wincred v1.2.0
|
||||
# github.com/danieljoos/wincred v1.2.3
|
||||
## explicit; go 1.18
|
||||
github.com/danieljoos/wincred
|
||||
# golang.org/x/sys v0.8.0
|
||||
## explicit; go 1.17
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user