Compare commits

...

10 Commits
4.3.3 ... 4.4.1

Author SHA1 Message Date
2294f40ee0 Merge pull request #739 from chrisallenlane/v4.4.1
V4.4.1
2023-12-13 09:17:16 -05:00
fe25019b14 chore: bump version to 4.4.1 2023-12-13 09:12:14 -05:00
bfb071c0b2 chore(lint): various changes to appease revive
- Add `package` comments
- Rename `opts` to `_` where unused
2023-12-13 09:10:20 -05:00
95a4e31b6c chore(deps): upgrade dependencies
Upgrade all dependencies to newest versions.
2023-12-13 08:29:02 -05:00
0d9c92c8c0 Merge pull request #707 from chrisallenlane/v4.4.0
V4.4.0
2022-11-05 13:11:22 -04:00
16c50bb659 chore: bump version to 4.4.0 2022-11-05 12:56:43 -04:00
1a85c9e9c8 feat: platform compatibility
Add experimental support for the following platforms:

- aix
- dragonfly
- illumos
- ios
- netbsd
- openbsd
- plan9
- solaris
2022-11-05 12:00:43 -04:00
c9ccefa607 chore(deps): remove yaml.v1
Remove errant `yaml.v1` dependency, and use `yaml.v2` everywhere.
2022-11-05 11:39:48 -04:00
3a6b6e58f0 chore(deps): update dependencies
`make vendor-update`
2022-11-05 10:15:15 -04:00
2edc0ee299 chore: add a comment
Add a small comment regarding a tricky edge-case in `gitdir.go`.
2022-08-28 06:54:29 -04:00
844 changed files with 55504 additions and 19218 deletions

View File

@ -9,13 +9,13 @@ On Unix-like systems, you may simply paste the following snippet into your termi
```sh ```sh
cd /tmp \ cd /tmp \
&& wget https://github.com/cheat/cheat/releases/download/4.3.3/cheat-linux-amd64.gz \ && wget https://github.com/cheat/cheat/releases/download/4.4.1/cheat-linux-amd64.gz \
&& gunzip cheat-linux-amd64.gz \ && gunzip cheat-linux-amd64.gz \
&& chmod +x cheat-linux-amd64 \ && chmod +x cheat-linux-amd64 \
&& sudo mv cheat-linux-amd64 /usr/local/bin/cheat && sudo mv cheat-linux-amd64 /usr/local/bin/cheat
``` ```
You may need to need to change the version number (`4.3.3`) and the archive You may need to need to change the version number (`4.4.1`) and the archive
(`cheat-linux-amd64.gz`) depending on your platform. (`cheat-linux-amd64.gz`) depending on your platform.
See the [releases page][releases] for a list of supported platforms. See the [releases page][releases] for a list of supported platforms.

View File

@ -35,13 +35,17 @@ releases := \
$(dist_dir)/cheat-linux-amd64 \ $(dist_dir)/cheat-linux-amd64 \
$(dist_dir)/cheat-linux-arm5 \ $(dist_dir)/cheat-linux-arm5 \
$(dist_dir)/cheat-linux-arm6 \ $(dist_dir)/cheat-linux-arm6 \
$(dist_dir)/cheat-linux-arm7 \
$(dist_dir)/cheat-linux-arm64 \ $(dist_dir)/cheat-linux-arm64 \
$(dist_dir)/cheat-linux-arm7 \
$(dist_dir)/cheat-netbsd-amd64 \
$(dist_dir)/cheat-openbsd-amd64 \
$(dist_dir)/cheat-plan9-amd64 \
$(dist_dir)/cheat-solaris-amd64 \
$(dist_dir)/cheat-windows-amd64.exe $(dist_dir)/cheat-windows-amd64.exe
## build: build an executable for your architecture ## build: build an executable for your architecture
.PHONY: build .PHONY: build
build: $(dist_dir) | clean generate fmt lint vet vendor man build: | clean $(dist_dir) generate fmt lint vet vendor man
$(GO) build $(BUILD_FLAGS) -o $(dist_dir)/cheat $(cmd_dir) $(GO) build $(BUILD_FLAGS) -o $(dist_dir)/cheat $(cmd_dir)
## build-release: build release executables ## build-release: build release executables
@ -83,6 +87,26 @@ $(dist_dir)/cheat-linux-arm64: prepare
GOARCH=arm64 GOOS=linux \ GOARCH=arm64 GOOS=linux \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz $(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-netbsd-amd64
$(dist_dir)/cheat-netbsd-amd64: prepare
GOARCH=amd64 GOOS=netbsd \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-openbsd-amd64
$(dist_dir)/cheat-openbsd-amd64: prepare
GOARCH=amd64 GOOS=openbsd \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-plan9-amd64
$(dist_dir)/cheat-plan9-amd64: prepare
GOARCH=amd64 GOOS=plan9 \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-solaris-amd64
$(dist_dir)/cheat-solaris-amd64: prepare
GOARCH=amd64 GOOS=solaris \
$(GO) build $(BUILD_FLAGS) -o $@ $(cmd_dir) && $(GZIP) $@ && chmod -x $@.gz
# cheat-windows-amd64 # cheat-windows-amd64
$(dist_dir)/cheat-windows-amd64.exe: prepare $(dist_dir)/cheat-windows-amd64.exe: prepare
GOARCH=amd64 GOOS=windows \ GOARCH=amd64 GOOS=windows \
@ -103,7 +127,7 @@ install: build
## clean: remove compiled executables ## clean: remove compiled executables
.PHONY: clean .PHONY: clean
clean: $(dist_dir) clean:
$(RM) -f $(dist_dir)/* $(cmd_dir)/str_config.go $(cmd_dir)/str_usage.go $(RM) -f $(dist_dir)/* $(cmd_dir)/str_config.go $(cmd_dir)/str_usage.go
## distclean: remove the tags file ## distclean: remove the tags file
@ -140,7 +164,7 @@ vendor:
## vendor-update: update vendored dependencies ## vendor-update: update vendored dependencies
vendor-update: vendor-update:
$(GO) get -t -u ./... && $(GO) mod vendor $(GO) get -t -u ./... && $(GO) mod vendor && $(GO) mod tidy && $(GO) mod verify
## fmt: run go fmt ## fmt: run go fmt
.PHONY: fmt .PHONY: fmt
@ -173,7 +197,7 @@ coverage:
check: | vendor fmt lint vet test check: | vendor fmt lint vet test
.PHONY: prepare .PHONY: prepare
prepare: | $(dist_dir) clean generate vendor fmt lint vet test prepare: | clean $(dist_dir) generate vendor fmt lint vet test
## docker-setup: create a docker image for use during development ## docker-setup: create a docker image for use during development
.PHONY: docker-setup .PHONY: docker-setup

View File

@ -6,6 +6,6 @@ import (
"github.com/cheat/cheat/internal/config" "github.com/cheat/cheat/internal/config"
) )
func cmdConf(opts map[string]interface{}, conf config.Config) { func cmdConf(_ map[string]interface{}, conf config.Config) {
fmt.Println(conf.Path) fmt.Println(conf.Path)
} }

View File

@ -10,7 +10,7 @@ import (
) )
// cmdDirectories lists the configured cheatpaths. // cmdDirectories lists the configured cheatpaths.
func cmdDirectories(opts map[string]interface{}, conf config.Config) { func cmdDirectories(_ map[string]interface{}, conf config.Config) {
// initialize a tabwriter to produce cleanly columnized output // initialize a tabwriter to produce cleanly columnized output
var out bytes.Buffer var out bytes.Buffer

View File

@ -10,7 +10,7 @@ import (
) )
// cmdTags lists all tags in use. // cmdTags lists all tags in use.
func cmdTags(opts map[string]interface{}, conf config.Config) { func cmdTags(_ map[string]interface{}, conf config.Config) {
// load the cheatsheets // load the cheatsheets
cheatsheets, err := sheets.Load(conf.Cheatpaths) cheatsheets, err := sheets.Load(conf.Cheatpaths)

View File

@ -1,3 +1,4 @@
// Package main serves as the executable entrypoint.
package main package main
//go:generate go run ../../build/embed.go //go:generate go run ../../build/embed.go
@ -16,7 +17,7 @@ import (
"github.com/cheat/cheat/internal/installer" "github.com/cheat/cheat/internal/installer"
) )
const version = "4.3.3" const version = "4.4.1"
func main() { func main() {

View File

@ -1,182 +1,150 @@
.\" Automatically generated by Pandoc 2.2.1 .\" Automatically generated by Pandoc 2.17.1.1
.\" .\"
.\" Define V font for inline verbatim, using C font in formats
.\" that render this, and otherwise B font.
.ie "\f[CB]x\f[]"x" \{\
. ftr V B
. ftr VI BI
. ftr VB B
. ftr VBI BI
.\}
.el \{\
. ftr V CR
. ftr VI CI
. ftr VB CB
. ftr VBI CBI
.\}
.TH "CHEAT" "1" "" "" "General Commands Manual" .TH "CHEAT" "1" "" "" "General Commands Manual"
.hy .hy
.SH NAME .SH NAME
.PP .PP
\f[B]cheat\f[] \[em] create and view command\-line cheatsheets \f[B]cheat\f[R] \[em] create and view command-line cheatsheets
.SH SYNOPSIS .SH SYNOPSIS
.PP .PP
\f[B]cheat\f[] [options] [\f[I]CHEATSHEET\f[]] \f[B]cheat\f[R] [options] [\f[I]CHEATSHEET\f[R]]
.SH DESCRIPTION .SH DESCRIPTION
.PP .PP
\f[B]cheat\f[] allows you to create and view interactive cheatsheets on \f[B]cheat\f[R] allows you to create and view interactive cheatsheets on
the command\-line. the command-line.
It was designed to help remind *nix system administrators of options for It was designed to help remind *nix system administrators of options for
commands that they use frequently, but not frequently enough to commands that they use frequently, but not frequently enough to
remember. remember.
.SH OPTIONS .SH OPTIONS
.TP .TP
.B \[en]init \[en]init
Print a config file to stdout. Print a config file to stdout.
.RS
.RE
.TP .TP
.B \-c, \[en]colorize -c, \[en]colorize
Colorize output. Colorize output.
.RS
.RE
.TP .TP
.B \-d, \[en]directories -d, \[en]directories
List cheatsheet directories. List cheatsheet directories.
.RS
.RE
.TP .TP
.B \-e, \[en]edit=\f[I]CHEATSHEET\f[] -e, \[en]edit=\f[I]CHEATSHEET\f[R]
Open \f[I]CHEATSHEET\f[] for editing. Open \f[I]CHEATSHEET\f[R] for editing.
.RS
.RE
.TP .TP
.B \-l, \[en]list -l, \[en]list
List available cheatsheets. List available cheatsheets.
.RS
.RE
.TP .TP
.B \-p, \[en]path=\f[I]PATH\f[] -p, \[en]path=\f[I]PATH\f[R]
Filter only to sheets found on path \f[I]PATH\f[]. Filter only to sheets found on path \f[I]PATH\f[R].
.RS
.RE
.TP .TP
.B \-r, \[en]regex -r, \[en]regex
Treat search \f[I]PHRASE\f[] as a regular expression. Treat search \f[I]PHRASE\f[R] as a regular expression.
.RS
.RE
.TP .TP
.B \-s, \[en]search=\f[I]PHRASE\f[] -s, \[en]search=\f[I]PHRASE\f[R]
Search cheatsheets for \f[I]PHRASE\f[]. Search cheatsheets for \f[I]PHRASE\f[R].
.RS
.RE
.TP .TP
.B \-t, \[en]tag=\f[I]TAG\f[] -t, \[en]tag=\f[I]TAG\f[R]
Filter only to sheets tagged with \f[I]TAG\f[]. Filter only to sheets tagged with \f[I]TAG\f[R].
.RS
.RE
.TP .TP
.B \-T, \[en]tags -T, \[en]tags
List all tags in use. List all tags in use.
.RS
.RE
.TP .TP
.B \-v, \[en]version -v, \[en]version
Print the version number. Print the version number.
.RS
.RE
.TP .TP
.B \[en]rm=\f[I]CHEATSHEET\f[] \[en]rm=\f[I]CHEATSHEET\f[R]
Remove (deletes) \f[I]CHEATSHEET\f[]. Remove (deletes) \f[I]CHEATSHEET\f[R].
.RS
.RE
.SH EXAMPLES .SH EXAMPLES
.TP .TP
.B To view the foo cheatsheet: To view the foo cheatsheet:
cheat \f[I]foo\f[] cheat \f[I]foo\f[R]
.RS
.RE
.TP .TP
.B To edit (or create) the foo cheatsheet: To edit (or create) the foo cheatsheet:
cheat \-e \f[I]foo\f[] cheat -e \f[I]foo\f[R]
.RS
.RE
.TP .TP
.B To edit (or create) the foo/bar cheatsheet on the `work' cheatpath: To edit (or create) the foo/bar cheatsheet on the `work' cheatpath:
cheat \-p \f[I]work\f[] \-e \f[I]foo/bar\f[] cheat -p \f[I]work\f[R] -e \f[I]foo/bar\f[R]
.RS
.RE
.TP .TP
.B To view all cheatsheet directories: To view all cheatsheet directories:
cheat \-d cheat -d
.RS
.RE
.TP .TP
.B To list all available cheatsheets: To list all available cheatsheets:
cheat \-l cheat -l
.RS
.RE
.TP .TP
.B To list all cheatsheets whose titles match `apt': To list all cheatsheets whose titles match `apt':
cheat \-l \f[I]apt\f[] cheat -l \f[I]apt\f[R]
.RS
.RE
.TP .TP
.B To list all tags in use: To list all tags in use:
cheat \-T cheat -T
.RS
.RE
.TP .TP
.B To list available cheatsheets that are tagged as `personal': To list available cheatsheets that are tagged as `personal':
cheat \-l \-t \f[I]personal\f[] cheat -l -t \f[I]personal\f[R]
.RS
.RE
.TP .TP
.B To search for `ssh' among all cheatsheets, and colorize matches: To search for `ssh' among all cheatsheets, and colorize matches:
cheat \-c \-s \f[I]ssh\f[] cheat -c -s \f[I]ssh\f[R]
.RS
.RE
.TP .TP
.B To search (by regex) for cheatsheets that contain an IP address: To search (by regex) for cheatsheets that contain an IP address:
cheat \-c \-r \-s \f[I]`(?:[0\-9]{1,3}.){3}[0\-9]{1,3}'\f[] cheat -c -r -s \f[I]`(?:[0-9]{1,3}.){3}[0-9]{1,3}'\f[R]
.RS
.RE
.TP .TP
.B To remove (delete) the foo/bar cheatsheet: To remove (delete) the foo/bar cheatsheet:
cheat \[en]rm \f[I]foo/bar\f[] cheat \[en]rm \f[I]foo/bar\f[R]
.RS
.RE
.SH FILES .SH FILES
.SS Configuration .SS Configuration
.PP .PP
\f[B]cheat\f[] is configured via a YAML file that is conventionally \f[B]cheat\f[R] is configured via a YAML file that is conventionally
named \f[I]conf.yaml\f[]. named \f[I]conf.yaml\f[R].
\f[B]cheat\f[] will search for \f[I]conf.yaml\f[] in varying locations, \f[B]cheat\f[R] will search for \f[I]conf.yaml\f[R] in varying
depending upon your platform: locations, depending upon your platform:
.SS Linux, OSX, and other Unixes .SS Linux, OSX, and other Unixes
.IP "1." 3 .IP "1." 3
\f[B]CHEAT_CONFIG_PATH\f[] \f[B]CHEAT_CONFIG_PATH\f[R]
.IP "2." 3 .IP "2." 3
\f[B]XDG_CONFIG_HOME\f[]/cheat/conf.yaml \f[B]XDG_CONFIG_HOME\f[R]/cheat/conf.yaml
.IP "3." 3 .IP "3." 3
\f[B]$HOME\f[]/.config/cheat/conf.yml \f[B]$HOME\f[R]/.config/cheat/conf.yml
.IP "4." 3 .IP "4." 3
\f[B]$HOME\f[]/.cheat/conf.yml \f[B]$HOME\f[R]/.cheat/conf.yml
.SS Windows .SS Windows
.IP "1." 3 .IP "1." 3
\f[B]CHEAT_CONFIG_PATH\f[] \f[B]CHEAT_CONFIG_PATH\f[R]
.IP "2." 3 .IP "2." 3
\f[B]APPDATA\f[]/cheat/conf.yml \f[B]APPDATA\f[R]/cheat/conf.yml
.IP "3." 3 .IP "3." 3
\f[B]PROGRAMDATA\f[]/cheat/conf.yml \f[B]PROGRAMDATA\f[R]/cheat/conf.yml
.PP .PP
\f[B]cheat\f[] will search in the order specified above. \f[B]cheat\f[R] will search in the order specified above.
The first \f[I]conf.yaml\f[] encountered will be respected. The first \f[I]conf.yaml\f[R] encountered will be respected.
.PP .PP
If \f[B]cheat\f[] cannot locate a config file, it will ask if you'd like If \f[B]cheat\f[R] cannot locate a config file, it will ask if you\[cq]d
to generate one automatically. like to generate one automatically.
Alternatively, you may also generate a config file manually by running Alternatively, you may also generate a config file manually by running
\f[B]cheat \[en]init\f[] and saving its output to the appropriate \f[B]cheat \[en]init\f[R] and saving its output to the appropriate
location for your platform. location for your platform.
.SS Cheatpaths .SS Cheatpaths
.PP .PP
\f[B]cheat\f[] reads its cheatsheets from \[lq]cheatpaths\[rq], which \f[B]cheat\f[R] reads its cheatsheets from \[lq]cheatpaths\[rq], which
are the directories in which cheatsheets are stored. are the directories in which cheatsheets are stored.
Cheatpaths may be configured in \f[I]conf.yaml\f[], and viewed via Cheatpaths may be configured in \f[I]conf.yaml\f[R], and viewed via
\f[B]cheat \-d\f[]. \f[B]cheat -d\f[R].
.PP .PP
For detailed instructions on how to configure cheatpaths, please refer For detailed instructions on how to configure cheatpaths, please refer
to the comments in conf.yml. to the comments in conf.yml.
.SS Autocompletion .SS Autocompletion
.PP .PP
Autocompletion scripts for \f[B]bash\f[], \f[B]zsh\f[], and Autocompletion scripts for \f[B]bash\f[R], \f[B]zsh\f[R], and
\f[B]fish\f[] are available for download: \f[B]fish\f[R] are available for download:
.IP \[bu] 2 .IP \[bu] 2
<https://github.com/cheat/cheat/blob/master/scripts/cheat.bash> <https://github.com/cheat/cheat/blob/master/scripts/cheat.bash>
.IP \[bu] 2 .IP \[bu] 2
@ -184,25 +152,22 @@ Autocompletion scripts for \f[B]bash\f[], \f[B]zsh\f[], and
.IP \[bu] 2 .IP \[bu] 2
<https://github.com/cheat/cheat/blob/master/scripts/cheat.zsh> <https://github.com/cheat/cheat/blob/master/scripts/cheat.zsh>
.PP .PP
The \f[B]bash\f[] and \f[B]zsh\f[] scripts provide optional integration The \f[B]bash\f[R] and \f[B]zsh\f[R] scripts provide optional
with \f[B]fzf\f[], if the latter is available on your \f[B]PATH\f[]. integration with \f[B]fzf\f[R], if the latter is available on your
\f[B]PATH\f[R].
.PP .PP
The installation process will vary per system and shell configuration, The installation process will vary per system and shell configuration,
and thus will not be discussed here. and thus will not be discussed here.
.SH ENVIRONMENT .SH ENVIRONMENT
.TP .TP
.B \f[B]CHEAT_CONFIG_PATH\f[] \f[B]CHEAT_CONFIG_PATH\f[R]
The path at which the config file is available. The path at which the config file is available.
If \f[B]CHEAT_CONFIG_PATH\f[] is set, all other config paths will be If \f[B]CHEAT_CONFIG_PATH\f[R] is set, all other config paths will be
ignored. ignored.
.RS
.RE
.TP .TP
.B \f[B]CHEAT_USE_FZF\f[] \f[B]CHEAT_USE_FZF\f[R]
If set, autocompletion scripts will attempt to integrate with If set, autocompletion scripts will attempt to integrate with
\f[B]fzf\f[]. \f[B]fzf\f[R].
.RS
.RE
.SH RETURN VALUES .SH RETURN VALUES
.IP "0." 3 .IP "0." 3
Successful termination Successful termination
@ -218,4 +183,4 @@ See GitHub issues: <https://github.com/cheat/cheat/issues>
Christopher Allen Lane <chris@chris-allen-lane.com> Christopher Allen Lane <chris@chris-allen-lane.com>
.SH SEE ALSO .SH SEE ALSO
.PP .PP
\f[B]fzf(1)\f[] \f[B]fzf(1)\f[R]

36
go.mod
View File

@ -6,29 +6,33 @@ require (
github.com/alecthomas/chroma v0.10.0 github.com/alecthomas/chroma v0.10.0
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
github.com/go-git/go-git/v5 v5.4.2 github.com/go-git/go-git/v5 v5.11.0
github.com/mattn/go-isatty v0.0.16 github.com/mattn/go-isatty v0.0.20
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
) )
require ( require (
github.com/Microsoft/go-winio v0.5.2 // indirect dario.cat/mergo v1.0.0 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect
github.com/cloudflare/circl v1.2.0 // indirect github.com/cloudflare/circl v1.3.6 // indirect
github.com/dlclark/regexp2 v1.7.0 // indirect github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg v1.5.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.3.1 // indirect github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/imdario/mergo v0.3.13 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/xanzy/ssh-agent v0.3.2 // indirect github.com/sergi/go-diff v1.3.1 // indirect
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d // indirect github.com/skeema/knownhosts v1.2.1 // indirect
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 // indirect golang.org/x/crypto v0.16.0 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/sys v0.15.0 // indirect
golang.org/x/tools v0.16.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect
) )

179
go.sum
View File

@ -1,143 +1,144 @@
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 h1:NsReiLpErIPzRrnogAXYwSoU7txA977LjDGrbkewJbg= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE=
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg=
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.2.0 h1:NheeISPSUcYftKlfrLuOo4T62FkmD4t4jviLfFFYaec= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cloudflare/circl v1.2.0/go.mod h1:Ch2UgYr6ti2KTtlejELlROl0YIYj7SLjAC8M+INXlMk= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4=
github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ=
github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/xanzy/ssh-agent v0.3.2 h1:eKj4SX2Fe7mui28ZgnFW5fmTz1EIr7ugo5s6wDxdHBM= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d h1:3qF+Z8Hkrw9sOhrFHti9TlB1Hkac1x+DNRkv0XQiFjo= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64 h1:UiNENfZ8gDvpiWw7IpOMQ27spWmThO1RwwdQVbJahJM= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,3 +1,5 @@
// Package cheatpath implements functions pertaining to cheatsheet file path
// management.
package cheatpath package cheatpath
// Cheatpath encapsulates cheatsheet path information // Cheatpath encapsulates cheatsheet path information

View File

@ -1,3 +1,4 @@
// Package config implements functions pertaining to configuration management.
package config package config
import ( import (
@ -24,7 +25,7 @@ type Config struct {
} }
// New returns a new Config struct // New returns a new Config struct
func New(opts map[string]interface{}, confPath string, resolve bool) (Config, error) { func New(_ map[string]interface{}, confPath string, resolve bool) (Config, error) {
// read the config file // read the config file
buf, err := os.ReadFile(confPath) buf, err := os.ReadFile(confPath)

View File

@ -28,7 +28,10 @@ func Paths(
} }
switch sys { switch sys {
case "android", "darwin", "linux", "freebsd":
// darwin/linux/unix
case "aix", "android", "darwin", "dragonfly", "freebsd", "illumos", "ios",
"linux", "netbsd", "openbsd", "plan9", "solaris":
paths := []string{} paths := []string{}
// don't include the `XDG_CONFIG_HOME` path if that envvar is not set // don't include the `XDG_CONFIG_HOME` path if that envvar is not set
@ -43,6 +46,8 @@ func Paths(
}...) }...)
return paths, nil return paths, nil
// windows
case "windows": case "windows":
return []string{ return []string{
filepath.Join(envvars["APPDATA"], "cheat", "conf.yml"), filepath.Join(envvars["APPDATA"], "cheat", "conf.yml"),

View File

@ -1,3 +1,5 @@
// Package display implement functions pertaining to writing formatted
// cheatsheet content to stdout, or alternatively the system pager.
package display package display
import ( import (
@ -6,7 +8,8 @@ import (
"github.com/cheat/cheat/internal/config" "github.com/cheat/cheat/internal/config"
) )
// Faint returns an faint string // Faint returns a faintly-colored string that's used to de-prioritize text
// written to stdout
func Faint(str string, conf config.Config) string { func Faint(str string, conf config.Config) string {
// make `str` faint only if colorization has been requested // make `str` faint only if colorization has been requested
if conf.Colorize { if conf.Colorize {

View File

@ -1,3 +1,5 @@
// Package installer implements functions that provide a first-time
// installation wizard.
package installer package installer
import ( import (

View File

@ -1,3 +1,4 @@
// Package mock implements mock functions used in unit-tests.
package mock package mock
import ( import (

View File

@ -1,3 +1,4 @@
// Package repo implements functions pertaining to the management of git repos.
package repo package repo
import ( import (

View File

@ -60,6 +60,7 @@ func GitDir(path string) (bool, error) {
2. A cheatpath is a repository 2. A cheatpath is a repository
3. A cheatpath is a repository, and contains a `.git*` file 3. A cheatpath is a repository, and contains a `.git*` file
4. A cheatpath is a submodule 4. A cheatpath is a submodule
5. A cheatpath is a hidden directory
Care must be taken to support the above on both Unix and Windows Care must be taken to support the above on both Unix and Windows
systems, which have different directory separators and line-endings. systems, which have different directory separators and line-endings.

View File

@ -5,7 +5,7 @@ import (
"runtime" "runtime"
"strings" "strings"
"gopkg.in/yaml.v1" "gopkg.in/yaml.v2"
) )
// Parse parses cheatsheet frontmatter // Parse parses cheatsheet frontmatter

View File

@ -1,3 +1,5 @@
// Package sheet implements functions pertaining to parsing, searching, and
// displaying cheatsheets.
package sheet package sheet
import ( import (

View File

@ -1,3 +1,5 @@
// Package sheets implements functions pertaining to loading, sorting,
// filtering, and tagging cheatsheets.
package sheets package sheets
import ( import (

View File

@ -9,4 +9,4 @@ name = "go"
enabled = true enabled = true
[analyzers.meta] [analyzers.meta]
import_path = "github.com/imdario/mergo" import_path = "dario.cat/mergo"

112
vendor/dario.cat/mergo/CONTRIBUTING.md vendored Normal file
View File

@ -0,0 +1,112 @@
<!-- omit in toc -->
# Contributing to mergo
First off, thanks for taking the time to contribute! ❤️
All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions. 🎉
> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
> - Star the project
> - Tweet about it
> - Refer this project in your project's readme
> - Mention the project at local meetups and tell your friends/colleagues
<!-- omit in toc -->
## Table of Contents
- [Code of Conduct](#code-of-conduct)
- [I Have a Question](#i-have-a-question)
- [I Want To Contribute](#i-want-to-contribute)
- [Reporting Bugs](#reporting-bugs)
- [Suggesting Enhancements](#suggesting-enhancements)
## Code of Conduct
This project and everyone participating in it is governed by the
[mergo Code of Conduct](https://github.com/imdario/mergoblob/master/CODE_OF_CONDUCT.md).
By participating, you are expected to uphold this code. Please report unacceptable behavior
to <>.
## I Have a Question
> If you want to ask a question, we assume that you have read the available [Documentation](https://pkg.go.dev/github.com/imdario/mergo).
Before you ask a question, it is best to search for existing [Issues](https://github.com/imdario/mergo/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first.
If you then still feel the need to ask a question and need clarification, we recommend the following:
- Open an [Issue](https://github.com/imdario/mergo/issues/new).
- Provide as much context as you can about what you're running into.
- Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant.
We will then take care of the issue as soon as possible.
## I Want To Contribute
> ### Legal Notice <!-- omit in toc -->
> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license.
### Reporting Bugs
<!-- omit in toc -->
#### Before Submitting a Bug Report
A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
- Make sure that you are using the latest version.
- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](). If you are looking for support, you might want to check [this section](#i-have-a-question)).
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](https://github.com/imdario/mergoissues?q=label%3Abug).
- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue.
- Collect information about the bug:
- Stack trace (Traceback)
- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
- Possibly your input and the output
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
<!-- omit in toc -->
#### How Do I Submit a Good Bug Report?
> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to .
<!-- You may add a PGP key to allow the messages to be sent encrypted as well. -->
We use GitHub issues to track bugs and errors. If you run into an issue with the project:
- Open an [Issue](https://github.com/imdario/mergo/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
- Explain the behavior you would expect and the actual behavior.
- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
- Provide the information you collected in the previous section.
Once it's filed:
- The project team will label the issue accordingly.
- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced.
- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be implemented by someone.
### Suggesting Enhancements
This section guides you through submitting an enhancement suggestion for mergo, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.
<!-- omit in toc -->
#### Before Submitting an Enhancement
- Make sure that you are using the latest version.
- Read the [documentation]() carefully and find out if the functionality is already covered, maybe by an individual configuration.
- Perform a [search](https://github.com/imdario/mergo/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
<!-- omit in toc -->
#### How Do I Submit a Good Enhancement Suggestion?
Enhancement suggestions are tracked as [GitHub issues](https://github.com/imdario/mergo/issues).
- Use a **clear and descriptive title** for the issue to identify the suggestion.
- Provide a **step-by-step description of the suggested enhancement** in as many details as possible.
- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you.
- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux. <!-- this should only be included if the project has a GUI -->
- **Explain why this enhancement would be useful** to most mergo users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
<!-- omit in toc -->
## Attribution
This guide is based on the **contributing-gen**. [Make your own](https://github.com/bttger/contributing-gen)!

View File

@ -1,17 +1,20 @@
# Mergo # Mergo
[![GoDoc][3]][4]
[![GitHub release][5]][6] [![GitHub release][5]][6]
[![GoCard][7]][8] [![GoCard][7]][8]
[![Build Status][1]][2] [![Test status][1]][2]
[![Coverage Status][9]][10] [![OpenSSF Scorecard][21]][22]
[![OpenSSF Best Practices][19]][20]
[![Coverage status][9]][10]
[![Sourcegraph][11]][12] [![Sourcegraph][11]][12]
[![FOSSA Status][13]][14] [![FOSSA status][13]][14]
[![Become my sponsor][15]][16]
[1]: https://travis-ci.org/imdario/mergo.png [![GoDoc][3]][4]
[2]: https://travis-ci.org/imdario/mergo [![Become my sponsor][15]][16]
[![Tidelift][17]][18]
[1]: https://github.com/imdario/mergo/workflows/tests/badge.svg?branch=master
[2]: https://github.com/imdario/mergo/actions/workflows/tests.yml
[3]: https://godoc.org/github.com/imdario/mergo?status.svg [3]: https://godoc.org/github.com/imdario/mergo?status.svg
[4]: https://godoc.org/github.com/imdario/mergo [4]: https://godoc.org/github.com/imdario/mergo
[5]: https://img.shields.io/github/release/imdario/mergo.svg [5]: https://img.shields.io/github/release/imdario/mergo.svg
@ -26,6 +29,12 @@
[14]: https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_shield [14]: https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_shield
[15]: https://img.shields.io/github/sponsors/imdario [15]: https://img.shields.io/github/sponsors/imdario
[16]: https://github.com/sponsors/imdario [16]: https://github.com/sponsors/imdario
[17]: https://tidelift.com/badges/package/go/github.com%2Fimdario%2Fmergo
[18]: https://tidelift.com/subscription/pkg/go-github.com-imdario-mergo
[19]: https://bestpractices.coreinfrastructure.org/projects/7177/badge
[20]: https://bestpractices.coreinfrastructure.org/projects/7177
[21]: https://api.securityscorecards.dev/projects/github.com/imdario/mergo/badge
[22]: https://api.securityscorecards.dev/projects/github.com/imdario/mergo
A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements. A helper to merge structs and maps in Golang. Useful for configuration default values, avoiding messy if-statements.
@ -37,13 +46,19 @@ Also a lovely [comune](http://en.wikipedia.org/wiki/Mergo) (municipality) in the
It is ready for production use. [It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, Microsoft, etc](https://github.com/imdario/mergo#mergo-in-the-wild). It is ready for production use. [It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, Microsoft, etc](https://github.com/imdario/mergo#mergo-in-the-wild).
### Important note ### Important notes
#### 1.0.0
In [1.0.0](//github.com/imdario/mergo/releases/tag/1.0.0) Mergo moves to a vanity URL `dario.cat/mergo`.
#### 0.3.9
Please keep in mind that a problematic PR broke [0.3.9](//github.com/imdario/mergo/releases/tag/0.3.9). I reverted it in [0.3.10](//github.com/imdario/mergo/releases/tag/0.3.10), and I consider it stable but not bug-free. Also, this version adds support for go modules. Please keep in mind that a problematic PR broke [0.3.9](//github.com/imdario/mergo/releases/tag/0.3.9). I reverted it in [0.3.10](//github.com/imdario/mergo/releases/tag/0.3.10), and I consider it stable but not bug-free. Also, this version adds support for go modules.
Keep in mind that in [0.3.2](//github.com/imdario/mergo/releases/tag/0.3.2), Mergo changed `Merge()`and `Map()` signatures to support [transformers](#transformers). I added an optional/variadic argument so that it won't break the existing code. Keep in mind that in [0.3.2](//github.com/imdario/mergo/releases/tag/0.3.2), Mergo changed `Merge()`and `Map()` signatures to support [transformers](#transformers). I added an optional/variadic argument so that it won't break the existing code.
If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with ```go get -u github.com/imdario/mergo```. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0). If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with ```go get -u dario.cat/mergo```. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0).
### Donations ### Donations
@ -55,7 +70,6 @@ If Mergo is useful to you, consider buying me a coffee, a beer, or making a mont
### Mergo in the wild ### Mergo in the wild
- [cli/cli](https://github.com/cli/cli)
- [moby/moby](https://github.com/moby/moby) - [moby/moby](https://github.com/moby/moby)
- [kubernetes/kubernetes](https://github.com/kubernetes/kubernetes) - [kubernetes/kubernetes](https://github.com/kubernetes/kubernetes)
- [vmware/dispatch](https://github.com/vmware/dispatch) - [vmware/dispatch](https://github.com/vmware/dispatch)
@ -102,11 +116,11 @@ If Mergo is useful to you, consider buying me a coffee, a beer, or making a mont
## Install ## Install
go get github.com/imdario/mergo go get dario.cat/mergo
// use in your .go code // use in your .go code
import ( import (
"github.com/imdario/mergo" "dario.cat/mergo"
) )
## Usage ## Usage
@ -144,7 +158,7 @@ package main
import ( import (
"fmt" "fmt"
"github.com/imdario/mergo" "dario.cat/mergo"
) )
type Foo struct { type Foo struct {
@ -180,9 +194,9 @@ package main
import ( import (
"fmt" "fmt"
"github.com/imdario/mergo" "dario.cat/mergo"
"reflect" "reflect"
"time" "time"
) )
type timeTransformer struct { type timeTransformer struct {
@ -231,5 +245,4 @@ Written by [Dario Castañé](http://dario.im).
[BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE). [BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE).
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_large) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fimdario%2Fmergo.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fimdario%2Fmergo?ref=badge_large)

14
vendor/dario.cat/mergo/SECURITY.md vendored Normal file
View File

@ -0,0 +1,14 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 0.3.x | :white_check_mark: |
| < 0.3 | :x: |
## Security contact information
To report a security vulnerability, please use the
[Tidelift security contact](https://tidelift.com/security).
Tidelift will coordinate the fix and disclosure.

View File

@ -8,30 +8,36 @@ A helper to merge structs and maps in Golang. Useful for configuration default v
Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection). Mergo merges same-type structs and maps by setting default values in zero-value fields. Mergo won't merge unexported (private) fields. It will do recursively any exported one. It also won't merge structs inside maps (because they are not addressable using Go reflection).
Status # Status
It is ready for production use. It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc. It is ready for production use. It is used in several projects by Docker, Google, The Linux Foundation, VMWare, Shopify, etc.
Important note # Important notes
1.0.0
In 1.0.0 Mergo moves to a vanity URL `dario.cat/mergo`.
0.3.9
Please keep in mind that a problematic PR broke 0.3.9. We reverted it in 0.3.10. We consider 0.3.10 as stable but not bug-free. . Also, this version adds suppot for go modules. Please keep in mind that a problematic PR broke 0.3.9. We reverted it in 0.3.10. We consider 0.3.10 as stable but not bug-free. . Also, this version adds suppot for go modules.
Keep in mind that in 0.3.2, Mergo changed Merge() and Map() signatures to support transformers. We added an optional/variadic argument so that it won't break the existing code. Keep in mind that in 0.3.2, Mergo changed Merge() and Map() signatures to support transformers. We added an optional/variadic argument so that it won't break the existing code.
If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with go get -u github.com/imdario/mergo. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0). If you were using Mergo before April 6th, 2015, please check your project works as intended after updating your local copy with go get -u dario.cat/mergo. I apologize for any issue caused by its previous behavior and any future bug that Mergo could cause in existing projects after the change (release 0.2.0).
Install # Install
Do your usual installation procedure: Do your usual installation procedure:
go get github.com/imdario/mergo go get dario.cat/mergo
// use in your .go code // use in your .go code
import ( import (
"github.com/imdario/mergo" "dario.cat/mergo"
) )
Usage # Usage
You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as they are zero values too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection). You can only merge same-type structs with exported fields initialized as zero value of their type and same-types maps. Mergo won't merge unexported (private) fields but will do recursively any exported one. It won't merge empty structs value as they are zero values too. Also, maps will be merged recursively except for structs inside maps (because they are not addressable using Go reflection).
@ -59,7 +65,7 @@ Here is a nice example:
import ( import (
"fmt" "fmt"
"github.com/imdario/mergo" "dario.cat/mergo"
) )
type Foo struct { type Foo struct {
@ -81,7 +87,7 @@ Here is a nice example:
// {two 2} // {two 2}
} }
Transformers # Transformers
Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, time.Time is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero time.Time? Transformers allow to merge specific types differently than in the default behavior. In other words, now you can customize how some types are merged. For example, time.Time is a struct; it doesn't have zero value but IsZero can return true because it has fields with zero value. How can we merge a non-zero time.Time?
@ -89,9 +95,9 @@ Transformers allow to merge specific types differently than in the default behav
import ( import (
"fmt" "fmt"
"github.com/imdario/mergo" "dario.cat/mergo"
"reflect" "reflect"
"time" "time"
) )
type timeTransformer struct { type timeTransformer struct {
@ -127,17 +133,16 @@ Transformers allow to merge specific types differently than in the default behav
// { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 } // { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 }
} }
Contact me # Contact me
If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): https://twitter.com/im_dario If I can help you, you have an idea or you are using Mergo in your projects, don't hesitate to drop me a line (or a pull request): https://twitter.com/im_dario
About # About
Written by Dario Castañé: https://da.rio.hn Written by Dario Castañé: https://da.rio.hn
License # License
BSD 3-Clause license, as Go language. BSD 3-Clause license, as Go language.
*/ */
package mergo package mergo

View File

@ -44,7 +44,7 @@ func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, conf
} }
} }
// Remember, remember... // Remember, remember...
visited[h] = &visit{addr, typ, seen} visited[h] = &visit{typ, seen, addr}
} }
zeroValue := reflect.Value{} zeroValue := reflect.Value{}
switch dst.Kind() { switch dst.Kind() {
@ -58,7 +58,7 @@ func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, conf
} }
fieldName := field.Name fieldName := field.Name
fieldName = changeInitialCase(fieldName, unicode.ToLower) fieldName = changeInitialCase(fieldName, unicode.ToLower)
if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v)) || overwrite) { if v, ok := dstMap[fieldName]; !ok || (isEmptyValue(reflect.ValueOf(v), !config.ShouldNotDereference) || overwrite) {
dstMap[fieldName] = src.Field(i).Interface() dstMap[fieldName] = src.Field(i).Interface()
} }
} }
@ -142,7 +142,7 @@ func MapWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
func _map(dst, src interface{}, opts ...func(*Config)) error { func _map(dst, src interface{}, opts ...func(*Config)) error {
if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr { if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr {
return ErrNonPointerAgument return ErrNonPointerArgument
} }
var ( var (
vDst, vSrc reflect.Value vDst, vSrc reflect.Value

View File

@ -38,10 +38,11 @@ func isExportedComponent(field *reflect.StructField) bool {
} }
type Config struct { type Config struct {
Transformers Transformers
Overwrite bool Overwrite bool
ShouldNotDereference bool
AppendSlice bool AppendSlice bool
TypeCheck bool TypeCheck bool
Transformers Transformers
overwriteWithEmptyValue bool overwriteWithEmptyValue bool
overwriteSliceWithEmptyValue bool overwriteSliceWithEmptyValue bool
sliceDeepCopy bool sliceDeepCopy bool
@ -76,7 +77,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
} }
} }
// Remember, remember... // Remember, remember...
visited[h] = &visit{addr, typ, seen} visited[h] = &visit{typ, seen, addr}
} }
if config.Transformers != nil && !isReflectNil(dst) && dst.IsValid() { if config.Transformers != nil && !isReflectNil(dst) && dst.IsValid() {
@ -95,7 +96,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
} }
} }
} else { } else {
if dst.CanSet() && (isReflectNil(dst) || overwrite) && (!isEmptyValue(src) || overwriteWithEmptySrc) { if dst.CanSet() && (isReflectNil(dst) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc) {
dst.Set(src) dst.Set(src)
} }
} }
@ -110,7 +111,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
} }
if src.Kind() != reflect.Map { if src.Kind() != reflect.Map {
if overwrite { if overwrite && dst.CanSet() {
dst.Set(src) dst.Set(src)
} }
return return
@ -162,7 +163,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
dstSlice = reflect.ValueOf(dstElement.Interface()) dstSlice = reflect.ValueOf(dstElement.Interface())
} }
if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice && !sliceDeepCopy { if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy {
if typeCheck && srcSlice.Type() != dstSlice.Type() { if typeCheck && srcSlice.Type() != dstSlice.Type() {
return fmt.Errorf("cannot override two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type()) return fmt.Errorf("cannot override two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
} }
@ -194,22 +195,38 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
dst.SetMapIndex(key, dstSlice) dst.SetMapIndex(key, dstSlice)
} }
} }
if dstElement.IsValid() && !isEmptyValue(dstElement) && (reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map || reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice) {
continue if dstElement.IsValid() && !isEmptyValue(dstElement, !config.ShouldNotDereference) {
if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice {
continue
}
if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map && reflect.TypeOf(dstElement.Interface()).Kind() == reflect.Map {
continue
}
} }
if srcElement.IsValid() && ((srcElement.Kind() != reflect.Ptr && overwrite) || !dstElement.IsValid() || isEmptyValue(dstElement)) { if srcElement.IsValid() && ((srcElement.Kind() != reflect.Ptr && overwrite) || !dstElement.IsValid() || isEmptyValue(dstElement, !config.ShouldNotDereference)) {
if dst.IsNil() { if dst.IsNil() {
dst.Set(reflect.MakeMap(dst.Type())) dst.Set(reflect.MakeMap(dst.Type()))
} }
dst.SetMapIndex(key, srcElement) dst.SetMapIndex(key, srcElement)
} }
} }
// Ensure that all keys in dst are deleted if they are not in src.
if overwriteWithEmptySrc {
for _, key := range dst.MapKeys() {
srcElement := src.MapIndex(key)
if !srcElement.IsValid() {
dst.SetMapIndex(key, reflect.Value{})
}
}
}
case reflect.Slice: case reflect.Slice:
if !dst.CanSet() { if !dst.CanSet() {
break break
} }
if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice && !sliceDeepCopy { if (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) && !config.AppendSlice && !sliceDeepCopy {
dst.Set(src) dst.Set(src)
} else if config.AppendSlice { } else if config.AppendSlice {
if src.Type() != dst.Type() { if src.Type() != dst.Type() {
@ -244,12 +261,18 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
if src.Kind() != reflect.Interface { if src.Kind() != reflect.Interface {
if dst.IsNil() || (src.Kind() != reflect.Ptr && overwrite) { if dst.IsNil() || (src.Kind() != reflect.Ptr && overwrite) {
if dst.CanSet() && (overwrite || isEmptyValue(dst)) { if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) {
dst.Set(src) dst.Set(src)
} }
} else if src.Kind() == reflect.Ptr { } else if src.Kind() == reflect.Ptr {
if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { if !config.ShouldNotDereference {
return if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
return
}
} else {
if overwriteWithEmptySrc || (overwrite && !src.IsNil()) || dst.IsNil() {
dst.Set(src)
}
} }
} else if dst.Elem().Type() == src.Type() { } else if dst.Elem().Type() == src.Type() {
if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil { if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil {
@ -262,7 +285,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
} }
if dst.IsNil() || overwrite { if dst.IsNil() || overwrite {
if dst.CanSet() && (overwrite || isEmptyValue(dst)) { if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) {
dst.Set(src) dst.Set(src)
} }
break break
@ -275,7 +298,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
break break
} }
default: default:
mustSet := (isEmptyValue(dst) || overwrite) && (!isEmptyValue(src) || overwriteWithEmptySrc) mustSet := (isEmptyValue(dst, !config.ShouldNotDereference) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc)
if mustSet { if mustSet {
if dst.CanSet() { if dst.CanSet() {
dst.Set(src) dst.Set(src)
@ -326,6 +349,12 @@ func WithOverrideEmptySlice(config *Config) {
config.overwriteSliceWithEmptyValue = true config.overwriteSliceWithEmptyValue = true
} }
// WithoutDereference prevents dereferencing pointers when evaluating whether they are empty
// (i.e. a non-nil pointer is never considered empty).
func WithoutDereference(config *Config) {
config.ShouldNotDereference = true
}
// WithAppendSlice will make merge append slices instead of overwriting it. // WithAppendSlice will make merge append slices instead of overwriting it.
func WithAppendSlice(config *Config) { func WithAppendSlice(config *Config) {
config.AppendSlice = true config.AppendSlice = true
@ -344,7 +373,7 @@ func WithSliceDeepCopy(config *Config) {
func merge(dst, src interface{}, opts ...func(*Config)) error { func merge(dst, src interface{}, opts ...func(*Config)) error {
if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr { if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr {
return ErrNonPointerAgument return ErrNonPointerArgument
} }
var ( var (
vDst, vSrc reflect.Value vDst, vSrc reflect.Value

View File

@ -20,7 +20,7 @@ var (
ErrNotSupported = errors.New("only structs, maps, and slices are supported") ErrNotSupported = errors.New("only structs, maps, and slices are supported")
ErrExpectedMapAsDestination = errors.New("dst was expected to be a map") ErrExpectedMapAsDestination = errors.New("dst was expected to be a map")
ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct") ErrExpectedStructAsDestination = errors.New("dst was expected to be a struct")
ErrNonPointerAgument = errors.New("dst must be a pointer") ErrNonPointerArgument = errors.New("dst must be a pointer")
) )
// During deepMerge, must keep track of checks that are // During deepMerge, must keep track of checks that are
@ -28,13 +28,13 @@ var (
// checks in progress are true when it reencounters them. // checks in progress are true when it reencounters them.
// Visited are stored in a map indexed by 17 * a1 + a2; // Visited are stored in a map indexed by 17 * a1 + a2;
type visit struct { type visit struct {
ptr uintptr
typ reflect.Type typ reflect.Type
next *visit next *visit
ptr uintptr
} }
// From src/pkg/encoding/json/encode.go. // From src/pkg/encoding/json/encode.go.
func isEmptyValue(v reflect.Value) bool { func isEmptyValue(v reflect.Value, shouldDereference bool) bool {
switch v.Kind() { switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String: case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0 return v.Len() == 0
@ -50,7 +50,10 @@ func isEmptyValue(v reflect.Value) bool {
if v.IsNil() { if v.IsNil() {
return true return true
} }
return isEmptyValue(v.Elem()) if shouldDereference {
return isEmptyValue(v.Elem(), shouldDereference)
}
return false
case reflect.Func: case reflect.Func:
return v.IsNil() return v.IsNil()
case reflect.Invalid: case reflect.Invalid:

1
vendor/github.com/Microsoft/go-winio/.gitattributes generated vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

View File

@ -1 +1,10 @@
.vscode/
*.exe *.exe
# testing
testdata
# go workspaces
go.work
go.work.sum

149
vendor/github.com/Microsoft/go-winio/.golangci.yml generated vendored Normal file
View File

@ -0,0 +1,149 @@
run:
skip-dirs:
- pkg/etw/sample
linters:
enable:
# style
- containedctx # struct contains a context
- dupl # duplicate code
- errname # erorrs are named correctly
- nolintlint # "//nolint" directives are properly explained
- revive # golint replacement
- unconvert # unnecessary conversions
- wastedassign
# bugs, performance, unused, etc ...
- contextcheck # function uses a non-inherited context
- errorlint # errors not wrapped for 1.13
- exhaustive # check exhaustiveness of enum switch statements
- gofmt # files are gofmt'ed
- gosec # security
- nilerr # returns nil even with non-nil error
- unparam # unused function params
issues:
exclude-rules:
# err is very often shadowed in nested scopes
- linters:
- govet
text: '^shadow: declaration of "err" shadows declaration'
# ignore long lines for skip autogen directives
- linters:
- revive
text: "^line-length-limit: "
source: "^//(go:generate|sys) "
#TODO: remove after upgrading to go1.18
# ignore comment spacing for nolint and sys directives
- linters:
- revive
text: "^comment-spacings: no space between comment delimiter and comment text"
source: "//(cspell:|nolint:|sys |todo)"
# not on go 1.18 yet, so no any
- linters:
- revive
text: "^use-any: since GO 1.18 'interface{}' can be replaced by 'any'"
# allow unjustified ignores of error checks in defer statements
- linters:
- nolintlint
text: "^directive `//nolint:errcheck` should provide explanation"
source: '^\s*defer '
# allow unjustified ignores of error lints for io.EOF
- linters:
- nolintlint
text: "^directive `//nolint:errorlint` should provide explanation"
source: '[=|!]= io.EOF'
linters-settings:
exhaustive:
default-signifies-exhaustive: true
govet:
enable-all: true
disable:
# struct order is often for Win32 compat
# also, ignore pointer bytes/GC issues for now until performance becomes an issue
- fieldalignment
check-shadowing: true
nolintlint:
allow-leading-space: false
require-explanation: true
require-specific: true
revive:
# revive is more configurable than static check, so likely the preferred alternative to static-check
# (once the perf issue is solved: https://github.com/golangci/golangci-lint/issues/2997)
enable-all-rules:
true
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md
rules:
# rules with required arguments
- name: argument-limit
disabled: true
- name: banned-characters
disabled: true
- name: cognitive-complexity
disabled: true
- name: cyclomatic
disabled: true
- name: file-header
disabled: true
- name: function-length
disabled: true
- name: function-result-limit
disabled: true
- name: max-public-structs
disabled: true
# geneally annoying rules
- name: add-constant # complains about any and all strings and integers
disabled: true
- name: confusing-naming # we frequently use "Foo()" and "foo()" together
disabled: true
- name: flag-parameter # excessive, and a common idiom we use
disabled: true
- name: unhandled-error # warns over common fmt.Print* and io.Close; rely on errcheck instead
disabled: true
# general config
- name: line-length-limit
arguments:
- 140
- name: var-naming
arguments:
- []
- - CID
- CRI
- CTRD
- DACL
- DLL
- DOS
- ETW
- FSCTL
- GCS
- GMSA
- HCS
- HV
- IO
- LCOW
- LDAP
- LPAC
- LTSC
- MMIO
- NT
- OCI
- PMEM
- PWSH
- RX
- SACl
- SID
- SMB
- TX
- VHD
- VHDX
- VMID
- VPCI
- WCOW
- WIM

View File

@ -13,16 +13,60 @@ Please see the LICENSE file for licensing information.
## Contributing ## Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) This project welcomes contributions and suggestions.
declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that
you have the right to, and actually do, grant us the rights to use your contribution.
For details, visit [Microsoft CLA](https://cla.microsoft.com).
When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR When you submit a pull request, a CLA-bot will automatically determine whether you need to
appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. provide a CLA and decorate the PR appropriately (e.g., label, comment).
Simply follow the instructions provided by the bot.
You will only need to do this once across all repos using our CLA.
We also require that contributors sign their commits using git commit -s or git commit --signoff to certify they either authored the work themselves Additionally, the pull request pipeline requires the following steps to be performed before
or otherwise have permission to use it in this project. Please see https://developercertificate.org/ for more info, as well as to make sure that you can mergining.
attest to the rules listed. Our CI uses the DCO Github app to ensure that all commits in a given PR are signed-off.
### Code Sign-Off
We require that contributors sign their commits using [`git commit --signoff`][git-commit-s]
to certify they either authored the work themselves or otherwise have permission to use it in this project.
A range of commits can be signed off using [`git rebase --signoff`][git-rebase-s].
Please see [the developer certificate](https://developercertificate.org) for more info,
as well as to make sure that you can attest to the rules listed.
Our CI uses the DCO Github app to ensure that all commits in a given PR are signed-off.
### Linting
Code must pass a linting stage, which uses [`golangci-lint`][lint].
The linting settings are stored in [`.golangci.yaml`](./.golangci.yaml), and can be run
automatically with VSCode by adding the following to your workspace or folder settings:
```json
"go.lintTool": "golangci-lint",
"go.lintOnSave": "package",
```
Additional editor [integrations options are also available][lint-ide].
Alternatively, `golangci-lint` can be [installed locally][lint-install] and run from the repo root:
```shell
# use . or specify a path to only lint a package
# to show all lint errors, use flags "--max-issues-per-linter=0 --max-same-issues=0"
> golangci-lint run ./...
```
### Go Generate
The pipeline checks that auto-generated code, via `go generate`, are up to date.
This can be done for the entire repo:
```shell
> go generate ./...
```
## Code of Conduct ## Code of Conduct
@ -30,8 +74,16 @@ This project has adopted the [Microsoft Open Source Code of Conduct](https://ope
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
## Special Thanks ## Special Thanks
Thanks to natefinch for the inspiration for this library. See https://github.com/natefinch/npipe
for another named pipe implementation. Thanks to [natefinch][natefinch] for the inspiration for this library.
See [npipe](https://github.com/natefinch/npipe) for another named pipe implementation.
[lint]: https://golangci-lint.run/
[lint-ide]: https://golangci-lint.run/usage/integrations/#editor-integration
[lint-install]: https://golangci-lint.run/usage/install/#local-installation
[git-commit-s]: https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--s
[git-rebase-s]: https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt---signoff
[natefinch]: https://github.com/natefinch

41
vendor/github.com/Microsoft/go-winio/SECURITY.md generated vendored Normal file
View File

@ -0,0 +1,41 @@
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.7 BLOCK -->
## Security
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
## Reporting Security Issues
**Please do not report security vulnerabilities through public GitHub issues.**
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
* Full paths of source file(s) related to the manifestation of the issue
* The location of the affected source code (tag/branch/commit or direct URL)
* Any special configuration required to reproduce the issue
* Step-by-step instructions to reproduce the issue
* Proof-of-concept or exploit code (if possible)
* Impact of the issue, including how an attacker might exploit the issue
This information will help us triage your report more quickly.
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
## Preferred Languages
We prefer all communications to be in English.
## Policy
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
<!-- END MICROSOFT SECURITY.MD BLOCK -->

View File

@ -1,3 +1,4 @@
//go:build windows
// +build windows // +build windows
package winio package winio
@ -7,11 +8,12 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"runtime" "runtime"
"syscall" "syscall"
"unicode/utf16" "unicode/utf16"
"golang.org/x/sys/windows"
) )
//sys backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead //sys backupRead(h syscall.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead
@ -24,7 +26,7 @@ const (
BackupAlternateData BackupAlternateData
BackupLink BackupLink
BackupPropertyData BackupPropertyData
BackupObjectId BackupObjectId //revive:disable-line:var-naming ID, not Id
BackupReparseData BackupReparseData
BackupSparseBlock BackupSparseBlock
BackupTxfsData BackupTxfsData
@ -34,14 +36,16 @@ const (
StreamSparseAttributes = uint32(8) StreamSparseAttributes = uint32(8)
) )
//nolint:revive // var-naming: ALL_CAPS
const ( const (
WRITE_DAC = 0x40000 WRITE_DAC = windows.WRITE_DAC
WRITE_OWNER = 0x80000 WRITE_OWNER = windows.WRITE_OWNER
ACCESS_SYSTEM_SECURITY = 0x1000000 ACCESS_SYSTEM_SECURITY = windows.ACCESS_SYSTEM_SECURITY
) )
// BackupHeader represents a backup stream of a file. // BackupHeader represents a backup stream of a file.
type BackupHeader struct { type BackupHeader struct {
//revive:disable-next-line:var-naming ID, not Id
Id uint32 // The backup stream ID Id uint32 // The backup stream ID
Attributes uint32 // Stream attributes Attributes uint32 // Stream attributes
Size int64 // The size of the stream in bytes Size int64 // The size of the stream in bytes
@ -49,8 +53,8 @@ type BackupHeader struct {
Offset int64 // The offset of the stream in the file (for BackupSparseBlock only). Offset int64 // The offset of the stream in the file (for BackupSparseBlock only).
} }
type win32StreamId struct { type win32StreamID struct {
StreamId uint32 StreamID uint32
Attributes uint32 Attributes uint32
Size uint64 Size uint64
NameSize uint32 NameSize uint32
@ -71,7 +75,7 @@ func NewBackupStreamReader(r io.Reader) *BackupStreamReader {
// Next returns the next backup stream and prepares for calls to Read(). It skips the remainder of the current stream if // Next returns the next backup stream and prepares for calls to Read(). It skips the remainder of the current stream if
// it was not completely read. // it was not completely read.
func (r *BackupStreamReader) Next() (*BackupHeader, error) { func (r *BackupStreamReader) Next() (*BackupHeader, error) {
if r.bytesLeft > 0 { if r.bytesLeft > 0 { //nolint:nestif // todo: flatten this
if s, ok := r.r.(io.Seeker); ok { if s, ok := r.r.(io.Seeker); ok {
// Make sure Seek on io.SeekCurrent sometimes succeeds // Make sure Seek on io.SeekCurrent sometimes succeeds
// before trying the actual seek. // before trying the actual seek.
@ -82,16 +86,16 @@ func (r *BackupStreamReader) Next() (*BackupHeader, error) {
r.bytesLeft = 0 r.bytesLeft = 0
} }
} }
if _, err := io.Copy(ioutil.Discard, r); err != nil { if _, err := io.Copy(io.Discard, r); err != nil {
return nil, err return nil, err
} }
} }
var wsi win32StreamId var wsi win32StreamID
if err := binary.Read(r.r, binary.LittleEndian, &wsi); err != nil { if err := binary.Read(r.r, binary.LittleEndian, &wsi); err != nil {
return nil, err return nil, err
} }
hdr := &BackupHeader{ hdr := &BackupHeader{
Id: wsi.StreamId, Id: wsi.StreamID,
Attributes: wsi.Attributes, Attributes: wsi.Attributes,
Size: int64(wsi.Size), Size: int64(wsi.Size),
} }
@ -102,7 +106,7 @@ func (r *BackupStreamReader) Next() (*BackupHeader, error) {
} }
hdr.Name = syscall.UTF16ToString(name) hdr.Name = syscall.UTF16ToString(name)
} }
if wsi.StreamId == BackupSparseBlock { if wsi.StreamID == BackupSparseBlock {
if err := binary.Read(r.r, binary.LittleEndian, &hdr.Offset); err != nil { if err := binary.Read(r.r, binary.LittleEndian, &hdr.Offset); err != nil {
return nil, err return nil, err
} }
@ -147,8 +151,8 @@ func (w *BackupStreamWriter) WriteHeader(hdr *BackupHeader) error {
return fmt.Errorf("missing %d bytes", w.bytesLeft) return fmt.Errorf("missing %d bytes", w.bytesLeft)
} }
name := utf16.Encode([]rune(hdr.Name)) name := utf16.Encode([]rune(hdr.Name))
wsi := win32StreamId{ wsi := win32StreamID{
StreamId: hdr.Id, StreamID: hdr.Id,
Attributes: hdr.Attributes, Attributes: hdr.Attributes,
Size: uint64(hdr.Size), Size: uint64(hdr.Size),
NameSize: uint32(len(name) * 2), NameSize: uint32(len(name) * 2),
@ -203,7 +207,7 @@ func (r *BackupFileReader) Read(b []byte) (int, error) {
var bytesRead uint32 var bytesRead uint32
err := backupRead(syscall.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx) err := backupRead(syscall.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx)
if err != nil { if err != nil {
return 0, &os.PathError{"BackupRead", r.f.Name(), err} return 0, &os.PathError{Op: "BackupRead", Path: r.f.Name(), Err: err}
} }
runtime.KeepAlive(r.f) runtime.KeepAlive(r.f)
if bytesRead == 0 { if bytesRead == 0 {
@ -216,7 +220,7 @@ func (r *BackupFileReader) Read(b []byte) (int, error) {
// the underlying file. // the underlying file.
func (r *BackupFileReader) Close() error { func (r *BackupFileReader) Close() error {
if r.ctx != 0 { if r.ctx != 0 {
backupRead(syscall.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx) _ = backupRead(syscall.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx)
runtime.KeepAlive(r.f) runtime.KeepAlive(r.f)
r.ctx = 0 r.ctx = 0
} }
@ -242,7 +246,7 @@ func (w *BackupFileWriter) Write(b []byte) (int, error) {
var bytesWritten uint32 var bytesWritten uint32
err := backupWrite(syscall.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx) err := backupWrite(syscall.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx)
if err != nil { if err != nil {
return 0, &os.PathError{"BackupWrite", w.f.Name(), err} return 0, &os.PathError{Op: "BackupWrite", Path: w.f.Name(), Err: err}
} }
runtime.KeepAlive(w.f) runtime.KeepAlive(w.f)
if int(bytesWritten) != len(b) { if int(bytesWritten) != len(b) {
@ -255,7 +259,7 @@ func (w *BackupFileWriter) Write(b []byte) (int, error) {
// close the underlying file. // close the underlying file.
func (w *BackupFileWriter) Close() error { func (w *BackupFileWriter) Close() error {
if w.ctx != 0 { if w.ctx != 0 {
backupWrite(syscall.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx) _ = backupWrite(syscall.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx)
runtime.KeepAlive(w.f) runtime.KeepAlive(w.f)
w.ctx = 0 w.ctx = 0
} }
@ -271,7 +275,13 @@ func OpenForBackup(path string, access uint32, share uint32, createmode uint32)
if err != nil { if err != nil {
return nil, err return nil, err
} }
h, err := syscall.CreateFile(&winPath[0], access, share, nil, createmode, syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OPEN_REPARSE_POINT, 0) h, err := syscall.CreateFile(&winPath[0],
access,
share,
nil,
createmode,
syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OPEN_REPARSE_POINT,
0)
if err != nil { if err != nil {
err = &os.PathError{Op: "open", Path: path, Err: err} err = &os.PathError{Op: "open", Path: path, Err: err}
return nil, err return nil, err

22
vendor/github.com/Microsoft/go-winio/doc.go generated vendored Normal file
View File

@ -0,0 +1,22 @@
// This package provides utilities for efficiently performing Win32 IO operations in Go.
// Currently, this package is provides support for genreal IO and management of
// - named pipes
// - files
// - [Hyper-V sockets]
//
// This code is similar to Go's [net] package, and uses IO completion ports to avoid
// blocking IO on system threads, allowing Go to reuse the thread to schedule other goroutines.
//
// This limits support to Windows Vista and newer operating systems.
//
// Additionally, this package provides support for:
// - creating and managing GUIDs
// - writing to [ETW]
// - opening and manageing VHDs
// - parsing [Windows Image files]
// - auto-generating Win32 API code
//
// [Hyper-V sockets]: https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/make-integration-service
// [ETW]: https://docs.microsoft.com/en-us/windows-hardware/drivers/devtest/event-tracing-for-windows--etw-
// [Windows Image files]: https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/work-with-windows-images
package winio

View File

@ -33,7 +33,7 @@ func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) {
err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info) err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info)
if err != nil { if err != nil {
err = errInvalidEaBuffer err = errInvalidEaBuffer
return return ea, nb, err
} }
nameOffset := fileFullEaInformationSize nameOffset := fileFullEaInformationSize
@ -43,7 +43,7 @@ func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) {
nextOffset := int(info.NextEntryOffset) nextOffset := int(info.NextEntryOffset)
if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) { if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) {
err = errInvalidEaBuffer err = errInvalidEaBuffer
return return ea, nb, err
} }
ea.Name = string(b[nameOffset : nameOffset+nameLen]) ea.Name = string(b[nameOffset : nameOffset+nameLen])
@ -52,7 +52,7 @@ func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) {
if info.NextEntryOffset != 0 { if info.NextEntryOffset != 0 {
nb = b[info.NextEntryOffset:] nb = b[info.NextEntryOffset:]
} }
return return ea, nb, err
} }
// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION // DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION
@ -67,7 +67,7 @@ func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) {
eas = append(eas, ea) eas = append(eas, ea)
b = nb b = nb
} }
return return eas, err
} }
func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error { func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error {

View File

@ -11,6 +11,8 @@ import (
"sync/atomic" "sync/atomic"
"syscall" "syscall"
"time" "time"
"golang.org/x/sys/windows"
) )
//sys cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) = CancelIoEx //sys cancelIoEx(file syscall.Handle, o *syscall.Overlapped) (err error) = CancelIoEx
@ -24,6 +26,8 @@ type atomicBool int32
func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 } func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 }
func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) } func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) }
func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) } func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) }
//revive:disable-next-line:predeclared Keep "new" to maintain consistency with "atomic" pkg
func (b *atomicBool) swap(new bool) bool { func (b *atomicBool) swap(new bool) bool {
var newInt int32 var newInt int32
if new { if new {
@ -32,11 +36,6 @@ func (b *atomicBool) swap(new bool) bool {
return atomic.SwapInt32((*int32)(b), newInt) == 1 return atomic.SwapInt32((*int32)(b), newInt) == 1
} }
const (
cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1
cFILE_SKIP_SET_EVENT_ON_HANDLE = 2
)
var ( var (
ErrFileClosed = errors.New("file has already been closed") ErrFileClosed = errors.New("file has already been closed")
ErrTimeout = &timeoutError{} ErrTimeout = &timeoutError{}
@ -44,28 +43,28 @@ var (
type timeoutError struct{} type timeoutError struct{}
func (e *timeoutError) Error() string { return "i/o timeout" } func (*timeoutError) Error() string { return "i/o timeout" }
func (e *timeoutError) Timeout() bool { return true } func (*timeoutError) Timeout() bool { return true }
func (e *timeoutError) Temporary() bool { return true } func (*timeoutError) Temporary() bool { return true }
type timeoutChan chan struct{} type timeoutChan chan struct{}
var ioInitOnce sync.Once var ioInitOnce sync.Once
var ioCompletionPort syscall.Handle var ioCompletionPort syscall.Handle
// ioResult contains the result of an asynchronous IO operation // ioResult contains the result of an asynchronous IO operation.
type ioResult struct { type ioResult struct {
bytes uint32 bytes uint32
err error err error
} }
// ioOperation represents an outstanding asynchronous Win32 IO // ioOperation represents an outstanding asynchronous Win32 IO.
type ioOperation struct { type ioOperation struct {
o syscall.Overlapped o syscall.Overlapped
ch chan ioResult ch chan ioResult
} }
func initIo() { func initIO() {
h, err := createIoCompletionPort(syscall.InvalidHandle, 0, 0, 0xffffffff) h, err := createIoCompletionPort(syscall.InvalidHandle, 0, 0, 0xffffffff)
if err != nil { if err != nil {
panic(err) panic(err)
@ -94,15 +93,15 @@ type deadlineHandler struct {
timedout atomicBool timedout atomicBool
} }
// makeWin32File makes a new win32File from an existing file handle // makeWin32File makes a new win32File from an existing file handle.
func makeWin32File(h syscall.Handle) (*win32File, error) { func makeWin32File(h syscall.Handle) (*win32File, error) {
f := &win32File{handle: h} f := &win32File{handle: h}
ioInitOnce.Do(initIo) ioInitOnce.Do(initIO)
_, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff) _, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = setFileCompletionNotificationModes(h, cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS|cFILE_SKIP_SET_EVENT_ON_HANDLE) err = setFileCompletionNotificationModes(h, windows.FILE_SKIP_COMPLETION_PORT_ON_SUCCESS|windows.FILE_SKIP_SET_EVENT_ON_HANDLE)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -121,14 +120,14 @@ func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
return f, nil return f, nil
} }
// closeHandle closes the resources associated with a Win32 handle // closeHandle closes the resources associated with a Win32 handle.
func (f *win32File) closeHandle() { func (f *win32File) closeHandle() {
f.wgLock.Lock() f.wgLock.Lock()
// Atomically set that we are closing, releasing the resources only once. // Atomically set that we are closing, releasing the resources only once.
if !f.closing.swap(true) { if !f.closing.swap(true) {
f.wgLock.Unlock() f.wgLock.Unlock()
// cancel all IO and wait for it to complete // cancel all IO and wait for it to complete
cancelIoEx(f.handle, nil) _ = cancelIoEx(f.handle, nil)
f.wg.Wait() f.wg.Wait()
// at this point, no new IO can start // at this point, no new IO can start
syscall.Close(f.handle) syscall.Close(f.handle)
@ -144,14 +143,14 @@ func (f *win32File) Close() error {
return nil return nil
} }
// IsClosed checks if the file has been closed // IsClosed checks if the file has been closed.
func (f *win32File) IsClosed() bool { func (f *win32File) IsClosed() bool {
return f.closing.isSet() return f.closing.isSet()
} }
// prepareIo prepares for a new IO operation. // prepareIO prepares for a new IO operation.
// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning. // The caller must call f.wg.Done() when the IO is finished, prior to Close() returning.
func (f *win32File) prepareIo() (*ioOperation, error) { func (f *win32File) prepareIO() (*ioOperation, error) {
f.wgLock.RLock() f.wgLock.RLock()
if f.closing.isSet() { if f.closing.isSet() {
f.wgLock.RUnlock() f.wgLock.RUnlock()
@ -164,7 +163,7 @@ func (f *win32File) prepareIo() (*ioOperation, error) {
return c, nil return c, nil
} }
// ioCompletionProcessor processes completed async IOs forever // ioCompletionProcessor processes completed async IOs forever.
func ioCompletionProcessor(h syscall.Handle) { func ioCompletionProcessor(h syscall.Handle) {
for { for {
var bytes uint32 var bytes uint32
@ -178,15 +177,17 @@ func ioCompletionProcessor(h syscall.Handle) {
} }
} }
// asyncIo processes the return value from ReadFile or WriteFile, blocking until // todo: helsaawy - create an asyncIO version that takes a context
// asyncIO processes the return value from ReadFile or WriteFile, blocking until
// the operation has actually completed. // the operation has actually completed.
func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) { func (f *win32File) asyncIO(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) {
if err != syscall.ERROR_IO_PENDING { if err != syscall.ERROR_IO_PENDING { //nolint:errorlint // err is Errno
return int(bytes), err return int(bytes), err
} }
if f.closing.isSet() { if f.closing.isSet() {
cancelIoEx(f.handle, &c.o) _ = cancelIoEx(f.handle, &c.o)
} }
var timeout timeoutChan var timeout timeoutChan
@ -200,7 +201,7 @@ func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, er
select { select {
case r = <-c.ch: case r = <-c.ch:
err = r.err err = r.err
if err == syscall.ERROR_OPERATION_ABORTED { if err == syscall.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno
if f.closing.isSet() { if f.closing.isSet() {
err = ErrFileClosed err = ErrFileClosed
} }
@ -210,10 +211,10 @@ func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, er
err = wsaGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags) err = wsaGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags)
} }
case <-timeout: case <-timeout:
cancelIoEx(f.handle, &c.o) _ = cancelIoEx(f.handle, &c.o)
r = <-c.ch r = <-c.ch
err = r.err err = r.err
if err == syscall.ERROR_OPERATION_ABORTED { if err == syscall.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno
err = ErrTimeout err = ErrTimeout
} }
} }
@ -221,13 +222,14 @@ func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, er
// runtime.KeepAlive is needed, as c is passed via native // runtime.KeepAlive is needed, as c is passed via native
// code to ioCompletionProcessor, c must remain alive // code to ioCompletionProcessor, c must remain alive
// until the channel read is complete. // until the channel read is complete.
// todo: (de)allocate *ioOperation via win32 heap functions, instead of needing to KeepAlive?
runtime.KeepAlive(c) runtime.KeepAlive(c)
return int(r.bytes), err return int(r.bytes), err
} }
// Read reads from a file handle. // Read reads from a file handle.
func (f *win32File) Read(b []byte) (int, error) { func (f *win32File) Read(b []byte) (int, error) {
c, err := f.prepareIo() c, err := f.prepareIO()
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -239,13 +241,13 @@ func (f *win32File) Read(b []byte) (int, error) {
var bytes uint32 var bytes uint32
err = syscall.ReadFile(f.handle, b, &bytes, &c.o) err = syscall.ReadFile(f.handle, b, &bytes, &c.o)
n, err := f.asyncIo(c, &f.readDeadline, bytes, err) n, err := f.asyncIO(c, &f.readDeadline, bytes, err)
runtime.KeepAlive(b) runtime.KeepAlive(b)
// Handle EOF conditions. // Handle EOF conditions.
if err == nil && n == 0 && len(b) != 0 { if err == nil && n == 0 && len(b) != 0 {
return 0, io.EOF return 0, io.EOF
} else if err == syscall.ERROR_BROKEN_PIPE { } else if err == syscall.ERROR_BROKEN_PIPE { //nolint:errorlint // err is Errno
return 0, io.EOF return 0, io.EOF
} else { } else {
return n, err return n, err
@ -254,7 +256,7 @@ func (f *win32File) Read(b []byte) (int, error) {
// Write writes to a file handle. // Write writes to a file handle.
func (f *win32File) Write(b []byte) (int, error) { func (f *win32File) Write(b []byte) (int, error) {
c, err := f.prepareIo() c, err := f.prepareIO()
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -266,7 +268,7 @@ func (f *win32File) Write(b []byte) (int, error) {
var bytes uint32 var bytes uint32
err = syscall.WriteFile(f.handle, b, &bytes, &c.o) err = syscall.WriteFile(f.handle, b, &bytes, &c.o)
n, err := f.asyncIo(c, &f.writeDeadline, bytes, err) n, err := f.asyncIO(c, &f.writeDeadline, bytes, err)
runtime.KeepAlive(b) runtime.KeepAlive(b)
return n, err return n, err
} }

View File

@ -1,3 +1,4 @@
//go:build windows
// +build windows // +build windows
package winio package winio
@ -14,13 +15,18 @@ import (
type FileBasicInfo struct { type FileBasicInfo struct {
CreationTime, LastAccessTime, LastWriteTime, ChangeTime windows.Filetime CreationTime, LastAccessTime, LastWriteTime, ChangeTime windows.Filetime
FileAttributes uint32 FileAttributes uint32
pad uint32 // padding _ uint32 // padding
} }
// GetFileBasicInfo retrieves times and attributes for a file. // GetFileBasicInfo retrieves times and attributes for a file.
func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) { func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
bi := &FileBasicInfo{} bi := &FileBasicInfo{}
if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil { if err := windows.GetFileInformationByHandleEx(
windows.Handle(f.Fd()),
windows.FileBasicInfo,
(*byte)(unsafe.Pointer(bi)),
uint32(unsafe.Sizeof(*bi)),
); err != nil {
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
} }
runtime.KeepAlive(f) runtime.KeepAlive(f)
@ -29,7 +35,12 @@ func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
// SetFileBasicInfo sets times and attributes for a file. // SetFileBasicInfo sets times and attributes for a file.
func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error { func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error {
if err := windows.SetFileInformationByHandle(windows.Handle(f.Fd()), windows.FileBasicInfo, (*byte)(unsafe.Pointer(bi)), uint32(unsafe.Sizeof(*bi))); err != nil { if err := windows.SetFileInformationByHandle(
windows.Handle(f.Fd()),
windows.FileBasicInfo,
(*byte)(unsafe.Pointer(bi)),
uint32(unsafe.Sizeof(*bi)),
); err != nil {
return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err} return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err}
} }
runtime.KeepAlive(f) runtime.KeepAlive(f)
@ -48,7 +59,10 @@ type FileStandardInfo struct {
// GetFileStandardInfo retrieves ended information for the file. // GetFileStandardInfo retrieves ended information for the file.
func GetFileStandardInfo(f *os.File) (*FileStandardInfo, error) { func GetFileStandardInfo(f *os.File) (*FileStandardInfo, error) {
si := &FileStandardInfo{} si := &FileStandardInfo{}
if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileStandardInfo, (*byte)(unsafe.Pointer(si)), uint32(unsafe.Sizeof(*si))); err != nil { if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()),
windows.FileStandardInfo,
(*byte)(unsafe.Pointer(si)),
uint32(unsafe.Sizeof(*si))); err != nil {
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
} }
runtime.KeepAlive(f) runtime.KeepAlive(f)
@ -65,7 +79,12 @@ type FileIDInfo struct {
// GetFileID retrieves the unique (volume, file ID) pair for a file. // GetFileID retrieves the unique (volume, file ID) pair for a file.
func GetFileID(f *os.File) (*FileIDInfo, error) { func GetFileID(f *os.File) (*FileIDInfo, error) {
fileID := &FileIDInfo{} fileID := &FileIDInfo{}
if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()), windows.FileIdInfo, (*byte)(unsafe.Pointer(fileID)), uint32(unsafe.Sizeof(*fileID))); err != nil { if err := windows.GetFileInformationByHandleEx(
windows.Handle(f.Fd()),
windows.FileIdInfo,
(*byte)(unsafe.Pointer(fileID)),
uint32(unsafe.Sizeof(*fileID)),
); err != nil {
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err} return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
} }
runtime.KeepAlive(f) runtime.KeepAlive(f)

View File

@ -4,6 +4,8 @@
package winio package winio
import ( import (
"context"
"errors"
"fmt" "fmt"
"io" "io"
"net" "net"
@ -12,16 +14,87 @@ import (
"time" "time"
"unsafe" "unsafe"
"golang.org/x/sys/windows"
"github.com/Microsoft/go-winio/internal/socket"
"github.com/Microsoft/go-winio/pkg/guid" "github.com/Microsoft/go-winio/pkg/guid"
) )
//sys bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socketError] = ws2_32.bind const afHVSock = 34 // AF_HYPERV
const ( // Well known Service and VM IDs
afHvSock = 34 // AF_HYPERV // https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/make-integration-service#vmid-wildcards
socketError = ^uintptr(0) // HvsockGUIDWildcard is the wildcard VmId for accepting connections from all partitions.
) func HvsockGUIDWildcard() guid.GUID { // 00000000-0000-0000-0000-000000000000
return guid.GUID{}
}
// HvsockGUIDBroadcast is the wildcard VmId for broadcasting sends to all partitions.
func HvsockGUIDBroadcast() guid.GUID { // ffffffff-ffff-ffff-ffff-ffffffffffff
return guid.GUID{
Data1: 0xffffffff,
Data2: 0xffff,
Data3: 0xffff,
Data4: [8]uint8{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
}
}
// HvsockGUIDLoopback is the Loopback VmId for accepting connections to the same partition as the connector.
func HvsockGUIDLoopback() guid.GUID { // e0e16197-dd56-4a10-9195-5ee7a155a838
return guid.GUID{
Data1: 0xe0e16197,
Data2: 0xdd56,
Data3: 0x4a10,
Data4: [8]uint8{0x91, 0x95, 0x5e, 0xe7, 0xa1, 0x55, 0xa8, 0x38},
}
}
// HvsockGUIDSiloHost is the address of a silo's host partition:
// - The silo host of a hosted silo is the utility VM.
// - The silo host of a silo on a physical host is the physical host.
func HvsockGUIDSiloHost() guid.GUID { // 36bd0c5c-7276-4223-88ba-7d03b654c568
return guid.GUID{
Data1: 0x36bd0c5c,
Data2: 0x7276,
Data3: 0x4223,
Data4: [8]byte{0x88, 0xba, 0x7d, 0x03, 0xb6, 0x54, 0xc5, 0x68},
}
}
// HvsockGUIDChildren is the wildcard VmId for accepting connections from the connector's child partitions.
func HvsockGUIDChildren() guid.GUID { // 90db8b89-0d35-4f79-8ce9-49ea0ac8b7cd
return guid.GUID{
Data1: 0x90db8b89,
Data2: 0xd35,
Data3: 0x4f79,
Data4: [8]uint8{0x8c, 0xe9, 0x49, 0xea, 0xa, 0xc8, 0xb7, 0xcd},
}
}
// HvsockGUIDParent is the wildcard VmId for accepting connections from the connector's parent partition.
// Listening on this VmId accepts connection from:
// - Inside silos: silo host partition.
// - Inside hosted silo: host of the VM.
// - Inside VM: VM host.
// - Physical host: Not supported.
func HvsockGUIDParent() guid.GUID { // a42e7cda-d03f-480c-9cc2-a4de20abb878
return guid.GUID{
Data1: 0xa42e7cda,
Data2: 0xd03f,
Data3: 0x480c,
Data4: [8]uint8{0x9c, 0xc2, 0xa4, 0xde, 0x20, 0xab, 0xb8, 0x78},
}
}
// hvsockVsockServiceTemplate is the Service GUID used for the VSOCK protocol.
func hvsockVsockServiceTemplate() guid.GUID { // 00000000-facb-11e6-bd58-64006a7986d3
return guid.GUID{
Data2: 0xfacb,
Data3: 0x11e6,
Data4: [8]uint8{0xbd, 0x58, 0x64, 0x00, 0x6a, 0x79, 0x86, 0xd3},
}
}
// An HvsockAddr is an address for a AF_HYPERV socket. // An HvsockAddr is an address for a AF_HYPERV socket.
type HvsockAddr struct { type HvsockAddr struct {
@ -36,8 +109,10 @@ type rawHvsockAddr struct {
ServiceID guid.GUID ServiceID guid.GUID
} }
var _ socket.RawSockaddr = &rawHvsockAddr{}
// Network returns the address's network name, "hvsock". // Network returns the address's network name, "hvsock".
func (addr *HvsockAddr) Network() string { func (*HvsockAddr) Network() string {
return "hvsock" return "hvsock"
} }
@ -47,14 +122,14 @@ func (addr *HvsockAddr) String() string {
// VsockServiceID returns an hvsock service ID corresponding to the specified AF_VSOCK port. // VsockServiceID returns an hvsock service ID corresponding to the specified AF_VSOCK port.
func VsockServiceID(port uint32) guid.GUID { func VsockServiceID(port uint32) guid.GUID {
g, _ := guid.FromString("00000000-facb-11e6-bd58-64006a7986d3") g := hvsockVsockServiceTemplate() // make a copy
g.Data1 = port g.Data1 = port
return g return g
} }
func (addr *HvsockAddr) raw() rawHvsockAddr { func (addr *HvsockAddr) raw() rawHvsockAddr {
return rawHvsockAddr{ return rawHvsockAddr{
Family: afHvSock, Family: afHVSock,
VMID: addr.VMID, VMID: addr.VMID,
ServiceID: addr.ServiceID, ServiceID: addr.ServiceID,
} }
@ -65,20 +140,48 @@ func (addr *HvsockAddr) fromRaw(raw *rawHvsockAddr) {
addr.ServiceID = raw.ServiceID addr.ServiceID = raw.ServiceID
} }
// Sockaddr returns a pointer to and the size of this struct.
//
// Implements the [socket.RawSockaddr] interface, and allows use in
// [socket.Bind] and [socket.ConnectEx].
func (r *rawHvsockAddr) Sockaddr() (unsafe.Pointer, int32, error) {
return unsafe.Pointer(r), int32(unsafe.Sizeof(rawHvsockAddr{})), nil
}
// Sockaddr interface allows use with `sockets.Bind()` and `.ConnectEx()`.
func (r *rawHvsockAddr) FromBytes(b []byte) error {
n := int(unsafe.Sizeof(rawHvsockAddr{}))
if len(b) < n {
return fmt.Errorf("got %d, want %d: %w", len(b), n, socket.ErrBufferSize)
}
copy(unsafe.Slice((*byte)(unsafe.Pointer(r)), n), b[:n])
if r.Family != afHVSock {
return fmt.Errorf("got %d, want %d: %w", r.Family, afHVSock, socket.ErrAddrFamily)
}
return nil
}
// HvsockListener is a socket listener for the AF_HYPERV address family. // HvsockListener is a socket listener for the AF_HYPERV address family.
type HvsockListener struct { type HvsockListener struct {
sock *win32File sock *win32File
addr HvsockAddr addr HvsockAddr
} }
var _ net.Listener = &HvsockListener{}
// HvsockConn is a connected socket of the AF_HYPERV address family. // HvsockConn is a connected socket of the AF_HYPERV address family.
type HvsockConn struct { type HvsockConn struct {
sock *win32File sock *win32File
local, remote HvsockAddr local, remote HvsockAddr
} }
func newHvSocket() (*win32File, error) { var _ net.Conn = &HvsockConn{}
fd, err := syscall.Socket(afHvSock, syscall.SOCK_STREAM, 1)
func newHVSocket() (*win32File, error) {
fd, err := syscall.Socket(afHVSock, syscall.SOCK_STREAM, 1)
if err != nil { if err != nil {
return nil, os.NewSyscallError("socket", err) return nil, os.NewSyscallError("socket", err)
} }
@ -94,12 +197,12 @@ func newHvSocket() (*win32File, error) {
// ListenHvsock listens for connections on the specified hvsock address. // ListenHvsock listens for connections on the specified hvsock address.
func ListenHvsock(addr *HvsockAddr) (_ *HvsockListener, err error) { func ListenHvsock(addr *HvsockAddr) (_ *HvsockListener, err error) {
l := &HvsockListener{addr: *addr} l := &HvsockListener{addr: *addr}
sock, err := newHvSocket() sock, err := newHVSocket()
if err != nil { if err != nil {
return nil, l.opErr("listen", err) return nil, l.opErr("listen", err)
} }
sa := addr.raw() sa := addr.raw()
err = bind(sock.handle, unsafe.Pointer(&sa), int32(unsafe.Sizeof(sa))) err = socket.Bind(windows.Handle(sock.handle), &sa)
if err != nil { if err != nil {
return nil, l.opErr("listen", os.NewSyscallError("socket", err)) return nil, l.opErr("listen", os.NewSyscallError("socket", err))
} }
@ -121,7 +224,7 @@ func (l *HvsockListener) Addr() net.Addr {
// Accept waits for the next connection and returns it. // Accept waits for the next connection and returns it.
func (l *HvsockListener) Accept() (_ net.Conn, err error) { func (l *HvsockListener) Accept() (_ net.Conn, err error) {
sock, err := newHvSocket() sock, err := newHVSocket()
if err != nil { if err != nil {
return nil, l.opErr("accept", err) return nil, l.opErr("accept", err)
} }
@ -130,27 +233,42 @@ func (l *HvsockListener) Accept() (_ net.Conn, err error) {
sock.Close() sock.Close()
} }
}() }()
c, err := l.sock.prepareIo() c, err := l.sock.prepareIO()
if err != nil { if err != nil {
return nil, l.opErr("accept", err) return nil, l.opErr("accept", err)
} }
defer l.sock.wg.Done() defer l.sock.wg.Done()
// AcceptEx, per documentation, requires an extra 16 bytes per address. // AcceptEx, per documentation, requires an extra 16 bytes per address.
//
// https://docs.microsoft.com/en-us/windows/win32/api/mswsock/nf-mswsock-acceptex
const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{})) const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{}))
var addrbuf [addrlen * 2]byte var addrbuf [addrlen * 2]byte
var bytes uint32 var bytes uint32
err = syscall.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0, addrlen, addrlen, &bytes, &c.o) err = syscall.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0 /* rxdatalen */, addrlen, addrlen, &bytes, &c.o)
_, err = l.sock.asyncIo(c, nil, bytes, err) if _, err = l.sock.asyncIO(c, nil, bytes, err); err != nil {
if err != nil {
return nil, l.opErr("accept", os.NewSyscallError("acceptex", err)) return nil, l.opErr("accept", os.NewSyscallError("acceptex", err))
} }
conn := &HvsockConn{ conn := &HvsockConn{
sock: sock, sock: sock,
} }
// The local address returned in the AcceptEx buffer is the same as the Listener socket's
// address. However, the service GUID reported by GetSockName is different from the Listeners
// socket, and is sometimes the same as the local address of the socket that dialed the
// address, with the service GUID.Data1 incremented, but othertimes is different.
// todo: does the local address matter? is the listener's address or the actual address appropriate?
conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0]))) conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0])))
conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen]))) conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen])))
// initialize the accepted socket and update its properties with those of the listening socket
if err = windows.Setsockopt(windows.Handle(sock.handle),
windows.SOL_SOCKET, windows.SO_UPDATE_ACCEPT_CONTEXT,
(*byte)(unsafe.Pointer(&l.sock.handle)), int32(unsafe.Sizeof(l.sock.handle))); err != nil {
return nil, conn.opErr("accept", os.NewSyscallError("setsockopt", err))
}
sock = nil sock = nil
return conn, nil return conn, nil
} }
@ -160,43 +278,171 @@ func (l *HvsockListener) Close() error {
return l.sock.Close() return l.sock.Close()
} }
/* Need to finish ConnectEx handling // HvsockDialer configures and dials a Hyper-V Socket (ie, [HvsockConn]).
func DialHvsock(ctx context.Context, addr *HvsockAddr) (*HvsockConn, error) { type HvsockDialer struct {
sock, err := newHvSocket() // Deadline is the time the Dial operation must connect before erroring.
Deadline time.Time
// Retries is the number of additional connects to try if the connection times out, is refused,
// or the host is unreachable
Retries uint
// RetryWait is the time to wait after a connection error to retry
RetryWait time.Duration
rt *time.Timer // redial wait timer
}
// Dial the Hyper-V socket at addr.
//
// See [HvsockDialer.Dial] for more information.
func Dial(ctx context.Context, addr *HvsockAddr) (conn *HvsockConn, err error) {
return (&HvsockDialer{}).Dial(ctx, addr)
}
// Dial attempts to connect to the Hyper-V socket at addr, and returns a connection if successful.
// Will attempt (HvsockDialer).Retries if dialing fails, waiting (HvsockDialer).RetryWait between
// retries.
//
// Dialing can be cancelled either by providing (HvsockDialer).Deadline, or cancelling ctx.
func (d *HvsockDialer) Dial(ctx context.Context, addr *HvsockAddr) (conn *HvsockConn, err error) {
op := "dial"
// create the conn early to use opErr()
conn = &HvsockConn{
remote: *addr,
}
if !d.Deadline.IsZero() {
var cancel context.CancelFunc
ctx, cancel = context.WithDeadline(ctx, d.Deadline)
defer cancel()
}
// preemptive timeout/cancellation check
if err = ctx.Err(); err != nil {
return nil, conn.opErr(op, err)
}
sock, err := newHVSocket()
if err != nil { if err != nil {
return nil, err return nil, conn.opErr(op, err)
} }
defer func() { defer func() {
if sock != nil { if sock != nil {
sock.Close() sock.Close()
} }
}() }()
c, err := sock.prepareIo()
sa := addr.raw()
err = socket.Bind(windows.Handle(sock.handle), &sa)
if err != nil { if err != nil {
return nil, err return nil, conn.opErr(op, os.NewSyscallError("bind", err))
}
c, err := sock.prepareIO()
if err != nil {
return nil, conn.opErr(op, err)
} }
defer sock.wg.Done() defer sock.wg.Done()
var bytes uint32 var bytes uint32
err = windows.ConnectEx(windows.Handle(sock.handle), sa, nil, 0, &bytes, &c.o) for i := uint(0); i <= d.Retries; i++ {
_, err = sock.asyncIo(ctx, c, nil, bytes, err) err = socket.ConnectEx(
windows.Handle(sock.handle),
&sa,
nil, // sendBuf
0, // sendDataLen
&bytes,
(*windows.Overlapped)(unsafe.Pointer(&c.o)))
_, err = sock.asyncIO(c, nil, bytes, err)
if i < d.Retries && canRedial(err) {
if err = d.redialWait(ctx); err == nil {
continue
}
}
break
}
if err != nil { if err != nil {
return nil, err return nil, conn.opErr(op, os.NewSyscallError("connectex", err))
} }
conn := &HvsockConn{
sock: sock, // update the connection properties, so shutdown can be used
remote: *addr, if err = windows.Setsockopt(
windows.Handle(sock.handle),
windows.SOL_SOCKET,
windows.SO_UPDATE_CONNECT_CONTEXT,
nil, // optvalue
0, // optlen
); err != nil {
return nil, conn.opErr(op, os.NewSyscallError("setsockopt", err))
} }
// get the local name
var sal rawHvsockAddr
err = socket.GetSockName(windows.Handle(sock.handle), &sal)
if err != nil {
return nil, conn.opErr(op, os.NewSyscallError("getsockname", err))
}
conn.local.fromRaw(&sal)
// one last check for timeout, since asyncIO doesn't check the context
if err = ctx.Err(); err != nil {
return nil, conn.opErr(op, err)
}
conn.sock = sock
sock = nil sock = nil
return conn, nil return conn, nil
} }
*/
// redialWait waits before attempting to redial, resetting the timer as appropriate.
func (d *HvsockDialer) redialWait(ctx context.Context) (err error) {
if d.RetryWait == 0 {
return nil
}
if d.rt == nil {
d.rt = time.NewTimer(d.RetryWait)
} else {
// should already be stopped and drained
d.rt.Reset(d.RetryWait)
}
select {
case <-ctx.Done():
case <-d.rt.C:
return nil
}
// stop and drain the timer
if !d.rt.Stop() {
<-d.rt.C
}
return ctx.Err()
}
// assumes error is a plain, unwrapped syscall.Errno provided by direct syscall.
func canRedial(err error) bool {
//nolint:errorlint // guaranteed to be an Errno
switch err {
case windows.WSAECONNREFUSED, windows.WSAENETUNREACH, windows.WSAETIMEDOUT,
windows.ERROR_CONNECTION_REFUSED, windows.ERROR_CONNECTION_UNAVAIL:
return true
default:
return false
}
}
func (conn *HvsockConn) opErr(op string, err error) error { func (conn *HvsockConn) opErr(op string, err error) error {
// translate from "file closed" to "socket closed"
if errors.Is(err, ErrFileClosed) {
err = socket.ErrSocketClosed
}
return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err} return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err}
} }
func (conn *HvsockConn) Read(b []byte) (int, error) { func (conn *HvsockConn) Read(b []byte) (int, error) {
c, err := conn.sock.prepareIo() c, err := conn.sock.prepareIO()
if err != nil { if err != nil {
return 0, conn.opErr("read", err) return 0, conn.opErr("read", err)
} }
@ -204,10 +450,11 @@ func (conn *HvsockConn) Read(b []byte) (int, error) {
buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))} buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))}
var flags, bytes uint32 var flags, bytes uint32
err = syscall.WSARecv(conn.sock.handle, &buf, 1, &bytes, &flags, &c.o, nil) err = syscall.WSARecv(conn.sock.handle, &buf, 1, &bytes, &flags, &c.o, nil)
n, err := conn.sock.asyncIo(c, &conn.sock.readDeadline, bytes, err) n, err := conn.sock.asyncIO(c, &conn.sock.readDeadline, bytes, err)
if err != nil { if err != nil {
if _, ok := err.(syscall.Errno); ok { var eno windows.Errno
err = os.NewSyscallError("wsarecv", err) if errors.As(err, &eno) {
err = os.NewSyscallError("wsarecv", eno)
} }
return 0, conn.opErr("read", err) return 0, conn.opErr("read", err)
} else if n == 0 { } else if n == 0 {
@ -230,7 +477,7 @@ func (conn *HvsockConn) Write(b []byte) (int, error) {
} }
func (conn *HvsockConn) write(b []byte) (int, error) { func (conn *HvsockConn) write(b []byte) (int, error) {
c, err := conn.sock.prepareIo() c, err := conn.sock.prepareIO()
if err != nil { if err != nil {
return 0, conn.opErr("write", err) return 0, conn.opErr("write", err)
} }
@ -238,10 +485,11 @@ func (conn *HvsockConn) write(b []byte) (int, error) {
buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))} buf := syscall.WSABuf{Buf: &b[0], Len: uint32(len(b))}
var bytes uint32 var bytes uint32
err = syscall.WSASend(conn.sock.handle, &buf, 1, &bytes, 0, &c.o, nil) err = syscall.WSASend(conn.sock.handle, &buf, 1, &bytes, 0, &c.o, nil)
n, err := conn.sock.asyncIo(c, &conn.sock.writeDeadline, bytes, err) n, err := conn.sock.asyncIO(c, &conn.sock.writeDeadline, bytes, err)
if err != nil { if err != nil {
if _, ok := err.(syscall.Errno); ok { var eno windows.Errno
err = os.NewSyscallError("wsasend", err) if errors.As(err, &eno) {
err = os.NewSyscallError("wsasend", eno)
} }
return 0, conn.opErr("write", err) return 0, conn.opErr("write", err)
} }
@ -257,13 +505,19 @@ func (conn *HvsockConn) IsClosed() bool {
return conn.sock.IsClosed() return conn.sock.IsClosed()
} }
// shutdown disables sending or receiving on a socket.
func (conn *HvsockConn) shutdown(how int) error { func (conn *HvsockConn) shutdown(how int) error {
if conn.IsClosed() { if conn.IsClosed() {
return ErrFileClosed return socket.ErrSocketClosed
} }
err := syscall.Shutdown(conn.sock.handle, how) err := syscall.Shutdown(conn.sock.handle, how)
if err != nil { if err != nil {
// If the connection was closed, shutdowns fail with "not connected"
if errors.Is(err, windows.WSAENOTCONN) ||
errors.Is(err, windows.WSAESHUTDOWN) {
err = socket.ErrSocketClosed
}
return os.NewSyscallError("shutdown", err) return os.NewSyscallError("shutdown", err)
} }
return nil return nil
@ -273,7 +527,7 @@ func (conn *HvsockConn) shutdown(how int) error {
func (conn *HvsockConn) CloseRead() error { func (conn *HvsockConn) CloseRead() error {
err := conn.shutdown(syscall.SHUT_RD) err := conn.shutdown(syscall.SHUT_RD)
if err != nil { if err != nil {
return conn.opErr("close", err) return conn.opErr("closeread", err)
} }
return nil return nil
} }
@ -283,7 +537,7 @@ func (conn *HvsockConn) CloseRead() error {
func (conn *HvsockConn) CloseWrite() error { func (conn *HvsockConn) CloseWrite() error {
err := conn.shutdown(syscall.SHUT_WR) err := conn.shutdown(syscall.SHUT_WR)
if err != nil { if err != nil {
return conn.opErr("close", err) return conn.opErr("closewrite", err)
} }
return nil return nil
} }
@ -300,8 +554,13 @@ func (conn *HvsockConn) RemoteAddr() net.Addr {
// SetDeadline implements the net.Conn SetDeadline method. // SetDeadline implements the net.Conn SetDeadline method.
func (conn *HvsockConn) SetDeadline(t time.Time) error { func (conn *HvsockConn) SetDeadline(t time.Time) error {
conn.SetReadDeadline(t) // todo: implement `SetDeadline` for `win32File`
conn.SetWriteDeadline(t) if err := conn.SetReadDeadline(t); err != nil {
return fmt.Errorf("set read deadline: %w", err)
}
if err := conn.SetWriteDeadline(t); err != nil {
return fmt.Errorf("set write deadline: %w", err)
}
return nil return nil
} }

View File

@ -0,0 +1,2 @@
// This package contains Win32 filesystem functionality.
package fs

202
vendor/github.com/Microsoft/go-winio/internal/fs/fs.go generated vendored Normal file
View File

@ -0,0 +1,202 @@
//go:build windows
package fs
import (
"golang.org/x/sys/windows"
"github.com/Microsoft/go-winio/internal/stringbuffer"
)
//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go fs.go
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
//sys CreateFile(name string, access AccessMask, mode FileShareMode, sa *syscall.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) [failretval==windows.InvalidHandle] = CreateFileW
const NullHandle windows.Handle = 0
// AccessMask defines standard, specific, and generic rights.
//
// Bitmask:
// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
// +---------------+---------------+-------------------------------+
// |G|G|G|G|Resvd|A| StandardRights| SpecificRights |
// |R|W|E|A| |S| | |
// +-+-------------+---------------+-------------------------------+
//
// GR Generic Read
// GW Generic Write
// GE Generic Exectue
// GA Generic All
// Resvd Reserved
// AS Access Security System
//
// https://learn.microsoft.com/en-us/windows/win32/secauthz/access-mask
//
// https://learn.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights
//
// https://learn.microsoft.com/en-us/windows/win32/fileio/file-access-rights-constants
type AccessMask = windows.ACCESS_MASK
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
const (
// Not actually any.
//
// For CreateFile: "query certain metadata such as file, directory, or device attributes without accessing that file or device"
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew#parameters
FILE_ANY_ACCESS AccessMask = 0
// Specific Object Access
// from ntioapi.h
FILE_READ_DATA AccessMask = (0x0001) // file & pipe
FILE_LIST_DIRECTORY AccessMask = (0x0001) // directory
FILE_WRITE_DATA AccessMask = (0x0002) // file & pipe
FILE_ADD_FILE AccessMask = (0x0002) // directory
FILE_APPEND_DATA AccessMask = (0x0004) // file
FILE_ADD_SUBDIRECTORY AccessMask = (0x0004) // directory
FILE_CREATE_PIPE_INSTANCE AccessMask = (0x0004) // named pipe
FILE_READ_EA AccessMask = (0x0008) // file & directory
FILE_READ_PROPERTIES AccessMask = FILE_READ_EA
FILE_WRITE_EA AccessMask = (0x0010) // file & directory
FILE_WRITE_PROPERTIES AccessMask = FILE_WRITE_EA
FILE_EXECUTE AccessMask = (0x0020) // file
FILE_TRAVERSE AccessMask = (0x0020) // directory
FILE_DELETE_CHILD AccessMask = (0x0040) // directory
FILE_READ_ATTRIBUTES AccessMask = (0x0080) // all
FILE_WRITE_ATTRIBUTES AccessMask = (0x0100) // all
FILE_ALL_ACCESS AccessMask = (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF)
FILE_GENERIC_READ AccessMask = (STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE)
FILE_GENERIC_WRITE AccessMask = (STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | SYNCHRONIZE)
FILE_GENERIC_EXECUTE AccessMask = (STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES | FILE_EXECUTE | SYNCHRONIZE)
SPECIFIC_RIGHTS_ALL AccessMask = 0x0000FFFF
// Standard Access
// from ntseapi.h
DELETE AccessMask = 0x0001_0000
READ_CONTROL AccessMask = 0x0002_0000
WRITE_DAC AccessMask = 0x0004_0000
WRITE_OWNER AccessMask = 0x0008_0000
SYNCHRONIZE AccessMask = 0x0010_0000
STANDARD_RIGHTS_REQUIRED AccessMask = 0x000F_0000
STANDARD_RIGHTS_READ AccessMask = READ_CONTROL
STANDARD_RIGHTS_WRITE AccessMask = READ_CONTROL
STANDARD_RIGHTS_EXECUTE AccessMask = READ_CONTROL
STANDARD_RIGHTS_ALL AccessMask = 0x001F_0000
)
type FileShareMode uint32
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
const (
FILE_SHARE_NONE FileShareMode = 0x00
FILE_SHARE_READ FileShareMode = 0x01
FILE_SHARE_WRITE FileShareMode = 0x02
FILE_SHARE_DELETE FileShareMode = 0x04
FILE_SHARE_VALID_FLAGS FileShareMode = 0x07
)
type FileCreationDisposition uint32
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
const (
// from winbase.h
CREATE_NEW FileCreationDisposition = 0x01
CREATE_ALWAYS FileCreationDisposition = 0x02
OPEN_EXISTING FileCreationDisposition = 0x03
OPEN_ALWAYS FileCreationDisposition = 0x04
TRUNCATE_EXISTING FileCreationDisposition = 0x05
)
// CreateFile and co. take flags or attributes together as one parameter.
// Define alias until we can use generics to allow both
// https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
type FileFlagOrAttribute uint32
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
const ( // from winnt.h
FILE_FLAG_WRITE_THROUGH FileFlagOrAttribute = 0x8000_0000
FILE_FLAG_OVERLAPPED FileFlagOrAttribute = 0x4000_0000
FILE_FLAG_NO_BUFFERING FileFlagOrAttribute = 0x2000_0000
FILE_FLAG_RANDOM_ACCESS FileFlagOrAttribute = 0x1000_0000
FILE_FLAG_SEQUENTIAL_SCAN FileFlagOrAttribute = 0x0800_0000
FILE_FLAG_DELETE_ON_CLOSE FileFlagOrAttribute = 0x0400_0000
FILE_FLAG_BACKUP_SEMANTICS FileFlagOrAttribute = 0x0200_0000
FILE_FLAG_POSIX_SEMANTICS FileFlagOrAttribute = 0x0100_0000
FILE_FLAG_OPEN_REPARSE_POINT FileFlagOrAttribute = 0x0020_0000
FILE_FLAG_OPEN_NO_RECALL FileFlagOrAttribute = 0x0010_0000
FILE_FLAG_FIRST_PIPE_INSTANCE FileFlagOrAttribute = 0x0008_0000
)
type FileSQSFlag = FileFlagOrAttribute
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
const ( // from winbase.h
SECURITY_ANONYMOUS FileSQSFlag = FileSQSFlag(SecurityAnonymous << 16)
SECURITY_IDENTIFICATION FileSQSFlag = FileSQSFlag(SecurityIdentification << 16)
SECURITY_IMPERSONATION FileSQSFlag = FileSQSFlag(SecurityImpersonation << 16)
SECURITY_DELEGATION FileSQSFlag = FileSQSFlag(SecurityDelegation << 16)
SECURITY_SQOS_PRESENT FileSQSFlag = 0x00100000
SECURITY_VALID_SQOS_FLAGS FileSQSFlag = 0x001F0000
)
// GetFinalPathNameByHandle flags
//
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew#parameters
type GetFinalPathFlag uint32
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
const (
GetFinalPathDefaultFlag GetFinalPathFlag = 0x0
FILE_NAME_NORMALIZED GetFinalPathFlag = 0x0
FILE_NAME_OPENED GetFinalPathFlag = 0x8
VOLUME_NAME_DOS GetFinalPathFlag = 0x0
VOLUME_NAME_GUID GetFinalPathFlag = 0x1
VOLUME_NAME_NT GetFinalPathFlag = 0x2
VOLUME_NAME_NONE GetFinalPathFlag = 0x4
)
// getFinalPathNameByHandle facilitates calling the Windows API GetFinalPathNameByHandle
// with the given handle and flags. It transparently takes care of creating a buffer of the
// correct size for the call.
//
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew
func GetFinalPathNameByHandle(h windows.Handle, flags GetFinalPathFlag) (string, error) {
b := stringbuffer.NewWString()
//TODO: can loop infinitely if Win32 keeps returning the same (or a larger) n?
for {
n, err := windows.GetFinalPathNameByHandle(h, b.Pointer(), b.Cap(), uint32(flags))
if err != nil {
return "", err
}
// If the buffer wasn't large enough, n will be the total size needed (including null terminator).
// Resize and try again.
if n > b.Cap() {
b.ResizeTo(n)
continue
}
// If the buffer is large enough, n will be the size not including the null terminator.
// Convert to a Go string and return.
return b.String(), nil
}
}

View File

@ -0,0 +1,12 @@
package fs
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level
type SecurityImpersonationLevel int32 // C default enums underlying type is `int`, which is Go `int32`
// Impersonation levels
const (
SecurityAnonymous SecurityImpersonationLevel = 0
SecurityIdentification SecurityImpersonationLevel = 1
SecurityImpersonation SecurityImpersonationLevel = 2
SecurityDelegation SecurityImpersonationLevel = 3
)

View File

@ -0,0 +1,64 @@
//go:build windows
// Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT.
package fs
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
errERROR_EINVAL error = syscall.EINVAL
)
// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return errERROR_EINVAL
case errnoERROR_IO_PENDING:
return errERROR_IO_PENDING
}
// TODO: add more here, after collecting data on the common
// error values see on Windows. (perhaps when running
// all.bat?)
return e
}
var (
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
procCreateFileW = modkernel32.NewProc("CreateFileW")
)
func CreateFile(name string, access AccessMask, mode FileShareMode, sa *syscall.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(name)
if err != nil {
return
}
return _CreateFile(_p0, access, mode, sa, createmode, attrs, templatefile)
}
func _CreateFile(name *uint16, access AccessMask, mode FileShareMode, sa *syscall.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) {
r0, _, e1 := syscall.Syscall9(procCreateFileW.Addr(), 7, uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile), 0, 0)
handle = windows.Handle(r0)
if handle == windows.InvalidHandle {
err = errnoErr(e1)
}
return
}

View File

@ -0,0 +1,20 @@
package socket
import (
"unsafe"
)
// RawSockaddr allows structs to be used with [Bind] and [ConnectEx]. The
// struct must meet the Win32 sockaddr requirements specified here:
// https://docs.microsoft.com/en-us/windows/win32/winsock/sockaddr-2
//
// Specifically, the struct size must be least larger than an int16 (unsigned short)
// for the address family.
type RawSockaddr interface {
// Sockaddr returns a pointer to the RawSockaddr and its struct size, allowing
// for the RawSockaddr's data to be overwritten by syscalls (if necessary).
//
// It is the callers responsibility to validate that the values are valid; invalid
// pointers or size can cause a panic.
Sockaddr() (unsafe.Pointer, int32, error)
}

View File

@ -0,0 +1,179 @@
//go:build windows
package socket
import (
"errors"
"fmt"
"net"
"sync"
"syscall"
"unsafe"
"github.com/Microsoft/go-winio/pkg/guid"
"golang.org/x/sys/windows"
)
//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go socket.go
//sys getsockname(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) [failretval==socketError] = ws2_32.getsockname
//sys getpeername(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) [failretval==socketError] = ws2_32.getpeername
//sys bind(s windows.Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socketError] = ws2_32.bind
const socketError = uintptr(^uint32(0))
var (
// todo(helsaawy): create custom error types to store the desired vs actual size and addr family?
ErrBufferSize = errors.New("buffer size")
ErrAddrFamily = errors.New("address family")
ErrInvalidPointer = errors.New("invalid pointer")
ErrSocketClosed = fmt.Errorf("socket closed: %w", net.ErrClosed)
)
// todo(helsaawy): replace these with generics, ie: GetSockName[S RawSockaddr](s windows.Handle) (S, error)
// GetSockName writes the local address of socket s to the [RawSockaddr] rsa.
// If rsa is not large enough, the [windows.WSAEFAULT] is returned.
func GetSockName(s windows.Handle, rsa RawSockaddr) error {
ptr, l, err := rsa.Sockaddr()
if err != nil {
return fmt.Errorf("could not retrieve socket pointer and size: %w", err)
}
// although getsockname returns WSAEFAULT if the buffer is too small, it does not set
// &l to the correct size, so--apart from doubling the buffer repeatedly--there is no remedy
return getsockname(s, ptr, &l)
}
// GetPeerName returns the remote address the socket is connected to.
//
// See [GetSockName] for more information.
func GetPeerName(s windows.Handle, rsa RawSockaddr) error {
ptr, l, err := rsa.Sockaddr()
if err != nil {
return fmt.Errorf("could not retrieve socket pointer and size: %w", err)
}
return getpeername(s, ptr, &l)
}
func Bind(s windows.Handle, rsa RawSockaddr) (err error) {
ptr, l, err := rsa.Sockaddr()
if err != nil {
return fmt.Errorf("could not retrieve socket pointer and size: %w", err)
}
return bind(s, ptr, l)
}
// "golang.org/x/sys/windows".ConnectEx and .Bind only accept internal implementations of the
// their sockaddr interface, so they cannot be used with HvsockAddr
// Replicate functionality here from
// https://cs.opensource.google/go/x/sys/+/master:windows/syscall_windows.go
// The function pointers to `AcceptEx`, `ConnectEx` and `GetAcceptExSockaddrs` must be loaded at
// runtime via a WSAIoctl call:
// https://docs.microsoft.com/en-us/windows/win32/api/Mswsock/nc-mswsock-lpfn_connectex#remarks
type runtimeFunc struct {
id guid.GUID
once sync.Once
addr uintptr
err error
}
func (f *runtimeFunc) Load() error {
f.once.Do(func() {
var s windows.Handle
s, f.err = windows.Socket(windows.AF_INET, windows.SOCK_STREAM, windows.IPPROTO_TCP)
if f.err != nil {
return
}
defer windows.CloseHandle(s) //nolint:errcheck
var n uint32
f.err = windows.WSAIoctl(s,
windows.SIO_GET_EXTENSION_FUNCTION_POINTER,
(*byte)(unsafe.Pointer(&f.id)),
uint32(unsafe.Sizeof(f.id)),
(*byte)(unsafe.Pointer(&f.addr)),
uint32(unsafe.Sizeof(f.addr)),
&n,
nil, // overlapped
0, // completionRoutine
)
})
return f.err
}
var (
// todo: add `AcceptEx` and `GetAcceptExSockaddrs`
WSAID_CONNECTEX = guid.GUID{ //revive:disable-line:var-naming ALL_CAPS
Data1: 0x25a207b9,
Data2: 0xddf3,
Data3: 0x4660,
Data4: [8]byte{0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e},
}
connectExFunc = runtimeFunc{id: WSAID_CONNECTEX}
)
func ConnectEx(
fd windows.Handle,
rsa RawSockaddr,
sendBuf *byte,
sendDataLen uint32,
bytesSent *uint32,
overlapped *windows.Overlapped,
) error {
if err := connectExFunc.Load(); err != nil {
return fmt.Errorf("failed to load ConnectEx function pointer: %w", err)
}
ptr, n, err := rsa.Sockaddr()
if err != nil {
return err
}
return connectEx(fd, ptr, n, sendBuf, sendDataLen, bytesSent, overlapped)
}
// BOOL LpfnConnectex(
// [in] SOCKET s,
// [in] const sockaddr *name,
// [in] int namelen,
// [in, optional] PVOID lpSendBuffer,
// [in] DWORD dwSendDataLength,
// [out] LPDWORD lpdwBytesSent,
// [in] LPOVERLAPPED lpOverlapped
// )
func connectEx(
s windows.Handle,
name unsafe.Pointer,
namelen int32,
sendBuf *byte,
sendDataLen uint32,
bytesSent *uint32,
overlapped *windows.Overlapped,
) (err error) {
// todo: after upgrading to 1.18, switch from syscall.Syscall9 to syscall.SyscallN
r1, _, e1 := syscall.Syscall9(connectExFunc.addr,
7,
uintptr(s),
uintptr(name),
uintptr(namelen),
uintptr(unsafe.Pointer(sendBuf)),
uintptr(sendDataLen),
uintptr(unsafe.Pointer(bytesSent)),
uintptr(unsafe.Pointer(overlapped)),
0,
0)
if r1 == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = syscall.EINVAL
}
}
return err
}

View File

@ -0,0 +1,72 @@
//go:build windows
// Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT.
package socket
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
errERROR_EINVAL error = syscall.EINVAL
)
// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return errERROR_EINVAL
case errnoERROR_IO_PENDING:
return errERROR_IO_PENDING
}
// TODO: add more here, after collecting data on the common
// error values see on Windows. (perhaps when running
// all.bat?)
return e
}
var (
modws2_32 = windows.NewLazySystemDLL("ws2_32.dll")
procbind = modws2_32.NewProc("bind")
procgetpeername = modws2_32.NewProc("getpeername")
procgetsockname = modws2_32.NewProc("getsockname")
)
func bind(s windows.Handle, name unsafe.Pointer, namelen int32) (err error) {
r1, _, e1 := syscall.Syscall(procbind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))
if r1 == socketError {
err = errnoErr(e1)
}
return
}
func getpeername(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) {
r1, _, e1 := syscall.Syscall(procgetpeername.Addr(), 3, uintptr(s), uintptr(name), uintptr(unsafe.Pointer(namelen)))
if r1 == socketError {
err = errnoErr(e1)
}
return
}
func getsockname(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) {
r1, _, e1 := syscall.Syscall(procgetsockname.Addr(), 3, uintptr(s), uintptr(name), uintptr(unsafe.Pointer(namelen)))
if r1 == socketError {
err = errnoErr(e1)
}
return
}

View File

@ -0,0 +1,132 @@
package stringbuffer
import (
"sync"
"unicode/utf16"
)
// TODO: worth exporting and using in mkwinsyscall?
// Uint16BufferSize is the buffer size in the pool, chosen somewhat arbitrarily to accommodate
// large path strings:
// MAX_PATH (260) + size of volume GUID prefix (49) + null terminator = 310.
const MinWStringCap = 310
// use *[]uint16 since []uint16 creates an extra allocation where the slice header
// is copied to heap and then referenced via pointer in the interface header that sync.Pool
// stores.
var pathPool = sync.Pool{ // if go1.18+ adds Pool[T], use that to store []uint16 directly
New: func() interface{} {
b := make([]uint16, MinWStringCap)
return &b
},
}
func newBuffer() []uint16 { return *(pathPool.Get().(*[]uint16)) }
// freeBuffer copies the slice header data, and puts a pointer to that in the pool.
// This avoids taking a pointer to the slice header in WString, which can be set to nil.
func freeBuffer(b []uint16) { pathPool.Put(&b) }
// WString is a wide string buffer ([]uint16) meant for storing UTF-16 encoded strings
// for interacting with Win32 APIs.
// Sizes are specified as uint32 and not int.
//
// It is not thread safe.
type WString struct {
// type-def allows casting to []uint16 directly, use struct to prevent that and allow adding fields in the future.
// raw buffer
b []uint16
}
// NewWString returns a [WString] allocated from a shared pool with an
// initial capacity of at least [MinWStringCap].
// Since the buffer may have been previously used, its contents are not guaranteed to be empty.
//
// The buffer should be freed via [WString.Free]
func NewWString() *WString {
return &WString{
b: newBuffer(),
}
}
func (b *WString) Free() {
if b.empty() {
return
}
freeBuffer(b.b)
b.b = nil
}
// ResizeTo grows the buffer to at least c and returns the new capacity, freeing the
// previous buffer back into pool.
func (b *WString) ResizeTo(c uint32) uint32 {
// allready sufficient (or n is 0)
if c <= b.Cap() {
return b.Cap()
}
if c <= MinWStringCap {
c = MinWStringCap
}
// allocate at-least double buffer size, as is done in [bytes.Buffer] and other places
if c <= 2*b.Cap() {
c = 2 * b.Cap()
}
b2 := make([]uint16, c)
if !b.empty() {
copy(b2, b.b)
freeBuffer(b.b)
}
b.b = b2
return c
}
// Buffer returns the underlying []uint16 buffer.
func (b *WString) Buffer() []uint16 {
if b.empty() {
return nil
}
return b.b
}
// Pointer returns a pointer to the first uint16 in the buffer.
// If the [WString.Free] has already been called, the pointer will be nil.
func (b *WString) Pointer() *uint16 {
if b.empty() {
return nil
}
return &b.b[0]
}
// String returns the returns the UTF-8 encoding of the UTF-16 string in the buffer.
//
// It assumes that the data is null-terminated.
func (b *WString) String() string {
// Using [windows.UTF16ToString] would require importing "golang.org/x/sys/windows"
// and would make this code Windows-only, which makes no sense.
// So copy UTF16ToString code into here.
// If other windows-specific code is added, switch to [windows.UTF16ToString]
s := b.b
for i, v := range s {
if v == 0 {
s = s[:i]
break
}
}
return string(utf16.Decode(s))
}
// Cap returns the underlying buffer capacity.
func (b *WString) Cap() uint32 {
if b.empty() {
return 0
}
return b.cap()
}
func (b *WString) cap() uint32 { return uint32(cap(b.b)) }
func (b *WString) empty() bool { return b == nil || b.cap() == 0 }

View File

@ -1,3 +1,4 @@
//go:build windows
// +build windows // +build windows
package winio package winio
@ -13,18 +14,21 @@ import (
"syscall" "syscall"
"time" "time"
"unsafe" "unsafe"
"golang.org/x/sys/windows"
"github.com/Microsoft/go-winio/internal/fs"
) )
//sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe //sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe
//sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW //sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW
//sys createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateFileW
//sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo //sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo
//sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW //sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
//sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc //sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc
//sys ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) = ntdll.NtCreateNamedPipeFile //sys ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntStatus) = ntdll.NtCreateNamedPipeFile
//sys rtlNtStatusToDosError(status ntstatus) (winerr error) = ntdll.RtlNtStatusToDosErrorNoTeb //sys rtlNtStatusToDosError(status ntStatus) (winerr error) = ntdll.RtlNtStatusToDosErrorNoTeb
//sys rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) = ntdll.RtlDosPathNameToNtPathName_U //sys rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntStatus) = ntdll.RtlDosPathNameToNtPathName_U
//sys rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) = ntdll.RtlDefaultNpAcl //sys rtlDefaultNpAcl(dacl *uintptr) (status ntStatus) = ntdll.RtlDefaultNpAcl
type ioStatusBlock struct { type ioStatusBlock struct {
Status, Information uintptr Status, Information uintptr
@ -51,45 +55,22 @@ type securityDescriptor struct {
Control uint16 Control uint16
Owner uintptr Owner uintptr
Group uintptr Group uintptr
Sacl uintptr Sacl uintptr //revive:disable-line:var-naming SACL, not Sacl
Dacl uintptr Dacl uintptr //revive:disable-line:var-naming DACL, not Dacl
} }
type ntstatus int32 type ntStatus int32
func (status ntstatus) Err() error { func (status ntStatus) Err() error {
if status >= 0 { if status >= 0 {
return nil return nil
} }
return rtlNtStatusToDosError(status) return rtlNtStatusToDosError(status)
} }
const (
cERROR_PIPE_BUSY = syscall.Errno(231)
cERROR_NO_DATA = syscall.Errno(232)
cERROR_PIPE_CONNECTED = syscall.Errno(535)
cERROR_SEM_TIMEOUT = syscall.Errno(121)
cSECURITY_SQOS_PRESENT = 0x100000
cSECURITY_ANONYMOUS = 0
cPIPE_TYPE_MESSAGE = 4
cPIPE_READMODE_MESSAGE = 2
cFILE_OPEN = 1
cFILE_CREATE = 2
cFILE_PIPE_MESSAGE_TYPE = 1
cFILE_PIPE_REJECT_REMOTE_CLIENTS = 2
cSE_DACL_PRESENT = 4
)
var ( var (
// ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed. // ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed.
// This error should match net.errClosing since docker takes a dependency on its text. ErrPipeListenerClosed = net.ErrClosed
ErrPipeListenerClosed = errors.New("use of closed network connection")
errPipeWriteClosed = errors.New("pipe has been closed for write") errPipeWriteClosed = errors.New("pipe has been closed for write")
) )
@ -116,9 +97,10 @@ func (f *win32Pipe) RemoteAddr() net.Addr {
} }
func (f *win32Pipe) SetDeadline(t time.Time) error { func (f *win32Pipe) SetDeadline(t time.Time) error {
f.SetReadDeadline(t) if err := f.SetReadDeadline(t); err != nil {
f.SetWriteDeadline(t) return err
return nil }
return f.SetWriteDeadline(t)
} }
// CloseWrite closes the write side of a message pipe in byte mode. // CloseWrite closes the write side of a message pipe in byte mode.
@ -157,14 +139,14 @@ func (f *win32MessageBytePipe) Read(b []byte) (int, error) {
return 0, io.EOF return 0, io.EOF
} }
n, err := f.win32File.Read(b) n, err := f.win32File.Read(b)
if err == io.EOF { if err == io.EOF { //nolint:errorlint
// If this was the result of a zero-byte read, then // If this was the result of a zero-byte read, then
// it is possible that the read was due to a zero-size // it is possible that the read was due to a zero-size
// message. Since we are simulating CloseWrite with a // message. Since we are simulating CloseWrite with a
// zero-byte message, ensure that all future Read() calls // zero-byte message, ensure that all future Read() calls
// also return EOF. // also return EOF.
f.readEOF = true f.readEOF = true
} else if err == syscall.ERROR_MORE_DATA { } else if err == syscall.ERROR_MORE_DATA { //nolint:errorlint // err is Errno
// ERROR_MORE_DATA indicates that the pipe's read mode is message mode // ERROR_MORE_DATA indicates that the pipe's read mode is message mode
// and the message still has more bytes. Treat this as a success, since // and the message still has more bytes. Treat this as a success, since
// this package presents all named pipes as byte streams. // this package presents all named pipes as byte streams.
@ -173,7 +155,7 @@ func (f *win32MessageBytePipe) Read(b []byte) (int, error) {
return n, err return n, err
} }
func (s pipeAddress) Network() string { func (pipeAddress) Network() string {
return "pipe" return "pipe"
} }
@ -182,18 +164,25 @@ func (s pipeAddress) String() string {
} }
// tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout. // tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout.
func tryDialPipe(ctx context.Context, path *string, access uint32) (syscall.Handle, error) { func tryDialPipe(ctx context.Context, path *string, access fs.AccessMask) (syscall.Handle, error) {
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return syscall.Handle(0), ctx.Err() return syscall.Handle(0), ctx.Err()
default: default:
h, err := createFile(*path, access, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_OVERLAPPED|cSECURITY_SQOS_PRESENT|cSECURITY_ANONYMOUS, 0) wh, err := fs.CreateFile(*path,
access,
0, // mode
nil, // security attributes
fs.OPEN_EXISTING,
fs.FILE_FLAG_OVERLAPPED|fs.SECURITY_SQOS_PRESENT|fs.SECURITY_ANONYMOUS,
0, // template file handle
)
h := syscall.Handle(wh)
if err == nil { if err == nil {
return h, nil return h, nil
} }
if err != cERROR_PIPE_BUSY { if err != windows.ERROR_PIPE_BUSY { //nolint:errorlint // err is Errno
return h, &os.PathError{Err: err, Op: "open", Path: *path} return h, &os.PathError{Err: err, Op: "open", Path: *path}
} }
// Wait 10 msec and try again. This is a rather simplistic // Wait 10 msec and try again. This is a rather simplistic
@ -213,9 +202,10 @@ func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
} else { } else {
absTimeout = time.Now().Add(2 * time.Second) absTimeout = time.Now().Add(2 * time.Second)
} }
ctx, _ := context.WithDeadline(context.Background(), absTimeout) ctx, cancel := context.WithDeadline(context.Background(), absTimeout)
defer cancel()
conn, err := DialPipeContext(ctx, path) conn, err := DialPipeContext(ctx, path)
if err == context.DeadlineExceeded { if errors.Is(err, context.DeadlineExceeded) {
return nil, ErrTimeout return nil, ErrTimeout
} }
return conn, err return conn, err
@ -232,7 +222,7 @@ func DialPipeContext(ctx context.Context, path string) (net.Conn, error) {
func DialPipeAccess(ctx context.Context, path string, access uint32) (net.Conn, error) { func DialPipeAccess(ctx context.Context, path string, access uint32) (net.Conn, error) {
var err error var err error
var h syscall.Handle var h syscall.Handle
h, err = tryDialPipe(ctx, &path, access) h, err = tryDialPipe(ctx, &path, fs.AccessMask(access))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -251,7 +241,7 @@ func DialPipeAccess(ctx context.Context, path string, access uint32) (net.Conn,
// If the pipe is in message mode, return a message byte pipe, which // If the pipe is in message mode, return a message byte pipe, which
// supports CloseWrite(). // supports CloseWrite().
if flags&cPIPE_TYPE_MESSAGE != 0 { if flags&windows.PIPE_TYPE_MESSAGE != 0 {
return &win32MessageBytePipe{ return &win32MessageBytePipe{
win32Pipe: win32Pipe{win32File: f, path: path}, win32Pipe: win32Pipe{win32File: f, path: path},
}, nil }, nil
@ -283,17 +273,22 @@ func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (sy
oa.Length = unsafe.Sizeof(oa) oa.Length = unsafe.Sizeof(oa)
var ntPath unicodeString var ntPath unicodeString
if err := rtlDosPathNameToNtPathName(&path16[0], &ntPath, 0, 0).Err(); err != nil { if err := rtlDosPathNameToNtPathName(&path16[0],
&ntPath,
0,
0,
).Err(); err != nil {
return 0, &os.PathError{Op: "open", Path: path, Err: err} return 0, &os.PathError{Op: "open", Path: path, Err: err}
} }
defer localFree(ntPath.Buffer) defer localFree(ntPath.Buffer)
oa.ObjectName = &ntPath oa.ObjectName = &ntPath
oa.Attributes = windows.OBJ_CASE_INSENSITIVE
// The security descriptor is only needed for the first pipe. // The security descriptor is only needed for the first pipe.
if first { if first {
if sd != nil { if sd != nil {
len := uint32(len(sd)) l := uint32(len(sd))
sdb := localAlloc(0, len) sdb := localAlloc(0, l)
defer localFree(sdb) defer localFree(sdb)
copy((*[0xffff]byte)(unsafe.Pointer(sdb))[:], sd) copy((*[0xffff]byte)(unsafe.Pointer(sdb))[:], sd)
oa.SecurityDescriptor = (*securityDescriptor)(unsafe.Pointer(sdb)) oa.SecurityDescriptor = (*securityDescriptor)(unsafe.Pointer(sdb))
@ -301,28 +296,28 @@ func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (sy
// Construct the default named pipe security descriptor. // Construct the default named pipe security descriptor.
var dacl uintptr var dacl uintptr
if err := rtlDefaultNpAcl(&dacl).Err(); err != nil { if err := rtlDefaultNpAcl(&dacl).Err(); err != nil {
return 0, fmt.Errorf("getting default named pipe ACL: %s", err) return 0, fmt.Errorf("getting default named pipe ACL: %w", err)
} }
defer localFree(dacl) defer localFree(dacl)
sdb := &securityDescriptor{ sdb := &securityDescriptor{
Revision: 1, Revision: 1,
Control: cSE_DACL_PRESENT, Control: windows.SE_DACL_PRESENT,
Dacl: dacl, Dacl: dacl,
} }
oa.SecurityDescriptor = sdb oa.SecurityDescriptor = sdb
} }
} }
typ := uint32(cFILE_PIPE_REJECT_REMOTE_CLIENTS) typ := uint32(windows.FILE_PIPE_REJECT_REMOTE_CLIENTS)
if c.MessageMode { if c.MessageMode {
typ |= cFILE_PIPE_MESSAGE_TYPE typ |= windows.FILE_PIPE_MESSAGE_TYPE
} }
disposition := uint32(cFILE_OPEN) disposition := uint32(windows.FILE_OPEN)
access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE | syscall.SYNCHRONIZE) access := uint32(syscall.GENERIC_READ | syscall.GENERIC_WRITE | syscall.SYNCHRONIZE)
if first { if first {
disposition = cFILE_CREATE disposition = windows.FILE_CREATE
// By not asking for read or write access, the named pipe file system // By not asking for read or write access, the named pipe file system
// will put this pipe into an initially disconnected state, blocking // will put this pipe into an initially disconnected state, blocking
// client connections until the next call with first == false. // client connections until the next call with first == false.
@ -335,7 +330,20 @@ func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (sy
h syscall.Handle h syscall.Handle
iosb ioStatusBlock iosb ioStatusBlock
) )
err = ntCreateNamedPipeFile(&h, access, &oa, &iosb, syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE, disposition, 0, typ, 0, 0, 0xffffffff, uint32(c.InputBufferSize), uint32(c.OutputBufferSize), &timeout).Err() err = ntCreateNamedPipeFile(&h,
access,
&oa,
&iosb,
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE,
disposition,
0,
typ,
0,
0,
0xffffffff,
uint32(c.InputBufferSize),
uint32(c.OutputBufferSize),
&timeout).Err()
if err != nil { if err != nil {
return 0, &os.PathError{Op: "open", Path: path, Err: err} return 0, &os.PathError{Op: "open", Path: path, Err: err}
} }
@ -380,7 +388,7 @@ func (l *win32PipeListener) makeConnectedServerPipe() (*win32File, error) {
p.Close() p.Close()
p = nil p = nil
err = <-ch err = <-ch
if err == nil || err == ErrFileClosed { if err == nil || err == ErrFileClosed { //nolint:errorlint // err is Errno
err = ErrPipeListenerClosed err = ErrPipeListenerClosed
} }
} }
@ -402,12 +410,12 @@ func (l *win32PipeListener) listenerRoutine() {
p, err = l.makeConnectedServerPipe() p, err = l.makeConnectedServerPipe()
// If the connection was immediately closed by the client, try // If the connection was immediately closed by the client, try
// again. // again.
if err != cERROR_NO_DATA { if err != windows.ERROR_NO_DATA { //nolint:errorlint // err is Errno
break break
} }
} }
responseCh <- acceptResponse{p, err} responseCh <- acceptResponse{p, err}
closed = err == ErrPipeListenerClosed closed = err == ErrPipeListenerClosed //nolint:errorlint // err is Errno
} }
} }
syscall.Close(l.firstHandle) syscall.Close(l.firstHandle)
@ -469,15 +477,15 @@ func ListenPipe(path string, c *PipeConfig) (net.Listener, error) {
} }
func connectPipe(p *win32File) error { func connectPipe(p *win32File) error {
c, err := p.prepareIo() c, err := p.prepareIO()
if err != nil { if err != nil {
return err return err
} }
defer p.wg.Done() defer p.wg.Done()
err = connectNamedPipe(p.handle, &c.o) err = connectNamedPipe(p.handle, &c.o)
_, err = p.asyncIo(c, nil, 0, err) _, err = p.asyncIO(c, nil, 0, err)
if err != nil && err != cERROR_PIPE_CONNECTED { if err != nil && err != windows.ERROR_PIPE_CONNECTED { //nolint:errorlint // err is Errno
return err return err
} }
return nil return nil

View File

@ -1,5 +1,3 @@
// +build windows
// Package guid provides a GUID type. The backing structure for a GUID is // Package guid provides a GUID type. The backing structure for a GUID is
// identical to that used by the golang.org/x/sys/windows GUID type. // identical to that used by the golang.org/x/sys/windows GUID type.
// There are two main binary encodings used for a GUID, the big-endian encoding, // There are two main binary encodings used for a GUID, the big-endian encoding,
@ -9,24 +7,26 @@ package guid
import ( import (
"crypto/rand" "crypto/rand"
"crypto/sha1" "crypto/sha1" //nolint:gosec // not used for secure application
"encoding" "encoding"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"strconv" "strconv"
) )
//go:generate go run golang.org/x/tools/cmd/stringer -type=Variant -trimprefix=Variant -linecomment
// Variant specifies which GUID variant (or "type") of the GUID. It determines // Variant specifies which GUID variant (or "type") of the GUID. It determines
// how the entirety of the rest of the GUID is interpreted. // how the entirety of the rest of the GUID is interpreted.
type Variant uint8 type Variant uint8
// The variants specified by RFC 4122. // The variants specified by RFC 4122 section 4.1.1.
const ( const (
// VariantUnknown specifies a GUID variant which does not conform to one of // VariantUnknown specifies a GUID variant which does not conform to one of
// the variant encodings specified in RFC 4122. // the variant encodings specified in RFC 4122.
VariantUnknown Variant = iota VariantUnknown Variant = iota
VariantNCS VariantNCS
VariantRFC4122 VariantRFC4122 // RFC 4122
VariantMicrosoft VariantMicrosoft
VariantFuture VariantFuture
) )
@ -36,6 +36,10 @@ const (
// hash of an input string. // hash of an input string.
type Version uint8 type Version uint8
func (v Version) String() string {
return strconv.FormatUint(uint64(v), 10)
}
var _ = (encoding.TextMarshaler)(GUID{}) var _ = (encoding.TextMarshaler)(GUID{})
var _ = (encoding.TextUnmarshaler)(&GUID{}) var _ = (encoding.TextUnmarshaler)(&GUID{})
@ -61,7 +65,7 @@ func NewV4() (GUID, error) {
// big-endian UTF16 stream of bytes. If that is desired, the string can be // big-endian UTF16 stream of bytes. If that is desired, the string can be
// encoded as such before being passed to this function. // encoded as such before being passed to this function.
func NewV5(namespace GUID, name []byte) (GUID, error) { func NewV5(namespace GUID, name []byte) (GUID, error) {
b := sha1.New() b := sha1.New() //nolint:gosec // not used for secure application
namespaceBytes := namespace.ToArray() namespaceBytes := namespace.ToArray()
b.Write(namespaceBytes[:]) b.Write(namespaceBytes[:])
b.Write(name) b.Write(name)

View File

@ -1,3 +1,4 @@
//go:build !windows
// +build !windows // +build !windows
package guid package guid

View File

@ -1,3 +1,6 @@
//go:build windows
// +build windows
package guid package guid
import "golang.org/x/sys/windows" import "golang.org/x/sys/windows"

View File

@ -0,0 +1,27 @@
// Code generated by "stringer -type=Variant -trimprefix=Variant -linecomment"; DO NOT EDIT.
package guid
import "strconv"
func _() {
// An "invalid array index" compiler error signifies that the constant values have changed.
// Re-run the stringer command to generate them again.
var x [1]struct{}
_ = x[VariantUnknown-0]
_ = x[VariantNCS-1]
_ = x[VariantRFC4122-2]
_ = x[VariantMicrosoft-3]
_ = x[VariantFuture-4]
}
const _Variant_name = "UnknownNCSRFC 4122MicrosoftFuture"
var _Variant_index = [...]uint8{0, 7, 10, 18, 27, 33}
func (i Variant) String() string {
if i >= Variant(len(_Variant_index)-1) {
return "Variant(" + strconv.FormatInt(int64(i), 10) + ")"
}
return _Variant_name[_Variant_index[i]:_Variant_index[i+1]]
}

View File

@ -1,3 +1,4 @@
//go:build windows
// +build windows // +build windows
package winio package winio
@ -24,22 +25,17 @@ import (
//sys lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) = advapi32.LookupPrivilegeDisplayNameW //sys lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) = advapi32.LookupPrivilegeDisplayNameW
const ( const (
SE_PRIVILEGE_ENABLED = 2 //revive:disable-next-line:var-naming ALL_CAPS
SE_PRIVILEGE_ENABLED = windows.SE_PRIVILEGE_ENABLED
ERROR_NOT_ALL_ASSIGNED syscall.Errno = 1300 //revive:disable-next-line:var-naming ALL_CAPS
ERROR_NOT_ALL_ASSIGNED syscall.Errno = windows.ERROR_NOT_ALL_ASSIGNED
SeBackupPrivilege = "SeBackupPrivilege" SeBackupPrivilege = "SeBackupPrivilege"
SeRestorePrivilege = "SeRestorePrivilege" SeRestorePrivilege = "SeRestorePrivilege"
SeSecurityPrivilege = "SeSecurityPrivilege" SeSecurityPrivilege = "SeSecurityPrivilege"
) )
const (
securityAnonymous = iota
securityIdentification
securityImpersonation
securityDelegation
)
var ( var (
privNames = make(map[string]uint64) privNames = make(map[string]uint64)
privNameMutex sync.Mutex privNameMutex sync.Mutex
@ -51,11 +47,9 @@ type PrivilegeError struct {
} }
func (e *PrivilegeError) Error() string { func (e *PrivilegeError) Error() string {
s := "" s := "Could not enable privilege "
if len(e.privileges) > 1 { if len(e.privileges) > 1 {
s = "Could not enable privileges " s = "Could not enable privileges "
} else {
s = "Could not enable privilege "
} }
for i, p := range e.privileges { for i, p := range e.privileges {
if i != 0 { if i != 0 {
@ -94,7 +88,7 @@ func RunWithPrivileges(names []string, fn func() error) error {
} }
func mapPrivileges(names []string) ([]uint64, error) { func mapPrivileges(names []string) ([]uint64, error) {
var privileges []uint64 privileges := make([]uint64, 0, len(names))
privNameMutex.Lock() privNameMutex.Lock()
defer privNameMutex.Unlock() defer privNameMutex.Unlock()
for _, name := range names { for _, name := range names {
@ -127,7 +121,7 @@ func enableDisableProcessPrivilege(names []string, action uint32) error {
return err return err
} }
p, _ := windows.GetCurrentProcess() p := windows.CurrentProcess()
var token windows.Token var token windows.Token
err = windows.OpenProcessToken(p, windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token) err = windows.OpenProcessToken(p, windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token)
if err != nil { if err != nil {
@ -140,10 +134,10 @@ func enableDisableProcessPrivilege(names []string, action uint32) error {
func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) error { func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) error {
var b bytes.Buffer var b bytes.Buffer
binary.Write(&b, binary.LittleEndian, uint32(len(privileges))) _ = binary.Write(&b, binary.LittleEndian, uint32(len(privileges)))
for _, p := range privileges { for _, p := range privileges {
binary.Write(&b, binary.LittleEndian, p) _ = binary.Write(&b, binary.LittleEndian, p)
binary.Write(&b, binary.LittleEndian, action) _ = binary.Write(&b, binary.LittleEndian, action)
} }
prevState := make([]byte, b.Len()) prevState := make([]byte, b.Len())
reqSize := uint32(0) reqSize := uint32(0)
@ -151,7 +145,7 @@ func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) e
if !success { if !success {
return err return err
} }
if err == ERROR_NOT_ALL_ASSIGNED { if err == ERROR_NOT_ALL_ASSIGNED { //nolint:errorlint // err is Errno
return &PrivilegeError{privileges} return &PrivilegeError{privileges}
} }
return nil return nil
@ -177,7 +171,7 @@ func getPrivilegeName(luid uint64) string {
} }
func newThreadToken() (windows.Token, error) { func newThreadToken() (windows.Token, error) {
err := impersonateSelf(securityImpersonation) err := impersonateSelf(windows.SecurityImpersonation)
if err != nil { if err != nil {
return 0, err return 0, err
} }

View File

@ -1,3 +1,6 @@
//go:build windows
// +build windows
package winio package winio
import ( import (
@ -113,16 +116,16 @@ func EncodeReparsePoint(rp *ReparsePoint) []byte {
} }
var b bytes.Buffer var b bytes.Buffer
binary.Write(&b, binary.LittleEndian, &data) _ = binary.Write(&b, binary.LittleEndian, &data)
if !rp.IsMountPoint { if !rp.IsMountPoint {
flags := uint32(0) flags := uint32(0)
if relative { if relative {
flags |= 1 flags |= 1
} }
binary.Write(&b, binary.LittleEndian, flags) _ = binary.Write(&b, binary.LittleEndian, flags)
} }
binary.Write(&b, binary.LittleEndian, ntTarget16) _ = binary.Write(&b, binary.LittleEndian, ntTarget16)
binary.Write(&b, binary.LittleEndian, target16) _ = binary.Write(&b, binary.LittleEndian, target16)
return b.Bytes() return b.Bytes()
} }

View File

@ -1,23 +1,25 @@
//go:build windows
// +build windows // +build windows
package winio package winio
import ( import (
"errors"
"syscall" "syscall"
"unsafe" "unsafe"
"golang.org/x/sys/windows"
) )
//sys lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountNameW //sys lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountNameW
//sys lookupAccountSid(systemName *uint16, sid *byte, name *uint16, nameSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountSidW
//sys convertSidToStringSid(sid *byte, str **uint16) (err error) = advapi32.ConvertSidToStringSidW //sys convertSidToStringSid(sid *byte, str **uint16) (err error) = advapi32.ConvertSidToStringSidW
//sys convertStringSidToSid(str *uint16, sid **byte) (err error) = advapi32.ConvertStringSidToSidW
//sys convertStringSecurityDescriptorToSecurityDescriptor(str string, revision uint32, sd *uintptr, size *uint32) (err error) = advapi32.ConvertStringSecurityDescriptorToSecurityDescriptorW //sys convertStringSecurityDescriptorToSecurityDescriptor(str string, revision uint32, sd *uintptr, size *uint32) (err error) = advapi32.ConvertStringSecurityDescriptorToSecurityDescriptorW
//sys convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) = advapi32.ConvertSecurityDescriptorToStringSecurityDescriptorW //sys convertSecurityDescriptorToStringSecurityDescriptor(sd *byte, revision uint32, secInfo uint32, sddl **uint16, sddlSize *uint32) (err error) = advapi32.ConvertSecurityDescriptorToStringSecurityDescriptorW
//sys localFree(mem uintptr) = LocalFree //sys localFree(mem uintptr) = LocalFree
//sys getSecurityDescriptorLength(sd uintptr) (len uint32) = advapi32.GetSecurityDescriptorLength //sys getSecurityDescriptorLength(sd uintptr) (len uint32) = advapi32.GetSecurityDescriptorLength
const (
cERROR_NONE_MAPPED = syscall.Errno(1332)
)
type AccountLookupError struct { type AccountLookupError struct {
Name string Name string
Err error Err error
@ -28,8 +30,10 @@ func (e *AccountLookupError) Error() string {
return "lookup account: empty account name specified" return "lookup account: empty account name specified"
} }
var s string var s string
switch e.Err { switch {
case cERROR_NONE_MAPPED: case errors.Is(e.Err, windows.ERROR_INVALID_SID):
s = "the security ID structure is invalid"
case errors.Is(e.Err, windows.ERROR_NONE_MAPPED):
s = "not found" s = "not found"
default: default:
s = e.Err.Error() s = e.Err.Error()
@ -37,6 +41,8 @@ func (e *AccountLookupError) Error() string {
return "lookup account " + e.Name + ": " + s return "lookup account " + e.Name + ": " + s
} }
func (e *AccountLookupError) Unwrap() error { return e.Err }
type SddlConversionError struct { type SddlConversionError struct {
Sddl string Sddl string
Err error Err error
@ -46,15 +52,19 @@ func (e *SddlConversionError) Error() string {
return "convert " + e.Sddl + ": " + e.Err.Error() return "convert " + e.Sddl + ": " + e.Err.Error()
} }
func (e *SddlConversionError) Unwrap() error { return e.Err }
// LookupSidByName looks up the SID of an account by name // LookupSidByName looks up the SID of an account by name
//
//revive:disable-next-line:var-naming SID, not Sid
func LookupSidByName(name string) (sid string, err error) { func LookupSidByName(name string) (sid string, err error) {
if name == "" { if name == "" {
return "", &AccountLookupError{name, cERROR_NONE_MAPPED} return "", &AccountLookupError{name, windows.ERROR_NONE_MAPPED}
} }
var sidSize, sidNameUse, refDomainSize uint32 var sidSize, sidNameUse, refDomainSize uint32
err = lookupAccountName(nil, name, nil, &sidSize, nil, &refDomainSize, &sidNameUse) err = lookupAccountName(nil, name, nil, &sidSize, nil, &refDomainSize, &sidNameUse)
if err != nil && err != syscall.ERROR_INSUFFICIENT_BUFFER { if err != nil && err != syscall.ERROR_INSUFFICIENT_BUFFER { //nolint:errorlint // err is Errno
return "", &AccountLookupError{name, err} return "", &AccountLookupError{name, err}
} }
sidBuffer := make([]byte, sidSize) sidBuffer := make([]byte, sidSize)
@ -73,6 +83,42 @@ func LookupSidByName(name string) (sid string, err error) {
return sid, nil return sid, nil
} }
// LookupNameBySid looks up the name of an account by SID
//
//revive:disable-next-line:var-naming SID, not Sid
func LookupNameBySid(sid string) (name string, err error) {
if sid == "" {
return "", &AccountLookupError{sid, windows.ERROR_NONE_MAPPED}
}
sidBuffer, err := windows.UTF16PtrFromString(sid)
if err != nil {
return "", &AccountLookupError{sid, err}
}
var sidPtr *byte
if err = convertStringSidToSid(sidBuffer, &sidPtr); err != nil {
return "", &AccountLookupError{sid, err}
}
defer localFree(uintptr(unsafe.Pointer(sidPtr)))
var nameSize, refDomainSize, sidNameUse uint32
err = lookupAccountSid(nil, sidPtr, nil, &nameSize, nil, &refDomainSize, &sidNameUse)
if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER { //nolint:errorlint // err is Errno
return "", &AccountLookupError{sid, err}
}
nameBuffer := make([]uint16, nameSize)
refDomainBuffer := make([]uint16, refDomainSize)
err = lookupAccountSid(nil, sidPtr, &nameBuffer[0], &nameSize, &refDomainBuffer[0], &refDomainSize, &sidNameUse)
if err != nil {
return "", &AccountLookupError{sid, err}
}
name = windows.UTF16ToString(nameBuffer)
return name, nil
}
func SddlToSecurityDescriptor(sddl string) ([]byte, error) { func SddlToSecurityDescriptor(sddl string) ([]byte, error) {
var sdBuffer uintptr var sdBuffer uintptr
err := convertStringSecurityDescriptorToSecurityDescriptor(sddl, 1, &sdBuffer, nil) err := convertStringSecurityDescriptorToSecurityDescriptor(sddl, 1, &sdBuffer, nil)
@ -87,7 +133,7 @@ func SddlToSecurityDescriptor(sddl string) ([]byte, error) {
func SecurityDescriptorToSddl(sd []byte) (string, error) { func SecurityDescriptorToSddl(sd []byte) (string, error) {
var sddl *uint16 var sddl *uint16
// The returned string length seems to including an aribtrary number of terminating NULs. // The returned string length seems to include an arbitrary number of terminating NULs.
// Don't use it. // Don't use it.
err := convertSecurityDescriptorToStringSecurityDescriptor(&sd[0], 1, 0xff, &sddl, nil) err := convertSecurityDescriptorToStringSecurityDescriptor(&sd[0], 1, 0xff, &sddl, nil)
if err != nil { if err != nil {

View File

@ -1,3 +1,5 @@
//go:build windows
package winio package winio
//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go file.go pipe.go sd.go fileinfo.go privilege.go backup.go hvsock.go //go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go ./*.go

5
vendor/github.com/Microsoft/go-winio/tools.go generated vendored Normal file
View File

@ -0,0 +1,5 @@
//go:build tools
package winio
import _ "golang.org/x/tools/cmd/stringer"

View File

@ -1,4 +1,6 @@
// Code generated by 'go generate'; DO NOT EDIT. //go:build windows
// Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT.
package winio package winio
@ -47,9 +49,11 @@ var (
procConvertSecurityDescriptorToStringSecurityDescriptorW = modadvapi32.NewProc("ConvertSecurityDescriptorToStringSecurityDescriptorW") procConvertSecurityDescriptorToStringSecurityDescriptorW = modadvapi32.NewProc("ConvertSecurityDescriptorToStringSecurityDescriptorW")
procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW") procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW")
procConvertStringSecurityDescriptorToSecurityDescriptorW = modadvapi32.NewProc("ConvertStringSecurityDescriptorToSecurityDescriptorW") procConvertStringSecurityDescriptorToSecurityDescriptorW = modadvapi32.NewProc("ConvertStringSecurityDescriptorToSecurityDescriptorW")
procConvertStringSidToSidW = modadvapi32.NewProc("ConvertStringSidToSidW")
procGetSecurityDescriptorLength = modadvapi32.NewProc("GetSecurityDescriptorLength") procGetSecurityDescriptorLength = modadvapi32.NewProc("GetSecurityDescriptorLength")
procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf") procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW") procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW")
procLookupAccountSidW = modadvapi32.NewProc("LookupAccountSidW")
procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW") procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW")
procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW") procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW")
procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW") procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
@ -59,7 +63,6 @@ var (
procBackupWrite = modkernel32.NewProc("BackupWrite") procBackupWrite = modkernel32.NewProc("BackupWrite")
procCancelIoEx = modkernel32.NewProc("CancelIoEx") procCancelIoEx = modkernel32.NewProc("CancelIoEx")
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe") procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
procCreateFileW = modkernel32.NewProc("CreateFileW")
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort") procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW") procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread") procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
@ -74,7 +77,6 @@ var (
procRtlDosPathNameToNtPathName_U = modntdll.NewProc("RtlDosPathNameToNtPathName_U") procRtlDosPathNameToNtPathName_U = modntdll.NewProc("RtlDosPathNameToNtPathName_U")
procRtlNtStatusToDosErrorNoTeb = modntdll.NewProc("RtlNtStatusToDosErrorNoTeb") procRtlNtStatusToDosErrorNoTeb = modntdll.NewProc("RtlNtStatusToDosErrorNoTeb")
procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult") procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult")
procbind = modws2_32.NewProc("bind")
) )
func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) { func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) {
@ -123,6 +125,14 @@ func _convertStringSecurityDescriptorToSecurityDescriptor(str *uint16, revision
return return
} }
func convertStringSidToSid(str *uint16, sid **byte) (err error) {
r1, _, e1 := syscall.Syscall(procConvertStringSidToSidW.Addr(), 2, uintptr(unsafe.Pointer(str)), uintptr(unsafe.Pointer(sid)), 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func getSecurityDescriptorLength(sd uintptr) (len uint32) { func getSecurityDescriptorLength(sd uintptr) (len uint32) {
r0, _, _ := syscall.Syscall(procGetSecurityDescriptorLength.Addr(), 1, uintptr(sd), 0, 0) r0, _, _ := syscall.Syscall(procGetSecurityDescriptorLength.Addr(), 1, uintptr(sd), 0, 0)
len = uint32(r0) len = uint32(r0)
@ -154,6 +164,14 @@ func _lookupAccountName(systemName *uint16, accountName *uint16, sid *byte, sidS
return return
} }
func lookupAccountSid(systemName *uint16, sid *byte, name *uint16, nameSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
r1, _, e1 := syscall.Syscall9(procLookupAccountSidW.Addr(), 7, uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse)), 0, 0)
if r1 == 0 {
err = errnoErr(e1)
}
return
}
func lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) { func lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
var _p0 *uint16 var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(systemName) _p0, err = syscall.UTF16PtrFromString(systemName)
@ -286,24 +304,6 @@ func connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) {
return return
} }
func createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(name)
if err != nil {
return
}
return _createFile(_p0, access, mode, sa, createmode, attrs, templatefile)
}
func _createFile(name *uint16, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall9(procCreateFileW.Addr(), 7, uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile), 0, 0)
handle = syscall.Handle(r0)
if handle == syscall.InvalidHandle {
err = errnoErr(e1)
}
return
}
func createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) { func createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount), 0, 0) r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount), 0, 0)
newport = syscall.Handle(r0) newport = syscall.Handle(r0)
@ -380,25 +380,25 @@ func setFileCompletionNotificationModes(h syscall.Handle, flags uint8) (err erro
return return
} }
func ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntstatus) { func ntCreateNamedPipeFile(pipe *syscall.Handle, access uint32, oa *objectAttributes, iosb *ioStatusBlock, share uint32, disposition uint32, options uint32, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntStatus) {
r0, _, _ := syscall.Syscall15(procNtCreateNamedPipeFile.Addr(), 14, uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout)), 0) r0, _, _ := syscall.Syscall15(procNtCreateNamedPipeFile.Addr(), 14, uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout)), 0)
status = ntstatus(r0) status = ntStatus(r0)
return return
} }
func rtlDefaultNpAcl(dacl *uintptr) (status ntstatus) { func rtlDefaultNpAcl(dacl *uintptr) (status ntStatus) {
r0, _, _ := syscall.Syscall(procRtlDefaultNpAcl.Addr(), 1, uintptr(unsafe.Pointer(dacl)), 0, 0) r0, _, _ := syscall.Syscall(procRtlDefaultNpAcl.Addr(), 1, uintptr(unsafe.Pointer(dacl)), 0, 0)
status = ntstatus(r0) status = ntStatus(r0)
return return
} }
func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntstatus) { func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntStatus) {
r0, _, _ := syscall.Syscall6(procRtlDosPathNameToNtPathName_U.Addr(), 4, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved), 0, 0) r0, _, _ := syscall.Syscall6(procRtlDosPathNameToNtPathName_U.Addr(), 4, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved), 0, 0)
status = ntstatus(r0) status = ntStatus(r0)
return return
} }
func rtlNtStatusToDosError(status ntstatus) (winerr error) { func rtlNtStatusToDosError(status ntStatus) (winerr error) {
r0, _, _ := syscall.Syscall(procRtlNtStatusToDosErrorNoTeb.Addr(), 1, uintptr(status), 0, 0) r0, _, _ := syscall.Syscall(procRtlNtStatusToDosErrorNoTeb.Addr(), 1, uintptr(status), 0, 0)
if r0 != 0 { if r0 != 0 {
winerr = syscall.Errno(r0) winerr = syscall.Errno(r0)
@ -417,11 +417,3 @@ func wsaGetOverlappedResult(h syscall.Handle, o *syscall.Overlapped, bytes *uint
} }
return return
} }
func bind(s syscall.Handle, name unsafe.Pointer, namelen int32) (err error) {
r1, _, e1 := syscall.Syscall(procbind.Addr(), 3, uintptr(s), uintptr(name), uintptr(namelen))
if r1 == socketError {
err = errnoErr(e1)
}
return
}

View File

@ -191,7 +191,7 @@ func (bitCurve *BitCurve) doubleJacobian(x, y, z *big.Int) (*big.Int, *big.Int,
return x3, y3, z3 return x3, y3, z3
} }
//TODO: double check if it is okay // TODO: double check if it is okay
// ScalarMult returns k*(Bx,By) where k is a number in big-endian form. // ScalarMult returns k*(Bx,By) where k is a number in big-endian form.
func (bitCurve *BitCurve) ScalarMult(Bx, By *big.Int, k []byte) (*big.Int, *big.Int) { func (bitCurve *BitCurve) ScalarMult(Bx, By *big.Int, k []byte) (*big.Int, *big.Int) {
// We have a slight problem in that the identity of the group (the // We have a slight problem in that the identity of the group (the
@ -239,7 +239,7 @@ func (bitCurve *BitCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) {
var mask = []byte{0xff, 0x1, 0x3, 0x7, 0xf, 0x1f, 0x3f, 0x7f} var mask = []byte{0xff, 0x1, 0x3, 0x7, 0xf, 0x1f, 0x3f, 0x7f}
//TODO: double check if it is okay // TODO: double check if it is okay
// GenerateKey returns a public/private key pair. The private key is generated // GenerateKey returns a public/private key pair. The private key is generated
// using the given reader, which must return random data. // using the given reader, which must return random data.
func (bitCurve *BitCurve) GenerateKey(rand io.Reader) (priv []byte, x, y *big.Int, err error) { func (bitCurve *BitCurve) GenerateKey(rand io.Reader) (priv []byte, x, y *big.Int, err error) {

View File

@ -80,4 +80,4 @@ func (curve *rcurve) ScalarMult(x1, y1 *big.Int, scalar []byte) (x, y *big.Int)
func (curve *rcurve) ScalarBaseMult(scalar []byte) (x, y *big.Int) { func (curve *rcurve) ScalarBaseMult(scalar []byte) (x, y *big.Int) {
return curve.fromTwisted(curve.twisted.ScalarBaseMult(scalar)) return curve.fromTwisted(curve.twisted.ScalarBaseMult(scalar))
} }

View File

@ -67,7 +67,7 @@ func (e *eax) Seal(dst, nonce, plaintext, adata []byte) []byte {
if len(nonce) > e.nonceSize { if len(nonce) > e.nonceSize {
panic("crypto/eax: Nonce too long for this instance") panic("crypto/eax: Nonce too long for this instance")
} }
ret, out := byteutil.SliceForAppend(dst, len(plaintext) + e.tagSize) ret, out := byteutil.SliceForAppend(dst, len(plaintext)+e.tagSize)
omacNonce := e.omacT(0, nonce) omacNonce := e.omacT(0, nonce)
omacAdata := e.omacT(1, adata) omacAdata := e.omacT(1, adata)
@ -85,7 +85,7 @@ func (e *eax) Seal(dst, nonce, plaintext, adata []byte) []byte {
return ret return ret
} }
func (e* eax) Open(dst, nonce, ciphertext, adata []byte) ([]byte, error) { func (e *eax) Open(dst, nonce, ciphertext, adata []byte) ([]byte, error) {
if len(nonce) > e.nonceSize { if len(nonce) > e.nonceSize {
panic("crypto/eax: Nonce too long for this instance") panic("crypto/eax: Nonce too long for this instance")
} }

View File

@ -41,7 +41,7 @@ func ShiftNBytesLeft(dst, x []byte, n int) {
bits := uint(n % 8) bits := uint(n % 8)
l := len(dst) l := len(dst)
for i := 0; i < l-1; i++ { for i := 0; i < l-1; i++ {
dst[i] = (dst[i] << bits) | (dst[i+1] >> uint(8 - bits)) dst[i] = (dst[i] << bits) | (dst[i+1] >> uint(8-bits))
} }
dst[l-1] = dst[l-1] << bits dst[l-1] = dst[l-1] << bits
@ -56,7 +56,6 @@ func XorBytesMut(X, Y []byte) {
} }
} }
// XorBytes assumes equal input length, puts X XOR Y into Z // XorBytes assumes equal input length, puts X XOR Y into Z
func XorBytes(Z, X, Y []byte) { func XorBytes(Z, X, Y []byte) {
for i := 0; i < len(X); i++ { for i := 0; i < len(X); i++ {
@ -67,10 +66,10 @@ func XorBytes(Z, X, Y []byte) {
// RightXor XORs smaller input (assumed Y) at the right of the larger input (assumed X) // RightXor XORs smaller input (assumed Y) at the right of the larger input (assumed X)
func RightXor(X, Y []byte) []byte { func RightXor(X, Y []byte) []byte {
offset := len(X) - len(Y) offset := len(X) - len(Y)
xored := make([]byte, len(X)); xored := make([]byte, len(X))
copy(xored, X) copy(xored, X)
for i := 0; i < len(Y); i++ { for i := 0; i < len(Y); i++ {
xored[offset + i] ^= Y[i] xored[offset+i] ^= Y[i]
} }
return xored return xored
} }
@ -89,4 +88,3 @@ func SliceForAppend(in []byte, n int) (head, tail []byte) {
tail = head[len(in):] tail = head[len(in):]
return return
} }

View File

@ -93,13 +93,13 @@ func NewOCBWithNonceAndTagSize(
return nil, ocbError("Custom tag length exceeds blocksize") return nil, ocbError("Custom tag length exceeds blocksize")
} }
return &ocb{ return &ocb{
block: block, block: block,
tagSize: tagSize, tagSize: tagSize,
nonceSize: nonceSize, nonceSize: nonceSize,
mask: initializeMaskTable(block), mask: initializeMaskTable(block),
reusableKtop: reusableKtop{ reusableKtop: reusableKtop{
noncePrefix: nil, noncePrefix: nil,
Ktop: nil, Ktop: nil,
}, },
}, nil }, nil
} }

View File

@ -4,21 +4,22 @@ package ocb
var rfc7253TestVectorTaglen96 = struct { var rfc7253TestVectorTaglen96 = struct {
key, nonce, header, plaintext, ciphertext string key, nonce, header, plaintext, ciphertext string
}{"0F0E0D0C0B0A09080706050403020100", }{"0F0E0D0C0B0A09080706050403020100",
"BBAA9988776655443322110D", "BBAA9988776655443322110D",
"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627", "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627",
"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627", "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627",
"1792A4E31E0755FB03E31B22116E6C2DDF9EFD6E33D536F1A0124B0A55BAE884ED93481529C76B6AD0C515F4D1CDD4FDAC4F02AA"} "1792A4E31E0755FB03E31B22116E6C2DDF9EFD6E33D536F1A0124B0A55BAE884ED93481529C76B6AD0C515F4D1CDD4FDAC4F02AA"}
var rfc7253AlgorithmTest = []struct { var rfc7253AlgorithmTest = []struct {
KEYLEN, TAGLEN int KEYLEN, TAGLEN int
OUTPUT string }{ OUTPUT string
{128, 128, "67E944D23256C5E0B6C61FA22FDF1EA2"}, }{
{192, 128, "F673F2C3E7174AAE7BAE986CA9F29E17"}, {128, 128, "67E944D23256C5E0B6C61FA22FDF1EA2"},
{256, 128, "D90EB8E9C977C88B79DD793D7FFA161C"}, {192, 128, "F673F2C3E7174AAE7BAE986CA9F29E17"},
{128, 96, "77A3D8E73589158D25D01209"}, {256, 128, "D90EB8E9C977C88B79DD793D7FFA161C"},
{192, 96, "05D56EAD2752C86BE6932C5E"}, {128, 96, "77A3D8E73589158D25D01209"},
{256, 96, "5458359AC23B0CBA9E6330DD"}, {192, 96, "05D56EAD2752C86BE6932C5E"},
{128, 64, "192C9B7BD90BA06A"}, {256, 96, "5458359AC23B0CBA9E6330DD"},
{192, 64, "0066BC6E0EF34E24"}, {128, 64, "192C9B7BD90BA06A"},
{256, 64, "7D4EA5D445501CBE"}, {192, 64, "0066BC6E0EF34E24"},
} {256, 64, "7D4EA5D445501CBE"},
}

View File

@ -10,19 +10,22 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"github.com/ProtonMail/go-crypto/openpgp/errors"
"io" "io"
"github.com/ProtonMail/go-crypto/openpgp/errors"
) )
// A Block represents an OpenPGP armored structure. // A Block represents an OpenPGP armored structure.
// //
// The encoded form is: // The encoded form is:
// -----BEGIN Type-----
// Headers
// //
// base64-encoded Bytes // -----BEGIN Type-----
// '=' base64 encoded checksum // Headers
// -----END Type----- //
// base64-encoded Bytes
// '=' base64 encoded checksum
// -----END Type-----
//
// where Headers is a possibly empty sequence of Key: Value lines. // where Headers is a possibly empty sequence of Key: Value lines.
// //
// Since the armored data can be very large, this package presents a streaming // Since the armored data can be very large, this package presents a streaming
@ -206,12 +209,16 @@ TryNextBlock:
break break
} }
i := bytes.Index(line, []byte(": ")) i := bytes.Index(line, []byte(":"))
if i == -1 { if i == -1 {
goto TryNextBlock goto TryNextBlock
} }
lastKey = string(line[:i]) lastKey = string(line[:i])
p.Header[lastKey] = string(line[i+2:]) var value string
if len(line) > i+2 {
value = string(line[i+2:])
}
p.Header[lastKey] = value
} }
p.lReader.in = r p.lReader.in = r

View File

@ -96,7 +96,8 @@ func (l *lineBreaker) Close() (err error) {
// trailer. // trailer.
// //
// It's built into a stack of io.Writers: // It's built into a stack of io.Writers:
// encoding -> base64 encoder -> lineBreaker -> out //
// encoding -> base64 encoder -> lineBreaker -> out
type encoding struct { type encoding struct {
out io.Writer out io.Writer
breaker *lineBreaker breaker *lineBreaker

View File

@ -34,7 +34,7 @@ type PrivateKey struct {
func NewPublicKey(curve ecc.ECDHCurve, kdfHash algorithm.Hash, kdfCipher algorithm.Cipher) *PublicKey { func NewPublicKey(curve ecc.ECDHCurve, kdfHash algorithm.Hash, kdfCipher algorithm.Cipher) *PublicKey {
return &PublicKey{ return &PublicKey{
curve: curve, curve: curve,
KDF: KDF{ KDF: KDF{
Hash: kdfHash, Hash: kdfHash,
Cipher: kdfCipher, Cipher: kdfCipher,
@ -167,7 +167,7 @@ func buildKey(pub *PublicKey, zb []byte, curveOID, fingerprint []byte, stripLead
if _, err := param.Write(fingerprint[:20]); err != nil { if _, err := param.Write(fingerprint[:20]); err != nil {
return nil, err return nil, err
} }
if param.Len() - len(curveOID) != 45 { if param.Len()-len(curveOID) != 45 {
return nil, errors.New("ecdh: malformed KDF Param") return nil, errors.New("ecdh: malformed KDF Param")
} }
@ -181,15 +181,19 @@ func buildKey(pub *PublicKey, zb []byte, curveOID, fingerprint []byte, stripLead
j := zbLen - 1 j := zbLen - 1
if stripLeading { if stripLeading {
// Work around old go crypto bug where the leading zeros are missing. // Work around old go crypto bug where the leading zeros are missing.
for ; i < zbLen && zb[i] == 0; i++ {} for i < zbLen && zb[i] == 0 {
i++
}
} }
if stripTrailing { if stripTrailing {
// Work around old OpenPGP.js bug where insignificant trailing zeros in // Work around old OpenPGP.js bug where insignificant trailing zeros in
// this little-endian number are missing. // this little-endian number are missing.
// (See https://github.com/openpgpjs/openpgpjs/pull/853.) // (See https://github.com/openpgpjs/openpgpjs/pull/853.)
for ; j >= 0 && zb[j] == 0; j-- {} for j >= 0 && zb[j] == 0 {
j--
}
} }
if _, err := h.Write(zb[i:j+1]); err != nil { if _, err := h.Write(zb[i : j+1]); err != nil {
return nil, err return nil, err
} }
if _, err := h.Write(param.Bytes()); err != nil { if _, err := h.Write(param.Bytes()); err != nil {

View File

@ -10,7 +10,7 @@ import (
) )
type PublicKey struct { type PublicKey struct {
X, Y *big.Int X, Y *big.Int
curve ecc.ECDSACurve curve ecc.ECDSACurve
} }

View File

@ -9,7 +9,7 @@ import (
) )
type PublicKey struct { type PublicKey struct {
X []byte X []byte
curve ecc.EdDSACurve curve ecc.EdDSACurve
} }

View File

@ -71,8 +71,8 @@ func Encrypt(random io.Reader, pub *PublicKey, msg []byte) (c1, c2 *big.Int, err
// returns the plaintext of the message. An error can result only if the // returns the plaintext of the message. An error can result only if the
// ciphertext is invalid. Users should keep in mind that this is a padding // ciphertext is invalid. Users should keep in mind that this is a padding
// oracle and thus, if exposed to an adaptive chosen ciphertext attack, can // oracle and thus, if exposed to an adaptive chosen ciphertext attack, can
// be used to break the cryptosystem. See ``Chosen Ciphertext Attacks // be used to break the cryptosystem. See Chosen Ciphertext Attacks
// Against Protocols Based on the RSA Encryption Standard PKCS #1'', Daniel // Against Protocols Based on the RSA Encryption Standard PKCS #1, Daniel
// Bleichenbacher, Advances in Cryptology (Crypto '98), // Bleichenbacher, Advances in Cryptology (Crypto '98),
func Decrypt(priv *PrivateKey, c1, c2 *big.Int) (msg []byte, err error) { func Decrypt(priv *PrivateKey, c1, c2 *big.Int) (msg []byte, err error) {
s := new(big.Int).Exp(c1, priv.X, priv.P) s := new(big.Int).Exp(c1, priv.X, priv.P)

24
vendor/github.com/ProtonMail/go-crypto/openpgp/hash.go generated vendored Normal file
View File

@ -0,0 +1,24 @@
package openpgp
import (
"crypto"
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
)
// HashIdToHash returns a crypto.Hash which corresponds to the given OpenPGP
// hash id.
func HashIdToHash(id byte) (h crypto.Hash, ok bool) {
return algorithm.HashIdToHash(id)
}
// HashIdToString returns the name of the hash function corresponding to the
// given OpenPGP hash id.
func HashIdToString(id byte) (name string, ok bool) {
return algorithm.HashIdToString(id)
}
// HashToHashId returns an OpenPGP hash id which corresponds the given Hash.
func HashToHashId(h crypto.Hash) (id byte, ok bool) {
return algorithm.HashToHashId(h)
}

View File

@ -16,7 +16,7 @@ type AEADMode uint8
const ( const (
AEADModeEAX = AEADMode(1) AEADModeEAX = AEADMode(1)
AEADModeOCB = AEADMode(2) AEADModeOCB = AEADMode(2)
AEADModeGCM = AEADMode(100) AEADModeGCM = AEADMode(3)
) )
// TagLength returns the length in bytes of authentication tags. // TagLength returns the length in bytes of authentication tags.

View File

@ -32,26 +32,25 @@ type Hash interface {
// The following vars mirror the crypto/Hash supported hash functions. // The following vars mirror the crypto/Hash supported hash functions.
var ( var (
MD5 Hash = cryptoHash{1, crypto.MD5} SHA1 Hash = cryptoHash{2, crypto.SHA1}
SHA1 Hash = cryptoHash{2, crypto.SHA1} SHA256 Hash = cryptoHash{8, crypto.SHA256}
RIPEMD160 Hash = cryptoHash{3, crypto.RIPEMD160} SHA384 Hash = cryptoHash{9, crypto.SHA384}
SHA256 Hash = cryptoHash{8, crypto.SHA256} SHA512 Hash = cryptoHash{10, crypto.SHA512}
SHA384 Hash = cryptoHash{9, crypto.SHA384} SHA224 Hash = cryptoHash{11, crypto.SHA224}
SHA512 Hash = cryptoHash{10, crypto.SHA512} SHA3_256 Hash = cryptoHash{12, crypto.SHA3_256}
SHA224 Hash = cryptoHash{11, crypto.SHA224} SHA3_512 Hash = cryptoHash{14, crypto.SHA3_512}
) )
// HashById represents the different hash functions specified for OpenPGP. See // HashById represents the different hash functions specified for OpenPGP. See
// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-14 // http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-14
var ( var (
HashById = map[uint8]Hash{ HashById = map[uint8]Hash{
MD5.Id(): MD5, SHA256.Id(): SHA256,
SHA1.Id(): SHA1, SHA384.Id(): SHA384,
RIPEMD160.Id(): RIPEMD160, SHA512.Id(): SHA512,
SHA256.Id(): SHA256, SHA224.Id(): SHA224,
SHA384.Id(): SHA384, SHA3_256.Id(): SHA3_256,
SHA512.Id(): SHA512, SHA3_512.Id(): SHA3_512,
SHA224.Id(): SHA224,
} }
) )
@ -68,13 +67,12 @@ func (h cryptoHash) Id() uint8 {
} }
var hashNames = map[uint8]string{ var hashNames = map[uint8]string{
MD5.Id(): "MD5", SHA256.Id(): "SHA256",
SHA1.Id(): "SHA1", SHA384.Id(): "SHA384",
RIPEMD160.Id(): "RIPEMD160", SHA512.Id(): "SHA512",
SHA256.Id(): "SHA256", SHA224.Id(): "SHA224",
SHA384.Id(): "SHA384", SHA3_256.Id(): "SHA3-256",
SHA512.Id(): "SHA512", SHA3_512.Id(): "SHA3-512",
SHA224.Id(): "SHA224",
} }
func (h cryptoHash) String() string { func (h cryptoHash) String() string {
@ -84,3 +82,62 @@ func (h cryptoHash) String() string {
} }
return s return s
} }
// HashIdToHash returns a crypto.Hash which corresponds to the given OpenPGP
// hash id.
func HashIdToHash(id byte) (h crypto.Hash, ok bool) {
if hash, ok := HashById[id]; ok {
return hash.HashFunc(), true
}
return 0, false
}
// HashIdToHashWithSha1 returns a crypto.Hash which corresponds to the given OpenPGP
// hash id, allowing sha1.
func HashIdToHashWithSha1(id byte) (h crypto.Hash, ok bool) {
if hash, ok := HashById[id]; ok {
return hash.HashFunc(), true
}
if id == SHA1.Id() {
return SHA1.HashFunc(), true
}
return 0, false
}
// HashIdToString returns the name of the hash function corresponding to the
// given OpenPGP hash id.
func HashIdToString(id byte) (name string, ok bool) {
if hash, ok := HashById[id]; ok {
return hash.String(), true
}
return "", false
}
// HashToHashId returns an OpenPGP hash id which corresponds the given Hash.
func HashToHashId(h crypto.Hash) (id byte, ok bool) {
for id, hash := range HashById {
if hash.HashFunc() == h {
return id, true
}
}
return 0, false
}
// HashToHashIdWithSha1 returns an OpenPGP hash id which corresponds the given Hash,
// allowing instances of SHA1
func HashToHashIdWithSha1(h crypto.Hash) (id byte, ok bool) {
for id, hash := range HashById {
if hash.HashFunc() == h {
return id, true
}
}
if h == SHA1.HashFunc() {
return SHA1.Id(), true
}
return 0, false
}

View File

@ -9,7 +9,7 @@ import (
x25519lib "github.com/cloudflare/circl/dh/x25519" x25519lib "github.com/cloudflare/circl/dh/x25519"
) )
type curve25519 struct {} type curve25519 struct{}
func NewCurve25519() *curve25519 { func NewCurve25519() *curve25519 {
return &curve25519{} return &curve25519{}
@ -21,14 +21,14 @@ func (c *curve25519) GetCurveName() string {
// MarshalBytePoint encodes the public point from native format, adding the prefix. // MarshalBytePoint encodes the public point from native format, adding the prefix.
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6 // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6
func (c *curve25519) MarshalBytePoint(point [] byte) []byte { func (c *curve25519) MarshalBytePoint(point []byte) []byte {
return append([]byte{0x40}, point...) return append([]byte{0x40}, point...)
} }
// UnmarshalBytePoint decodes the public point to native format, removing the prefix. // UnmarshalBytePoint decodes the public point to native format, removing the prefix.
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6 // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6
func (c *curve25519) UnmarshalBytePoint(point []byte) []byte { func (c *curve25519) UnmarshalBytePoint(point []byte) []byte {
if len(point) != x25519lib.Size + 1 { if len(point) != x25519lib.Size+1 {
return nil return nil
} }

View File

@ -11,76 +11,76 @@ import (
type CurveInfo struct { type CurveInfo struct {
GenName string GenName string
Oid *encoding.OID Oid *encoding.OID
Curve Curve Curve Curve
} }
var Curves = []CurveInfo{ var Curves = []CurveInfo{
{ {
// NIST P-256 // NIST P-256
GenName: "P256", GenName: "P256",
Oid: encoding.NewOID([]byte{0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07}), Oid: encoding.NewOID([]byte{0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07}),
Curve: NewGenericCurve(elliptic.P256()), Curve: NewGenericCurve(elliptic.P256()),
}, },
{ {
// NIST P-384 // NIST P-384
GenName: "P384", GenName: "P384",
Oid: encoding.NewOID([]byte{0x2B, 0x81, 0x04, 0x00, 0x22}), Oid: encoding.NewOID([]byte{0x2B, 0x81, 0x04, 0x00, 0x22}),
Curve: NewGenericCurve(elliptic.P384()), Curve: NewGenericCurve(elliptic.P384()),
}, },
{ {
// NIST P-521 // NIST P-521
GenName: "P521", GenName: "P521",
Oid: encoding.NewOID([]byte{0x2B, 0x81, 0x04, 0x00, 0x23}), Oid: encoding.NewOID([]byte{0x2B, 0x81, 0x04, 0x00, 0x23}),
Curve: NewGenericCurve(elliptic.P521()), Curve: NewGenericCurve(elliptic.P521()),
}, },
{ {
// SecP256k1 // SecP256k1
GenName: "SecP256k1", GenName: "SecP256k1",
Oid: encoding.NewOID([]byte{0x2B, 0x81, 0x04, 0x00, 0x0A}), Oid: encoding.NewOID([]byte{0x2B, 0x81, 0x04, 0x00, 0x0A}),
Curve: NewGenericCurve(bitcurves.S256()), Curve: NewGenericCurve(bitcurves.S256()),
}, },
{ {
// Curve25519 // Curve25519
GenName: "Curve25519", GenName: "Curve25519",
Oid: encoding.NewOID([]byte{0x2B, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01}), Oid: encoding.NewOID([]byte{0x2B, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01}),
Curve: NewCurve25519(), Curve: NewCurve25519(),
}, },
{ {
// X448 // X448
GenName: "Curve448", GenName: "Curve448",
Oid: encoding.NewOID([]byte{0x2B, 0x65, 0x6F}), Oid: encoding.NewOID([]byte{0x2B, 0x65, 0x6F}),
Curve: NewX448(), Curve: NewX448(),
}, },
{ {
// Ed25519 // Ed25519
GenName: "Curve25519", GenName: "Curve25519",
Oid: encoding.NewOID([]byte{0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, 0x47, 0x0F, 0x01}), Oid: encoding.NewOID([]byte{0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, 0x47, 0x0F, 0x01}),
Curve: NewEd25519(), Curve: NewEd25519(),
}, },
{ {
// Ed448 // Ed448
GenName: "Curve448", GenName: "Curve448",
Oid: encoding.NewOID([]byte{0x2B, 0x65, 0x71}), Oid: encoding.NewOID([]byte{0x2B, 0x65, 0x71}),
Curve: NewEd448(), Curve: NewEd448(),
}, },
{ {
// BrainpoolP256r1 // BrainpoolP256r1
GenName: "BrainpoolP256", GenName: "BrainpoolP256",
Oid: encoding.NewOID([]byte{0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07}), Oid: encoding.NewOID([]byte{0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07}),
Curve: NewGenericCurve(brainpool.P256r1()), Curve: NewGenericCurve(brainpool.P256r1()),
}, },
{ {
// BrainpoolP384r1 // BrainpoolP384r1
GenName: "BrainpoolP384", GenName: "BrainpoolP384",
Oid: encoding.NewOID([]byte{0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B}), Oid: encoding.NewOID([]byte{0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B}),
Curve: NewGenericCurve(brainpool.P384r1()), Curve: NewGenericCurve(brainpool.P384r1()),
}, },
{ {
// BrainpoolP512r1 // BrainpoolP512r1
GenName: "BrainpoolP512", GenName: "BrainpoolP512",
Oid: encoding.NewOID([]byte{0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D}), Oid: encoding.NewOID([]byte{0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D}),
Curve: NewGenericCurve(brainpool.P512r1()), Curve: NewGenericCurve(brainpool.P512r1()),
}, },
} }

View File

@ -38,7 +38,7 @@ type EdDSACurve interface {
type ECDHCurve interface { type ECDHCurve interface {
Curve Curve
MarshalBytePoint([]byte) (encoded []byte) MarshalBytePoint([]byte) (encoded []byte)
UnmarshalBytePoint(encoded []byte) ([]byte) UnmarshalBytePoint(encoded []byte) []byte
MarshalByteSecret(d []byte) []byte MarshalByteSecret(d []byte) []byte
UnmarshalByteSecret(d []byte) []byte UnmarshalByteSecret(d []byte) []byte
GenerateECDH(rand io.Reader) (point []byte, secret []byte, err error) GenerateECDH(rand io.Reader) (point []byte, secret []byte, err error)

View File

@ -10,7 +10,8 @@ import (
) )
const ed25519Size = 32 const ed25519Size = 32
type ed25519 struct {}
type ed25519 struct{}
func NewEd25519() *ed25519 { func NewEd25519() *ed25519 {
return &ed25519{} return &ed25519{}
@ -29,7 +30,7 @@ func (c *ed25519) MarshalBytePoint(x []byte) []byte {
// UnmarshalBytePoint decodes a point from prefixed format to native. // UnmarshalBytePoint decodes a point from prefixed format to native.
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5 // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
func (c *ed25519) UnmarshalBytePoint(point []byte) (x []byte) { func (c *ed25519) UnmarshalBytePoint(point []byte) (x []byte) {
if len(point) != ed25519lib.PublicKeySize + 1 { if len(point) != ed25519lib.PublicKeySize+1 {
return nil return nil
} }
@ -52,7 +53,7 @@ func (c *ed25519) UnmarshalByteSecret(s []byte) (d []byte) {
// Handle stripped leading zeroes // Handle stripped leading zeroes
d = make([]byte, ed25519lib.SeedSize) d = make([]byte, ed25519lib.SeedSize)
copy(d[ed25519lib.SeedSize - len(s):], s) copy(d[ed25519lib.SeedSize-len(s):], s)
return return
} }

View File

@ -9,7 +9,7 @@ import (
ed448lib "github.com/cloudflare/circl/sign/ed448" ed448lib "github.com/cloudflare/circl/sign/ed448"
) )
type ed448 struct {} type ed448 struct{}
func NewEd448() *ed448 { func NewEd448() *ed448 {
return &ed448{} return &ed448{}
@ -29,7 +29,7 @@ func (c *ed448) MarshalBytePoint(x []byte) []byte {
// UnmarshalBytePoint decodes a point from prefixed format to native. // UnmarshalBytePoint decodes a point from prefixed format to native.
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5 // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
func (c *ed448) UnmarshalBytePoint(point []byte) (x []byte) { func (c *ed448) UnmarshalBytePoint(point []byte) (x []byte) {
if len(point) != ed448lib.PublicKeySize + 1 { if len(point) != ed448lib.PublicKeySize+1 {
return nil return nil
} }
@ -48,7 +48,7 @@ func (c *ed448) MarshalByteSecret(d []byte) []byte {
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5 // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
func (c *ed448) UnmarshalByteSecret(s []byte) (d []byte) { func (c *ed448) UnmarshalByteSecret(s []byte) (d []byte) {
// Check prefixed size // Check prefixed size
if len(s) != ed448lib.SeedSize + 1 { if len(s) != ed448lib.SeedSize+1 {
return nil return nil
} }
@ -66,7 +66,7 @@ func (c *ed448) MarshalSignature(sig []byte) (r, s []byte) {
// UnmarshalSignature decodes R and S in the native format. Only R is used, in prefixed native format. // UnmarshalSignature decodes R and S in the native format. Only R is used, in prefixed native format.
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.2.3.3.2 // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.2.3.3.2
func (c *ed448) UnmarshalSignature(r, s []byte) (sig []byte) { func (c *ed448) UnmarshalSignature(r, s []byte) (sig []byte) {
if len(r) != ed448lib.SignatureSize + 1 { if len(r) != ed448lib.SignatureSize+1 {
return nil return nil
} }

View File

@ -9,7 +9,7 @@ import (
x448lib "github.com/cloudflare/circl/dh/x448" x448lib "github.com/cloudflare/circl/dh/x448"
) )
type x448 struct {} type x448 struct{}
func NewX448() *x448 { func NewX448() *x448 {
return &x448{} return &x448{}
@ -28,7 +28,7 @@ func (c *x448) MarshalBytePoint(point []byte) []byte {
// UnmarshalBytePoint decodes a point from prefixed format to native. // UnmarshalBytePoint decodes a point from prefixed format to native.
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6 // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6
func (c *x448) UnmarshalBytePoint(point []byte) []byte { func (c *x448) UnmarshalBytePoint(point []byte) []byte {
if len(point) != x448lib.Size + 1 { if len(point) != x448lib.Size+1 {
return nil return nil
} }
@ -44,7 +44,7 @@ func (c *x448) MarshalByteSecret(d []byte) []byte {
// UnmarshalByteSecret decodes a scalar from prefixed format to native. // UnmarshalByteSecret decodes a scalar from prefixed format to native.
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6.1.2 // See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6.1.2
func (c *x448) UnmarshalByteSecret(d []byte) []byte { func (c *x448) UnmarshalByteSecret(d []byte) []byte {
if len(d) != x448lib.Size + 1 { if len(d) != x448lib.Size+1 {
return nil return nil
} }
@ -73,7 +73,9 @@ func (c *x448) GenerateECDH(rand io.Reader) (point []byte, secret []byte, err er
func (c *x448) Encaps(rand io.Reader, point []byte) (ephemeral, sharedSecret []byte, err error) { func (c *x448) Encaps(rand io.Reader, point []byte) (ephemeral, sharedSecret []byte, err error) {
var pk, ss x448lib.Key var pk, ss x448lib.Key
seed, e, err := c.generateKeyPairBytes(rand) seed, e, err := c.generateKeyPairBytes(rand)
if err != nil {
return nil, nil, err
}
copy(pk[:], point) copy(pk[:], point)
x448lib.Shared(&ss, &seed, &pk) x448lib.Shared(&ss, &seed, &pk)

View File

@ -82,27 +82,24 @@ func (t *Entity) addUserId(name, comment, email string, config *packet.Config, c
isPrimaryId := len(t.Identities) == 0 isPrimaryId := len(t.Identities) == 0
selfSignature := &packet.Signature{ selfSignature := createSignaturePacket(&primary.PublicKey, packet.SigTypePositiveCert, config)
Version: primary.PublicKey.Version, selfSignature.CreationTime = creationTime
SigType: packet.SigTypePositiveCert, selfSignature.KeyLifetimeSecs = &keyLifetimeSecs
PubKeyAlgo: primary.PublicKey.PubKeyAlgo, selfSignature.IsPrimaryId = &isPrimaryId
Hash: config.Hash(), selfSignature.FlagsValid = true
CreationTime: creationTime, selfSignature.FlagSign = true
KeyLifetimeSecs: &keyLifetimeSecs, selfSignature.FlagCertify = true
IssuerKeyId: &primary.PublicKey.KeyId, selfSignature.SEIPDv1 = true // true by default, see 5.8 vs. 5.14
IssuerFingerprint: primary.PublicKey.Fingerprint, selfSignature.SEIPDv2 = config.AEAD() != nil
IsPrimaryId: &isPrimaryId,
FlagsValid: true,
FlagSign: true,
FlagCertify: true,
MDC: true, // true by default, see 5.8 vs. 5.14
AEAD: config.AEAD() != nil,
V5Keys: config != nil && config.V5Keys,
}
// Set the PreferredHash for the SelfSignature from the packet.Config. // Set the PreferredHash for the SelfSignature from the packet.Config.
// If it is not the must-implement algorithm from rfc4880bis, append that. // If it is not the must-implement algorithm from rfc4880bis, append that.
selfSignature.PreferredHash = []uint8{hashToHashId(config.Hash())} hash, ok := algorithm.HashToHashId(config.Hash())
if !ok {
return errors.UnsupportedError("unsupported preferred hash function")
}
selfSignature.PreferredHash = []uint8{hash}
if config.Hash() != crypto.SHA256 { if config.Hash() != crypto.SHA256 {
selfSignature.PreferredHash = append(selfSignature.PreferredHash, hashToHashId(crypto.SHA256)) selfSignature.PreferredHash = append(selfSignature.PreferredHash, hashToHashId(crypto.SHA256))
} }
@ -123,9 +120,16 @@ func (t *Entity) addUserId(name, comment, email string, config *packet.Config, c
} }
// And for DefaultMode. // And for DefaultMode.
selfSignature.PreferredAEAD = []uint8{uint8(config.AEAD().Mode())} modes := []uint8{uint8(config.AEAD().Mode())}
if config.AEAD().Mode() != packet.AEADModeEAX { if config.AEAD().Mode() != packet.AEADModeOCB {
selfSignature.PreferredAEAD = append(selfSignature.PreferredAEAD, uint8(packet.AEADModeEAX)) modes = append(modes, uint8(packet.AEADModeOCB))
}
// For preferred (AES256, GCM), we'll generate (AES256, GCM), (AES256, OCB), (AES128, GCM), (AES128, OCB)
for _, cipher := range selfSignature.PreferredSymmetric {
for _, mode := range modes {
selfSignature.PreferredCipherSuites = append(selfSignature.PreferredCipherSuites, [2]uint8{cipher, mode})
}
} }
// User ID binding signature // User ID binding signature
@ -153,42 +157,30 @@ func (e *Entity) AddSigningSubkey(config *packet.Config) error {
return err return err
} }
sub := packet.NewSignerPrivateKey(creationTime, subPrivRaw) sub := packet.NewSignerPrivateKey(creationTime, subPrivRaw)
sub.IsSubkey = true
if config != nil && config.V5Keys {
sub.UpgradeToV5()
}
subkey := Subkey{ subkey := Subkey{
PublicKey: &sub.PublicKey, PublicKey: &sub.PublicKey,
PrivateKey: sub, PrivateKey: sub,
Sig: &packet.Signature{
Version: e.PrimaryKey.Version,
CreationTime: creationTime,
KeyLifetimeSecs: &keyLifetimeSecs,
SigType: packet.SigTypeSubkeyBinding,
PubKeyAlgo: e.PrimaryKey.PubKeyAlgo,
Hash: config.Hash(),
FlagsValid: true,
FlagSign: true,
IssuerKeyId: &e.PrimaryKey.KeyId,
EmbeddedSignature: &packet.Signature{
Version: e.PrimaryKey.Version,
CreationTime: creationTime,
SigType: packet.SigTypePrimaryKeyBinding,
PubKeyAlgo: sub.PublicKey.PubKeyAlgo,
Hash: config.Hash(),
IssuerKeyId: &e.PrimaryKey.KeyId,
},
},
}
if config != nil && config.V5Keys {
subkey.PublicKey.UpgradeToV5()
} }
subkey.Sig = createSignaturePacket(e.PrimaryKey, packet.SigTypeSubkeyBinding, config)
subkey.Sig.CreationTime = creationTime
subkey.Sig.KeyLifetimeSecs = &keyLifetimeSecs
subkey.Sig.FlagsValid = true
subkey.Sig.FlagSign = true
subkey.Sig.EmbeddedSignature = createSignaturePacket(subkey.PublicKey, packet.SigTypePrimaryKeyBinding, config)
subkey.Sig.EmbeddedSignature.CreationTime = creationTime
err = subkey.Sig.EmbeddedSignature.CrossSignKey(subkey.PublicKey, e.PrimaryKey, subkey.PrivateKey, config) err = subkey.Sig.EmbeddedSignature.CrossSignKey(subkey.PublicKey, e.PrimaryKey, subkey.PrivateKey, config)
if err != nil { if err != nil {
return err return err
} }
subkey.PublicKey.IsSubkey = true err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey, config)
subkey.PrivateKey.IsSubkey = true if err != nil {
if err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey, config); err != nil {
return err return err
} }
@ -210,30 +202,24 @@ func (e *Entity) addEncryptionSubkey(config *packet.Config, creationTime time.Ti
return err return err
} }
sub := packet.NewDecrypterPrivateKey(creationTime, subPrivRaw) sub := packet.NewDecrypterPrivateKey(creationTime, subPrivRaw)
sub.IsSubkey = true
if config != nil && config.V5Keys {
sub.UpgradeToV5()
}
subkey := Subkey{ subkey := Subkey{
PublicKey: &sub.PublicKey, PublicKey: &sub.PublicKey,
PrivateKey: sub, PrivateKey: sub,
Sig: &packet.Signature{
Version: e.PrimaryKey.Version,
CreationTime: creationTime,
KeyLifetimeSecs: &keyLifetimeSecs,
SigType: packet.SigTypeSubkeyBinding,
PubKeyAlgo: e.PrimaryKey.PubKeyAlgo,
Hash: config.Hash(),
FlagsValid: true,
FlagEncryptStorage: true,
FlagEncryptCommunications: true,
IssuerKeyId: &e.PrimaryKey.KeyId,
},
}
if config != nil && config.V5Keys {
subkey.PublicKey.UpgradeToV5()
} }
subkey.Sig = createSignaturePacket(e.PrimaryKey, packet.SigTypeSubkeyBinding, config)
subkey.Sig.CreationTime = creationTime
subkey.Sig.KeyLifetimeSecs = &keyLifetimeSecs
subkey.Sig.FlagsValid = true
subkey.Sig.FlagEncryptStorage = true
subkey.Sig.FlagEncryptCommunications = true
subkey.PublicKey.IsSubkey = true err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey, config)
subkey.PrivateKey.IsSubkey = true if err != nil {
if err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey, config); err != nil {
return err return err
} }

View File

@ -150,11 +150,9 @@ func (e *Entity) EncryptionKey(now time.Time) (Key, bool) {
return Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig, subkey.Revocations}, true return Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig, subkey.Revocations}, true
} }
// If we don't have any candidate subkeys for encryption and // If we don't have any subkeys for encryption and the primary key
// the primary key doesn't have any usage metadata then we // is marked as OK to encrypt with, then we can use it.
// assume that the primary key is ok. Or, if the primary key is if i.SelfSignature.FlagsValid && i.SelfSignature.FlagEncryptCommunications &&
// marked as ok to encrypt with, then we can obviously use it.
if !i.SelfSignature.FlagsValid || i.SelfSignature.FlagEncryptCommunications &&
e.PrimaryKey.PubKeyAlgo.CanEncrypt() { e.PrimaryKey.PubKeyAlgo.CanEncrypt() {
return Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature, e.Revocations}, true return Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature, e.Revocations}, true
} }
@ -162,7 +160,6 @@ func (e *Entity) EncryptionKey(now time.Time) (Key, bool) {
return Key{}, false return Key{}, false
} }
// CertificationKey return the best candidate Key for certifying a key with this // CertificationKey return the best candidate Key for certifying a key with this
// Entity. // Entity.
func (e *Entity) CertificationKey(now time.Time) (Key, bool) { func (e *Entity) CertificationKey(now time.Time) (Key, bool) {
@ -203,8 +200,8 @@ func (e *Entity) signingKeyByIdUsage(now time.Time, id uint64, flags int) (Key,
var maxTime time.Time var maxTime time.Time
for idx, subkey := range e.Subkeys { for idx, subkey := range e.Subkeys {
if subkey.Sig.FlagsValid && if subkey.Sig.FlagsValid &&
(flags & packet.KeyFlagCertify == 0 || subkey.Sig.FlagCertify) && (flags&packet.KeyFlagCertify == 0 || subkey.Sig.FlagCertify) &&
(flags & packet.KeyFlagSign == 0 || subkey.Sig.FlagSign) && (flags&packet.KeyFlagSign == 0 || subkey.Sig.FlagSign) &&
subkey.PublicKey.PubKeyAlgo.CanSign() && subkey.PublicKey.PubKeyAlgo.CanSign() &&
!subkey.PublicKey.KeyExpired(subkey.Sig, now) && !subkey.PublicKey.KeyExpired(subkey.Sig, now) &&
!subkey.Sig.SigExpired(now) && !subkey.Sig.SigExpired(now) &&
@ -221,12 +218,11 @@ func (e *Entity) signingKeyByIdUsage(now time.Time, id uint64, flags int) (Key,
return Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig, subkey.Revocations}, true return Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig, subkey.Revocations}, true
} }
// If we have no candidate subkey then we assume that it's ok to sign // If we don't have any subkeys for signing and the primary key
// with the primary key. Or, if the primary key is marked as ok to // is marked as OK to sign with, then we can use it.
// sign with, then we can use it. if i.SelfSignature.FlagsValid &&
if !i.SelfSignature.FlagsValid || ( (flags&packet.KeyFlagCertify == 0 || i.SelfSignature.FlagCertify) &&
(flags & packet.KeyFlagCertify == 0 || i.SelfSignature.FlagCertify) && (flags&packet.KeyFlagSign == 0 || i.SelfSignature.FlagSign) &&
(flags & packet.KeyFlagSign == 0 || i.SelfSignature.FlagSign)) &&
e.PrimaryKey.PubKeyAlgo.CanSign() && e.PrimaryKey.PubKeyAlgo.CanSign() &&
(id == 0 || e.PrimaryKey.KeyId == id) { (id == 0 || e.PrimaryKey.KeyId == id) {
return Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature, e.Revocations}, true return Key{e, e.PrimaryKey, e.PrivateKey, i.SelfSignature, e.Revocations}, true
@ -256,6 +252,44 @@ func (e *Entity) Revoked(now time.Time) bool {
return revoked(e.Revocations, now) return revoked(e.Revocations, now)
} }
// EncryptPrivateKeys encrypts all non-encrypted keys in the entity with the same key
// derived from the provided passphrase. Public keys and dummy keys are ignored,
// and don't cause an error to be returned.
func (e *Entity) EncryptPrivateKeys(passphrase []byte, config *packet.Config) error {
var keysToEncrypt []*packet.PrivateKey
// Add entity private key to encrypt.
if e.PrivateKey != nil && !e.PrivateKey.Dummy() && !e.PrivateKey.Encrypted {
keysToEncrypt = append(keysToEncrypt, e.PrivateKey)
}
// Add subkeys to encrypt.
for _, sub := range e.Subkeys {
if sub.PrivateKey != nil && !sub.PrivateKey.Dummy() && !sub.PrivateKey.Encrypted {
keysToEncrypt = append(keysToEncrypt, sub.PrivateKey)
}
}
return packet.EncryptPrivateKeys(keysToEncrypt, passphrase, config)
}
// DecryptPrivateKeys decrypts all encrypted keys in the entitiy with the given passphrase.
// Avoids recomputation of similar s2k key derivations. Public keys and dummy keys are ignored,
// and don't cause an error to be returned.
func (e *Entity) DecryptPrivateKeys(passphrase []byte) error {
var keysToDecrypt []*packet.PrivateKey
// Add entity private key to decrypt.
if e.PrivateKey != nil && !e.PrivateKey.Dummy() && e.PrivateKey.Encrypted {
keysToDecrypt = append(keysToDecrypt, e.PrivateKey)
}
// Add subkeys to decrypt.
for _, sub := range e.Subkeys {
if sub.PrivateKey != nil && !sub.PrivateKey.Dummy() && sub.PrivateKey.Encrypted {
keysToDecrypt = append(keysToDecrypt, sub.PrivateKey)
}
}
return packet.DecryptPrivateKeys(keysToDecrypt, passphrase)
}
// Revoked returns whether the identity has been revoked by a self-signature. // Revoked returns whether the identity has been revoked by a self-signature.
// Note that third-party revocation signatures are not supported. // Note that third-party revocation signatures are not supported.
func (i *Identity) Revoked(now time.Time) bool { func (i *Identity) Revoked(now time.Time) bool {
@ -303,7 +337,11 @@ func (el EntityList) KeysById(id uint64) (keys []Key) {
// the bitwise-OR of packet.KeyFlag* values. // the bitwise-OR of packet.KeyFlag* values.
func (el EntityList) KeysByIdUsage(id uint64, requiredUsage byte) (keys []Key) { func (el EntityList) KeysByIdUsage(id uint64, requiredUsage byte) (keys []Key) {
for _, key := range el.KeysById(id) { for _, key := range el.KeysById(id) {
if key.SelfSignature != nil && key.SelfSignature.FlagsValid && requiredUsage != 0 { if requiredUsage != 0 {
if key.SelfSignature == nil || !key.SelfSignature.FlagsValid {
continue
}
var usage byte var usage byte
if key.SelfSignature.FlagCertify { if key.SelfSignature.FlagCertify {
usage |= packet.KeyFlagCertify usage |= packet.KeyFlagCertify
@ -331,7 +369,7 @@ func (el EntityList) KeysByIdUsage(id uint64, requiredUsage byte) (keys []Key) {
func (el EntityList) DecryptionKeys() (keys []Key) { func (el EntityList) DecryptionKeys() (keys []Key) {
for _, e := range el { for _, e := range el {
for _, subKey := range e.Subkeys { for _, subKey := range e.Subkeys {
if subKey.PrivateKey != nil && (!subKey.Sig.FlagsValid || subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications) { if subKey.PrivateKey != nil && subKey.Sig.FlagsValid && (subKey.Sig.FlagEncryptStorage || subKey.Sig.FlagEncryptCommunications) {
keys = append(keys, Key{e, subKey.PublicKey, subKey.PrivateKey, subKey.Sig, subKey.Revocations}) keys = append(keys, Key{e, subKey.PublicKey, subKey.PrivateKey, subKey.Sig, subKey.Revocations})
} }
} }
@ -466,7 +504,7 @@ EachPacket:
// Else, ignoring the signature as it does not follow anything // Else, ignoring the signature as it does not follow anything
// we would know to attach it to. // we would know to attach it to.
case *packet.PrivateKey: case *packet.PrivateKey:
if pkt.IsSubkey == false { if !pkt.IsSubkey {
packets.Unread(p) packets.Unread(p)
break EachPacket break EachPacket
} }
@ -475,7 +513,7 @@ EachPacket:
return nil, err return nil, err
} }
case *packet.PublicKey: case *packet.PublicKey:
if pkt.IsSubkey == false { if !pkt.IsSubkey {
packets.Unread(p) packets.Unread(p)
break EachPacket break EachPacket
} }
@ -751,18 +789,7 @@ func (e *Entity) SignIdentity(identity string, signer *Entity, config *packet.Co
return errors.InvalidArgumentError("given identity string not found in Entity") return errors.InvalidArgumentError("given identity string not found in Entity")
} }
sig := &packet.Signature{ sig := createSignaturePacket(certificationKey.PublicKey, packet.SigTypeGenericCert, config)
Version: certificationKey.PrivateKey.Version,
SigType: packet.SigTypeGenericCert,
PubKeyAlgo: certificationKey.PrivateKey.PubKeyAlgo,
Hash: config.Hash(),
CreationTime: config.Now(),
IssuerKeyId: &certificationKey.PrivateKey.KeyId,
}
if config.SigLifetime() != 0 {
sig.SigLifetimeSecs = &config.SigLifetimeSecs
}
signingUserID := config.SigningUserId() signingUserID := config.SigningUserId()
if signingUserID != "" { if signingUserID != "" {
@ -783,16 +810,9 @@ func (e *Entity) SignIdentity(identity string, signer *Entity, config *packet.Co
// specified reason code and text (RFC4880 section-5.2.3.23). // specified reason code and text (RFC4880 section-5.2.3.23).
// If config is nil, sensible defaults will be used. // If config is nil, sensible defaults will be used.
func (e *Entity) RevokeKey(reason packet.ReasonForRevocation, reasonText string, config *packet.Config) error { func (e *Entity) RevokeKey(reason packet.ReasonForRevocation, reasonText string, config *packet.Config) error {
revSig := &packet.Signature{ revSig := createSignaturePacket(e.PrimaryKey, packet.SigTypeKeyRevocation, config)
Version: e.PrimaryKey.Version, revSig.RevocationReason = &reason
CreationTime: config.Now(), revSig.RevocationReasonText = reasonText
SigType: packet.SigTypeKeyRevocation,
PubKeyAlgo: e.PrimaryKey.PubKeyAlgo,
Hash: config.Hash(),
RevocationReason: &reason,
RevocationReasonText: reasonText,
IssuerKeyId: &e.PrimaryKey.KeyId,
}
if err := revSig.RevokeKey(e.PrimaryKey, e.PrivateKey, config); err != nil { if err := revSig.RevokeKey(e.PrimaryKey, e.PrivateKey, config); err != nil {
return err return err
@ -809,16 +829,9 @@ func (e *Entity) RevokeSubkey(sk *Subkey, reason packet.ReasonForRevocation, rea
return errors.InvalidArgumentError("given subkey is not associated with this key") return errors.InvalidArgumentError("given subkey is not associated with this key")
} }
revSig := &packet.Signature{ revSig := createSignaturePacket(e.PrimaryKey, packet.SigTypeSubkeyRevocation, config)
Version: e.PrimaryKey.Version, revSig.RevocationReason = &reason
CreationTime: config.Now(), revSig.RevocationReasonText = reasonText
SigType: packet.SigTypeSubkeyRevocation,
PubKeyAlgo: e.PrimaryKey.PubKeyAlgo,
Hash: config.Hash(),
RevocationReason: &reason,
RevocationReasonText: reasonText,
IssuerKeyId: &e.PrimaryKey.KeyId,
}
if err := revSig.RevokeSubkey(sk.PublicKey, e.PrivateKey, config); err != nil { if err := revSig.RevokeSubkey(sk.PublicKey, e.PrivateKey, config); err != nil {
return err return err

View File

@ -518,3 +518,21 @@ XLCBln+wdewpU4ChEffMUDRBfqfQco/YsMqWV7bHJHAO0eC/DMKCjyU90xdH7R/d
QgqsfguR1PqPuJxpXV4bSr6CGAAAAA== QgqsfguR1PqPuJxpXV4bSr6CGAAAAA==
=MSvh =MSvh
-----END PGP PRIVATE KEY BLOCK-----` -----END PGP PRIVATE KEY BLOCK-----`
const keyWithNotation = `-----BEGIN PGP PRIVATE KEY BLOCK-----
xVgEY9gIshYJKwYBBAHaRw8BAQdAF25fSM8OpFlXZhop4Qpqo5ywGZ4jgWlR
ppjhIKDthREAAQC+LFpzFcMJYcjxGKzBGHN0Px2jU4d04YSRnFAik+lVVQ6u
zRdUZXN0IDx0ZXN0QGV4YW1wbGUuY29tPsLACgQQFgoAfAUCY9gIsgQLCQcI
CRD/utJOCym8pR0UgAAAAAAQAAR0ZXh0QGV4YW1wbGUuY29tdGVzdB8UAAAA
AAASAARiaW5hcnlAZXhhbXBsZS5jb20AAQIDAxUICgQWAAIBAhkBAhsDAh4B
FiEEEMCQTUVGKgCX5rDQ/7rSTgspvKUAAPl5AP9Npz90LxzrB97Qr2DrGwfG
wuYn4FSYwtuPfZHHeoIabwD/QEbvpQJ/NBb9EAZuow4Rirlt1yv19mmnF+j5
8yUzhQjHXQRj2AiyEgorBgEEAZdVAQUBAQdARXAo30DmKcyUg6co7OUm0RNT
z9iqFbDBzA8A47JEt1MDAQgHAAD/XKK3lBm0SqMR558HLWdBrNG6NqKuqb5X
joCML987ZNgRD8J4BBgWCAAqBQJj2AiyCRD/utJOCym8pQIbDBYhBBDAkE1F
RioAl+aw0P+60k4LKbylAADRxgEAg7UfBDiDPp5LHcW9D+SgFHk6+GyEU4ev
VppQxdtxPvAA/34snHBX7Twnip1nMt7P4e2hDiw/hwQ7oqioOvc6jMkP
=Z8YJ
-----END PGP PRIVATE KEY BLOCK-----
`

View File

@ -4,6 +4,14 @@ package packet
import "math/bits" import "math/bits"
// CipherSuite contains a combination of Cipher and Mode
type CipherSuite struct {
// The cipher function
Cipher CipherFunction
// The AEAD mode of operation.
Mode AEADMode
}
// AEADConfig collects a number of AEAD parameters along with sensible defaults. // AEADConfig collects a number of AEAD parameters along with sensible defaults.
// A nil AEADConfig is valid and results in all default values. // A nil AEADConfig is valid and results in all default values.
type AEADConfig struct { type AEADConfig struct {
@ -15,12 +23,13 @@ type AEADConfig struct {
// Mode returns the AEAD mode of operation. // Mode returns the AEAD mode of operation.
func (conf *AEADConfig) Mode() AEADMode { func (conf *AEADConfig) Mode() AEADMode {
// If no preference is specified, OCB is used (which is mandatory to implement).
if conf == nil || conf.DefaultMode == 0 { if conf == nil || conf.DefaultMode == 0 {
return AEADModeEAX return AEADModeOCB
} }
mode := conf.DefaultMode mode := conf.DefaultMode
if mode != AEADModeEAX && mode != AEADModeOCB && if mode != AEADModeEAX && mode != AEADModeOCB && mode != AEADModeGCM {
mode != AEADModeExperimentalGCM {
panic("AEAD mode unsupported") panic("AEAD mode unsupported")
} }
return mode return mode
@ -28,6 +37,8 @@ func (conf *AEADConfig) Mode() AEADMode {
// ChunkSizeByte returns the byte indicating the chunk size. The effective // ChunkSizeByte returns the byte indicating the chunk size. The effective
// chunk size is computed with the formula uint64(1) << (chunkSizeByte + 6) // chunk size is computed with the formula uint64(1) << (chunkSizeByte + 6)
// limit to 16 = 4 MiB
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.13.2
func (conf *AEADConfig) ChunkSizeByte() byte { func (conf *AEADConfig) ChunkSizeByte() byte {
if conf == nil || conf.ChunkSize == 0 { if conf == nil || conf.ChunkSize == 0 {
return 12 // 1 << (12 + 6) == 262144 bytes return 12 // 1 << (12 + 6) == 262144 bytes
@ -38,8 +49,8 @@ func (conf *AEADConfig) ChunkSizeByte() byte {
switch { switch {
case exponent < 6: case exponent < 6:
exponent = 6 exponent = 6
case exponent > 27: case exponent > 16:
exponent = 27 exponent = 16
} }
return byte(exponent - 6) return byte(exponent - 6)

View File

@ -0,0 +1,264 @@
// Copyright (C) 2019 ProtonTech AG
package packet
import (
"bytes"
"crypto/cipher"
"encoding/binary"
"io"
"github.com/ProtonMail/go-crypto/openpgp/errors"
)
// aeadCrypter is an AEAD opener/sealer, its configuration, and data for en/decryption.
type aeadCrypter struct {
aead cipher.AEAD
chunkSize int
initialNonce []byte
associatedData []byte // Chunk-independent associated data
chunkIndex []byte // Chunk counter
packetTag packetType // SEIP packet (v2) or AEAD Encrypted Data packet
bytesProcessed int // Amount of plaintext bytes encrypted/decrypted
buffer bytes.Buffer // Buffered bytes across chunks
}
// computeNonce takes the incremental index and computes an eXclusive OR with
// the least significant 8 bytes of the receivers' initial nonce (see sec.
// 5.16.1 and 5.16.2). It returns the resulting nonce.
func (wo *aeadCrypter) computeNextNonce() (nonce []byte) {
if wo.packetTag == packetTypeSymmetricallyEncryptedIntegrityProtected {
return append(wo.initialNonce, wo.chunkIndex...)
}
nonce = make([]byte, len(wo.initialNonce))
copy(nonce, wo.initialNonce)
offset := len(wo.initialNonce) - 8
for i := 0; i < 8; i++ {
nonce[i+offset] ^= wo.chunkIndex[i]
}
return
}
// incrementIndex performs an integer increment by 1 of the integer represented by the
// slice, modifying it accordingly.
func (wo *aeadCrypter) incrementIndex() error {
index := wo.chunkIndex
if len(index) == 0 {
return errors.AEADError("Index has length 0")
}
for i := len(index) - 1; i >= 0; i-- {
if index[i] < 255 {
index[i]++
return nil
}
index[i] = 0
}
return errors.AEADError("cannot further increment index")
}
// aeadDecrypter reads and decrypts bytes. It buffers extra decrypted bytes when
// necessary, similar to aeadEncrypter.
type aeadDecrypter struct {
aeadCrypter // Embedded ciphertext opener
reader io.Reader // 'reader' is a partialLengthReader
peekedBytes []byte // Used to detect last chunk
eof bool
}
// Read decrypts bytes and reads them into dst. It decrypts when necessary and
// buffers extra decrypted bytes. It returns the number of bytes copied into dst
// and an error.
func (ar *aeadDecrypter) Read(dst []byte) (n int, err error) {
// Return buffered plaintext bytes from previous calls
if ar.buffer.Len() > 0 {
return ar.buffer.Read(dst)
}
// Return EOF if we've previously validated the final tag
if ar.eof {
return 0, io.EOF
}
// Read a chunk
tagLen := ar.aead.Overhead()
cipherChunkBuf := new(bytes.Buffer)
_, errRead := io.CopyN(cipherChunkBuf, ar.reader, int64(ar.chunkSize+tagLen))
cipherChunk := cipherChunkBuf.Bytes()
if errRead != nil && errRead != io.EOF {
return 0, errRead
}
decrypted, errChunk := ar.openChunk(cipherChunk)
if errChunk != nil {
return 0, errChunk
}
// Return decrypted bytes, buffering if necessary
if len(dst) < len(decrypted) {
n = copy(dst, decrypted[:len(dst)])
ar.buffer.Write(decrypted[len(dst):])
} else {
n = copy(dst, decrypted)
}
// Check final authentication tag
if errRead == io.EOF {
errChunk := ar.validateFinalTag(ar.peekedBytes)
if errChunk != nil {
return n, errChunk
}
ar.eof = true // Mark EOF for when we've returned all buffered data
}
return
}
// Close is noOp. The final authentication tag of the stream was already
// checked in the last Read call. In the future, this function could be used to
// wipe the reader and peeked, decrypted bytes, if necessary.
func (ar *aeadDecrypter) Close() (err error) {
return nil
}
// openChunk decrypts and checks integrity of an encrypted chunk, returning
// the underlying plaintext and an error. It accesses peeked bytes from next
// chunk, to identify the last chunk and decrypt/validate accordingly.
func (ar *aeadDecrypter) openChunk(data []byte) ([]byte, error) {
tagLen := ar.aead.Overhead()
// Restore carried bytes from last call
chunkExtra := append(ar.peekedBytes, data...)
// 'chunk' contains encrypted bytes, followed by an authentication tag.
chunk := chunkExtra[:len(chunkExtra)-tagLen]
ar.peekedBytes = chunkExtra[len(chunkExtra)-tagLen:]
adata := ar.associatedData
if ar.aeadCrypter.packetTag == packetTypeAEADEncrypted {
adata = append(ar.associatedData, ar.chunkIndex...)
}
nonce := ar.computeNextNonce()
plainChunk, err := ar.aead.Open(nil, nonce, chunk, adata)
if err != nil {
return nil, err
}
ar.bytesProcessed += len(plainChunk)
if err = ar.aeadCrypter.incrementIndex(); err != nil {
return nil, err
}
return plainChunk, nil
}
// Checks the summary tag. It takes into account the total decrypted bytes into
// the associated data. It returns an error, or nil if the tag is valid.
func (ar *aeadDecrypter) validateFinalTag(tag []byte) error {
// Associated: tag, version, cipher, aead, chunk size, ...
amountBytes := make([]byte, 8)
binary.BigEndian.PutUint64(amountBytes, uint64(ar.bytesProcessed))
adata := ar.associatedData
if ar.aeadCrypter.packetTag == packetTypeAEADEncrypted {
// ... index ...
adata = append(ar.associatedData, ar.chunkIndex...)
}
// ... and total number of encrypted octets
adata = append(adata, amountBytes...)
nonce := ar.computeNextNonce()
_, err := ar.aead.Open(nil, nonce, tag, adata)
if err != nil {
return err
}
return nil
}
// aeadEncrypter encrypts and writes bytes. It encrypts when necessary according
// to the AEAD block size, and buffers the extra encrypted bytes for next write.
type aeadEncrypter struct {
aeadCrypter // Embedded plaintext sealer
writer io.WriteCloser // 'writer' is a partialLengthWriter
}
// Write encrypts and writes bytes. It encrypts when necessary and buffers extra
// plaintext bytes for next call. When the stream is finished, Close() MUST be
// called to append the final tag.
func (aw *aeadEncrypter) Write(plaintextBytes []byte) (n int, err error) {
// Append plaintextBytes to existing buffered bytes
n, err = aw.buffer.Write(plaintextBytes)
if err != nil {
return n, err
}
// Encrypt and write chunks
for aw.buffer.Len() >= aw.chunkSize {
plainChunk := aw.buffer.Next(aw.chunkSize)
encryptedChunk, err := aw.sealChunk(plainChunk)
if err != nil {
return n, err
}
_, err = aw.writer.Write(encryptedChunk)
if err != nil {
return n, err
}
}
return
}
// Close encrypts and writes the remaining buffered plaintext if any, appends
// the final authentication tag, and closes the embedded writer. This function
// MUST be called at the end of a stream.
func (aw *aeadEncrypter) Close() (err error) {
// Encrypt and write a chunk if there's buffered data left, or if we haven't
// written any chunks yet.
if aw.buffer.Len() > 0 || aw.bytesProcessed == 0 {
plainChunk := aw.buffer.Bytes()
lastEncryptedChunk, err := aw.sealChunk(plainChunk)
if err != nil {
return err
}
_, err = aw.writer.Write(lastEncryptedChunk)
if err != nil {
return err
}
}
// Compute final tag (associated data: packet tag, version, cipher, aead,
// chunk size...
adata := aw.associatedData
if aw.aeadCrypter.packetTag == packetTypeAEADEncrypted {
// ... index ...
adata = append(aw.associatedData, aw.chunkIndex...)
}
// ... and total number of encrypted octets
amountBytes := make([]byte, 8)
binary.BigEndian.PutUint64(amountBytes, uint64(aw.bytesProcessed))
adata = append(adata, amountBytes...)
nonce := aw.computeNextNonce()
finalTag := aw.aead.Seal(nil, nonce, nil, adata)
_, err = aw.writer.Write(finalTag)
if err != nil {
return err
}
return aw.writer.Close()
}
// sealChunk Encrypts and authenticates the given chunk.
func (aw *aeadEncrypter) sealChunk(data []byte) ([]byte, error) {
if len(data) > aw.chunkSize {
return nil, errors.AEADError("chunk exceeds maximum length")
}
if aw.associatedData == nil {
return nil, errors.AEADError("can't seal without headers")
}
adata := aw.associatedData
if aw.aeadCrypter.packetTag == packetTypeAEADEncrypted {
adata = append(aw.associatedData, aw.chunkIndex...)
}
nonce := aw.computeNextNonce()
encrypted := aw.aead.Seal(nil, nonce, data, adata)
aw.bytesProcessed += len(data)
if err := aw.aeadCrypter.incrementIndex(); err != nil {
return nil, err
}
return encrypted, nil
}

View File

@ -3,17 +3,14 @@
package packet package packet
import ( import (
"bytes"
"crypto/cipher"
"crypto/rand"
"encoding/binary"
"io" "io"
"github.com/ProtonMail/go-crypto/openpgp/errors" "github.com/ProtonMail/go-crypto/openpgp/errors"
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
) )
// AEADEncrypted represents an AEAD Encrypted Packet (tag 20, RFC4880bis-5.16). // AEADEncrypted represents an AEAD Encrypted Packet.
// See https://www.ietf.org/archive/id/draft-koch-openpgp-2015-rfc4880bis-00.html#name-aead-encrypted-data-packet-t
type AEADEncrypted struct { type AEADEncrypted struct {
cipher CipherFunction cipher CipherFunction
mode AEADMode mode AEADMode
@ -25,33 +22,6 @@ type AEADEncrypted struct {
// Only currently defined version // Only currently defined version
const aeadEncryptedVersion = 1 const aeadEncryptedVersion = 1
// An AEAD opener/sealer, its configuration, and data for en/decryption.
type aeadCrypter struct {
aead cipher.AEAD
chunkSize int
initialNonce []byte
associatedData []byte // Chunk-independent associated data
chunkIndex []byte // Chunk counter
bytesProcessed int // Amount of plaintext bytes encrypted/decrypted
buffer bytes.Buffer // Buffered bytes across chunks
}
// aeadEncrypter encrypts and writes bytes. It encrypts when necessary according
// to the AEAD block size, and buffers the extra encrypted bytes for next write.
type aeadEncrypter struct {
aeadCrypter // Embedded plaintext sealer
writer io.WriteCloser // 'writer' is a partialLengthWriter
}
// aeadDecrypter reads and decrypts bytes. It buffers extra decrypted bytes when
// necessary, similar to aeadEncrypter.
type aeadDecrypter struct {
aeadCrypter // Embedded ciphertext opener
reader io.Reader // 'reader' is a partialLengthReader
peekedBytes []byte // Used to detect last chunk
eof bool
}
func (ae *AEADEncrypted) parse(buf io.Reader) error { func (ae *AEADEncrypted) parse(buf io.Reader) error {
headerData := make([]byte, 4) headerData := make([]byte, 4)
if n, err := io.ReadFull(buf, headerData); n < 4 { if n, err := io.ReadFull(buf, headerData); n < 4 {
@ -59,10 +29,14 @@ func (ae *AEADEncrypted) parse(buf io.Reader) error {
} }
// Read initial nonce // Read initial nonce
mode := AEADMode(headerData[2]) mode := AEADMode(headerData[2])
nonceLen := mode.NonceLength() nonceLen := mode.IvLength()
if nonceLen == 0 {
// This packet supports only EAX and OCB
// https://www.ietf.org/archive/id/draft-koch-openpgp-2015-rfc4880bis-00.html#name-aead-encrypted-data-packet-t
if nonceLen == 0 || mode > AEADModeOCB {
return errors.AEADError("unknown mode") return errors.AEADError("unknown mode")
} }
initialNonce := make([]byte, nonceLen) initialNonce := make([]byte, nonceLen)
if n, err := io.ReadFull(buf, initialNonce); n < nonceLen { if n, err := io.ReadFull(buf, initialNonce); n < nonceLen {
return errors.AEADError("could not read aead nonce:" + err.Error()) return errors.AEADError("could not read aead nonce:" + err.Error())
@ -75,7 +49,7 @@ func (ae *AEADEncrypted) parse(buf io.Reader) error {
} }
ae.cipher = CipherFunction(c) ae.cipher = CipherFunction(c)
ae.mode = mode ae.mode = mode
ae.chunkSizeByte = byte(headerData[3]) ae.chunkSizeByte = headerData[3]
return nil return nil
} }
@ -105,225 +79,13 @@ func (ae *AEADEncrypted) decrypt(key []byte) (io.ReadCloser, error) {
initialNonce: ae.initialNonce, initialNonce: ae.initialNonce,
associatedData: ae.associatedData(), associatedData: ae.associatedData(),
chunkIndex: make([]byte, 8), chunkIndex: make([]byte, 8),
packetTag: packetTypeAEADEncrypted,
}, },
reader: ae.Contents, reader: ae.Contents,
peekedBytes: peekedBytes}, nil peekedBytes: peekedBytes}, nil
} }
// Read decrypts bytes and reads them into dst. It decrypts when necessary and // associatedData for chunks: tag, version, cipher, mode, chunk size byte
// buffers extra decrypted bytes. It returns the number of bytes copied into dst
// and an error.
func (ar *aeadDecrypter) Read(dst []byte) (n int, err error) {
// Return buffered plaintext bytes from previous calls
if ar.buffer.Len() > 0 {
return ar.buffer.Read(dst)
}
// Return EOF if we've previously validated the final tag
if ar.eof {
return 0, io.EOF
}
// Read a chunk
tagLen := ar.aead.Overhead()
cipherChunkBuf := new(bytes.Buffer)
_, errRead := io.CopyN(cipherChunkBuf, ar.reader, int64(ar.chunkSize + tagLen))
cipherChunk := cipherChunkBuf.Bytes()
if errRead != nil && errRead != io.EOF {
return 0, errRead
}
decrypted, errChunk := ar.openChunk(cipherChunk)
if errChunk != nil {
return 0, errChunk
}
// Return decrypted bytes, buffering if necessary
if len(dst) < len(decrypted) {
n = copy(dst, decrypted[:len(dst)])
ar.buffer.Write(decrypted[len(dst):])
} else {
n = copy(dst, decrypted)
}
// Check final authentication tag
if errRead == io.EOF {
errChunk := ar.validateFinalTag(ar.peekedBytes)
if errChunk != nil {
return n, errChunk
}
ar.eof = true // Mark EOF for when we've returned all buffered data
}
return
}
// Close is noOp. The final authentication tag of the stream was already
// checked in the last Read call. In the future, this function could be used to
// wipe the reader and peeked, decrypted bytes, if necessary.
func (ar *aeadDecrypter) Close() (err error) {
return nil
}
// SerializeAEADEncrypted initializes the aeadCrypter and returns a writer.
// This writer encrypts and writes bytes (see aeadEncrypter.Write()).
func SerializeAEADEncrypted(w io.Writer, key []byte, cipher CipherFunction, mode AEADMode, config *Config) (io.WriteCloser, error) {
writeCloser := noOpCloser{w}
writer, err := serializeStreamHeader(writeCloser, packetTypeAEADEncrypted)
if err != nil {
return nil, err
}
// Data for en/decryption: tag, version, cipher, aead mode, chunk size
aeadConf := config.AEAD()
prefix := []byte{
0xD4,
aeadEncryptedVersion,
byte(config.Cipher()),
byte(aeadConf.Mode()),
aeadConf.ChunkSizeByte(),
}
n, err := writer.Write(prefix[1:])
if err != nil || n < 4 {
return nil, errors.AEADError("could not write AEAD headers")
}
// Sample nonce
nonceLen := aeadConf.Mode().NonceLength()
nonce := make([]byte, nonceLen)
n, err = rand.Read(nonce)
if err != nil {
panic("Could not sample random nonce")
}
_, err = writer.Write(nonce)
if err != nil {
return nil, err
}
blockCipher := CipherFunction(config.Cipher()).new(key)
alg := AEADMode(aeadConf.Mode()).new(blockCipher)
chunkSize := decodeAEADChunkSize(aeadConf.ChunkSizeByte())
return &aeadEncrypter{
aeadCrypter: aeadCrypter{
aead: alg,
chunkSize: chunkSize,
associatedData: prefix,
chunkIndex: make([]byte, 8),
initialNonce: nonce,
},
writer: writer}, nil
}
// Write encrypts and writes bytes. It encrypts when necessary and buffers extra
// plaintext bytes for next call. When the stream is finished, Close() MUST be
// called to append the final tag.
func (aw *aeadEncrypter) Write(plaintextBytes []byte) (n int, err error) {
// Append plaintextBytes to existing buffered bytes
n, err = aw.buffer.Write(plaintextBytes)
if err != nil {
return n, err
}
// Encrypt and write chunks
for aw.buffer.Len() >= aw.chunkSize {
plainChunk := aw.buffer.Next(aw.chunkSize)
encryptedChunk, err := aw.sealChunk(plainChunk)
if err != nil {
return n, err
}
_, err = aw.writer.Write(encryptedChunk)
if err != nil {
return n, err
}
}
return
}
// Close encrypts and writes the remaining buffered plaintext if any, appends
// the final authentication tag, and closes the embedded writer. This function
// MUST be called at the end of a stream.
func (aw *aeadEncrypter) Close() (err error) {
// Encrypt and write a chunk if there's buffered data left, or if we haven't
// written any chunks yet.
if aw.buffer.Len() > 0 || aw.bytesProcessed == 0 {
plainChunk := aw.buffer.Bytes()
lastEncryptedChunk, err := aw.sealChunk(plainChunk)
if err != nil {
return err
}
_, err = aw.writer.Write(lastEncryptedChunk)
if err != nil {
return err
}
}
// Compute final tag (associated data: packet tag, version, cipher, aead,
// chunk size, index, total number of encrypted octets).
adata := append(aw.associatedData[:], aw.chunkIndex[:]...)
adata = append(adata, make([]byte, 8)...)
binary.BigEndian.PutUint64(adata[13:], uint64(aw.bytesProcessed))
nonce := aw.computeNextNonce()
finalTag := aw.aead.Seal(nil, nonce, nil, adata)
_, err = aw.writer.Write(finalTag)
if err != nil {
return err
}
return aw.writer.Close()
}
// sealChunk Encrypts and authenticates the given chunk.
func (aw *aeadEncrypter) sealChunk(data []byte) ([]byte, error) {
if len(data) > aw.chunkSize {
return nil, errors.AEADError("chunk exceeds maximum length")
}
if aw.associatedData == nil {
return nil, errors.AEADError("can't seal without headers")
}
adata := append(aw.associatedData, aw.chunkIndex...)
nonce := aw.computeNextNonce()
encrypted := aw.aead.Seal(nil, nonce, data, adata)
aw.bytesProcessed += len(data)
if err := aw.aeadCrypter.incrementIndex(); err != nil {
return nil, err
}
return encrypted, nil
}
// openChunk decrypts and checks integrity of an encrypted chunk, returning
// the underlying plaintext and an error. It access peeked bytes from next
// chunk, to identify the last chunk and decrypt/validate accordingly.
func (ar *aeadDecrypter) openChunk(data []byte) ([]byte, error) {
tagLen := ar.aead.Overhead()
// Restore carried bytes from last call
chunkExtra := append(ar.peekedBytes, data...)
// 'chunk' contains encrypted bytes, followed by an authentication tag.
chunk := chunkExtra[:len(chunkExtra)-tagLen]
ar.peekedBytes = chunkExtra[len(chunkExtra)-tagLen:]
adata := append(ar.associatedData, ar.chunkIndex...)
nonce := ar.computeNextNonce()
plainChunk, err := ar.aead.Open(nil, nonce, chunk, adata)
if err != nil {
return nil, err
}
ar.bytesProcessed += len(plainChunk)
if err = ar.aeadCrypter.incrementIndex(); err != nil {
return nil, err
}
return plainChunk, nil
}
// Checks the summary tag. It takes into account the total decrypted bytes into
// the associated data. It returns an error, or nil if the tag is valid.
func (ar *aeadDecrypter) validateFinalTag(tag []byte) error {
// Associated: tag, version, cipher, aead, chunk size, index, and octets
amountBytes := make([]byte, 8)
binary.BigEndian.PutUint64(amountBytes, uint64(ar.bytesProcessed))
adata := append(ar.associatedData, ar.chunkIndex...)
adata = append(adata, amountBytes...)
nonce := ar.computeNextNonce()
_, err := ar.aead.Open(nil, nonce, tag, adata)
if err != nil {
return err
}
return nil
}
// Associated data for chunks: tag, version, cipher, mode, chunk size byte
func (ae *AEADEncrypted) associatedData() []byte { func (ae *AEADEncrypted) associatedData() []byte {
return []byte{ return []byte{
0xD4, 0xD4,
@ -332,33 +94,3 @@ func (ae *AEADEncrypted) associatedData() []byte {
byte(ae.mode), byte(ae.mode),
ae.chunkSizeByte} ae.chunkSizeByte}
} }
// computeNonce takes the incremental index and computes an eXclusive OR with
// the least significant 8 bytes of the receivers' initial nonce (see sec.
// 5.16.1 and 5.16.2). It returns the resulting nonce.
func (wo *aeadCrypter) computeNextNonce() (nonce []byte) {
nonce = make([]byte, len(wo.initialNonce))
copy(nonce, wo.initialNonce)
offset := len(wo.initialNonce) - 8
for i := 0; i < 8; i++ {
nonce[i+offset] ^= wo.chunkIndex[i]
}
return
}
// incrementIndex performs an integer increment by 1 of the integer represented by the
// slice, modifying it accordingly.
func (wo *aeadCrypter) incrementIndex() error {
index := wo.chunkIndex
if len(index) == 0 {
return errors.AEADError("Index has length 0")
}
for i := len(index) - 1; i >= 0; i-- {
if index[i] < 255 {
index[i]++
return nil
}
index[i] = 0
}
return errors.AEADError("cannot further increment index")
}

View File

@ -10,6 +10,8 @@ import (
"io" "io"
"math/big" "math/big"
"time" "time"
"github.com/ProtonMail/go-crypto/openpgp/s2k"
) )
// Config collects a number of parameters along with sensible defaults. // Config collects a number of parameters along with sensible defaults.
@ -33,16 +35,24 @@ type Config struct {
DefaultCompressionAlgo CompressionAlgo DefaultCompressionAlgo CompressionAlgo
// CompressionConfig configures the compression settings. // CompressionConfig configures the compression settings.
CompressionConfig *CompressionConfig CompressionConfig *CompressionConfig
// S2KCount is only used for symmetric encryption. It // S2K (String to Key) config, used for key derivation in the context of secret key encryption
// determines the strength of the passphrase stretching when // and password-encrypted data.
// If nil, the default configuration is used
S2KConfig *s2k.Config
// Iteration count for Iterated S2K (String to Key).
// Only used if sk2.Mode is nil.
// This value is duplicated here from s2k.Config for backwards compatibility.
// It determines the strength of the passphrase stretching when
// the said passphrase is hashed to produce a key. S2KCount // the said passphrase is hashed to produce a key. S2KCount
// should be between 1024 and 65011712, inclusive. If Config // should be between 65536 and 65011712, inclusive. If Config
// is nil or S2KCount is 0, the value 65536 used. Not all // is nil or S2KCount is 0, the value 16777216 used. Not all
// values in the above range can be represented. S2KCount will // values in the above range can be represented. S2KCount will
// be rounded up to the next representable value if it cannot // be rounded up to the next representable value if it cannot
// be encoded exactly. When set, it is strongly encrouraged to // be encoded exactly. When set, it is strongly encrouraged to
// use a value that is at least 65536. See RFC 4880 Section // use a value that is at least 65536. See RFC 4880 Section
// 3.7.1.3. // 3.7.1.3.
//
// Deprecated: SK2Count should be configured in S2KConfig instead.
S2KCount int S2KCount int
// RSABits is the number of bits in new RSA keys made with NewEntity. // RSABits is the number of bits in new RSA keys made with NewEntity.
// If zero, then 2048 bit keys are created. // If zero, then 2048 bit keys are created.
@ -85,6 +95,21 @@ type Config struct {
// when producing a generic certification signature onto an existing user ID. // when producing a generic certification signature onto an existing user ID.
// The identity must be present in the signer Entity. // The identity must be present in the signer Entity.
SigningIdentity string SigningIdentity string
// InsecureAllowUnauthenticatedMessages controls, whether it is tolerated to read
// encrypted messages without Modification Detection Code (MDC).
// MDC is mandated by the IETF OpenPGP Crypto Refresh draft and has long been implemented
// in most OpenPGP implementations. Messages without MDC are considered unnecessarily
// insecure and should be prevented whenever possible.
// In case one needs to deal with messages from very old OpenPGP implementations, there
// might be no other way than to tolerate the missing MDC. Setting this flag, allows this
// mode of operation. It should be considered a measure of last resort.
InsecureAllowUnauthenticatedMessages bool
// KnownNotations is a map of Notation Data names to bools, which controls
// the notation names that are allowed to be present in critical Notation Data
// signature subpackets.
KnownNotations map[string]bool
// SignatureNotations is a list of Notations to be added to any signatures.
SignatureNotations []*Notation
} }
func (c *Config) Random() io.Reader { func (c *Config) Random() io.Reader {
@ -110,9 +135,9 @@ func (c *Config) Cipher() CipherFunction {
func (c *Config) Now() time.Time { func (c *Config) Now() time.Time {
if c == nil || c.Time == nil { if c == nil || c.Time == nil {
return time.Now() return time.Now().Truncate(time.Second)
} }
return c.Time() return c.Time().Truncate(time.Second)
} }
// KeyLifetime returns the validity period of the key. // KeyLifetime returns the validity period of the key.
@ -138,13 +163,6 @@ func (c *Config) Compression() CompressionAlgo {
return c.DefaultCompressionAlgo return c.DefaultCompressionAlgo
} }
func (c *Config) PasswordHashIterations() int {
if c == nil || c.S2KCount == 0 {
return 0
}
return c.S2KCount
}
func (c *Config) RSAModulusBits() int { func (c *Config) RSAModulusBits() int {
if c == nil || c.RSABits == 0 { if c == nil || c.RSABits == 0 {
return 2048 return 2048
@ -166,6 +184,27 @@ func (c *Config) CurveName() Curve {
return c.Curve return c.Curve
} }
// Deprecated: The hash iterations should now be queried via the S2K() method.
func (c *Config) PasswordHashIterations() int {
if c == nil || c.S2KCount == 0 {
return 0
}
return c.S2KCount
}
func (c *Config) S2K() *s2k.Config {
if c == nil {
return nil
}
// for backwards compatibility
if c != nil && c.S2KCount > 0 && c.S2KConfig == nil {
return &s2k.Config{
S2KCount: c.S2KCount,
}
}
return c.S2KConfig
}
func (c *Config) AEAD() *AEADConfig { func (c *Config) AEAD() *AEADConfig {
if c == nil { if c == nil {
return nil return nil
@ -186,3 +225,24 @@ func (c *Config) SigningUserId() string {
} }
return c.SigningIdentity return c.SigningIdentity
} }
func (c *Config) AllowUnauthenticatedMessages() bool {
if c == nil {
return false
}
return c.InsecureAllowUnauthenticatedMessages
}
func (c *Config) KnownNotation(notationName string) bool {
if c == nil {
return false
}
return c.KnownNotations[notationName]
}
func (c *Config) Notations() []*Notation {
if c == nil {
return nil
}
return c.SignatureNotations
}

View File

@ -25,7 +25,7 @@ const encryptedKeyVersion = 3
type EncryptedKey struct { type EncryptedKey struct {
KeyId uint64 KeyId uint64
Algo PublicKeyAlgorithm Algo PublicKeyAlgorithm
CipherFunc CipherFunction // only valid after a successful Decrypt CipherFunc CipherFunction // only valid after a successful Decrypt for a v3 packet
Key []byte // only valid after a successful Decrypt Key []byte // only valid after a successful Decrypt
encryptedMPI1, encryptedMPI2 encoding.Field encryptedMPI1, encryptedMPI2 encoding.Field
@ -123,6 +123,10 @@ func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error {
} }
e.CipherFunc = CipherFunction(b[0]) e.CipherFunc = CipherFunction(b[0])
if !e.CipherFunc.IsSupported() {
return errors.UnsupportedError("unsupported encryption function")
}
e.Key = b[1 : len(b)-2] e.Key = b[1 : len(b)-2]
expectedChecksum := uint16(b[len(b)-2])<<8 | uint16(b[len(b)-1]) expectedChecksum := uint16(b[len(b)-2])<<8 | uint16(b[len(b)-1])
checksum := checksumKeyMaterial(e.Key) checksum := checksumKeyMaterial(e.Key)

View File

@ -0,0 +1,29 @@
package packet
// Notation type represents a Notation Data subpacket
// see https://tools.ietf.org/html/rfc4880#section-5.2.3.16
type Notation struct {
Name string
Value []byte
IsCritical bool
IsHumanReadable bool
}
func (notation *Notation) getData() []byte {
nameData := []byte(notation.Name)
nameLen := len(nameData)
valueLen := len(notation.Value)
data := make([]byte, 8+nameLen+valueLen)
if notation.IsHumanReadable {
data[0] = 0x80
}
data[4] = byte(nameLen >> 8)
data[5] = byte(nameLen)
data[6] = byte(valueLen >> 8)
data[7] = byte(valueLen)
copy(data[8:8+nameLen], nameData)
copy(data[8+nameLen:], notation.Value)
return data
}

View File

@ -8,7 +8,7 @@ import (
"crypto" "crypto"
"encoding/binary" "encoding/binary"
"github.com/ProtonMail/go-crypto/openpgp/errors" "github.com/ProtonMail/go-crypto/openpgp/errors"
"github.com/ProtonMail/go-crypto/openpgp/s2k" "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
"io" "io"
"strconv" "strconv"
) )
@ -37,7 +37,7 @@ func (ops *OnePassSignature) parse(r io.Reader) (err error) {
} }
var ok bool var ok bool
ops.Hash, ok = s2k.HashIdToHash(buf[2]) ops.Hash, ok = algorithm.HashIdToHashWithSha1(buf[2])
if !ok { if !ok {
return errors.UnsupportedError("hash function: " + strconv.Itoa(int(buf[2]))) return errors.UnsupportedError("hash function: " + strconv.Itoa(int(buf[2])))
} }
@ -55,7 +55,7 @@ func (ops *OnePassSignature) Serialize(w io.Writer) error {
buf[0] = onePassSignatureVersion buf[0] = onePassSignatureVersion
buf[1] = uint8(ops.SigType) buf[1] = uint8(ops.SigType)
var ok bool var ok bool
buf[2], ok = s2k.HashToHashId(ops.Hash) buf[2], ok = algorithm.HashToHashIdWithSha1(ops.Hash)
if !ok { if !ok {
return errors.UnsupportedError("hash type: " + strconv.Itoa(int(ops.Hash))) return errors.UnsupportedError("hash type: " + strconv.Itoa(int(ops.Hash)))
} }

View File

@ -84,8 +84,9 @@ func (or *OpaqueReader) Next() (op *OpaquePacket, err error) {
// OpaqueSubpacket represents an unparsed OpenPGP subpacket, // OpaqueSubpacket represents an unparsed OpenPGP subpacket,
// as found in signature and user attribute packets. // as found in signature and user attribute packets.
type OpaqueSubpacket struct { type OpaqueSubpacket struct {
SubType uint8 SubType uint8
Contents []byte EncodedLength []byte // Store the original encoded length for signature verifications.
Contents []byte
} }
// OpaqueSubpackets extracts opaque, unparsed OpenPGP subpackets from // OpaqueSubpackets extracts opaque, unparsed OpenPGP subpackets from
@ -109,6 +110,7 @@ func OpaqueSubpackets(contents []byte) (result []*OpaqueSubpacket, err error) {
func nextSubpacket(contents []byte) (subHeaderLen int, subPacket *OpaqueSubpacket, err error) { func nextSubpacket(contents []byte) (subHeaderLen int, subPacket *OpaqueSubpacket, err error) {
// RFC 4880, section 5.2.3.1 // RFC 4880, section 5.2.3.1
var subLen uint32 var subLen uint32
var encodedLength []byte
if len(contents) < 1 { if len(contents) < 1 {
goto Truncated goto Truncated
} }
@ -119,6 +121,7 @@ func nextSubpacket(contents []byte) (subHeaderLen int, subPacket *OpaqueSubpacke
if len(contents) < subHeaderLen { if len(contents) < subHeaderLen {
goto Truncated goto Truncated
} }
encodedLength = contents[0:1]
subLen = uint32(contents[0]) subLen = uint32(contents[0])
contents = contents[1:] contents = contents[1:]
case contents[0] < 255: case contents[0] < 255:
@ -126,6 +129,7 @@ func nextSubpacket(contents []byte) (subHeaderLen int, subPacket *OpaqueSubpacke
if len(contents) < subHeaderLen { if len(contents) < subHeaderLen {
goto Truncated goto Truncated
} }
encodedLength = contents[0:2]
subLen = uint32(contents[0]-192)<<8 + uint32(contents[1]) + 192 subLen = uint32(contents[0]-192)<<8 + uint32(contents[1]) + 192
contents = contents[2:] contents = contents[2:]
default: default:
@ -133,16 +137,19 @@ func nextSubpacket(contents []byte) (subHeaderLen int, subPacket *OpaqueSubpacke
if len(contents) < subHeaderLen { if len(contents) < subHeaderLen {
goto Truncated goto Truncated
} }
encodedLength = contents[0:5]
subLen = uint32(contents[1])<<24 | subLen = uint32(contents[1])<<24 |
uint32(contents[2])<<16 | uint32(contents[2])<<16 |
uint32(contents[3])<<8 | uint32(contents[3])<<8 |
uint32(contents[4]) uint32(contents[4])
contents = contents[5:] contents = contents[5:]
} }
if subLen > uint32(len(contents)) || subLen == 0 { if subLen > uint32(len(contents)) || subLen == 0 {
goto Truncated goto Truncated
} }
subPacket.SubType = contents[0] subPacket.SubType = contents[0]
subPacket.EncodedLength = encodedLength
subPacket.Contents = contents[1:subLen] subPacket.Contents = contents[1:subLen]
return return
Truncated: Truncated:
@ -152,7 +159,9 @@ Truncated:
func (osp *OpaqueSubpacket) Serialize(w io.Writer) (err error) { func (osp *OpaqueSubpacket) Serialize(w io.Writer) (err error) {
buf := make([]byte, 6) buf := make([]byte, 6)
n := serializeSubpacketLength(buf, len(osp.Contents)+1) copy(buf, osp.EncodedLength)
n := len(osp.EncodedLength)
buf[n] = osp.SubType buf[n] = osp.SubType
if _, err = w.Write(buf[:n+1]); err != nil { if _, err = w.Write(buf[:n+1]); err != nil {
return return

View File

@ -302,21 +302,21 @@ func consumeAll(r io.Reader) (n int64, err error) {
type packetType uint8 type packetType uint8
const ( const (
packetTypeEncryptedKey packetType = 1 packetTypeEncryptedKey packetType = 1
packetTypeSignature packetType = 2 packetTypeSignature packetType = 2
packetTypeSymmetricKeyEncrypted packetType = 3 packetTypeSymmetricKeyEncrypted packetType = 3
packetTypeOnePassSignature packetType = 4 packetTypeOnePassSignature packetType = 4
packetTypePrivateKey packetType = 5 packetTypePrivateKey packetType = 5
packetTypePublicKey packetType = 6 packetTypePublicKey packetType = 6
packetTypePrivateSubkey packetType = 7 packetTypePrivateSubkey packetType = 7
packetTypeCompressed packetType = 8 packetTypeCompressed packetType = 8
packetTypeSymmetricallyEncrypted packetType = 9 packetTypeSymmetricallyEncrypted packetType = 9
packetTypeLiteralData packetType = 11 packetTypeLiteralData packetType = 11
packetTypeUserId packetType = 13 packetTypeUserId packetType = 13
packetTypePublicSubkey packetType = 14 packetTypePublicSubkey packetType = 14
packetTypeUserAttribute packetType = 17 packetTypeUserAttribute packetType = 17
packetTypeSymmetricallyEncryptedMDC packetType = 18 packetTypeSymmetricallyEncryptedIntegrityProtected packetType = 18
packetTypeAEADEncrypted packetType = 20 packetTypeAEADEncrypted packetType = 20
) )
// EncryptedDataPacket holds encrypted data. It is currently implemented by // EncryptedDataPacket holds encrypted data. It is currently implemented by
@ -361,9 +361,9 @@ func Read(r io.Reader) (p Packet, err error) {
p = new(UserId) p = new(UserId)
case packetTypeUserAttribute: case packetTypeUserAttribute:
p = new(UserAttribute) p = new(UserAttribute)
case packetTypeSymmetricallyEncryptedMDC: case packetTypeSymmetricallyEncryptedIntegrityProtected:
se := new(SymmetricallyEncrypted) se := new(SymmetricallyEncrypted)
se.MDC = true se.IntegrityProtected = true
p = se p = se
case packetTypeAEADEncrypted: case packetTypeAEADEncrypted:
p = new(AEADEncrypted) p = new(AEADEncrypted)
@ -384,18 +384,18 @@ func Read(r io.Reader) (p Packet, err error) {
type SignatureType uint8 type SignatureType uint8
const ( const (
SigTypeBinary SignatureType = 0x00 SigTypeBinary SignatureType = 0x00
SigTypeText = 0x01 SigTypeText = 0x01
SigTypeGenericCert = 0x10 SigTypeGenericCert = 0x10
SigTypePersonaCert = 0x11 SigTypePersonaCert = 0x11
SigTypeCasualCert = 0x12 SigTypeCasualCert = 0x12
SigTypePositiveCert = 0x13 SigTypePositiveCert = 0x13
SigTypeSubkeyBinding = 0x18 SigTypeSubkeyBinding = 0x18
SigTypePrimaryKeyBinding = 0x19 SigTypePrimaryKeyBinding = 0x19
SigTypeDirectSignature = 0x1F SigTypeDirectSignature = 0x1F
SigTypeKeyRevocation = 0x20 SigTypeKeyRevocation = 0x20
SigTypeSubkeyRevocation = 0x28 SigTypeSubkeyRevocation = 0x28
SigTypeCertificationRevocation = 0x30 SigTypeCertificationRevocation = 0x30
) )
// PublicKeyAlgorithm represents the different public key system specified for // PublicKeyAlgorithm represents the different public key system specified for
@ -455,6 +455,11 @@ func (cipher CipherFunction) KeySize() int {
return algorithm.CipherFunction(cipher).KeySize() return algorithm.CipherFunction(cipher).KeySize()
} }
// IsSupported returns true if the cipher is supported from the library
func (cipher CipherFunction) IsSupported() bool {
return algorithm.CipherFunction(cipher).KeySize() > 0
}
// blockSize returns the block size, in bytes, of cipher. // blockSize returns the block size, in bytes, of cipher.
func (cipher CipherFunction) blockSize() int { func (cipher CipherFunction) blockSize() int {
return algorithm.CipherFunction(cipher).BlockSize() return algorithm.CipherFunction(cipher).BlockSize()
@ -490,15 +495,16 @@ const (
// AEADMode represents the different Authenticated Encryption with Associated // AEADMode represents the different Authenticated Encryption with Associated
// Data specified for OpenPGP. // Data specified for OpenPGP.
// See https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.6
type AEADMode algorithm.AEADMode type AEADMode algorithm.AEADMode
const ( const (
AEADModeEAX AEADMode = 1 AEADModeEAX AEADMode = 1
AEADModeOCB AEADMode = 2 AEADModeOCB AEADMode = 2
AEADModeExperimentalGCM AEADMode = 100 AEADModeGCM AEADMode = 3
) )
func (mode AEADMode) NonceLength() int { func (mode AEADMode) IvLength() int {
return algorithm.AEADMode(mode).NonceLength() return algorithm.AEADMode(mode).NonceLength()
} }
@ -527,13 +533,19 @@ const (
type Curve string type Curve string
const ( const (
Curve25519 Curve = "Curve25519" Curve25519 Curve = "Curve25519"
Curve448 Curve = "Curve448" Curve448 Curve = "Curve448"
CurveNistP256 Curve = "P256" CurveNistP256 Curve = "P256"
CurveNistP384 Curve = "P384" CurveNistP384 Curve = "P384"
CurveNistP521 Curve = "P521" CurveNistP521 Curve = "P521"
CurveSecP256k1 Curve = "SecP256k1" CurveSecP256k1 Curve = "SecP256k1"
CurveBrainpoolP256 Curve = "BrainpoolP256" CurveBrainpoolP256 Curve = "BrainpoolP256"
CurveBrainpoolP384 Curve = "BrainpoolP384" CurveBrainpoolP384 Curve = "BrainpoolP384"
CurveBrainpoolP512 Curve = "BrainpoolP512" CurveBrainpoolP512 Curve = "BrainpoolP512"
) )
// TrustLevel represents a trust level per RFC4880 5.2.3.13
type TrustLevel uint8
// TrustAmount represents a trust amount per RFC4880 5.2.3.13
type TrustAmount uint8

View File

@ -49,7 +49,7 @@ type PrivateKey struct {
s2kParams *s2k.Params s2kParams *s2k.Params
} }
//S2KType s2k packet type // S2KType s2k packet type
type S2KType uint8 type S2KType uint8
const ( const (
@ -179,6 +179,9 @@ func (pk *PrivateKey) parse(r io.Reader) (err error) {
return return
} }
pk.cipher = CipherFunction(buf[0]) pk.cipher = CipherFunction(buf[0])
if pk.cipher != 0 && !pk.cipher.IsSupported() {
return errors.UnsupportedError("unsupported cipher function in private key")
}
pk.s2kParams, err = s2k.ParseIntoParams(r) pk.s2kParams, err = s2k.ParseIntoParams(r)
if err != nil { if err != nil {
return return
@ -367,8 +370,8 @@ func serializeECDHPrivateKey(w io.Writer, priv *ecdh.PrivateKey) error {
return err return err
} }
// Decrypt decrypts an encrypted private key using a passphrase. // decrypt decrypts an encrypted private key using a decryption key.
func (pk *PrivateKey) Decrypt(passphrase []byte) error { func (pk *PrivateKey) decrypt(decryptionKey []byte) error {
if pk.Dummy() { if pk.Dummy() {
return errors.ErrDummyPrivateKey("dummy key found") return errors.ErrDummyPrivateKey("dummy key found")
} }
@ -376,9 +379,7 @@ func (pk *PrivateKey) Decrypt(passphrase []byte) error {
return nil return nil
} }
key := make([]byte, pk.cipher.KeySize()) block := pk.cipher.new(decryptionKey)
pk.s2k(key, passphrase)
block := pk.cipher.new(key)
cfb := cipher.NewCFBDecrypter(block, pk.iv) cfb := cipher.NewCFBDecrypter(block, pk.iv)
data := make([]byte, len(pk.encryptedData)) data := make([]byte, len(pk.encryptedData))
@ -427,35 +428,79 @@ func (pk *PrivateKey) Decrypt(passphrase []byte) error {
return nil return nil
} }
// Encrypt encrypts an unencrypted private key using a passphrase. func (pk *PrivateKey) decryptWithCache(passphrase []byte, keyCache *s2k.Cache) error {
func (pk *PrivateKey) Encrypt(passphrase []byte) error { if pk.Dummy() {
return errors.ErrDummyPrivateKey("dummy key found")
}
if !pk.Encrypted {
return nil
}
key, err := keyCache.GetOrComputeDerivedKey(passphrase, pk.s2kParams, pk.cipher.KeySize())
if err != nil {
return err
}
return pk.decrypt(key)
}
// Decrypt decrypts an encrypted private key using a passphrase.
func (pk *PrivateKey) Decrypt(passphrase []byte) error {
if pk.Dummy() {
return errors.ErrDummyPrivateKey("dummy key found")
}
if !pk.Encrypted {
return nil
}
key := make([]byte, pk.cipher.KeySize())
pk.s2k(key, passphrase)
return pk.decrypt(key)
}
// DecryptPrivateKeys decrypts all encrypted keys with the given config and passphrase.
// Avoids recomputation of similar s2k key derivations.
func DecryptPrivateKeys(keys []*PrivateKey, passphrase []byte) error {
// Create a cache to avoid recomputation of key derviations for the same passphrase.
s2kCache := &s2k.Cache{}
for _, key := range keys {
if key != nil && !key.Dummy() && key.Encrypted {
err := key.decryptWithCache(passphrase, s2kCache)
if err != nil {
return err
}
}
}
return nil
}
// encrypt encrypts an unencrypted private key.
func (pk *PrivateKey) encrypt(key []byte, params *s2k.Params, cipherFunction CipherFunction) error {
if pk.Dummy() {
return errors.ErrDummyPrivateKey("dummy key found")
}
if pk.Encrypted {
return nil
}
// check if encryptionKey has the correct size
if len(key) != cipherFunction.KeySize() {
return errors.InvalidArgumentError("supplied encryption key has the wrong size")
}
priv := bytes.NewBuffer(nil) priv := bytes.NewBuffer(nil)
err := pk.serializePrivateKey(priv) err := pk.serializePrivateKey(priv)
if err != nil { if err != nil {
return err return err
} }
//Default config of private key encryption pk.cipher = cipherFunction
pk.cipher = CipherAES256 pk.s2kParams = params
s2kConfig := &s2k.Config{
S2KMode: 3, //Iterated
S2KCount: 65536,
Hash: crypto.SHA256,
}
pk.s2kParams, err = s2k.Generate(rand.Reader, s2kConfig)
if err != nil {
return err
}
privateKeyBytes := priv.Bytes()
key := make([]byte, pk.cipher.KeySize())
pk.sha1Checksum = true
pk.s2k, err = pk.s2kParams.Function() pk.s2k, err = pk.s2kParams.Function()
if err != nil { if err != nil {
return err return err
} }
pk.s2k(key, passphrase)
privateKeyBytes := priv.Bytes()
pk.sha1Checksum = true
block := pk.cipher.new(key) block := pk.cipher.new(key)
pk.iv = make([]byte, pk.cipher.blockSize()) pk.iv = make([]byte, pk.cipher.blockSize())
_, err = rand.Read(pk.iv) _, err = rand.Read(pk.iv)
@ -486,6 +531,62 @@ func (pk *PrivateKey) Encrypt(passphrase []byte) error {
return err return err
} }
// EncryptWithConfig encrypts an unencrypted private key using the passphrase and the config.
func (pk *PrivateKey) EncryptWithConfig(passphrase []byte, config *Config) error {
params, err := s2k.Generate(config.Random(), config.S2K())
if err != nil {
return err
}
// Derive an encryption key with the configured s2k function.
key := make([]byte, config.Cipher().KeySize())
s2k, err := params.Function()
if err != nil {
return err
}
s2k(key, passphrase)
// Encrypt the private key with the derived encryption key.
return pk.encrypt(key, params, config.Cipher())
}
// EncryptPrivateKeys encrypts all unencrypted keys with the given config and passphrase.
// Only derives one key from the passphrase, which is then used to encrypt each key.
func EncryptPrivateKeys(keys []*PrivateKey, passphrase []byte, config *Config) error {
params, err := s2k.Generate(config.Random(), config.S2K())
if err != nil {
return err
}
// Derive an encryption key with the configured s2k function.
encryptionKey := make([]byte, config.Cipher().KeySize())
s2k, err := params.Function()
if err != nil {
return err
}
s2k(encryptionKey, passphrase)
for _, key := range keys {
if key != nil && !key.Dummy() && !key.Encrypted {
err = key.encrypt(encryptionKey, params, config.Cipher())
if err != nil {
return err
}
}
}
return nil
}
// Encrypt encrypts an unencrypted private key using a passphrase.
func (pk *PrivateKey) Encrypt(passphrase []byte) error {
// Default config of private key encryption
config := &Config{
S2KConfig: &s2k.Config{
S2KMode: s2k.IteratedSaltedS2K,
S2KCount: 65536,
Hash: crypto.SHA256,
} ,
DefaultCipher: CipherAES256,
}
return pk.EncryptWithConfig(passphrase, config)
}
func (pk *PrivateKey) serializePrivateKey(w io.Writer) (err error) { func (pk *PrivateKey) serializePrivateKey(w io.Writer) (err error) {
switch priv := pk.PrivateKey.(type) { switch priv := pk.PrivateKey.(type) {
case *rsa.PrivateKey: case *rsa.PrivateKey:

View File

@ -415,6 +415,10 @@ func (pk *PublicKey) parseEdDSA(r io.Reader) (err error) {
return return
} }
if len(pk.p.Bytes()) == 0 {
return errors.StructuralError("empty EdDSA public key")
}
pub := eddsa.NewPublicKey(c) pub := eddsa.NewPublicKey(c)
switch flag := pk.p.Bytes()[0]; flag { switch flag := pk.p.Bytes()[0]; flag {
@ -596,7 +600,7 @@ func (pk *PublicKey) VerifySignature(signed hash.Hash, sig *Signature) (err erro
} }
signed.Write(sig.HashSuffix) signed.Write(sig.HashSuffix)
hashBytes := signed.Sum(nil) hashBytes := signed.Sum(nil)
if hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1] { if sig.Version == 5 && (hashBytes[0] != sig.HashTag[0] || hashBytes[1] != sig.HashTag[1]) {
return errors.SignatureError("hash tag doesn't match") return errors.SignatureError("hash tag doesn't match")
} }

View File

@ -17,8 +17,8 @@ import (
"github.com/ProtonMail/go-crypto/openpgp/ecdsa" "github.com/ProtonMail/go-crypto/openpgp/ecdsa"
"github.com/ProtonMail/go-crypto/openpgp/eddsa" "github.com/ProtonMail/go-crypto/openpgp/eddsa"
"github.com/ProtonMail/go-crypto/openpgp/errors" "github.com/ProtonMail/go-crypto/openpgp/errors"
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
"github.com/ProtonMail/go-crypto/openpgp/internal/encoding" "github.com/ProtonMail/go-crypto/openpgp/internal/encoding"
"github.com/ProtonMail/go-crypto/openpgp/s2k"
) )
const ( const (
@ -66,11 +66,24 @@ type Signature struct {
SigLifetimeSecs, KeyLifetimeSecs *uint32 SigLifetimeSecs, KeyLifetimeSecs *uint32
PreferredSymmetric, PreferredHash, PreferredCompression []uint8 PreferredSymmetric, PreferredHash, PreferredCompression []uint8
PreferredAEAD []uint8 PreferredCipherSuites [][2]uint8
IssuerKeyId *uint64 IssuerKeyId *uint64
IssuerFingerprint []byte IssuerFingerprint []byte
SignerUserId *string SignerUserId *string
IsPrimaryId *bool IsPrimaryId *bool
Notations []*Notation
// TrustLevel and TrustAmount can be set by the signer to assert that
// the key is not only valid but also trustworthy at the specified
// level.
// See RFC 4880, section 5.2.3.13 for details.
TrustLevel TrustLevel
TrustAmount TrustAmount
// TrustRegularExpression can be used in conjunction with trust Signature
// packets to limit the scope of the trust that is extended.
// See RFC 4880, section 5.2.3.14 for details.
TrustRegularExpression *string
// PolicyURI can be set to the URI of a document that describes the // PolicyURI can be set to the URI of a document that describes the
// policy under which the signature was issued. See RFC 4880, section // policy under which the signature was issued. See RFC 4880, section
@ -89,8 +102,8 @@ type Signature struct {
// In a self-signature, these flags are set there is a features subpacket // In a self-signature, these flags are set there is a features subpacket
// indicating that the issuer implementation supports these features // indicating that the issuer implementation supports these features
// (section 5.2.5.25). // see https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh#features-subpacket
MDC, AEAD, V5Keys bool SEIPDv1, SEIPDv2 bool
// EmbeddedSignature, if non-nil, is a signature of the parent key, by // EmbeddedSignature, if non-nil, is a signature of the parent key, by
// this key. This prevents an attacker from claiming another's signing // this key. This prevents an attacker from claiming another's signing
@ -126,7 +139,13 @@ func (sig *Signature) parse(r io.Reader) (err error) {
} }
var ok bool var ok bool
sig.Hash, ok = s2k.HashIdToHash(buf[2])
if sig.Version < 5 {
sig.Hash, ok = algorithm.HashIdToHashWithSha1(buf[2])
} else {
sig.Hash, ok = algorithm.HashIdToHash(buf[2])
}
if !ok { if !ok {
return errors.UnsupportedError("hash function " + strconv.Itoa(int(buf[2]))) return errors.UnsupportedError("hash function " + strconv.Itoa(int(buf[2])))
} }
@ -137,7 +156,11 @@ func (sig *Signature) parse(r io.Reader) (err error) {
if err != nil { if err != nil {
return return
} }
sig.buildHashSuffix(hashedSubpackets) err = sig.buildHashSuffix(hashedSubpackets)
if err != nil {
return
}
err = parseSignatureSubpackets(sig, hashedSubpackets, true) err = parseSignatureSubpackets(sig, hashedSubpackets, true)
if err != nil { if err != nil {
return return
@ -221,9 +244,12 @@ type signatureSubpacketType uint8
const ( const (
creationTimeSubpacket signatureSubpacketType = 2 creationTimeSubpacket signatureSubpacketType = 2
signatureExpirationSubpacket signatureSubpacketType = 3 signatureExpirationSubpacket signatureSubpacketType = 3
trustSubpacket signatureSubpacketType = 5
regularExpressionSubpacket signatureSubpacketType = 6
keyExpirationSubpacket signatureSubpacketType = 9 keyExpirationSubpacket signatureSubpacketType = 9
prefSymmetricAlgosSubpacket signatureSubpacketType = 11 prefSymmetricAlgosSubpacket signatureSubpacketType = 11
issuerSubpacket signatureSubpacketType = 16 issuerSubpacket signatureSubpacketType = 16
notationDataSubpacket signatureSubpacketType = 20
prefHashAlgosSubpacket signatureSubpacketType = 21 prefHashAlgosSubpacket signatureSubpacketType = 21
prefCompressionSubpacket signatureSubpacketType = 22 prefCompressionSubpacket signatureSubpacketType = 22
primaryUserIdSubpacket signatureSubpacketType = 25 primaryUserIdSubpacket signatureSubpacketType = 25
@ -234,7 +260,7 @@ const (
featuresSubpacket signatureSubpacketType = 30 featuresSubpacket signatureSubpacketType = 30
embeddedSignatureSubpacket signatureSubpacketType = 32 embeddedSignatureSubpacket signatureSubpacketType = 32
issuerFingerprintSubpacket signatureSubpacketType = 33 issuerFingerprintSubpacket signatureSubpacketType = 33
prefAeadAlgosSubpacket signatureSubpacketType = 34 prefCipherSuitesSubpacket signatureSubpacketType = 39
) )
// parseSignatureSubpacket parses a single subpacket. len(subpacket) is >= 1. // parseSignatureSubpacket parses a single subpacket. len(subpacket) is >= 1.
@ -245,6 +271,10 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
packetType signatureSubpacketType packetType signatureSubpacketType
isCritical bool isCritical bool
) )
if len(subpacket) == 0 {
err = errors.StructuralError("zero length signature subpacket")
return
}
switch { switch {
case subpacket[0] < 192: case subpacket[0] < 192:
length = uint32(subpacket[0]) length = uint32(subpacket[0])
@ -278,12 +308,14 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
isCritical = subpacket[0]&0x80 == 0x80 isCritical = subpacket[0]&0x80 == 0x80
subpacket = subpacket[1:] subpacket = subpacket[1:]
sig.rawSubpackets = append(sig.rawSubpackets, outputSubpacket{isHashed, packetType, isCritical, subpacket}) sig.rawSubpackets = append(sig.rawSubpackets, outputSubpacket{isHashed, packetType, isCritical, subpacket})
if !isHashed &&
packetType != issuerSubpacket &&
packetType != issuerFingerprintSubpacket &&
packetType != embeddedSignatureSubpacket {
return
}
switch packetType { switch packetType {
case creationTimeSubpacket: case creationTimeSubpacket:
if !isHashed {
err = errors.StructuralError("signature creation time in non-hashed area")
return
}
if len(subpacket) != 4 { if len(subpacket) != 4 {
err = errors.StructuralError("signature creation time not four bytes") err = errors.StructuralError("signature creation time not four bytes")
return return
@ -292,20 +324,35 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
sig.CreationTime = time.Unix(int64(t), 0) sig.CreationTime = time.Unix(int64(t), 0)
case signatureExpirationSubpacket: case signatureExpirationSubpacket:
// Signature expiration time, section 5.2.3.10 // Signature expiration time, section 5.2.3.10
if !isHashed {
return
}
if len(subpacket) != 4 { if len(subpacket) != 4 {
err = errors.StructuralError("expiration subpacket with bad length") err = errors.StructuralError("expiration subpacket with bad length")
return return
} }
sig.SigLifetimeSecs = new(uint32) sig.SigLifetimeSecs = new(uint32)
*sig.SigLifetimeSecs = binary.BigEndian.Uint32(subpacket) *sig.SigLifetimeSecs = binary.BigEndian.Uint32(subpacket)
case keyExpirationSubpacket: case trustSubpacket:
// Key expiration time, section 5.2.3.6 if len(subpacket) != 2 {
if !isHashed { err = errors.StructuralError("trust subpacket with bad length")
return return
} }
// Trust level and amount, section 5.2.3.13
sig.TrustLevel = TrustLevel(subpacket[0])
sig.TrustAmount = TrustAmount(subpacket[1])
case regularExpressionSubpacket:
if len(subpacket) == 0 {
err = errors.StructuralError("regexp subpacket with bad length")
return
}
// Trust regular expression, section 5.2.3.14
// RFC specifies the string should be null-terminated; remove a null byte from the end
if subpacket[len(subpacket)-1] != 0x00 {
err = errors.StructuralError("expected regular expression to be null-terminated")
return
}
trustRegularExpression := string(subpacket[:len(subpacket)-1])
sig.TrustRegularExpression = &trustRegularExpression
case keyExpirationSubpacket:
// Key expiration time, section 5.2.3.6
if len(subpacket) != 4 { if len(subpacket) != 4 {
err = errors.StructuralError("key expiration subpacket with bad length") err = errors.StructuralError("key expiration subpacket with bad length")
return return
@ -314,41 +361,52 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
*sig.KeyLifetimeSecs = binary.BigEndian.Uint32(subpacket) *sig.KeyLifetimeSecs = binary.BigEndian.Uint32(subpacket)
case prefSymmetricAlgosSubpacket: case prefSymmetricAlgosSubpacket:
// Preferred symmetric algorithms, section 5.2.3.7 // Preferred symmetric algorithms, section 5.2.3.7
if !isHashed {
return
}
sig.PreferredSymmetric = make([]byte, len(subpacket)) sig.PreferredSymmetric = make([]byte, len(subpacket))
copy(sig.PreferredSymmetric, subpacket) copy(sig.PreferredSymmetric, subpacket)
case issuerSubpacket: case issuerSubpacket:
// Issuer, section 5.2.3.5
if sig.Version > 4 { if sig.Version > 4 {
err = errors.StructuralError("issuer subpacket found in v5 key") err = errors.StructuralError("issuer subpacket found in v5 key")
return
} }
// Issuer, section 5.2.3.5
if len(subpacket) != 8 { if len(subpacket) != 8 {
err = errors.StructuralError("issuer subpacket with bad length") err = errors.StructuralError("issuer subpacket with bad length")
return return
} }
sig.IssuerKeyId = new(uint64) sig.IssuerKeyId = new(uint64)
*sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket) *sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket)
case prefHashAlgosSubpacket: case notationDataSubpacket:
// Preferred hash algorithms, section 5.2.3.8 // Notation data, section 5.2.3.16
if !isHashed { if len(subpacket) < 8 {
err = errors.StructuralError("notation data subpacket with bad length")
return return
} }
nameLength := uint32(subpacket[4])<<8 | uint32(subpacket[5])
valueLength := uint32(subpacket[6])<<8 | uint32(subpacket[7])
if len(subpacket) != int(nameLength)+int(valueLength)+8 {
err = errors.StructuralError("notation data subpacket with bad length")
return
}
notation := Notation{
IsHumanReadable: (subpacket[0] & 0x80) == 0x80,
Name: string(subpacket[8:(nameLength + 8)]),
Value: subpacket[(nameLength + 8):(valueLength + nameLength + 8)],
IsCritical: isCritical,
}
sig.Notations = append(sig.Notations, &notation)
case prefHashAlgosSubpacket:
// Preferred hash algorithms, section 5.2.3.8
sig.PreferredHash = make([]byte, len(subpacket)) sig.PreferredHash = make([]byte, len(subpacket))
copy(sig.PreferredHash, subpacket) copy(sig.PreferredHash, subpacket)
case prefCompressionSubpacket: case prefCompressionSubpacket:
// Preferred compression algorithms, section 5.2.3.9 // Preferred compression algorithms, section 5.2.3.9
if !isHashed {
return
}
sig.PreferredCompression = make([]byte, len(subpacket)) sig.PreferredCompression = make([]byte, len(subpacket))
copy(sig.PreferredCompression, subpacket) copy(sig.PreferredCompression, subpacket)
case primaryUserIdSubpacket: case primaryUserIdSubpacket:
// Primary User ID, section 5.2.3.19 // Primary User ID, section 5.2.3.19
if !isHashed {
return
}
if len(subpacket) != 1 { if len(subpacket) != 1 {
err = errors.StructuralError("primary user id subpacket with bad length") err = errors.StructuralError("primary user id subpacket with bad length")
return return
@ -359,9 +417,6 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
} }
case keyFlagsSubpacket: case keyFlagsSubpacket:
// Key flags, section 5.2.3.21 // Key flags, section 5.2.3.21
if !isHashed {
return
}
if len(subpacket) == 0 { if len(subpacket) == 0 {
err = errors.StructuralError("empty key flags subpacket") err = errors.StructuralError("empty key flags subpacket")
return return
@ -393,9 +448,6 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
sig.SignerUserId = &userId sig.SignerUserId = &userId
case reasonForRevocationSubpacket: case reasonForRevocationSubpacket:
// Reason For Revocation, section 5.2.3.23 // Reason For Revocation, section 5.2.3.23
if !isHashed {
return
}
if len(subpacket) == 0 { if len(subpacket) == 0 {
err = errors.StructuralError("empty revocation reason subpacket") err = errors.StructuralError("empty revocation reason subpacket")
return return
@ -407,18 +459,13 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
// Features subpacket, section 5.2.3.24 specifies a very general // Features subpacket, section 5.2.3.24 specifies a very general
// mechanism for OpenPGP implementations to signal support for new // mechanism for OpenPGP implementations to signal support for new
// features. // features.
if !isHashed {
return
}
if len(subpacket) > 0 { if len(subpacket) > 0 {
if subpacket[0]&0x01 != 0 { if subpacket[0]&0x01 != 0 {
sig.MDC = true sig.SEIPDv1 = true
} }
if subpacket[0]&0x02 != 0 { // 0x02 and 0x04 are reserved
sig.AEAD = true if subpacket[0]&0x08 != 0 {
} sig.SEIPDv2 = true
if subpacket[0]&0x04 != 0 {
sig.V5Keys = true
} }
} }
case embeddedSignatureSubpacket: case embeddedSignatureSubpacket:
@ -441,11 +488,12 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
} }
case policyUriSubpacket: case policyUriSubpacket:
// Policy URI, section 5.2.3.20 // Policy URI, section 5.2.3.20
if !isHashed {
return
}
sig.PolicyURI = string(subpacket) sig.PolicyURI = string(subpacket)
case issuerFingerprintSubpacket: case issuerFingerprintSubpacket:
if len(subpacket) == 0 {
err = errors.StructuralError("empty issuer fingerprint subpacket")
return
}
v, l := subpacket[0], len(subpacket[1:]) v, l := subpacket[0], len(subpacket[1:])
if v == 5 && l != 32 || v != 5 && l != 20 { if v == 5 && l != 32 || v != 5 && l != 20 {
return nil, errors.StructuralError("bad fingerprint length") return nil, errors.StructuralError("bad fingerprint length")
@ -458,13 +506,19 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
} else { } else {
*sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket[13:21]) *sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket[13:21])
} }
case prefAeadAlgosSubpacket: case prefCipherSuitesSubpacket:
// Preferred symmetric algorithms, section 5.2.3.8 // Preferred AEAD cipher suites
if !isHashed { // See https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#name-preferred-aead-ciphersuites
if len(subpacket)%2 != 0 {
err = errors.StructuralError("invalid aead cipher suite length")
return return
} }
sig.PreferredAEAD = make([]byte, len(subpacket))
copy(sig.PreferredAEAD, subpacket) sig.PreferredCipherSuites = make([][2]byte, len(subpacket)/2)
for i := 0; i < len(subpacket)/2; i++ {
sig.PreferredCipherSuites[i] = [2]uint8{subpacket[2*i], subpacket[2*i+1]}
}
default: default:
if isCritical { if isCritical {
err = errors.UnsupportedError("unknown critical signature subpacket type " + strconv.Itoa(int(packetType))) err = errors.UnsupportedError("unknown critical signature subpacket type " + strconv.Itoa(int(packetType)))
@ -562,7 +616,15 @@ func (sig *Signature) SigExpired(currentTime time.Time) bool {
// buildHashSuffix constructs the HashSuffix member of sig in preparation for signing. // buildHashSuffix constructs the HashSuffix member of sig in preparation for signing.
func (sig *Signature) buildHashSuffix(hashedSubpackets []byte) (err error) { func (sig *Signature) buildHashSuffix(hashedSubpackets []byte) (err error) {
hash, ok := s2k.HashToHashId(sig.Hash) var hashId byte
var ok bool
if sig.Version < 5 {
hashId, ok = algorithm.HashToHashIdWithSha1(sig.Hash)
} else {
hashId, ok = algorithm.HashToHashId(sig.Hash)
}
if !ok { if !ok {
sig.HashSuffix = nil sig.HashSuffix = nil
return errors.InvalidArgumentError("hash cannot be represented in OpenPGP: " + strconv.Itoa(int(sig.Hash))) return errors.InvalidArgumentError("hash cannot be represented in OpenPGP: " + strconv.Itoa(int(sig.Hash)))
@ -572,7 +634,7 @@ func (sig *Signature) buildHashSuffix(hashedSubpackets []byte) (err error) {
uint8(sig.Version), uint8(sig.Version),
uint8(sig.SigType), uint8(sig.SigType),
uint8(sig.PubKeyAlgo), uint8(sig.PubKeyAlgo),
uint8(hash), uint8(hashId),
uint8(len(hashedSubpackets) >> 8), uint8(len(hashedSubpackets) >> 8),
uint8(len(hashedSubpackets)), uint8(len(hashedSubpackets)),
}) })
@ -842,7 +904,7 @@ func (sig *Signature) buildSubpackets(issuer PublicKey) (subpackets []outputSubp
if sig.IssuerKeyId != nil && sig.Version == 4 { if sig.IssuerKeyId != nil && sig.Version == 4 {
keyId := make([]byte, 8) keyId := make([]byte, 8)
binary.BigEndian.PutUint64(keyId, *sig.IssuerKeyId) binary.BigEndian.PutUint64(keyId, *sig.IssuerKeyId)
subpackets = append(subpackets, outputSubpacket{true, issuerSubpacket, true, keyId}) subpackets = append(subpackets, outputSubpacket{true, issuerSubpacket, false, keyId})
} }
if sig.IssuerFingerprint != nil { if sig.IssuerFingerprint != nil {
contents := append([]uint8{uint8(issuer.Version)}, sig.IssuerFingerprint...) contents := append([]uint8{uint8(issuer.Version)}, sig.IssuerFingerprint...)
@ -885,23 +947,40 @@ func (sig *Signature) buildSubpackets(issuer PublicKey) (subpackets []outputSubp
subpackets = append(subpackets, outputSubpacket{true, keyFlagsSubpacket, false, []byte{flags}}) subpackets = append(subpackets, outputSubpacket{true, keyFlagsSubpacket, false, []byte{flags}})
} }
for _, notation := range sig.Notations {
subpackets = append(
subpackets,
outputSubpacket{
true,
notationDataSubpacket,
notation.IsCritical,
notation.getData(),
})
}
// The following subpackets may only appear in self-signatures. // The following subpackets may only appear in self-signatures.
var features = byte(0x00) var features = byte(0x00)
if sig.MDC { if sig.SEIPDv1 {
features |= 0x01 features |= 0x01
} }
if sig.AEAD { if sig.SEIPDv2 {
features |= 0x02 features |= 0x08
}
if sig.V5Keys {
features |= 0x04
} }
if features != 0x00 { if features != 0x00 {
subpackets = append(subpackets, outputSubpacket{true, featuresSubpacket, false, []byte{features}}) subpackets = append(subpackets, outputSubpacket{true, featuresSubpacket, false, []byte{features}})
} }
if sig.TrustLevel != 0 {
subpackets = append(subpackets, outputSubpacket{true, trustSubpacket, true, []byte{byte(sig.TrustLevel), byte(sig.TrustAmount)}})
}
if sig.TrustRegularExpression != nil {
// RFC specifies the string should be null-terminated; add a null byte to the end
subpackets = append(subpackets, outputSubpacket{true, regularExpressionSubpacket, true, []byte(*sig.TrustRegularExpression + "\000")})
}
if sig.KeyLifetimeSecs != nil && *sig.KeyLifetimeSecs != 0 { if sig.KeyLifetimeSecs != nil && *sig.KeyLifetimeSecs != 0 {
keyLifetime := make([]byte, 4) keyLifetime := make([]byte, 4)
binary.BigEndian.PutUint32(keyLifetime, *sig.KeyLifetimeSecs) binary.BigEndian.PutUint32(keyLifetime, *sig.KeyLifetimeSecs)
@ -928,8 +1007,13 @@ func (sig *Signature) buildSubpackets(issuer PublicKey) (subpackets []outputSubp
subpackets = append(subpackets, outputSubpacket{true, policyUriSubpacket, false, []uint8(sig.PolicyURI)}) subpackets = append(subpackets, outputSubpacket{true, policyUriSubpacket, false, []uint8(sig.PolicyURI)})
} }
if len(sig.PreferredAEAD) > 0 { if len(sig.PreferredCipherSuites) > 0 {
subpackets = append(subpackets, outputSubpacket{true, prefAeadAlgosSubpacket, false, sig.PreferredAEAD}) serialized := make([]byte, len(sig.PreferredCipherSuites)*2)
for i, cipherSuite := range sig.PreferredCipherSuites {
serialized[2*i] = cipherSuite[0]
serialized[2*i+1] = cipherSuite[1]
}
subpackets = append(subpackets, outputSubpacket{true, prefCipherSuitesSubpacket, false, serialized})
} }
// Revocation reason appears only in revocation signatures and is serialized as per section 5.2.3.23. // Revocation reason appears only in revocation signatures and is serialized as per section 5.2.3.23.
@ -971,7 +1055,7 @@ func (sig *Signature) AddMetadataToHashSuffix() {
n := sig.HashSuffix[len(sig.HashSuffix)-8:] n := sig.HashSuffix[len(sig.HashSuffix)-8:]
l := uint64( l := uint64(
uint64(n[0])<<56 | uint64(n[1])<<48 | uint64(n[2])<<40 | uint64(n[3])<<32 | uint64(n[0])<<56 | uint64(n[1])<<48 | uint64(n[2])<<40 | uint64(n[3])<<32 |
uint64(n[4])<<24 | uint64(n[5])<<16 | uint64(n[6])<<8 | uint64(n[7])) uint64(n[4])<<24 | uint64(n[5])<<16 | uint64(n[6])<<8 | uint64(n[7]))
suffix := bytes.NewBuffer(nil) suffix := bytes.NewBuffer(nil)
suffix.Write(sig.HashSuffix[:l]) suffix.Write(sig.HashSuffix[:l])

View File

@ -14,8 +14,8 @@ import (
"github.com/ProtonMail/go-crypto/openpgp/s2k" "github.com/ProtonMail/go-crypto/openpgp/s2k"
) )
// This is the largest session key that we'll support. Since no 512-bit cipher // This is the largest session key that we'll support. Since at most 256-bit cipher
// has even been seriously used, this is comfortably large. // is supported in OpenPGP, this is large enough to contain also the auth tag.
const maxSessionKeySizeInBytes = 64 const maxSessionKeySizeInBytes = 64
// SymmetricKeyEncrypted represents a passphrase protected session key. See RFC // SymmetricKeyEncrypted represents a passphrase protected session key. See RFC
@ -25,13 +25,16 @@ type SymmetricKeyEncrypted struct {
CipherFunc CipherFunction CipherFunc CipherFunction
Mode AEADMode Mode AEADMode
s2k func(out, in []byte) s2k func(out, in []byte)
aeadNonce []byte iv []byte
encryptedKey []byte encryptedKey []byte // Contains also the authentication tag for AEAD
} }
// parse parses an SymmetricKeyEncrypted packet as specified in
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#name-symmetric-key-encrypted-ses
func (ske *SymmetricKeyEncrypted) parse(r io.Reader) error { func (ske *SymmetricKeyEncrypted) parse(r io.Reader) error {
// RFC 4880, section 5.3. var buf [1]byte
var buf [2]byte
// Version
if _, err := readFull(r, buf[:]); err != nil { if _, err := readFull(r, buf[:]); err != nil {
return err return err
} }
@ -39,17 +42,22 @@ func (ske *SymmetricKeyEncrypted) parse(r io.Reader) error {
if ske.Version != 4 && ske.Version != 5 { if ske.Version != 4 && ske.Version != 5 {
return errors.UnsupportedError("unknown SymmetricKeyEncrypted version") return errors.UnsupportedError("unknown SymmetricKeyEncrypted version")
} }
ske.CipherFunc = CipherFunction(buf[1])
if ske.CipherFunc.KeySize() == 0 { // Cipher function
return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(buf[1]))) if _, err := readFull(r, buf[:]); err != nil {
return err
}
ske.CipherFunc = CipherFunction(buf[0])
if !ske.CipherFunc.IsSupported() {
return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(buf[0])))
} }
if ske.Version == 5 { if ske.Version == 5 {
mode := make([]byte, 1) // AEAD mode
if _, err := r.Read(mode); err != nil { if _, err := readFull(r, buf[:]); err != nil {
return errors.StructuralError("cannot read AEAD octet from packet") return errors.StructuralError("cannot read AEAD octet from packet")
} }
ske.Mode = AEADMode(mode[0]) ske.Mode = AEADMode(buf[0])
} }
var err error var err error
@ -61,13 +69,14 @@ func (ske *SymmetricKeyEncrypted) parse(r io.Reader) error {
} }
if ske.Version == 5 { if ske.Version == 5 {
// AEAD nonce // AEAD IV
nonce := make([]byte, ske.Mode.NonceLength()) iv := make([]byte, ske.Mode.IvLength())
_, err := readFull(r, nonce) _, err := readFull(r, iv)
if err != nil && err != io.ErrUnexpectedEOF { if err != nil {
return err return errors.StructuralError("cannot read AEAD IV")
} }
ske.aeadNonce = nonce
ske.iv = iv
} }
encryptedKey := make([]byte, maxSessionKeySizeInBytes) encryptedKey := make([]byte, maxSessionKeySizeInBytes)
@ -128,11 +137,10 @@ func (ske *SymmetricKeyEncrypted) decryptV4(key []byte) ([]byte, CipherFunction,
} }
func (ske *SymmetricKeyEncrypted) decryptV5(key []byte) ([]byte, error) { func (ske *SymmetricKeyEncrypted) decryptV5(key []byte) ([]byte, error) {
blockCipher := CipherFunction(ske.CipherFunc).new(key)
aead := ske.Mode.new(blockCipher)
adata := []byte{0xc3, byte(5), byte(ske.CipherFunc), byte(ske.Mode)} adata := []byte{0xc3, byte(5), byte(ske.CipherFunc), byte(ske.Mode)}
plaintextKey, err := aead.Open(nil, ske.aeadNonce, ske.encryptedKey, adata) aead := getEncryptedKeyAeadInstance(ske.CipherFunc, ske.Mode, key, adata)
plaintextKey, err := aead.Open(nil, ske.iv, ske.encryptedKey, adata)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -142,17 +150,12 @@ func (ske *SymmetricKeyEncrypted) decryptV5(key []byte) ([]byte, error) {
// SerializeSymmetricKeyEncrypted serializes a symmetric key packet to w. // SerializeSymmetricKeyEncrypted serializes a symmetric key packet to w.
// The packet contains a random session key, encrypted by a key derived from // The packet contains a random session key, encrypted by a key derived from
// the given passphrase. The session key is returned and must be passed to // the given passphrase. The session key is returned and must be passed to
// SerializeSymmetricallyEncrypted or SerializeAEADEncrypted, depending on // SerializeSymmetricallyEncrypted.
// whether config.AEADConfig != nil.
// If config is nil, sensible defaults will be used. // If config is nil, sensible defaults will be used.
func SerializeSymmetricKeyEncrypted(w io.Writer, passphrase []byte, config *Config) (key []byte, err error) { func SerializeSymmetricKeyEncrypted(w io.Writer, passphrase []byte, config *Config) (key []byte, err error) {
cipherFunc := config.Cipher() cipherFunc := config.Cipher()
keySize := cipherFunc.KeySize()
if keySize == 0 {
return nil, errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(cipherFunc)))
}
sessionKey := make([]byte, keySize) sessionKey := make([]byte, cipherFunc.KeySize())
_, err = io.ReadFull(config.Random(), sessionKey) _, err = io.ReadFull(config.Random(), sessionKey)
if err != nil { if err != nil {
return return
@ -169,9 +172,8 @@ func SerializeSymmetricKeyEncrypted(w io.Writer, passphrase []byte, config *Conf
// SerializeSymmetricKeyEncryptedReuseKey serializes a symmetric key packet to w. // SerializeSymmetricKeyEncryptedReuseKey serializes a symmetric key packet to w.
// The packet contains the given session key, encrypted by a key derived from // The packet contains the given session key, encrypted by a key derived from
// the given passphrase. The session key must be passed to // the given passphrase. The returned session key must be passed to
// SerializeSymmetricallyEncrypted or SerializeAEADEncrypted, depending on // SerializeSymmetricallyEncrypted.
// whether config.AEADConfig != nil.
// If config is nil, sensible defaults will be used. // If config is nil, sensible defaults will be used.
func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, passphrase []byte, config *Config) (err error) { func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, passphrase []byte, config *Config) (err error) {
var version int var version int
@ -181,16 +183,17 @@ func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, pass
version = 4 version = 4
} }
cipherFunc := config.Cipher() cipherFunc := config.Cipher()
keySize := cipherFunc.KeySize() // cipherFunc must be AES
if keySize == 0 { if !cipherFunc.IsSupported() || cipherFunc < CipherAES128 || cipherFunc > CipherAES256 {
return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(cipherFunc))) return errors.UnsupportedError("unsupported cipher: " + strconv.Itoa(int(cipherFunc)))
} }
keySize := cipherFunc.KeySize()
s2kBuf := new(bytes.Buffer) s2kBuf := new(bytes.Buffer)
keyEncryptingKey := make([]byte, keySize) keyEncryptingKey := make([]byte, keySize)
// s2k.Serialize salts and stretches the passphrase, and writes the // s2k.Serialize salts and stretches the passphrase, and writes the
// resulting key to keyEncryptingKey and the s2k descriptor to s2kBuf. // resulting key to keyEncryptingKey and the s2k descriptor to s2kBuf.
err = s2k.Serialize(s2kBuf, keyEncryptingKey, config.Random(), passphrase, &s2k.Config{Hash: config.Hash(), S2KCount: config.PasswordHashIterations()}) err = s2k.Serialize(s2kBuf, keyEncryptingKey, config.Random(), passphrase, config.S2K())
if err != nil { if err != nil {
return return
} }
@ -201,20 +204,20 @@ func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, pass
case 4: case 4:
packetLength = 2 /* header */ + len(s2kBytes) + 1 /* cipher type */ + keySize packetLength = 2 /* header */ + len(s2kBytes) + 1 /* cipher type */ + keySize
case 5: case 5:
nonceLen := config.AEAD().Mode().NonceLength() ivLen := config.AEAD().Mode().IvLength()
tagLen := config.AEAD().Mode().TagLength() tagLen := config.AEAD().Mode().TagLength()
packetLength = 3 + len(s2kBytes) + nonceLen + keySize + tagLen packetLength = 3 + len(s2kBytes) + ivLen + keySize + tagLen
} }
err = serializeHeader(w, packetTypeSymmetricKeyEncrypted, packetLength) err = serializeHeader(w, packetTypeSymmetricKeyEncrypted, packetLength)
if err != nil { if err != nil {
return return
} }
buf := make([]byte, 2)
// Symmetric Key Encrypted Version // Symmetric Key Encrypted Version
buf[0] = byte(version) buf := []byte{byte(version)}
// Cipher function // Cipher function
buf[1] = byte(cipherFunc) buf = append(buf, byte(cipherFunc))
if version == 5 { if version == 5 {
// AEAD mode // AEAD mode
@ -241,19 +244,20 @@ func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, pass
return return
} }
case 5: case 5:
blockCipher := cipherFunc.new(keyEncryptingKey)
mode := config.AEAD().Mode() mode := config.AEAD().Mode()
aead := mode.new(blockCipher) adata := []byte{0xc3, byte(5), byte(cipherFunc), byte(mode)}
// Sample nonce using random reader aead := getEncryptedKeyAeadInstance(cipherFunc, mode, keyEncryptingKey, adata)
nonce := make([]byte, config.AEAD().Mode().NonceLength())
_, err = io.ReadFull(config.Random(), nonce) // Sample iv using random reader
iv := make([]byte, config.AEAD().Mode().IvLength())
_, err = io.ReadFull(config.Random(), iv)
if err != nil { if err != nil {
return return
} }
// Seal and write (encryptedData includes auth. tag) // Seal and write (encryptedData includes auth. tag)
adata := []byte{0xc3, byte(5), byte(cipherFunc), byte(mode)}
encryptedData := aead.Seal(nil, nonce, sessionKey, adata) encryptedData := aead.Seal(nil, iv, sessionKey, adata)
_, err = w.Write(nonce) _, err = w.Write(iv)
if err != nil { if err != nil {
return return
} }
@ -265,3 +269,8 @@ func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, pass
return return
} }
func getEncryptedKeyAeadInstance(c CipherFunction, mode AEADMode, inputKey, associatedData []byte) (aead cipher.AEAD) {
blockCipher := c.new(inputKey)
return mode.new(blockCipher)
}

View File

@ -5,40 +5,56 @@
package packet package packet
import ( import (
"crypto/cipher"
"crypto/sha1"
"crypto/subtle"
"hash"
"io" "io"
"strconv"
"github.com/ProtonMail/go-crypto/openpgp/errors" "github.com/ProtonMail/go-crypto/openpgp/errors"
) )
const aeadSaltSize = 32
// SymmetricallyEncrypted represents a symmetrically encrypted byte string. The // SymmetricallyEncrypted represents a symmetrically encrypted byte string. The
// encrypted Contents will consist of more OpenPGP packets. See RFC 4880, // encrypted Contents will consist of more OpenPGP packets. See RFC 4880,
// sections 5.7 and 5.13. // sections 5.7 and 5.13.
type SymmetricallyEncrypted struct { type SymmetricallyEncrypted struct {
MDC bool // true iff this is a type 18 packet and thus has an embedded MAC. Version int
Contents io.Reader Contents io.Reader // contains tag for version 2
prefix []byte IntegrityProtected bool // If true it is type 18 (with MDC or AEAD). False is packet type 9
// Specific to version 1
prefix []byte
// Specific to version 2
Cipher CipherFunction
Mode AEADMode
ChunkSizeByte byte
Salt [aeadSaltSize]byte
} }
const symmetricallyEncryptedVersion = 1 const (
symmetricallyEncryptedVersionMdc = 1
symmetricallyEncryptedVersionAead = 2
)
func (se *SymmetricallyEncrypted) parse(r io.Reader) error { func (se *SymmetricallyEncrypted) parse(r io.Reader) error {
if se.MDC { if se.IntegrityProtected {
// See RFC 4880, section 5.13. // See RFC 4880, section 5.13.
var buf [1]byte var buf [1]byte
_, err := readFull(r, buf[:]) _, err := readFull(r, buf[:])
if err != nil { if err != nil {
return err return err
} }
if buf[0] != symmetricallyEncryptedVersion {
switch buf[0] {
case symmetricallyEncryptedVersionMdc:
se.Version = symmetricallyEncryptedVersionMdc
case symmetricallyEncryptedVersionAead:
se.Version = symmetricallyEncryptedVersionAead
if err := se.parseAead(r); err != nil {
return err
}
default:
return errors.UnsupportedError("unknown SymmetricallyEncrypted version") return errors.UnsupportedError("unknown SymmetricallyEncrypted version")
} }
} else {
return errors.UnsupportedError("Symmetrically encrypted packets without MDC are not supported")
} }
se.Contents = r se.Contents = r
return nil return nil
@ -48,245 +64,27 @@ func (se *SymmetricallyEncrypted) parse(r io.Reader) error {
// packet can be read. An incorrect key will only be detected after trying // packet can be read. An incorrect key will only be detected after trying
// to decrypt the entire data. // to decrypt the entire data.
func (se *SymmetricallyEncrypted) Decrypt(c CipherFunction, key []byte) (io.ReadCloser, error) { func (se *SymmetricallyEncrypted) Decrypt(c CipherFunction, key []byte) (io.ReadCloser, error) {
keySize := c.KeySize() if se.Version == symmetricallyEncryptedVersionAead {
if keySize == 0 { return se.decryptAead(key)
return nil, errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(c)))
}
if len(key) != keySize {
return nil, errors.InvalidArgumentError("SymmetricallyEncrypted: incorrect key length")
} }
if se.prefix == nil { return se.decryptMdc(c, key)
se.prefix = make([]byte, c.blockSize()+2)
_, err := readFull(se.Contents, se.prefix)
if err != nil {
return nil, err
}
} else if len(se.prefix) != c.blockSize()+2 {
return nil, errors.InvalidArgumentError("can't try ciphers with different block lengths")
}
ocfbResync := OCFBResync
if se.MDC {
// MDC packets use a different form of OCFB mode.
ocfbResync = OCFBNoResync
}
s := NewOCFBDecrypter(c.new(key), se.prefix, ocfbResync)
plaintext := cipher.StreamReader{S: s, R: se.Contents}
if se.MDC {
// MDC packets have an embedded hash that we need to check.
h := sha1.New()
h.Write(se.prefix)
return &seMDCReader{in: plaintext, h: h}, nil
}
// Otherwise, we just need to wrap plaintext so that it's a valid ReadCloser.
return seReader{plaintext}, nil
}
// seReader wraps an io.Reader with a no-op Close method.
type seReader struct {
in io.Reader
}
func (ser seReader) Read(buf []byte) (int, error) {
return ser.in.Read(buf)
}
func (ser seReader) Close() error {
return nil
}
const mdcTrailerSize = 1 /* tag byte */ + 1 /* length byte */ + sha1.Size
// An seMDCReader wraps an io.Reader, maintains a running hash and keeps hold
// of the most recent 22 bytes (mdcTrailerSize). Upon EOF, those bytes form an
// MDC packet containing a hash of the previous Contents which is checked
// against the running hash. See RFC 4880, section 5.13.
type seMDCReader struct {
in io.Reader
h hash.Hash
trailer [mdcTrailerSize]byte
scratch [mdcTrailerSize]byte
trailerUsed int
error bool
eof bool
}
func (ser *seMDCReader) Read(buf []byte) (n int, err error) {
if ser.error {
err = io.ErrUnexpectedEOF
return
}
if ser.eof {
err = io.EOF
return
}
// If we haven't yet filled the trailer buffer then we must do that
// first.
for ser.trailerUsed < mdcTrailerSize {
n, err = ser.in.Read(ser.trailer[ser.trailerUsed:])
ser.trailerUsed += n
if err == io.EOF {
if ser.trailerUsed != mdcTrailerSize {
n = 0
err = io.ErrUnexpectedEOF
ser.error = true
return
}
ser.eof = true
n = 0
return
}
if err != nil {
n = 0
return
}
}
// If it's a short read then we read into a temporary buffer and shift
// the data into the caller's buffer.
if len(buf) <= mdcTrailerSize {
n, err = readFull(ser.in, ser.scratch[:len(buf)])
copy(buf, ser.trailer[:n])
ser.h.Write(buf[:n])
copy(ser.trailer[:], ser.trailer[n:])
copy(ser.trailer[mdcTrailerSize-n:], ser.scratch[:])
if n < len(buf) {
ser.eof = true
err = io.EOF
}
return
}
n, err = ser.in.Read(buf[mdcTrailerSize:])
copy(buf, ser.trailer[:])
ser.h.Write(buf[:n])
copy(ser.trailer[:], buf[n:])
if err == io.EOF {
ser.eof = true
}
return
}
// This is a new-format packet tag byte for a type 19 (MDC) packet.
const mdcPacketTagByte = byte(0x80) | 0x40 | 19
func (ser *seMDCReader) Close() error {
if ser.error {
return errors.ErrMDCMissing
}
for !ser.eof {
// We haven't seen EOF so we need to read to the end
var buf [1024]byte
_, err := ser.Read(buf[:])
if err == io.EOF {
break
}
if err != nil {
return errors.ErrMDCMissing
}
}
ser.h.Write(ser.trailer[:2])
final := ser.h.Sum(nil)
if subtle.ConstantTimeCompare(final, ser.trailer[2:]) != 1 {
return errors.ErrMDCHashMismatch
}
// The hash already includes the MDC header, but we still check its value
// to confirm encryption correctness
if ser.trailer[0] != mdcPacketTagByte || ser.trailer[1] != sha1.Size {
return errors.ErrMDCMissing
}
return nil
}
// An seMDCWriter writes through to an io.WriteCloser while maintains a running
// hash of the data written. On close, it emits an MDC packet containing the
// running hash.
type seMDCWriter struct {
w io.WriteCloser
h hash.Hash
}
func (w *seMDCWriter) Write(buf []byte) (n int, err error) {
w.h.Write(buf)
return w.w.Write(buf)
}
func (w *seMDCWriter) Close() (err error) {
var buf [mdcTrailerSize]byte
buf[0] = mdcPacketTagByte
buf[1] = sha1.Size
w.h.Write(buf[:2])
digest := w.h.Sum(nil)
copy(buf[2:], digest)
_, err = w.w.Write(buf[:])
if err != nil {
return
}
return w.w.Close()
}
// noOpCloser is like an ioutil.NopCloser, but for an io.Writer.
type noOpCloser struct {
w io.Writer
}
func (c noOpCloser) Write(data []byte) (n int, err error) {
return c.w.Write(data)
}
func (c noOpCloser) Close() error {
return nil
} }
// SerializeSymmetricallyEncrypted serializes a symmetrically encrypted packet // SerializeSymmetricallyEncrypted serializes a symmetrically encrypted packet
// to w and returns a WriteCloser to which the to-be-encrypted packets can be // to w and returns a WriteCloser to which the to-be-encrypted packets can be
// written. // written.
// If config is nil, sensible defaults will be used. // If config is nil, sensible defaults will be used.
func SerializeSymmetricallyEncrypted(w io.Writer, c CipherFunction, key []byte, config *Config) (Contents io.WriteCloser, err error) { func SerializeSymmetricallyEncrypted(w io.Writer, c CipherFunction, aeadSupported bool, cipherSuite CipherSuite, key []byte, config *Config) (Contents io.WriteCloser, err error) {
if c.KeySize() != len(key) {
return nil, errors.InvalidArgumentError("SymmetricallyEncrypted.Serialize: bad key length")
}
writeCloser := noOpCloser{w} writeCloser := noOpCloser{w}
ciphertext, err := serializeStreamHeader(writeCloser, packetTypeSymmetricallyEncryptedMDC) ciphertext, err := serializeStreamHeader(writeCloser, packetTypeSymmetricallyEncryptedIntegrityProtected)
if err != nil { if err != nil {
return return
} }
_, err = ciphertext.Write([]byte{symmetricallyEncryptedVersion}) if aeadSupported {
if err != nil { return serializeSymmetricallyEncryptedAead(ciphertext, cipherSuite, config.AEADConfig.ChunkSizeByte(), config.Random(), key)
return
} }
block := c.new(key) return serializeSymmetricallyEncryptedMdc(ciphertext, c, key, config)
blockSize := block.BlockSize()
iv := make([]byte, blockSize)
_, err = config.Random().Read(iv)
if err != nil {
return
}
s, prefix := NewOCFBEncrypter(block, iv, OCFBNoResync)
_, err = ciphertext.Write(prefix)
if err != nil {
return
}
plaintext := cipher.StreamWriter{S: s, W: ciphertext}
h := sha1.New()
h.Write(iv)
h.Write(iv[blockSize-2:])
Contents = &seMDCWriter{w: plaintext, h: h}
return
} }

Some files were not shown because too many files have changed in this diff Show More