Merge pull request #739 from chrisallenlane/v4.4.1

V4.4.1
This commit is contained in:
Chris Allen Lane 2023-12-13 09:17:16 -05:00 committed by GitHub
commit 2294f40ee0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
782 changed files with 28959 additions and 12962 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.4.0/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.4.0`) 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

@ -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.4.0" 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]

37
go.mod
View File

@ -6,30 +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.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
) )
require ( require (
github.com/Microsoft/go-winio v0.6.0 // indirect dario.cat/mergo v1.0.0 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // 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.1.0 // indirect github.com/skeema/knownhosts v1.2.1 // indirect
golang.org/x/mod v0.6.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/net v0.1.0 // indirect golang.org/x/crypto v0.16.0 // indirect
golang.org/x/sys v0.1.0 // indirect golang.org/x/mod v0.14.0 // indirect
golang.org/x/tools v0.2.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
) )

182
go.sum
View File

@ -1,146 +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/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE=
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 h1:ra2OtmuW0AE5csawV4YXMNGNQQXvLRps3z2Z59OPO+I= github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8=
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
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.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= 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.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= 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.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/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.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= 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.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= 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.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= 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.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

@ -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

@ -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,7 +194,7 @@ package main
import ( import (
"fmt" "fmt"
"github.com/imdario/mergo" "dario.cat/mergo"
"reflect" "reflect"
"time" "time"
) )
@ -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,7 +95,7 @@ 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"
) )
@ -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) {
if dstElement.IsValid() && !isEmptyValue(dstElement, !config.ShouldNotDereference) {
if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice {
continue 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,13 +261,19 @@ 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 !config.ShouldNotDereference {
if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil { if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
return 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 {
return return
@ -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:

View File

@ -8,12 +8,8 @@ linters:
- containedctx # struct contains a context - containedctx # struct contains a context
- dupl # duplicate code - dupl # duplicate code
- errname # erorrs are named correctly - errname # erorrs are named correctly
- goconst # strings that should be constants
- godot # comments end in a period
- misspell
- nolintlint # "//nolint" directives are properly explained - nolintlint # "//nolint" directives are properly explained
- revive # golint replacement - revive # golint replacement
- stylecheck # golint replacement, less configurable than revive
- unconvert # unnecessary conversions - unconvert # unnecessary conversions
- wastedassign - wastedassign
@ -23,10 +19,7 @@ linters:
- exhaustive # check exhaustiveness of enum switch statements - exhaustive # check exhaustiveness of enum switch statements
- gofmt # files are gofmt'ed - gofmt # files are gofmt'ed
- gosec # security - gosec # security
- nestif # deeply nested ifs
- nilerr # returns nil even with non-nil error - nilerr # returns nil even with non-nil error
- prealloc # slices that can be pre-allocated
- structcheck # unused struct fields
- unparam # unused function params - unparam # unused function params
issues: issues:
@ -42,6 +35,18 @@ issues:
text: "^line-length-limit: " text: "^line-length-limit: "
source: "^//(go:generate|sys) " 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 # allow unjustified ignores of error checks in defer statements
- linters: - linters:
- nolintlint - nolintlint
@ -56,6 +61,8 @@ issues:
linters-settings: linters-settings:
exhaustive:
default-signifies-exhaustive: true
govet: govet:
enable-all: true enable-all: true
disable: disable:
@ -98,6 +105,8 @@ linters-settings:
disabled: true disabled: true
- name: flag-parameter # excessive, and a common idiom we use - name: flag-parameter # excessive, and a common idiom we use
disabled: true disabled: true
- name: unhandled-error # warns over common fmt.Print* and io.Close; rely on errcheck instead
disabled: true
# general config # general config
- name: line-length-limit - name: line-length-limit
arguments: arguments:
@ -138,7 +147,3 @@ linters-settings:
- VPCI - VPCI
- WCOW - WCOW
- WIM - WIM
stylecheck:
checks:
- "all"
- "-ST1003" # use revive's var naming

View File

@ -23,7 +23,7 @@ import (
const afHVSock = 34 // AF_HYPERV const afHVSock = 34 // AF_HYPERV
// Well known Service and VM IDs // Well known Service and VM IDs
//https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/make-integration-service#vmid-wildcards // https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/make-integration-service#vmid-wildcards
// HvsockGUIDWildcard is the wildcard VmId for accepting connections from all partitions. // HvsockGUIDWildcard is the wildcard VmId for accepting connections from all partitions.
func HvsockGUIDWildcard() guid.GUID { // 00000000-0000-0000-0000-000000000000 func HvsockGUIDWildcard() guid.GUID { // 00000000-0000-0000-0000-000000000000
@ -31,7 +31,7 @@ func HvsockGUIDWildcard() guid.GUID { // 00000000-0000-0000-0000-000000000000
} }
// HvsockGUIDBroadcast is the wildcard VmId for broadcasting sends to all partitions. // HvsockGUIDBroadcast is the wildcard VmId for broadcasting sends to all partitions.
func HvsockGUIDBroadcast() guid.GUID { //ffffffff-ffff-ffff-ffff-ffffffffffff func HvsockGUIDBroadcast() guid.GUID { // ffffffff-ffff-ffff-ffff-ffffffffffff
return guid.GUID{ return guid.GUID{
Data1: 0xffffffff, Data1: 0xffffffff,
Data2: 0xffff, Data2: 0xffff,
@ -246,7 +246,7 @@ func (l *HvsockListener) Accept() (_ net.Conn, err error) {
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 /*rxdatalen*/, addrlen, addrlen, &bytes, &c.o) err = syscall.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0 /* rxdatalen */, addrlen, addrlen, &bytes, &c.o)
if _, err = l.sock.asyncIO(c, nil, bytes, err); err != nil { if _, err = l.sock.asyncIO(c, nil, bytes, err); err != nil {
return nil, l.opErr("accept", os.NewSyscallError("acceptex", err)) return nil, l.opErr("accept", os.NewSyscallError("acceptex", err))
} }

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

@ -100,8 +100,8 @@ func (f *runtimeFunc) Load() error {
(*byte)(unsafe.Pointer(&f.addr)), (*byte)(unsafe.Pointer(&f.addr)),
uint32(unsafe.Sizeof(f.addr)), uint32(unsafe.Sizeof(f.addr)),
&n, &n,
nil, //overlapped nil, // overlapped
0, //completionRoutine 0, // completionRoutine
) )
}) })
return f.err return f.err

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

@ -16,11 +16,12 @@ import (
"unsafe" "unsafe"
"golang.org/x/sys/windows" "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
@ -163,19 +164,21 @@ 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, wh, err := fs.CreateFile(*path,
access, access,
0, 0, // mode
nil, nil, // security attributes
syscall.OPEN_EXISTING, fs.OPEN_EXISTING,
windows.FILE_FLAG_OVERLAPPED|windows.SECURITY_SQOS_PRESENT|windows.SECURITY_ANONYMOUS, fs.FILE_FLAG_OVERLAPPED|fs.SECURITY_SQOS_PRESENT|fs.SECURITY_ANONYMOUS,
0) 0, // template file handle
)
h := syscall.Handle(wh)
if err == nil { if err == nil {
return h, nil return h, nil
} }
@ -219,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
} }
@ -279,6 +282,7 @@ func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (sy
} }
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 {

View File

@ -63,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")
@ -305,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)

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

@ -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

@ -11,7 +11,8 @@ var rfc7253TestVectorTaglen96 = struct {
var rfc7253AlgorithmTest = []struct { var rfc7253AlgorithmTest = []struct {
KEYLEN, TAGLEN int KEYLEN, TAGLEN int
OUTPUT string }{ OUTPUT string
}{
{128, 128, "67E944D23256C5E0B6C61FA22FDF1EA2"}, {128, 128, "67E944D23256C5E0B6C61FA22FDF1EA2"},
{192, 128, "F673F2C3E7174AAE7BAE986CA9F29E17"}, {192, 128, "F673F2C3E7174AAE7BAE986CA9F29E17"},
{256, 128, "D90EB8E9C977C88B79DD793D7FFA161C"}, {256, 128, "D90EB8E9C977C88B79DD793D7FFA161C"},
@ -21,4 +22,4 @@ var rfc7253AlgorithmTest = []struct {
{128, 64, "192C9B7BD90BA06A"}, {128, 64, "192C9B7BD90BA06A"},
{192, 64, "0066BC6E0EF34E24"}, {192, 64, "0066BC6E0EF34E24"},
{256, 64, "7D4EA5D445501CBE"}, {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----- // -----BEGIN Type-----
// Headers // Headers
// //
// base64-encoded Bytes // base64-encoded Bytes
// '=' base64 encoded checksum // '=' base64 encoded checksum
// -----END Type----- // -----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,6 +96,7 @@ 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

View File

@ -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

@ -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}
RIPEMD160 Hash = cryptoHash{3, crypto.RIPEMD160}
SHA256 Hash = cryptoHash{8, crypto.SHA256} SHA256 Hash = cryptoHash{8, crypto.SHA256}
SHA384 Hash = cryptoHash{9, crypto.SHA384} SHA384 Hash = cryptoHash{9, crypto.SHA384}
SHA512 Hash = cryptoHash{10, crypto.SHA512} SHA512 Hash = cryptoHash{10, crypto.SHA512}
SHA224 Hash = cryptoHash{11, crypto.SHA224} SHA224 Hash = cryptoHash{11, crypto.SHA224}
SHA3_256 Hash = cryptoHash{12, crypto.SHA3_256}
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,
SHA1.Id(): SHA1,
RIPEMD160.Id(): RIPEMD160,
SHA256.Id(): SHA256, SHA256.Id(): SHA256,
SHA384.Id(): SHA384, SHA384.Id(): SHA384,
SHA512.Id(): SHA512, SHA512.Id(): SHA512,
SHA224.Id(): SHA224, SHA224.Id(): SHA224,
SHA3_256.Id(): SHA3_256,
SHA3_512.Id(): SHA3_512,
} }
) )
@ -68,13 +67,12 @@ func (h cryptoHash) Id() uint8 {
} }
var hashNames = map[uint8]string{ var hashNames = map[uint8]string{
MD5.Id(): "MD5",
SHA1.Id(): "SHA1",
RIPEMD160.Id(): "RIPEMD160",
SHA256.Id(): "SHA256", SHA256.Id(): "SHA256",
SHA384.Id(): "SHA384", SHA384.Id(): "SHA384",
SHA512.Id(): "SHA512", SHA512.Id(): "SHA512",
SHA224.Id(): "SHA224", SHA224.Id(): "SHA224",
SHA3_256.Id(): "SHA3-256",
SHA3_512.Id(): "SHA3-512",
} }
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

@ -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.
@ -94,6 +104,12 @@ type Config struct {
// might be no other way than to tolerate the missing MDC. Setting this flag, allows this // 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. // mode of operation. It should be considered a measure of last resort.
InsecureAllowUnauthenticatedMessages bool 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 {
@ -119,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.
@ -147,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
@ -175,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
@ -202,3 +232,17 @@ func (c *Config) AllowUnauthenticatedMessages() bool {
} }
return c.InsecureAllowUnauthenticatedMessages 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

@ -315,7 +315,7 @@ const (
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
) )
@ -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)
@ -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()
} }
@ -537,3 +543,9 @@ const (
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})
switch packetType { if !isHashed &&
case creationTimeSubpacket: packetType != issuerSubpacket &&
if !isHashed { packetType != issuerFingerprintSubpacket &&
err = errors.StructuralError("signature creation time in non-hashed area") packetType != embeddedSignatureSubpacket {
return return
} }
switch packetType {
case creationTimeSubpacket:
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.

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,36 +5,54 @@
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
IntegrityProtected bool // If true it is type 18 (with MDC or AEAD). False is packet type 9
// Specific to version 1
prefix []byte 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")
} }
} }
@ -46,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
} }

View File

@ -0,0 +1,156 @@
// Copyright 2023 Proton AG. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package packet
import (
"crypto/cipher"
"crypto/sha256"
"io"
"github.com/ProtonMail/go-crypto/openpgp/errors"
"golang.org/x/crypto/hkdf"
)
// parseAead parses a V2 SEIPD packet (AEAD) as specified in
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.13.2
func (se *SymmetricallyEncrypted) parseAead(r io.Reader) error {
headerData := make([]byte, 3)
if n, err := io.ReadFull(r, headerData); n < 3 {
return errors.StructuralError("could not read aead header: " + err.Error())
}
// Cipher
se.Cipher = CipherFunction(headerData[0])
// cipherFunc must have block size 16 to use AEAD
if se.Cipher.blockSize() != 16 {
return errors.UnsupportedError("invalid aead cipher: " + string(se.Cipher))
}
// Mode
se.Mode = AEADMode(headerData[1])
if se.Mode.TagLength() == 0 {
return errors.UnsupportedError("unknown aead mode: " + string(se.Mode))
}
// Chunk size
se.ChunkSizeByte = headerData[2]
if se.ChunkSizeByte > 16 {
return errors.UnsupportedError("invalid aead chunk size byte: " + string(se.ChunkSizeByte))
}
// Salt
if n, err := io.ReadFull(r, se.Salt[:]); n < aeadSaltSize {
return errors.StructuralError("could not read aead salt: " + err.Error())
}
return nil
}
// associatedData for chunks: tag, version, cipher, mode, chunk size byte
func (se *SymmetricallyEncrypted) associatedData() []byte {
return []byte{
0xD2,
symmetricallyEncryptedVersionAead,
byte(se.Cipher),
byte(se.Mode),
se.ChunkSizeByte,
}
}
// decryptAead decrypts a V2 SEIPD packet (AEAD) as specified in
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.13.2
func (se *SymmetricallyEncrypted) decryptAead(inputKey []byte) (io.ReadCloser, error) {
aead, nonce := getSymmetricallyEncryptedAeadInstance(se.Cipher, se.Mode, inputKey, se.Salt[:], se.associatedData())
// Carry the first tagLen bytes
tagLen := se.Mode.TagLength()
peekedBytes := make([]byte, tagLen)
n, err := io.ReadFull(se.Contents, peekedBytes)
if n < tagLen || (err != nil && err != io.EOF) {
return nil, errors.StructuralError("not enough data to decrypt:" + err.Error())
}
return &aeadDecrypter{
aeadCrypter: aeadCrypter{
aead: aead,
chunkSize: decodeAEADChunkSize(se.ChunkSizeByte),
initialNonce: nonce,
associatedData: se.associatedData(),
chunkIndex: make([]byte, 8),
packetTag: packetTypeSymmetricallyEncryptedIntegrityProtected,
},
reader: se.Contents,
peekedBytes: peekedBytes,
}, nil
}
// serializeSymmetricallyEncryptedAead encrypts to a writer a V2 SEIPD packet (AEAD) as specified in
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.13.2
func serializeSymmetricallyEncryptedAead(ciphertext io.WriteCloser, cipherSuite CipherSuite, chunkSizeByte byte, rand io.Reader, inputKey []byte) (Contents io.WriteCloser, err error) {
// cipherFunc must have block size 16 to use AEAD
if cipherSuite.Cipher.blockSize() != 16 {
return nil, errors.InvalidArgumentError("invalid aead cipher function")
}
if cipherSuite.Cipher.KeySize() != len(inputKey) {
return nil, errors.InvalidArgumentError("error in aead serialization: bad key length")
}
// Data for en/decryption: tag, version, cipher, aead mode, chunk size
prefix := []byte{
0xD2,
symmetricallyEncryptedVersionAead,
byte(cipherSuite.Cipher),
byte(cipherSuite.Mode),
chunkSizeByte,
}
// Write header (that correspond to prefix except first byte)
n, err := ciphertext.Write(prefix[1:])
if err != nil || n < 4 {
return nil, err
}
// Random salt
salt := make([]byte, aeadSaltSize)
if _, err := rand.Read(salt); err != nil {
return nil, err
}
if _, err := ciphertext.Write(salt); err != nil {
return nil, err
}
aead, nonce := getSymmetricallyEncryptedAeadInstance(cipherSuite.Cipher, cipherSuite.Mode, inputKey, salt, prefix)
return &aeadEncrypter{
aeadCrypter: aeadCrypter{
aead: aead,
chunkSize: decodeAEADChunkSize(chunkSizeByte),
associatedData: prefix,
chunkIndex: make([]byte, 8),
initialNonce: nonce,
packetTag: packetTypeSymmetricallyEncryptedIntegrityProtected,
},
writer: ciphertext,
}, nil
}
func getSymmetricallyEncryptedAeadInstance(c CipherFunction, mode AEADMode, inputKey, salt, associatedData []byte) (aead cipher.AEAD, nonce []byte) {
hkdfReader := hkdf.New(sha256.New, inputKey, salt, associatedData)
encryptionKey := make([]byte, c.KeySize())
_, _ = readFull(hkdfReader, encryptionKey)
// Last 64 bits of nonce are the counter
nonce = make([]byte, mode.IvLength()-8)
_, _ = readFull(hkdfReader, nonce)
blockCipher := c.new(encryptionKey)
aead = mode.new(blockCipher)
return
}

View File

@ -0,0 +1,256 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package packet
import (
"crypto/cipher"
"crypto/sha1"
"crypto/subtle"
"hash"
"io"
"strconv"
"github.com/ProtonMail/go-crypto/openpgp/errors"
)
// seMdcReader wraps an io.Reader with a no-op Close method.
type seMdcReader struct {
in io.Reader
}
func (ser seMdcReader) Read(buf []byte) (int, error) {
return ser.in.Read(buf)
}
func (ser seMdcReader) Close() error {
return nil
}
func (se *SymmetricallyEncrypted) decryptMdc(c CipherFunction, key []byte) (io.ReadCloser, error) {
if !c.IsSupported() {
return nil, errors.UnsupportedError("unsupported cipher: " + strconv.Itoa(int(c)))
}
if len(key) != c.KeySize() {
return nil, errors.InvalidArgumentError("SymmetricallyEncrypted: incorrect key length")
}
if se.prefix == nil {
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.IntegrityProtected {
// 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.IntegrityProtected {
// IntegrityProtected 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 seMdcReader{plaintext}, 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 (Integrity Protected) 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
}
func serializeSymmetricallyEncryptedMdc(ciphertext io.WriteCloser, c CipherFunction, key []byte, config *Config) (Contents io.WriteCloser, err error) {
// Disallow old cipher suites
if !c.IsSupported() || c < CipherAES128 {
return nil, errors.InvalidArgumentError("invalid mdc cipher function")
}
if c.KeySize() != len(key) {
return nil, errors.InvalidArgumentError("error in mdc serialization: bad key length")
}
_, err = ciphertext.Write([]byte{symmetricallyEncryptedVersionMdc})
if err != nil {
return
}
block := c.new(key)
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
}

View File

@ -8,13 +8,16 @@ package openpgp // import "github.com/ProtonMail/go-crypto/openpgp"
import ( import (
"crypto" "crypto"
_ "crypto/sha256" _ "crypto/sha256"
_ "crypto/sha512"
"hash" "hash"
"io" "io"
"strconv" "strconv"
"github.com/ProtonMail/go-crypto/openpgp/armor" "github.com/ProtonMail/go-crypto/openpgp/armor"
"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/packet" "github.com/ProtonMail/go-crypto/openpgp/packet"
_ "golang.org/x/crypto/sha3"
) )
// SignatureType is the armor type for a PGP signature. // SignatureType is the armor type for a PGP signature.
@ -131,8 +134,8 @@ ParsePackets:
} }
} }
case *packet.SymmetricallyEncrypted: case *packet.SymmetricallyEncrypted:
if !p.MDC && !config.AllowUnauthenticatedMessages() { if !p.IntegrityProtected && !config.AllowUnauthenticatedMessages() {
return nil, errors.UnsupportedError("message is not authenticated") return nil, errors.UnsupportedError("message is not integrity protected")
} }
edp = p edp = p
break ParsePackets break ParsePackets
@ -208,13 +211,11 @@ FindKey:
if len(symKeys) != 0 && passphrase != nil { if len(symKeys) != 0 && passphrase != nil {
for _, s := range symKeys { for _, s := range symKeys {
key, cipherFunc, err := s.Decrypt(passphrase) key, cipherFunc, err := s.Decrypt(passphrase)
// On wrong passphrase, session key decryption is very likely to result in an invalid cipherFunc: // In v4, on wrong passphrase, session key decryption is very likely to result in an invalid cipherFunc:
// only for < 5% of cases we will proceed to decrypt the data // only for < 5% of cases we will proceed to decrypt the data
if err == nil { if err == nil {
decrypted, err = edp.Decrypt(cipherFunc, key) decrypted, err = edp.Decrypt(cipherFunc, key)
// TODO: ErrKeyIncorrect is no longer thrown on SEIP decryption, if err != nil {
// but it might still be relevant for when we implement AEAD decryption (otherwise, remove?)
if err != nil && err != errors.ErrKeyIncorrect {
return nil, err return nil, err
} }
if decrypted != nil { if decrypted != nil {
@ -304,14 +305,14 @@ FindLiteralData:
// should be preprocessed (i.e. to normalize line endings). Thus this function // should be preprocessed (i.e. to normalize line endings). Thus this function
// returns two hashes. The second should be used to hash the message itself and // returns two hashes. The second should be used to hash the message itself and
// performs any needed preprocessing. // performs any needed preprocessing.
func hashForSignature(hashId crypto.Hash, sigType packet.SignatureType) (hash.Hash, hash.Hash, error) { func hashForSignature(hashFunc crypto.Hash, sigType packet.SignatureType) (hash.Hash, hash.Hash, error) {
if hashId == crypto.MD5 { if _, ok := algorithm.HashToHashIdWithSha1(hashFunc); !ok {
return nil, nil, errors.UnsupportedError("insecure hash algorithm: MD5") return nil, nil, errors.UnsupportedError("unsupported hash function")
} }
if !hashId.Available() { if !hashFunc.Available() {
return nil, nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashId))) return nil, nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashFunc)))
} }
h := hashId.New() h := hashFunc.New()
switch sigType { switch sigType {
case packet.SigTypeBinary: case packet.SigTypeBinary:
@ -383,19 +384,7 @@ func (scr *signatureCheckReader) Read(buf []byte) (int, error) {
key := scr.md.SignedBy key := scr.md.SignedBy
signatureError := key.PublicKey.VerifySignature(scr.h, sig) signatureError := key.PublicKey.VerifySignature(scr.h, sig)
if signatureError == nil { if signatureError == nil {
now := scr.config.Now() signatureError = checkSignatureDetails(key, sig, scr.config)
if key.Revoked(now) ||
key.Entity.Revoked(now) || // primary key is revoked (redundant if key is the primary key)
key.Entity.PrimaryIdentity().Revoked(now) {
signatureError = errors.ErrKeyRevoked
}
if sig.SigExpired(now) {
signatureError = errors.ErrSignatureExpired
}
if key.PublicKey.KeyExpired(key.SelfSignature, now) ||
key.SelfSignature.SigExpired(now) {
signatureError = errors.ErrKeyExpired
}
} }
scr.md.Signature = sig scr.md.Signature = sig
scr.md.SignatureError = signatureError scr.md.SignatureError = signatureError
@ -434,8 +423,24 @@ func (scr *signatureCheckReader) Read(buf []byte) (int, error) {
return n, nil return n, nil
} }
// VerifyDetachedSignature takes a signed file and a detached signature and
// returns the signature packet and the entity the signature was signed by,
// if any, and a possible signature verification error.
// If the signer isn't known, ErrUnknownIssuer is returned.
func VerifyDetachedSignature(keyring KeyRing, signed, signature io.Reader, config *packet.Config) (sig *packet.Signature, signer *Entity, err error) {
var expectedHashes []crypto.Hash
return verifyDetachedSignature(keyring, signed, signature, expectedHashes, config)
}
// VerifyDetachedSignatureAndHash performs the same actions as
// VerifyDetachedSignature and checks that the expected hash functions were used.
func VerifyDetachedSignatureAndHash(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, config *packet.Config) (sig *packet.Signature, signer *Entity, err error) {
return verifyDetachedSignature(keyring, signed, signature, expectedHashes, config)
}
// CheckDetachedSignature takes a signed file and a detached signature and // CheckDetachedSignature takes a signed file and a detached signature and
// returns the signer if the signature is valid. If the signer isn't known, // returns the entity the signature was signed by, if any, and a possible
// signature verification error. If the signer isn't known,
// ErrUnknownIssuer is returned. // ErrUnknownIssuer is returned.
func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader, config *packet.Config) (signer *Entity, err error) { func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader, config *packet.Config) (signer *Entity, err error) {
var expectedHashes []crypto.Hash var expectedHashes []crypto.Hash
@ -445,6 +450,11 @@ func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader, config
// CheckDetachedSignatureAndHash performs the same actions as // CheckDetachedSignatureAndHash performs the same actions as
// CheckDetachedSignature and checks that the expected hash functions were used. // CheckDetachedSignature and checks that the expected hash functions were used.
func CheckDetachedSignatureAndHash(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, config *packet.Config) (signer *Entity, err error) { func CheckDetachedSignatureAndHash(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, config *packet.Config) (signer *Entity, err error) {
_, signer, err = verifyDetachedSignature(keyring, signed, signature, expectedHashes, config)
return
}
func verifyDetachedSignature(keyring KeyRing, signed, signature io.Reader, expectedHashes []crypto.Hash, config *packet.Config) (sig *packet.Signature, signer *Entity, err error) {
var issuerKeyId uint64 var issuerKeyId uint64
var hashFunc crypto.Hash var hashFunc crypto.Hash
var sigType packet.SignatureType var sigType packet.SignatureType
@ -453,23 +463,22 @@ func CheckDetachedSignatureAndHash(keyring KeyRing, signed, signature io.Reader,
expectedHashesLen := len(expectedHashes) expectedHashesLen := len(expectedHashes)
packets := packet.NewReader(signature) packets := packet.NewReader(signature)
var sig *packet.Signature
for { for {
p, err = packets.Next() p, err = packets.Next()
if err == io.EOF { if err == io.EOF {
return nil, errors.ErrUnknownIssuer return nil, nil, errors.ErrUnknownIssuer
} }
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
var ok bool var ok bool
sig, ok = p.(*packet.Signature) sig, ok = p.(*packet.Signature)
if !ok { if !ok {
return nil, errors.StructuralError("non signature packet found") return nil, nil, errors.StructuralError("non signature packet found")
} }
if sig.IssuerKeyId == nil { if sig.IssuerKeyId == nil {
return nil, errors.StructuralError("signature doesn't have an issuer") return nil, nil, errors.StructuralError("signature doesn't have an issuer")
} }
issuerKeyId = *sig.IssuerKeyId issuerKeyId = *sig.IssuerKeyId
hashFunc = sig.Hash hashFunc = sig.Hash
@ -480,7 +489,7 @@ func CheckDetachedSignatureAndHash(keyring KeyRing, signed, signature io.Reader,
break break
} }
if i+1 == expectedHashesLen { if i+1 == expectedHashesLen {
return nil, errors.StructuralError("hash algorithm mismatch with cleartext message headers") return nil, nil, errors.StructuralError("hash algorithm mismatch with cleartext message headers")
} }
} }
@ -496,34 +505,21 @@ func CheckDetachedSignatureAndHash(keyring KeyRing, signed, signature io.Reader,
h, wrappedHash, err := hashForSignature(hashFunc, sigType) h, wrappedHash, err := hashForSignature(hashFunc, sigType)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
if _, err := io.Copy(wrappedHash, signed); err != nil && err != io.EOF { if _, err := io.Copy(wrappedHash, signed); err != nil && err != io.EOF {
return nil, err return nil, nil, err
} }
for _, key := range keys { for _, key := range keys {
err = key.PublicKey.VerifySignature(h, sig) err = key.PublicKey.VerifySignature(h, sig)
if err == nil { if err == nil {
now := config.Now() return sig, key.Entity, checkSignatureDetails(&key, sig, config)
if key.Revoked(now) ||
key.Entity.Revoked(now) || // primary key is revoked (redundant if key is the primary key)
key.Entity.PrimaryIdentity().Revoked(now) {
return key.Entity, errors.ErrKeyRevoked
}
if sig.SigExpired(now) {
return key.Entity, errors.ErrSignatureExpired
}
if key.PublicKey.KeyExpired(key.SelfSignature, now) ||
key.SelfSignature.SigExpired(now) {
return key.Entity, errors.ErrKeyExpired
}
return key.Entity, nil
} }
} }
return nil, err return nil, nil, err
} }
// CheckArmoredDetachedSignature performs the same actions as // CheckArmoredDetachedSignature performs the same actions as
@ -536,3 +532,61 @@ func CheckArmoredDetachedSignature(keyring KeyRing, signed, signature io.Reader,
return CheckDetachedSignature(keyring, signed, body, config) return CheckDetachedSignature(keyring, signed, body, config)
} }
// checkSignatureDetails returns an error if:
// - The signature (or one of the binding signatures mentioned below)
// has a unknown critical notation data subpacket
// - The primary key of the signing entity is revoked
// - The primary identity is revoked
// - The signature is expired
// - The primary key of the signing entity is expired according to the
// primary identity binding signature
//
// ... or, if the signature was signed by a subkey and:
// - The signing subkey is revoked
// - The signing subkey is expired according to the subkey binding signature
// - The signing subkey binding signature is expired
// - The signing subkey cross-signature is expired
//
// NOTE: The order of these checks is important, as the caller may choose to
// ignore ErrSignatureExpired or ErrKeyExpired errors, but should never
// ignore any other errors.
//
// TODO: Also return an error if:
// - The primary key is expired according to a direct-key signature
// - (For V5 keys only:) The direct-key signature (exists and) is expired
func checkSignatureDetails(key *Key, signature *packet.Signature, config *packet.Config) error {
now := config.Now()
primaryIdentity := key.Entity.PrimaryIdentity()
signedBySubKey := key.PublicKey != key.Entity.PrimaryKey
sigsToCheck := []*packet.Signature{signature, primaryIdentity.SelfSignature}
if signedBySubKey {
sigsToCheck = append(sigsToCheck, key.SelfSignature, key.SelfSignature.EmbeddedSignature)
}
for _, sig := range sigsToCheck {
for _, notation := range sig.Notations {
if notation.IsCritical && !config.KnownNotation(notation.Name) {
return errors.SignatureError("unknown critical notation: " + notation.Name)
}
}
}
if key.Entity.Revoked(now) || // primary key is revoked
(signedBySubKey && key.Revoked(now)) || // subkey is revoked
primaryIdentity.Revoked(now) { // primary identity is revoked
return errors.ErrKeyRevoked
}
if key.Entity.PrimaryKey.KeyExpired(primaryIdentity.SelfSignature, now) { // primary key is expired
return errors.ErrKeyExpired
}
if signedBySubKey {
if key.PublicKey.KeyExpired(key.SelfSignature, now) { // subkey is expired
return errors.ErrKeyExpired
}
}
for _, sig := range sigsToCheck {
if sig.SigExpired(now) { // any of the relevant signatures are expired
return errors.ErrSignatureExpired
}
}
return nil
}

View File

@ -1,8 +1,8 @@
package openpgp package openpgp
const testKey1KeyId = 0xA34D7E18C20C31BB const testKey1KeyId uint64 = 0xA34D7E18C20C31BB
const testKey3KeyId = 0x338934250CCC0360 const testKey3KeyId uint64 = 0x338934250CCC0360
const testKeyP256KeyId = 0xd44a2c495918513e const testKeyP256KeyId uint64 = 0xd44a2c495918513e
const signedInput = "Signed message\nline 2\nline 3\n" const signedInput = "Signed message\nline 2\nline 3\n"
const signedTextInput = "Signed message\r\nline 2\r\nline 3\r\n" const signedTextInput = "Signed message\r\nline 2\r\nline 3\r\n"
@ -106,7 +106,7 @@ const unknownHashFunctionHex = `8a00000040040001990006050253863c24000a09103b4fe6
const rsaSignatureBadMPIlength = `8a00000040040001030006050253863c24000a09103b4fe6acc0b21f32ffff0101010101010101010101010101010101010101010101010101010101010101010101010101` const rsaSignatureBadMPIlength = `8a00000040040001030006050253863c24000a09103b4fe6acc0b21f32ffff0101010101010101010101010101010101010101010101010101010101010101010101010101`
const missingHashFunctionHex = `8a00000040040001030006050253863c24000a09103b4fe6acc0b21f32ffff0101010101010101010101010101010101010101010101010101010101010101010101` const missingHashFunctionHex = `8a00000040040001030006050253863c24000a09103b4fe6acc0b21f32ffff0101010101010101010101010101010101010101010101010101010101010101010101010101`
const campbellQuine = `a0b001000300fcffa0b001000d00f2ff000300fcffa0b001000d00f2ff8270a01c00000500faff8270a01c00000500faff000500faff001400ebff8270a01c00000500faff000500faff001400ebff428821c400001400ebff428821c400001400ebff428821c400001400ebff428821c400001400ebff428821c400000000ffff000000ffff000b00f4ff428821c400000000ffff000000ffff000b00f4ff0233214c40000100feff000233214c40000100feff0000` const campbellQuine = `a0b001000300fcffa0b001000d00f2ff000300fcffa0b001000d00f2ff8270a01c00000500faff8270a01c00000500faff000500faff001400ebff8270a01c00000500faff000500faff001400ebff428821c400001400ebff428821c400001400ebff428821c400001400ebff428821c400001400ebff428821c400000000ffff000000ffff000b00f4ff428821c400000000ffff000000ffff000b00f4ff0233214c40000100feff000233214c40000100feff0000`
@ -171,3 +171,104 @@ y29VPonFXqi2zKkpZrvyvZxg+n5e8Nt9wNbuxeCd3QD/TtO2s+JvjrE4Siwv
UQdl5MlBka1QSNbMq2Bz7XwNPg4= UQdl5MlBka1QSNbMq2Bz7XwNPg4=
=6lbM =6lbM
-----END PGP MESSAGE-----` -----END PGP MESSAGE-----`
const keyWithExpiredCrossSig = `-----BEGIN PGP PUBLIC KEY BLOCK-----
xsDNBF2lnPIBDAC5cL9PQoQLTMuhjbYvb4Ncuuo0bfmgPRFywX53jPhoFf4Zg6mv
/seOXpgecTdOcVttfzC8ycIKrt3aQTiwOG/ctaR4Bk/t6ayNFfdUNxHWk4WCKzdz
/56fW2O0F23qIRd8UUJp5IIlN4RDdRCtdhVQIAuzvp2oVy/LaS2kxQoKvph/5pQ/
5whqsyroEWDJoSV0yOb25B/iwk/pLUFoyhDG9bj0kIzDxrEqW+7Ba8nocQlecMF3
X5KMN5kp2zraLv9dlBBpWW43XktjcCZgMy20SouraVma8Je/ECwUWYUiAZxLIlMv
9CurEOtxUw6N3RdOtLmYZS9uEnn5y1UkF88o8Nku890uk6BrewFzJyLAx5wRZ4F0
qV/yq36UWQ0JB/AUGhHVPdFf6pl6eaxBwT5GXvbBUibtf8YI2og5RsgTWtXfU7eb
SGXrl5ZMpbA6mbfhd0R8aPxWfmDWiIOhBufhMCvUHh1sApMKVZnvIff9/0Dca3wb
vLIwa3T4CyshfT0AEQEAAc0hQm9iIEJhYmJhZ2UgPGJvYkBvcGVucGdwLmV4YW1w
bGU+wsEABBMBCgATBYJeO2eVAgsJAxUICgKbAQIeAQAhCRD7/MgqAV5zMBYhBNGm
bhojsYLJmA94jPv8yCoBXnMwKWUMAJ3FKZfJ2mXvh+GFqgymvK4NoKkDRPB0CbUN
aDdG7ZOizQrWXo7Da2MYIZ6eZUDqBKLdhZ5gZfVnisDfu/yeCgpENaKib1MPHpA8
nZQjnPejbBDomNqY8HRzr5jvXNlwywBpjWGtegCKUY9xbSynjbfzIlMrWL4S+Rfl
+bOOQKRyYJWXmECmVyqY8cz2VUYmETjNcwC8VCDUxQnhtcCJ7Aej22hfYwVEPb/J
BsJBPq8WECCiGfJ9Y2y6TF+62KzG9Kfs5hqUeHhQy8V4TSi479ewwL7DH86XmIIK
chSANBS+7iyMtctjNZfmF9zYdGJFvjI/mbBR/lK66E515Inuf75XnL8hqlXuwqvG
ni+i03Aet1DzULZEIio4uIU6ioc1lGO9h7K2Xn4S7QQH1QoISNMWqXibUR0RCGjw
FsEDTt2QwJl8XXxoJCooM7BCcCQo+rMNVUHDjIwrdoQjPld3YZsUQQRcqH6bLuln
cfn5ufl8zTGWKydoj/iTz8KcjZ7w187AzQRdpZzyAQwA1jC/XGxjK6ddgrRfW9j+
s/U00++EvIsgTs2kr3Rg0GP7FLWV0YNtR1mpl55/bEl7yAxCDTkOgPUMXcaKlnQh
6zrlt6H53mF6Bvs3inOHQvOsGtU0dqvb1vkTF0juLiJgPlM7pWv+pNQ6IA39vKoQ
sTMBv4v5vYNXP9GgKbg8inUNT17BxzZYHfw5+q63ectgDm2on1e8CIRCZ76oBVwz
dkVxoy3gjh1eENlk2D4P0uJNZzF1Q8GV67yLANGMCDICE/OkWn6daipYDzW4iJQt
YPUWP4hWhjdm+CK+hg6IQUEn2Vtvi16D2blRP8BpUNNa4fNuylWVuJV76rIHvsLZ
1pbM3LHpRgE8s6jivS3Rz3WRs0TmWCNnvHPqWizQ3VTy+r3UQVJ5AmhJDrZdZq9i
aUIuZ01PoE1+CHiJwuxPtWvVAxf2POcm1M/F1fK1J0e+lKlQuyonTXqXR22Y41wr
fP2aPk3nPSTW2DUAf3vRMZg57ZpRxLEhEMxcM4/LMR+PABEBAAHCwrIEGAEKAAkF
gl8sAVYCmwIB3QkQ+/zIKgFeczDA+qAEGQEKAAwFgl47Z5UFgwB4TOAAIQkQfC+q
Tfk8N7IWIQQd3OFfCSF87i87N2B8L6pN+Tw3st58C/0exp0X2U4LqicSHEOSqHZj
jiysdqIELHGyo5DSPv92UFPp36aqjF9OFgtNNwSa56fmAVCD4+hor/fKARRIeIjF
qdIC5Y/9a4B10NQFJa5lsvB38x/d39LI2kEoglZnqWgdJskROo3vNQF4KlIcm6FH
dn4WI8UkC5oUUcrpZVMSKoacIaxLwqnXT42nIVgYYuqrd/ZagZZjG5WlrTOd5+NI
zi/l0fWProcPHGLjmAh4Thu8i7omtVw1nQaMnq9I77ffg3cPDgXknYrLL+q8xXh/
0mEJyIhnmPwllWCSZuLv9DrD5pOexFfdlwXhf6cLzNpW6QhXD/Tf5KrqIPr9aOv8
9xaEEXWh0vEby2kIsI2++ft+vfdIyxYw/wKqx0awTSnuBV1rG3z1dswX4BfoY66x
Bz3KOVqlz9+mG/FTRQwrgPvR+qgLCHbuotxoGN7fzW+PI75hQG5JQAqhsC9sHjQH
UrI21/VUNwzfw3v5pYsWuFb5bdQ3ASJetICQiMy7IW8WIQTRpm4aI7GCyZgPeIz7
/MgqAV5zMG6/C/wLpPl/9e6Hf5wmXIUwpZNQbNZvpiCcyx9sXsHXaycOQVxn3McZ
nYOUP9/mobl1tIeDQyTNbkxWjU0zzJl8XQsDZerb5098pg+x7oGIL7M1vn5s5JMl
owROourqF88JEtOBxLMxlAM7X4hB48xKQ3Hu9hS1GdnqLKki4MqRGl4l5FUwyGOM
GjyS3TzkfiDJNwQxybQiC9n57ij20ieNyLfuWCMLcNNnZUgZtnF6wCctoq/0ZIWu
a7nvuA/XC2WW9YjEJJiWdy5109pqac+qWiY11HWy/nms4gpMdxVpT0RhrKGWq4o0
M5q3ZElOoeN70UO3OSbU5EVrG7gB1GuwF9mTHUVlV0veSTw0axkta3FGT//XfSpD
lRrCkyLzwq0M+UUHQAuYpAfobDlDdnxxOD2jm5GyTzak3GSVFfjW09QFVO6HlGp5
01/jtzkUiS6nwoHHkfnyn0beZuR8X6KlcrzLB0VFgQFLmkSM9cSOgYhD0PTu9aHb
hW1Hj9AO8lzggBQ=
=Nt+N
-----END PGP PUBLIC KEY BLOCK-----
`
const sigFromKeyWithExpiredCrossSig = `-----BEGIN PGP SIGNATURE-----
wsDzBAABCgAGBYJfLAFsACEJEHwvqk35PDeyFiEEHdzhXwkhfO4vOzdgfC+qTfk8
N7KiqwwAts4QGB7v9bABCC2qkTxJhmStC0wQMcHRcjL/qAiVnmasQWmvE9KVsdm3
AaXd8mIx4a37/RRvr9dYrY2eE4uw72cMqPxNja2tvVXkHQvk1oEUqfkvbXs4ypKI
NyeTWjXNOTZEbg0hbm3nMy+Wv7zgB1CEvAsEboLDJlhGqPcD+X8a6CJGrBGUBUrv
KVmZr3U6vEzClz3DBLpoddCQseJRhT4YM1nKmBlZ5quh2LFgTSpajv5OsZheqt9y
EZAPbqmLhDmWRQwGzkWHKceKS7nZ/ox2WK6OS7Ob8ZGZkM64iPo6/EGj5Yc19vQN
AGiIaPEGszBBWlOpHTPhNm0LB0nMWqqaT87oNYwP8CQuuxDb6rKJ2lffCmZH27Lb
UbQZcH8J+0UhpeaiadPZxH5ATJAcenmVtVVMLVOFnm+eIlxzov9ntpgGYt8hLdXB
ITEG9mMgp3TGS9ZzSifMZ8UGtHdp9QdBg8NEVPFzDOMGxpc/Bftav7RRRuPiAER+
7A5CBid5
=aQkm
-----END PGP SIGNATURE-----
`
const signedMessageWithCriticalNotation = `-----BEGIN PGP MESSAGE-----
owGbwMvMwMH4oOW7S46CznTG09xJDDE3Wl1KUotLuDousDAwcjBYiSmyXL+48d6x
U1PSGUxcj8IUszKBVMpMaWAAAgEGZpAeh9SKxNyCnFS95PzcytRiBi5OAZjyXXzM
f8WYLqv7TXP61Sa4rqT12CI3xaN73YS2pt089f96odCKaEPnWJ3iSGmzJaW/ug10
2Zo8Wj2k4s7t8wt4H3HtTu+y5UZfV3VOO+l//sdE/o+Lsub8FZH7/eOq7OnbNp4n
vwjE8mqJXetNMfj8r2SCyvkEnlVRYR+/mnge+ib56FdJ8uKtqSxyvgA=
=fRXs
-----END PGP MESSAGE-----`
const criticalNotationSigner = `-----BEGIN PGP PUBLIC KEY BLOCK-----
mI0EUmEvTgEEANyWtQQMOybQ9JltDqmaX0WnNPJeLILIM36sw6zL0nfTQ5zXSS3+
fIF6P29lJFxpblWk02PSID5zX/DYU9/zjM2xPO8Oa4xo0cVTOTLj++Ri5mtr//f5
GLsIXxFrBJhD/ghFsL3Op0GXOeLJ9A5bsOn8th7x6JucNKuaRB6bQbSPABEBAAG0
JFRlc3QgTWNUZXN0aW5ndG9uIDx0ZXN0QGV4YW1wbGUuY29tPoi5BBMBAgAjBQJS
YS9OAhsvBwsJCAcDAgEGFQgCCQoLBBYCAwECHgECF4AACgkQSmNhOk1uQJQwDAP6
AgrTyqkRlJVqz2pb46TfbDM2TDF7o9CBnBzIGoxBhlRwpqALz7z2kxBDmwpQa+ki
Bq3jZN/UosY9y8bhwMAlnrDY9jP1gdCo+H0sD48CdXybblNwaYpwqC8VSpDdTndf
9j2wE/weihGp/DAdy/2kyBCaiOY1sjhUfJ1GogF49rC4jQRSYS9OAQQA6R/PtBFa
JaT4jq10yqASk4sqwVMsc6HcifM5lSdxzExFP74naUMMyEsKHP53QxTF0Grqusag
Qg/ZtgT0CN1HUM152y7ACOdp1giKjpMzOTQClqCoclyvWOFB+L/SwGEIJf7LSCEr
woBuJifJc8xAVr0XX0JthoW+uP91eTQ3XpsAEQEAAYkBPQQYAQIACQUCUmEvTgIb
LgCoCRBKY2E6TW5AlJ0gBBkBAgAGBQJSYS9OAAoJEOCE90RsICyXuqIEANmmiRCA
SF7YK7PvFkieJNwzeK0V3F2lGX+uu6Y3Q/Zxdtwc4xR+me/CSBmsURyXTO29OWhP
GLszPH9zSJU9BdDi6v0yNprmFPX/1Ng0Abn/sCkwetvjxC1YIvTLFwtUL/7v6NS2
bZpsUxRTg9+cSrMWWSNjiY9qUKajm1tuzPDZXAUEAMNmAN3xXN/Kjyvj2OK2ck0X
W748sl/tc3qiKPMJ+0AkMF7Pjhmh9nxqE9+QCEl7qinFqqBLjuzgUhBU4QlwX1GD
AtNTq6ihLMD5v1d82ZC7tNatdlDMGWnIdvEMCv2GZcuIqDQ9rXWs49e7tq1NncLY
hz3tYjKhoFTKEIq3y3Pp
=h/aX
-----END PGP PUBLIC KEY BLOCK-----`

View File

@ -3,7 +3,8 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package s2k implements the various OpenPGP string-to-key transforms as // Package s2k implements the various OpenPGP string-to-key transforms as
// specified in RFC 4800 section 3.7.1. // specified in RFC 4800 section 3.7.1, and Argon2 specified in
// draft-ietf-openpgp-crypto-refresh-08 section 3.7.1.4.
package s2k // import "github.com/ProtonMail/go-crypto/openpgp/s2k" package s2k // import "github.com/ProtonMail/go-crypto/openpgp/s2k"
import ( import (
@ -14,70 +15,47 @@ import (
"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"
"golang.org/x/crypto/argon2"
) )
// Config collects configuration parameters for s2k key-stretching type Mode uint8
// transformations. A nil *Config is valid and results in all default
// values. Currently, Config is used only by the Serialize function in // Defines the default S2KMode constants
// this package. //
type Config struct { // 0 (simple), 1(salted), 3(iterated), 4(argon2)
// S2KMode is the mode of s2k function. const (
// It can be 0 (simple), 1(salted), 3(iterated) SimpleS2K Mode = 0
// 2(reserved) 100-110(private/experimental). SaltedS2K Mode = 1
S2KMode uint8 IteratedSaltedS2K Mode = 3
// Hash is the default hash function to be used. If Argon2S2K Mode = 4
// nil, SHA256 is used. GnuS2K Mode = 101
Hash crypto.Hash )
// S2KCount is only used for symmetric encryption. It
// determines the strength of the passphrase stretching when const Argon2SaltSize int = 16
// the said passphrase is hashed to produce a key. S2KCount
// should be between 65536 and 65011712, inclusive. If Config
// is nil or S2KCount is 0, the value 16777216 used. Not all
// values in the above range can be represented. S2KCount will
// be rounded up to the next representable value if it cannot
// be encoded exactly. See RFC 4880 Section 3.7.1.3.
S2KCount int
}
// Params contains all the parameters of the s2k packet // Params contains all the parameters of the s2k packet
type Params struct { type Params struct {
// mode is the mode of s2k function. // mode is the mode of s2k function.
// It can be 0 (simple), 1(salted), 3(iterated) // It can be 0 (simple), 1(salted), 3(iterated)
// 2(reserved) 100-110(private/experimental). // 2(reserved) 100-110(private/experimental).
mode uint8 mode Mode
// hashId is the ID of the hash function used in any of the modes // hashId is the ID of the hash function used in any of the modes
hashId byte hashId byte
// salt is a byte array to use as a salt in hashing process // salt is a byte array to use as a salt in hashing process or argon2
salt []byte saltBytes [Argon2SaltSize]byte
// countByte is used to determine how many rounds of hashing are to // countByte is used to determine how many rounds of hashing are to
// be performed in s2k mode 3. See RFC 4880 Section 3.7.1.3. // be performed in s2k mode 3. See RFC 4880 Section 3.7.1.3.
countByte byte countByte byte
} // passes is a parameter in Argon2 to determine the number of iterations
// See RFC the crypto refresh Section 3.7.1.4.
func (c *Config) hash() crypto.Hash { passes byte
if c == nil || uint(c.Hash) == 0 { // parallelism is a parameter in Argon2 to determine the degree of paralellism
return crypto.SHA256 // See RFC the crypto refresh Section 3.7.1.4.
} parallelism byte
// memoryExp is a parameter in Argon2 to determine the memory usage
return c.Hash // i.e., 2 ** memoryExp kibibytes
} // See RFC the crypto refresh Section 3.7.1.4.
memoryExp byte
// EncodedCount get encoded count
func (c *Config) EncodedCount() uint8 {
if c == nil || c.S2KCount == 0 {
return 224 // The common case. Corresponding to 16777216
}
i := c.S2KCount
switch {
case i < 65536:
i = 65536
case i > 65011712:
i = 65011712
}
return encodeCount(i)
} }
// encodeCount converts an iterative "count" in the range 1024 to // encodeCount converts an iterative "count" in the range 1024 to
@ -106,6 +84,31 @@ func decodeCount(c uint8) int {
return (16 + int(c&15)) << (uint32(c>>4) + 6) return (16 + int(c&15)) << (uint32(c>>4) + 6)
} }
// encodeMemory converts the Argon2 "memory" in the range parallelism*8 to
// 2**31, inclusive, to an encoded memory. The return value is the
// octet that is actually stored in the GPG file. encodeMemory panics
// if is not in the above range
// See OpenPGP crypto refresh Section 3.7.1.4.
func encodeMemory(memory uint32, parallelism uint8) uint8 {
if memory < (8 * uint32(parallelism)) || memory > uint32(2147483648) {
panic("Memory argument memory is outside the required range")
}
for exp := 3; exp < 31; exp++ {
compare := decodeMemory(uint8(exp))
if compare >= memory {
return uint8(exp)
}
}
return 31
}
// decodeMemory computes the decoded memory in kibibytes as 2**memoryExponent
func decodeMemory(memoryExponent uint8) uint32 {
return uint32(1) << memoryExponent
}
// Simple writes to out the result of computing the Simple S2K function (RFC // Simple writes to out the result of computing the Simple S2K function (RFC
// 4880, section 3.7.1.1) using the given hash and input passphrase. // 4880, section 3.7.1.1) using the given hash and input passphrase.
func Simple(out []byte, h hash.Hash, in []byte) { func Simple(out []byte, h hash.Hash, in []byte) {
@ -169,25 +172,53 @@ func Iterated(out []byte, h hash.Hash, in []byte, salt []byte, count int) {
} }
} }
// Argon2 writes to out the key derived from the password (in) with the Argon2
// function (the crypto refresh, section 3.7.1.4)
func Argon2(out []byte, in []byte, salt []byte, passes uint8, paralellism uint8, memoryExp uint8) {
key := argon2.IDKey(in, salt, uint32(passes), decodeMemory(memoryExp), paralellism, uint32(len(out)))
copy(out[:], key)
}
// Generate generates valid parameters from given configuration. // Generate generates valid parameters from given configuration.
// It will enforce salted + hashed s2k method // It will enforce the Iterated and Salted or Argon2 S2K method.
func Generate(rand io.Reader, c *Config) (*Params, error) { func Generate(rand io.Reader, c *Config) (*Params, error) {
hashId, ok := HashToHashId(c.Hash) var params *Params
if c != nil && c.Mode() == Argon2S2K {
// handle Argon2 case
argonConfig := c.Argon2()
params = &Params{
mode: Argon2S2K,
passes: argonConfig.Passes(),
parallelism: argonConfig.Parallelism(),
memoryExp: argonConfig.EncodedMemory(),
}
} else if c != nil && c.PassphraseIsHighEntropy && c.Mode() == SaltedS2K { // Allow SaltedS2K if PassphraseIsHighEntropy
hashId, ok := algorithm.HashToHashId(c.hash())
if !ok { if !ok {
return nil, errors.UnsupportedError("no such hash") return nil, errors.UnsupportedError("no such hash")
} }
params := &Params{ params = &Params{
mode: 3, // Enforce iterared + salted method mode: SaltedS2K,
hashId: hashId,
}
} else { // Enforce IteratedSaltedS2K method otherwise
hashId, ok := algorithm.HashToHashId(c.hash())
if !ok {
return nil, errors.UnsupportedError("no such hash")
}
if c != nil {
c.S2KMode = IteratedSaltedS2K
}
params = &Params{
mode: IteratedSaltedS2K,
hashId: hashId, hashId: hashId,
salt: make([]byte, 8),
countByte: c.EncodedCount(), countByte: c.EncodedCount(),
} }
}
if _, err := io.ReadFull(rand, params.salt); err != nil { if _, err := io.ReadFull(rand, params.salt()); err != nil {
return nil, err return nil, err
} }
return params, nil return params, nil
} }
@ -207,45 +238,60 @@ func Parse(r io.Reader) (f func(out, in []byte), err error) {
// ParseIntoParams reads a binary specification for a string-to-key // ParseIntoParams reads a binary specification for a string-to-key
// transformation from r and returns a struct describing the s2k parameters. // transformation from r and returns a struct describing the s2k parameters.
func ParseIntoParams(r io.Reader) (params *Params, err error) { func ParseIntoParams(r io.Reader) (params *Params, err error) {
var buf [9]byte var buf [Argon2SaltSize + 3]byte
_, err = io.ReadFull(r, buf[:2]) _, err = io.ReadFull(r, buf[:1])
if err != nil { if err != nil {
return return
} }
params = &Params{ params = &Params{
mode: buf[0], mode: Mode(buf[0]),
hashId: buf[1],
} }
switch params.mode { switch params.mode {
case 0: case SimpleS2K:
return params, nil _, err = io.ReadFull(r, buf[:1])
case 1:
_, err = io.ReadFull(r, buf[:8])
if err != nil { if err != nil {
return nil, err return nil, err
} }
params.hashId = buf[0]
params.salt = buf[:8]
return params, nil return params, nil
case 3: case SaltedS2K:
_, err = io.ReadFull(r, buf[:9]) _, err = io.ReadFull(r, buf[:9])
if err != nil { if err != nil {
return nil, err return nil, err
} }
params.hashId = buf[0]
params.salt = buf[:8] copy(params.salt(), buf[1:9])
params.countByte = buf[8]
return params, nil return params, nil
case 101: case IteratedSaltedS2K:
// This is a GNU extension. See _, err = io.ReadFull(r, buf[:10])
// https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/DETAILS;h=fe55ae16ab4e26d8356dc574c9e8bc935e71aef1;hb=23191d7851eae2217ecdac6484349849a24fd94a#l1109 if err != nil {
if _, err = io.ReadFull(r, buf[:4]); err != nil {
return nil, err return nil, err
} }
if buf[0] == 'G' && buf[1] == 'N' && buf[2] == 'U' && buf[3] == 1 { params.hashId = buf[0]
copy(params.salt(), buf[1:9])
params.countByte = buf[9]
return params, nil
case Argon2S2K:
_, err = io.ReadFull(r, buf[:Argon2SaltSize+3])
if err != nil {
return nil, err
}
copy(params.salt(), buf[:Argon2SaltSize])
params.passes = buf[Argon2SaltSize]
params.parallelism = buf[Argon2SaltSize+1]
params.memoryExp = buf[Argon2SaltSize+2]
return params, nil
case GnuS2K:
// This is a GNU extension. See
// https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/DETAILS;h=fe55ae16ab4e26d8356dc574c9e8bc935e71aef1;hb=23191d7851eae2217ecdac6484349849a24fd94a#l1109
if _, err = io.ReadFull(r, buf[:5]); err != nil {
return nil, err
}
params.hashId = buf[0]
if buf[1] == 'G' && buf[2] == 'N' && buf[3] == 'U' && buf[4] == 1 {
return params, nil return params, nil
} }
return nil, errors.UnsupportedError("GNU S2K extension") return nil, errors.UnsupportedError("GNU S2K extension")
@ -255,39 +301,56 @@ func ParseIntoParams(r io.Reader) (params *Params, err error) {
} }
func (params *Params) Dummy() bool { func (params *Params) Dummy() bool {
return params != nil && params.mode == 101 return params != nil && params.mode == GnuS2K
}
func (params *Params) salt() []byte {
switch params.mode {
case SaltedS2K, IteratedSaltedS2K: return params.saltBytes[:8]
case Argon2S2K: return params.saltBytes[:Argon2SaltSize]
default: return nil
}
} }
func (params *Params) Function() (f func(out, in []byte), err error) { func (params *Params) Function() (f func(out, in []byte), err error) {
if params.Dummy() { if params.Dummy() {
return nil, errors.ErrDummyPrivateKey("dummy key found") return nil, errors.ErrDummyPrivateKey("dummy key found")
} }
hashObj, ok := HashIdToHash(params.hashId) var hashObj crypto.Hash
if params.mode != Argon2S2K {
var ok bool
hashObj, ok = algorithm.HashIdToHashWithSha1(params.hashId)
if !ok { if !ok {
return nil, errors.UnsupportedError("hash for S2K function: " + strconv.Itoa(int(params.hashId))) return nil, errors.UnsupportedError("hash for S2K function: " + strconv.Itoa(int(params.hashId)))
} }
if !hashObj.Available() { if !hashObj.Available() {
return nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashObj))) return nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashObj)))
} }
}
switch params.mode { switch params.mode {
case 0: case SimpleS2K:
f := func(out, in []byte) { f := func(out, in []byte) {
Simple(out, hashObj.New(), in) Simple(out, hashObj.New(), in)
} }
return f, nil return f, nil
case 1: case SaltedS2K:
f := func(out, in []byte) { f := func(out, in []byte) {
Salted(out, hashObj.New(), in, params.salt) Salted(out, hashObj.New(), in, params.salt())
} }
return f, nil return f, nil
case 3: case IteratedSaltedS2K:
f := func(out, in []byte) { f := func(out, in []byte) {
Iterated(out, hashObj.New(), in, params.salt, decodeCount(params.countByte)) Iterated(out, hashObj.New(), in, params.salt(), decodeCount(params.countByte))
} }
return f, nil
case Argon2S2K:
f := func(out, in []byte) {
Argon2(out, in, params.salt(), params.passes, params.parallelism, params.memoryExp)
}
return f, nil return f, nil
} }
@ -295,23 +358,28 @@ func (params *Params) Function() (f func(out, in []byte), err error) {
} }
func (params *Params) Serialize(w io.Writer) (err error) { func (params *Params) Serialize(w io.Writer) (err error) {
if _, err = w.Write([]byte{params.mode}); err != nil { if _, err = w.Write([]byte{uint8(params.mode)}); err != nil {
return return
} }
if params.mode != Argon2S2K {
if _, err = w.Write([]byte{params.hashId}); err != nil { if _, err = w.Write([]byte{params.hashId}); err != nil {
return return
} }
}
if params.Dummy() { if params.Dummy() {
_, err = w.Write(append([]byte("GNU"), 1)) _, err = w.Write(append([]byte("GNU"), 1))
return return
} }
if params.mode > 0 { if params.mode > 0 {
if _, err = w.Write(params.salt); err != nil { if _, err = w.Write(params.salt()); err != nil {
return return
} }
if params.mode == 3 { if params.mode == IteratedSaltedS2K {
_, err = w.Write([]byte{params.countByte}) _, err = w.Write([]byte{params.countByte})
} }
if params.mode == Argon2S2K {
_, err = w.Write([]byte{params.passes, params.parallelism, params.memoryExp})
}
} }
return return
} }
@ -337,31 +405,3 @@ func Serialize(w io.Writer, key []byte, rand io.Reader, passphrase []byte, c *Co
f(key, passphrase) f(key, passphrase)
return nil return nil
} }
// 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 := algorithm.HashById[id]; ok {
return hash.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 := algorithm.HashById[id]; ok {
return hash.String(), true
}
return "", false
}
// HashIdToHash returns an OpenPGP hash id which corresponds the given Hash.
func HashToHashId(h crypto.Hash) (id byte, ok bool) {
for id, hash := range algorithm.HashById {
if hash.HashFunc() == h {
return id, true
}
}
return 0, false
}

View File

@ -0,0 +1,26 @@
package s2k
// Cache stores keys derived with s2k functions from one passphrase
// to avoid recomputation if multiple items are encrypted with
// the same parameters.
type Cache map[Params][]byte
// GetOrComputeDerivedKey tries to retrieve the key
// for the given s2k parameters from the cache.
// If there is no hit, it derives the key with the s2k function from the passphrase,
// updates the cache, and returns the key.
func (c *Cache) GetOrComputeDerivedKey(passphrase []byte, params *Params, expectedKeySize int) ([]byte, error) {
key, found := (*c)[*params]
if !found || len(key) != expectedKeySize {
var err error
derivedKey := make([]byte, expectedKeySize)
s2k, err := params.Function()
if err != nil {
return nil, err
}
s2k(derivedKey, passphrase)
(*c)[*params] = key
return derivedKey, nil
}
return key, nil
}

View File

@ -0,0 +1,129 @@
package s2k
import "crypto"
// Config collects configuration parameters for s2k key-stretching
// transformations. A nil *Config is valid and results in all default
// values.
type Config struct {
// S2K (String to Key) mode, used for key derivation in the context of secret key encryption
// and passphrase-encrypted data. Either s2k.Argon2S2K or s2k.IteratedSaltedS2K may be used.
// If the passphrase is a high-entropy key, indicated by setting PassphraseIsHighEntropy to true,
// s2k.SaltedS2K can also be used.
// Note: Argon2 is the strongest option but not all OpenPGP implementations are compatible with it
//(pending standardisation).
// 0 (simple), 1(salted), 3(iterated), 4(argon2)
// 2(reserved) 100-110(private/experimental).
S2KMode Mode
// Only relevant if S2KMode is not set to s2k.Argon2S2K.
// Hash is the default hash function to be used. If
// nil, SHA256 is used.
Hash crypto.Hash
// Argon2 parameters for S2K (String to Key).
// Only relevant if S2KMode is set to s2k.Argon2S2K.
// If nil, default parameters are used.
// For more details on the choice of parameters, see https://tools.ietf.org/html/rfc9106#section-4.
Argon2Config *Argon2Config
// Only relevant if S2KMode is set to s2k.IteratedSaltedS2K.
// Iteration count for Iterated S2K (String to Key). It
// determines the strength of the passphrase stretching when
// the said passphrase is hashed to produce a key. S2KCount
// should be between 65536 and 65011712, inclusive. If Config
// is nil or S2KCount is 0, the value 16777216 used. Not all
// values in the above range can be represented. S2KCount will
// be rounded up to the next representable value if it cannot
// be encoded exactly. When set, it is strongly encrouraged to
// use a value that is at least 65536. See RFC 4880 Section
// 3.7.1.3.
S2KCount int
// Indicates whether the passphrase passed by the application is a
// high-entropy key (e.g. it's randomly generated or derived from
// another passphrase using a strong key derivation function).
// When true, allows the S2KMode to be s2k.SaltedS2K.
// When the passphrase is not a high-entropy key, using SaltedS2K is
// insecure, and not allowed by draft-ietf-openpgp-crypto-refresh-08.
PassphraseIsHighEntropy bool
}
// Argon2Config stores the Argon2 parameters
// A nil *Argon2Config is valid and results in all default
type Argon2Config struct {
NumberOfPasses uint8
DegreeOfParallelism uint8
// The memory parameter for Argon2 specifies desired memory usage in kibibytes.
// For example memory=64*1024 sets the memory cost to ~64 MB.
Memory uint32
}
func (c *Config) Mode() Mode {
if c == nil {
return IteratedSaltedS2K
}
return c.S2KMode
}
func (c *Config) hash() crypto.Hash {
if c == nil || uint(c.Hash) == 0 {
return crypto.SHA256
}
return c.Hash
}
func (c *Config) Argon2() *Argon2Config {
if c == nil || c.Argon2Config == nil {
return nil
}
return c.Argon2Config
}
// EncodedCount get encoded count
func (c *Config) EncodedCount() uint8 {
if c == nil || c.S2KCount == 0 {
return 224 // The common case. Corresponding to 16777216
}
i := c.S2KCount
switch {
case i < 65536:
i = 65536
case i > 65011712:
i = 65011712
}
return encodeCount(i)
}
func (c *Argon2Config) Passes() uint8 {
if c == nil || c.NumberOfPasses == 0 {
return 3
}
return c.NumberOfPasses
}
func (c *Argon2Config) Parallelism() uint8 {
if c == nil || c.DegreeOfParallelism == 0 {
return 4
}
return c.DegreeOfParallelism
}
func (c *Argon2Config) EncodedMemory() uint8 {
if c == nil || c.Memory == 0 {
return 16 // 64 MiB of RAM
}
memory := c.Memory
lowerBound := uint32(c.Parallelism())*8
upperBound := uint32(2147483648)
switch {
case memory < lowerBound:
memory = lowerBound
case memory > upperBound:
memory = upperBound
}
return encodeMemory(memory, c.Parallelism())
}

View File

@ -13,8 +13,8 @@ import (
"github.com/ProtonMail/go-crypto/openpgp/armor" "github.com/ProtonMail/go-crypto/openpgp/armor"
"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/packet" "github.com/ProtonMail/go-crypto/openpgp/packet"
"github.com/ProtonMail/go-crypto/openpgp/s2k"
) )
// DetachSign signs message with the private key from signer (which must // DetachSign signs message with the private key from signer (which must
@ -70,15 +70,11 @@ func detachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.S
if signingKey.PrivateKey.Encrypted { if signingKey.PrivateKey.Encrypted {
return errors.InvalidArgumentError("signing key is encrypted") return errors.InvalidArgumentError("signing key is encrypted")
} }
if _, ok := algorithm.HashToHashId(config.Hash()); !ok {
return errors.InvalidArgumentError("invalid hash function")
}
sig := new(packet.Signature) sig := createSignaturePacket(signingKey.PublicKey, sigType, config)
sig.SigType = sigType
sig.PubKeyAlgo = signingKey.PrivateKey.PubKeyAlgo
sig.Hash = config.Hash()
sig.CreationTime = config.Now()
sigLifetimeSecs := config.SigLifetime()
sig.SigLifetimeSecs = &sigLifetimeSecs
sig.IssuerKeyId = &signingKey.PrivateKey.KeyId
h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType) h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType)
if err != nil { if err != nil {
@ -125,17 +121,14 @@ func SymmetricallyEncrypt(ciphertext io.Writer, passphrase []byte, hints *FileHi
} }
var w io.WriteCloser var w io.WriteCloser
if config.AEAD() != nil { cipherSuite := packet.CipherSuite{
w, err = packet.SerializeAEADEncrypted(ciphertext, key, config.Cipher(), config.AEAD().Mode(), config) Cipher: config.Cipher(),
Mode: config.AEAD().Mode(),
}
w, err = packet.SerializeSymmetricallyEncrypted(ciphertext, config.Cipher(), config.AEAD() != nil, cipherSuite, key, config)
if err != nil { if err != nil {
return return
} }
} else {
w, err = packet.SerializeSymmetricallyEncrypted(ciphertext, config.Cipher(), key, config)
if err != nil {
return
}
}
literalData := w literalData := w
if algo := config.Compression(); algo != packet.CompressionNone { if algo := config.Compression(); algo != packet.CompressionNone {
@ -173,8 +166,25 @@ func intersectPreferences(a []uint8, b []uint8) (intersection []uint8) {
return a[:j] return a[:j]
} }
// intersectPreferences mutates and returns a prefix of a that contains only
// the values in the intersection of a and b. The order of a is preserved.
func intersectCipherSuites(a [][2]uint8, b [][2]uint8) (intersection [][2]uint8) {
var j int
for _, v := range a {
for _, v2 := range b {
if v[0] == v2[0] && v[1] == v2[1] {
a[j] = v
j++
break
}
}
}
return a[:j]
}
func hashToHashId(h crypto.Hash) uint8 { func hashToHashId(h crypto.Hash) uint8 {
v, ok := s2k.HashToHashId(h) v, ok := algorithm.HashToHashId(h)
if !ok { if !ok {
panic("tried to convert unknown hash") panic("tried to convert unknown hash")
} }
@ -240,7 +250,7 @@ func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entit
var hash crypto.Hash var hash crypto.Hash
for _, hashId := range candidateHashes { for _, hashId := range candidateHashes {
if h, ok := s2k.HashIdToHash(hashId); ok && h.Available() { if h, ok := algorithm.HashIdToHash(hashId); ok && h.Available() {
hash = h hash = h
break break
} }
@ -249,7 +259,7 @@ func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entit
// If the hash specified by config is a candidate, we'll use that. // If the hash specified by config is a candidate, we'll use that.
if configuredHash := config.Hash(); configuredHash.Available() { if configuredHash := config.Hash(); configuredHash.Available() {
for _, hashId := range candidateHashes { for _, hashId := range candidateHashes {
if h, ok := s2k.HashIdToHash(hashId); ok && h == configuredHash { if h, ok := algorithm.HashIdToHash(hashId); ok && h == configuredHash {
hash = h hash = h
break break
} }
@ -258,7 +268,7 @@ func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entit
if hash == 0 { if hash == 0 {
hashId := candidateHashes[0] hashId := candidateHashes[0]
name, ok := s2k.HashIdToString(hashId) name, ok := algorithm.HashIdToString(hashId)
if !ok { if !ok {
name = "#" + strconv.Itoa(int(hashId)) name = "#" + strconv.Itoa(int(hashId))
} }
@ -329,39 +339,39 @@ func encrypt(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *En
// These are the possible ciphers that we'll use for the message. // These are the possible ciphers that we'll use for the message.
candidateCiphers := []uint8{ candidateCiphers := []uint8{
uint8(packet.CipherAES128),
uint8(packet.CipherAES256), uint8(packet.CipherAES256),
uint8(packet.CipherCAST5), uint8(packet.CipherAES128),
} }
// These are the possible hash functions that we'll use for the signature. // These are the possible hash functions that we'll use for the signature.
candidateHashes := []uint8{ candidateHashes := []uint8{
hashToHashId(crypto.SHA256), hashToHashId(crypto.SHA256),
hashToHashId(crypto.SHA384), hashToHashId(crypto.SHA384),
hashToHashId(crypto.SHA512), hashToHashId(crypto.SHA512),
hashToHashId(crypto.SHA1), hashToHashId(crypto.SHA3_256),
hashToHashId(crypto.RIPEMD160), hashToHashId(crypto.SHA3_512),
} }
candidateAeadModes := []uint8{
uint8(packet.AEADModeEAX), // Prefer GCM if everyone supports it
uint8(packet.AEADModeOCB), candidateCipherSuites := [][2]uint8{
uint8(packet.AEADModeExperimentalGCM), {uint8(packet.CipherAES256), uint8(packet.AEADModeGCM)},
{uint8(packet.CipherAES256), uint8(packet.AEADModeEAX)},
{uint8(packet.CipherAES256), uint8(packet.AEADModeOCB)},
{uint8(packet.CipherAES128), uint8(packet.AEADModeGCM)},
{uint8(packet.CipherAES128), uint8(packet.AEADModeEAX)},
{uint8(packet.CipherAES128), uint8(packet.AEADModeOCB)},
} }
candidateCompression := []uint8{ candidateCompression := []uint8{
uint8(packet.CompressionNone), uint8(packet.CompressionNone),
uint8(packet.CompressionZIP), uint8(packet.CompressionZIP),
uint8(packet.CompressionZLIB), uint8(packet.CompressionZLIB),
} }
// In the event that a recipient doesn't specify any supported ciphers
// or hash functions, these are the ones that we assume that every
// implementation supports.
defaultCiphers := candidateCiphers[0:1]
defaultHashes := candidateHashes[0:1]
defaultAeadModes := candidateAeadModes[0:1]
defaultCompression := candidateCompression[0:1]
encryptKeys := make([]Key, len(to)) encryptKeys := make([]Key, len(to))
// AEAD is used only if every key supports it.
aeadSupported := true // AEAD is used only if config enables it and every key supports it
aeadSupported := config.AEAD() != nil
for i := range to { for i := range to {
var ok bool var ok bool
@ -371,38 +381,37 @@ func encrypt(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *En
} }
sig := to[i].PrimaryIdentity().SelfSignature sig := to[i].PrimaryIdentity().SelfSignature
if sig.AEAD == false { if !sig.SEIPDv2 {
aeadSupported = false aeadSupported = false
} }
preferredSymmetric := sig.PreferredSymmetric candidateCiphers = intersectPreferences(candidateCiphers, sig.PreferredSymmetric)
if len(preferredSymmetric) == 0 { candidateHashes = intersectPreferences(candidateHashes, sig.PreferredHash)
preferredSymmetric = defaultCiphers candidateCipherSuites = intersectCipherSuites(candidateCipherSuites, sig.PreferredCipherSuites)
} candidateCompression = intersectPreferences(candidateCompression, sig.PreferredCompression)
preferredHashes := sig.PreferredHash
if len(preferredHashes) == 0 {
preferredHashes = defaultHashes
}
preferredAeadModes := sig.PreferredAEAD
if len(preferredAeadModes) == 0 {
preferredAeadModes = defaultAeadModes
}
preferredCompression := sig.PreferredCompression
if len(preferredCompression) == 0 {
preferredCompression = defaultCompression
}
candidateCiphers = intersectPreferences(candidateCiphers, preferredSymmetric)
candidateHashes = intersectPreferences(candidateHashes, preferredHashes)
candidateAeadModes = intersectPreferences(candidateAeadModes, preferredAeadModes)
candidateCompression = intersectPreferences(candidateCompression, preferredCompression)
} }
if len(candidateCiphers) == 0 || len(candidateHashes) == 0 || len(candidateAeadModes) == 0 { // In the event that the intersection of supported algorithms is empty we use the ones
return nil, errors.InvalidArgumentError("cannot encrypt because recipient set shares no common algorithms") // labelled as MUST that every implementation supports.
if len(candidateCiphers) == 0 {
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.3
candidateCiphers = []uint8{uint8(packet.CipherAES128)}
}
if len(candidateHashes) == 0 {
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#hash-algos
candidateHashes = []uint8{hashToHashId(crypto.SHA256)}
}
if len(candidateCipherSuites) == 0 {
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.6
candidateCipherSuites = [][2]uint8{{uint8(packet.CipherAES128), uint8(packet.AEADModeOCB)}}
} }
cipher := packet.CipherFunction(candidateCiphers[0]) cipher := packet.CipherFunction(candidateCiphers[0])
mode := packet.AEADMode(candidateAeadModes[0]) aeadCipherSuite := packet.CipherSuite{
Cipher: packet.CipherFunction(candidateCipherSuites[0][0]),
Mode: packet.AEADMode(candidateCipherSuites[0][1]),
}
// If the cipher specified by config is a candidate, we'll use that. // If the cipher specified by config is a candidate, we'll use that.
configuredCipher := config.Cipher() configuredCipher := config.Cipher()
for _, c := range candidateCiphers { for _, c := range candidateCiphers {
@ -425,17 +434,11 @@ func encrypt(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *En
} }
var payload io.WriteCloser var payload io.WriteCloser
if config.AEAD() != nil && aeadSupported { payload, err = packet.SerializeSymmetricallyEncrypted(dataWriter, cipher, aeadSupported, aeadCipherSuite, symKey, config)
payload, err = packet.SerializeAEADEncrypted(dataWriter, symKey, cipher, mode, config)
if err != nil { if err != nil {
return return
} }
} else {
payload, err = packet.SerializeSymmetricallyEncrypted(dataWriter, cipher, symKey, config)
if err != nil {
return
}
}
payload, err = handleCompression(payload, candidateCompression, config) payload, err = handleCompression(payload, candidateCompression, config)
if err != nil { if err != nil {
return nil, err return nil, err
@ -458,8 +461,8 @@ func Sign(output io.Writer, signed *Entity, hints *FileHints, config *packet.Con
hashToHashId(crypto.SHA256), hashToHashId(crypto.SHA256),
hashToHashId(crypto.SHA384), hashToHashId(crypto.SHA384),
hashToHashId(crypto.SHA512), hashToHashId(crypto.SHA512),
hashToHashId(crypto.SHA1), hashToHashId(crypto.SHA3_256),
hashToHashId(crypto.RIPEMD160), hashToHashId(crypto.SHA3_512),
} }
defaultHashes := candidateHashes[0:1] defaultHashes := candidateHashes[0:1]
preferredHashes := signed.PrimaryIdentity().SelfSignature.PreferredHash preferredHashes := signed.PrimaryIdentity().SelfSignature.PreferredHash
@ -502,15 +505,9 @@ func (s signatureWriter) Write(data []byte) (int, error) {
} }
func (s signatureWriter) Close() error { func (s signatureWriter) Close() error {
sig := &packet.Signature{ sig := createSignaturePacket(&s.signer.PublicKey, s.sigType, s.config)
Version: s.signer.Version, sig.Hash = s.hashType
SigType: s.sigType, sig.Metadata = s.metadata
PubKeyAlgo: s.signer.PubKeyAlgo,
Hash: s.hashType,
CreationTime: s.config.Now(),
IssuerKeyId: &s.signer.KeyId,
Metadata: s.metadata,
}
if err := sig.Sign(s.h, s.signer, s.config); err != nil { if err := sig.Sign(s.h, s.signer, s.config); err != nil {
return err return err
@ -524,6 +521,21 @@ func (s signatureWriter) Close() error {
return s.encryptedData.Close() return s.encryptedData.Close()
} }
func createSignaturePacket(signer *packet.PublicKey, sigType packet.SignatureType, config *packet.Config) *packet.Signature {
sigLifetimeSecs := config.SigLifetime()
return &packet.Signature{
Version: signer.Version,
SigType: sigType,
PubKeyAlgo: signer.PubKeyAlgo,
Hash: config.Hash(),
CreationTime: config.Now(),
IssuerKeyId: &signer.KeyId,
IssuerFingerprint: signer.Fingerprint,
Notations: config.Notations(),
SigLifetimeSecs: &sigLifetimeSecs,
}
}
// noOpCloser is like an ioutil.NopCloser, but for an io.Writer. // noOpCloser is like an ioutil.NopCloser, but for an io.Writer.
// TODO: we have two of these in OpenPGP packages alone. This probably needs // TODO: we have two of these in OpenPGP packages alone. This probably needs
// to be promoted somewhere more common. // to be promoted somewhere more common.
@ -545,6 +557,9 @@ func handleCompression(compressed io.WriteCloser, candidateCompression []uint8,
if confAlgo == packet.CompressionNone { if confAlgo == packet.CompressionNone {
return return
} }
// Set algorithm labelled as MUST as fallback
// https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-9.4
finalAlgo := packet.CompressionNone finalAlgo := packet.CompressionNone
// if compression specified by config available we will use it // if compression specified by config available we will use it
for _, c := range candidateCompression { for _, c := range candidateCompression {

View File

@ -1,42 +0,0 @@
# bufpipe: Buffered Pipe
[![CircleCI](https://img.shields.io/circleci/build/github/acomagu/bufpipe.svg?style=flat-square)](https://circleci.com/gh/acomagu/bufpipe) [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/acomagu/bufpipe)
The buffered version of io.Pipe. It's safe for concurrent use.
## How does it differ from io.Pipe?
Writes never block because the pipe has variable-sized buffer.
```Go
r, w := bufpipe.New(nil)
io.WriteString(w, "abc") // No blocking.
io.WriteString(w, "def") // No blocking, too.
w.Close()
io.Copy(os.Stdout, r)
// Output: abcdef
```
[Playground](https://play.golang.org/p/PdyBAS3pVob)
## How does it differ from bytes.Buffer?
Reads block if the internal buffer is empty until the writer is closed.
```Go
r, w := bufpipe.New(nil)
done := make(chan struct{})
go func() {
io.Copy(os.Stdout, r) // The reads block until the writer is closed.
done <- struct{}{}
}()
io.WriteString(w, "abc")
io.WriteString(w, "def")
w.Close()
<-done
// Output: abcdef
```
[Playground](https://play.golang.org/p/UppmyLeRgX6)

View File

@ -1,128 +0,0 @@
package bufpipe
import (
"bytes"
"errors"
"io"
"sync"
)
// ErrClosedPipe is the error used for read or write operations on a closed pipe.
var ErrClosedPipe = errors.New("bufpipe: read/write on closed pipe")
type pipe struct {
cond *sync.Cond
buf *bytes.Buffer
rerr, werr error
}
// A PipeReader is the read half of a pipe.
type PipeReader struct {
*pipe
}
// A PipeWriter is the write half of a pipe.
type PipeWriter struct {
*pipe
}
// New creates a synchronous pipe using buf as its initial contents. It can be
// used to connect code expecting an io.Reader with code expecting an io.Writer.
//
// Unlike io.Pipe, writes never block because the internal buffer has variable
// size. Reads block only when the buffer is empty.
//
// It is safe to call Read and Write in parallel with each other or with Close.
// Parallel calls to Read and parallel calls to Write are also safe: the
// individual calls will be gated sequentially.
//
// The new pipe takes ownership of buf, and the caller should not use buf after
// this call. New is intended to prepare a PipeReader to read existing data. It
// can also be used to set the initial size of the internal buffer for writing.
// To do that, buf should have the desired capacity but a length of zero.
func New(buf []byte) (*PipeReader, *PipeWriter) {
p := &pipe{
buf: bytes.NewBuffer(buf),
cond: sync.NewCond(new(sync.Mutex)),
}
return &PipeReader{
pipe: p,
}, &PipeWriter{
pipe: p,
}
}
// Read implements the standard Read interface: it reads data from the pipe,
// reading from the internal buffer, otherwise blocking until a writer arrives
// or the write end is closed. If the write end is closed with an error, that
// error is returned as err; otherwise err is io.EOF.
func (r *PipeReader) Read(data []byte) (int, error) {
r.cond.L.Lock()
defer r.cond.L.Unlock()
RETRY:
n, err := r.buf.Read(data)
// If not closed and no read, wait for writing.
if err == io.EOF && r.rerr == nil && n == 0 {
r.cond.Wait()
goto RETRY
}
if err == io.EOF {
return n, r.rerr
}
return n, err
}
// Close closes the reader; subsequent writes from the write half of the pipe
// will return error ErrClosedPipe.
func (r *PipeReader) Close() error {
return r.CloseWithError(nil)
}
// CloseWithError closes the reader; subsequent writes to the write half of the
// pipe will return the error err.
func (r *PipeReader) CloseWithError(err error) error {
r.cond.L.Lock()
defer r.cond.L.Unlock()
if err == nil {
err = ErrClosedPipe
}
r.werr = err
return nil
}
// Write implements the standard Write interface: it writes data to the internal
// buffer. If the read end is closed with an error, that err is returned as err;
// otherwise err is ErrClosedPipe.
func (w *PipeWriter) Write(data []byte) (int, error) {
w.cond.L.Lock()
defer w.cond.L.Unlock()
if w.werr != nil {
return 0, w.werr
}
n, err := w.buf.Write(data)
w.cond.Signal()
return n, err
}
// Close closes the writer; subsequent reads from the read half of the pipe will
// return io.EOF once the internal buffer get empty.
func (w *PipeWriter) Close() error {
return w.CloseWithError(nil)
}
// Close closes the writer; subsequent reads from the read half of the pipe will
// return err once the internal buffer get empty.
func (w *PipeWriter) CloseWithError(err error) error {
w.cond.L.Lock()
defer w.cond.L.Unlock()
if err == nil {
err = io.EOF
}
w.rerr = err
return nil
}

View File

@ -1,2 +0,0 @@
// Package bufpipe provides a IO pipe, has variable-sized buffer.
package bufpipe

View File

@ -15,6 +15,5 @@ References:
- [2] Curve25519 by Bernstein (https://cr.yp.to/ecdh.html) - [2] Curve25519 by Bernstein (https://cr.yp.to/ecdh.html)
- [3] Bernstein (https://cr.yp.to/ecdh.html#validate) - [3] Bernstein (https://cr.yp.to/ecdh.html#validate)
- [4] Cremers&Jackson (https://eprint.iacr.org/2019/526) - [4] Cremers&Jackson (https://eprint.iacr.org/2019/526)
*/ */
package x25519 package x25519

View File

@ -22,11 +22,11 @@ func (k *Key) clamp(in *Key) *Key {
// isValidPubKey verifies if the public key is not a low-order point. // isValidPubKey verifies if the public key is not a low-order point.
func (k *Key) isValidPubKey() bool { func (k *Key) isValidPubKey() bool {
fp.Modp((*fp.Elt)(k)) fp.Modp((*fp.Elt)(k))
isLowOrder := false var isLowOrder int
for _, P := range lowOrderPoints { for _, P := range lowOrderPoints {
isLowOrder = isLowOrder || subtle.ConstantTimeCompare(P[:], k[:]) != 0 isLowOrder |= subtle.ConstantTimeCompare(P[:], k[:])
} }
return !isLowOrder return isLowOrder == 0
} }
// KeyGen obtains a public key given a secret key. // KeyGen obtains a public key given a secret key.

View File

@ -3,7 +3,9 @@ package x25519
import "github.com/cloudflare/circl/math/fp25519" import "github.com/cloudflare/circl/math/fp25519"
// tableGenerator contains the set of points: // tableGenerator contains the set of points:
//
// t[i] = (xi+1)/(xi-1), // t[i] = (xi+1)/(xi-1),
//
// where (xi,yi) = 2^iG and G is the generator point // where (xi,yi) = 2^iG and G is the generator point
// Size = (256)*(256/8) = 8192 bytes. // Size = (256)*(256/8) = 8192 bytes.
var tableGenerator = [256 * fp25519.Size]byte{ var tableGenerator = [256 * fp25519.Size]byte{

View File

@ -15,6 +15,5 @@ References:
- [2] Curve25519 by Bernstein (https://cr.yp.to/ecdh.html) - [2] Curve25519 by Bernstein (https://cr.yp.to/ecdh.html)
- [3] Bernstein (https://cr.yp.to/ecdh.html#validate) - [3] Bernstein (https://cr.yp.to/ecdh.html#validate)
- [4] Cremers&Jackson (https://eprint.iacr.org/2019/526) - [4] Cremers&Jackson (https://eprint.iacr.org/2019/526)
*/ */
package x448 package x448

View File

@ -22,11 +22,11 @@ func (k *Key) clamp(in *Key) *Key {
// isValidPubKey verifies if the public key is not a low-order point. // isValidPubKey verifies if the public key is not a low-order point.
func (k *Key) isValidPubKey() bool { func (k *Key) isValidPubKey() bool {
fp.Modp((*fp.Elt)(k)) fp.Modp((*fp.Elt)(k))
isLowOrder := false var isLowOrder int
for _, P := range lowOrderPoints { for _, P := range lowOrderPoints {
isLowOrder = isLowOrder || subtle.ConstantTimeCompare(P[:], k[:]) != 0 isLowOrder |= subtle.ConstantTimeCompare(P[:], k[:])
} }
return !isLowOrder return isLowOrder == 0
} }
// KeyGen obtains a public key given a secret key. // KeyGen obtains a public key given a secret key.

View File

@ -3,7 +3,9 @@ package x448
import fp "github.com/cloudflare/circl/math/fp448" import fp "github.com/cloudflare/circl/math/fp448"
// tableGenerator contains the set of points: // tableGenerator contains the set of points:
//
// t[i] = (xi+1)/(xi-1), // t[i] = (xi+1)/(xi-1),
//
// where (xi,yi) = 2^iG and G is the generator point // where (xi,yi) = 2^iG and G is the generator point
// Size = (448)*(448/8) = 25088 bytes. // Size = (448)*(448/8) = 25088 bytes.
var tableGenerator = [448 * fp.Size]byte{ var tableGenerator = [448 * fp.Size]byte{

View File

@ -8,7 +8,7 @@ import (
// ScalarSize is the size (in bytes) of scalars. // ScalarSize is the size (in bytes) of scalars.
const ScalarSize = 56 // 448 / 8 const ScalarSize = 56 // 448 / 8
//_N is the number of 64-bit words to store scalars. // _N is the number of 64-bit words to store scalars.
const _N = 7 // 448 / 64 const _N = 7 // 448 / 64
// Scalar represents a positive integer stored in little-endian order. // Scalar represents a positive integer stored in little-endian order.

View File

@ -9,7 +9,7 @@ import (
fp "github.com/cloudflare/circl/math/fp448" fp "github.com/cloudflare/circl/math/fp448"
) )
// twistCurve is -x^2+y^2=1-39082x^2y^2 and is 4-isogeneous to Goldilocks. // twistCurve is -x^2+y^2=1-39082x^2y^2 and is 4-isogenous to Goldilocks.
type twistCurve struct{} type twistCurve struct{}
// Identity returns the identity point. // Identity returns the identity point.

View File

@ -8,8 +8,7 @@
// Both types of hash function use the "sponge" construction and the Keccak // Both types of hash function use the "sponge" construction and the Keccak
// permutation. For a detailed specification see http://keccak.noekeon.org/ // permutation. For a detailed specification see http://keccak.noekeon.org/
// //
// // # Guidance
// Guidance
// //
// If you aren't sure what function you need, use SHAKE256 with at least 64 // If you aren't sure what function you need, use SHAKE256 with at least 64
// bytes of output. The SHAKE instances are faster than the SHA3 instances; // bytes of output. The SHAKE instances are faster than the SHA3 instances;
@ -19,8 +18,7 @@
// secret key to the input, hash with SHAKE256 and read at least 32 bytes of // secret key to the input, hash with SHAKE256 and read at least 32 bytes of
// output. // output.
// //
// // # Security strengths
// Security strengths
// //
// The SHA3-x (x equals 224, 256, 384, or 512) functions have a security // The SHA3-x (x equals 224, 256, 384, or 512) functions have a security
// strength against preimage attacks of x bits. Since they only produce "x" // strength against preimage attacks of x bits. Since they only produce "x"
@ -31,8 +29,7 @@
// is used. Requesting more than 64 or 32 bytes of output, respectively, does // is used. Requesting more than 64 or 32 bytes of output, respectively, does
// not increase the collision-resistance of the SHAKE functions. // not increase the collision-resistance of the SHAKE functions.
// //
// // # The sponge construction
// The sponge construction
// //
// A sponge builds a pseudo-random function from a public pseudo-random // A sponge builds a pseudo-random function from a public pseudo-random
// permutation, by applying the permutation to a state of "rate + capacity" // permutation, by applying the permutation to a state of "rate + capacity"
@ -50,8 +47,7 @@
// Since the KeccakF-1600 permutation is 1600 bits (200 bytes) wide, this means // Since the KeccakF-1600 permutation is 1600 bits (200 bytes) wide, this means
// that the security strength of a sponge instance is equal to (1600 - bitrate) / 2. // that the security strength of a sponge instance is equal to (1600 - bitrate) / 2.
// //
// // # Recommendations
// Recommendations
// //
// The SHAKE functions are recommended for most new uses. They can produce // The SHAKE functions are recommended for most new uses. They can produce
// output of arbitrary length. SHAKE256, with an output length of at least // output of arbitrary length. SHAKE256, with an output length of at least

View File

@ -2,19 +2,25 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build !amd64 || appengine || gccgo
// +build !amd64 appengine gccgo
package sha3 package sha3
// KeccakF1600 applies the Keccak permutation to a 1600b-wide // KeccakF1600 applies the Keccak permutation to a 1600b-wide
// state represented as a slice of 25 uint64s. // state represented as a slice of 25 uint64s.
func KeccakF1600(a *[25]uint64) { // If turbo is true, applies the 12-round variant instead of the
// regular 24-round variant.
// nolint:funlen
func KeccakF1600(a *[25]uint64, turbo bool) {
// Implementation translated from Keccak-inplace.c // Implementation translated from Keccak-inplace.c
// in the keccak reference code. // in the keccak reference code.
var t, bc0, bc1, bc2, bc3, bc4, d0, d1, d2, d3, d4 uint64 var t, bc0, bc1, bc2, bc3, bc4, d0, d1, d2, d3, d4 uint64
for i := 0; i < 24; i += 4 { i := 0
if turbo {
i = 12
}
for ; i < 24; i += 4 {
// Combines the 5 steps in each round into 2 steps. // Combines the 5 steps in each round into 2 steps.
// Unrolls 4 rounds per loop and spreads some steps across rounds. // Unrolls 4 rounds per loop and spreads some steps across rounds.

View File

@ -51,6 +51,7 @@ type State struct {
// Specific to SHA-3 and SHAKE. // Specific to SHA-3 and SHAKE.
outputLen int // the default output size in bytes outputLen int // the default output size in bytes
state spongeDirection // whether the sponge is absorbing or squeezing state spongeDirection // whether the sponge is absorbing or squeezing
turbo bool // Whether we're using 12 rounds instead of 24
} }
// BlockSize returns the rate of sponge underlying this hash function. // BlockSize returns the rate of sponge underlying this hash function.
@ -86,11 +87,11 @@ func (d *State) permute() {
xorIn(d, d.buf()) xorIn(d, d.buf())
d.bufe = 0 d.bufe = 0
d.bufo = 0 d.bufo = 0
KeccakF1600(&d.a) KeccakF1600(&d.a, d.turbo)
case spongeSqueezing: case spongeSqueezing:
// If we're squeezing, we need to apply the permutation before // If we're squeezing, we need to apply the permutation before
// copying more output. // copying more output.
KeccakF1600(&d.a) KeccakF1600(&d.a, d.turbo)
d.bufe = d.rate d.bufe = d.rate
d.bufo = 0 d.bufo = 0
copyOut(d, d.buf()) copyOut(d, d.buf())
@ -136,7 +137,7 @@ func (d *State) Write(p []byte) (written int, err error) {
// The fast path; absorb a full "rate" bytes of input and apply the permutation. // The fast path; absorb a full "rate" bytes of input and apply the permutation.
xorIn(d, p[:d.rate]) xorIn(d, p[:d.rate])
p = p[d.rate:] p = p[d.rate:]
KeccakF1600(&d.a) KeccakF1600(&d.a, d.turbo)
} else { } else {
// The slow path; buffer the input until we can fill the sponge, and then xor it in. // The slow path; buffer the input until we can fill the sponge, and then xor it in.
todo := d.rate - bufl todo := d.rate - bufl
@ -193,3 +194,7 @@ func (d *State) Sum(in []byte) []byte {
_, _ = dup.Read(hash) _, _ = dup.Read(hash)
return append(in, hash...) return append(in, hash...)
} }
func (d *State) IsAbsorbing() bool {
return d.state == spongeAbsorbing
}

View File

@ -57,6 +57,17 @@ func NewShake128() State {
return State{rate: rate128, dsbyte: dsbyteShake} return State{rate: rate128, dsbyte: dsbyteShake}
} }
// NewTurboShake128 creates a new TurboSHAKE128 variable-output-length ShakeHash.
// Its generic security strength is 128 bits against all attacks if at
// least 32 bytes of its output are used.
// D is the domain separation byte and must be between 0x01 and 0x7f inclusive.
func NewTurboShake128(D byte) State {
if D == 0 || D > 0x7f {
panic("turboshake: D out of range")
}
return State{rate: rate128, dsbyte: D, turbo: true}
}
// NewShake256 creates a new SHAKE256 variable-output-length ShakeHash. // NewShake256 creates a new SHAKE256 variable-output-length ShakeHash.
// Its generic security strength is 256 bits against all attacks if // Its generic security strength is 256 bits against all attacks if
// at least 64 bytes of its output are used. // at least 64 bytes of its output are used.
@ -64,6 +75,17 @@ func NewShake256() State {
return State{rate: rate256, dsbyte: dsbyteShake} return State{rate: rate256, dsbyte: dsbyteShake}
} }
// NewTurboShake256 creates a new TurboSHAKE256 variable-output-length ShakeHash.
// Its generic security strength is 256 bits against all attacks if
// at least 64 bytes of its output are used.
// D is the domain separation byte and must be between 0x01 and 0x7f inclusive.
func NewTurboShake256(D byte) State {
if D == 0 || D > 0x7f {
panic("turboshake: D out of range")
}
return State{rate: rate256, dsbyte: D, turbo: true}
}
// ShakeSum128 writes an arbitrary-length digest of data into hash. // ShakeSum128 writes an arbitrary-length digest of data into hash.
func ShakeSum128(hash, data []byte) { func ShakeSum128(hash, data []byte) {
h := NewShake128() h := NewShake128()
@ -77,3 +99,21 @@ func ShakeSum256(hash, data []byte) {
_, _ = h.Write(data) _, _ = h.Write(data)
_, _ = h.Read(hash) _, _ = h.Read(hash)
} }
// TurboShakeSum128 writes an arbitrary-length digest of data into hash.
func TurboShakeSum128(hash, data []byte, D byte) {
h := NewTurboShake128(D)
_, _ = h.Write(data)
_, _ = h.Read(hash)
}
// TurboShakeSum256 writes an arbitrary-length digest of data into hash.
func TurboShakeSum256(hash, data []byte, D byte) {
h := NewTurboShake256(D)
_, _ = h.Write(data)
_, _ = h.Read(hash)
}
func (d *State) SwitchDS(D byte) {
d.dsbyte = D
}

View File

@ -99,6 +99,7 @@
// Uses: AX, DX, R8-R15, FLAGS // Uses: AX, DX, R8-R15, FLAGS
// Instr: x86_64, bmi2, adx // Instr: x86_64, bmi2, adx
#define integerMulAdx(z,x,y) \ #define integerMulAdx(z,x,y) \
MOVL $0,R15; \
MOVQ 0+y, DX; XORL AX, AX; \ MOVQ 0+y, DX; XORL AX, AX; \
MULXQ 0+x, AX, R8; MOVQ AX, 0+z; \ MULXQ 0+x, AX, R8; MOVQ AX, 0+z; \
MULXQ 8+x, AX, R9; ADCXQ AX, R8; \ MULXQ 8+x, AX, R9; ADCXQ AX, R8; \

View File

@ -158,6 +158,7 @@
// Uses: AX, DX, R8-R15, FLAGS // Uses: AX, DX, R8-R15, FLAGS
// Instr: x86_64, bmi2, adx // Instr: x86_64, bmi2, adx
#define integerMulAdx(z,x,y) \ #define integerMulAdx(z,x,y) \
MOVL $0,R15; \
MOVQ 0+y, DX; XORL AX, AX; MOVQ $0, R8; \ MOVQ 0+y, DX; XORL AX, AX; MOVQ $0, R8; \
MULXQ 0+x, AX, R9; MOVQ AX, 0+z; \ MULXQ 0+x, AX, R9; MOVQ AX, 0+z; \
MULXQ 8+x, AX, R10; ADCXQ AX, R9; \ MULXQ 8+x, AX, R10; ADCXQ AX, R9; \

View File

@ -2,6 +2,7 @@
// +build gofuzz // +build gofuzz
// How to run the fuzzer: // How to run the fuzzer:
//
// $ go get -u github.com/dvyukov/go-fuzz/go-fuzz // $ go get -u github.com/dvyukov/go-fuzz/go-fuzz
// $ go get -u github.com/dvyukov/go-fuzz/go-fuzz-build // $ go get -u github.com/dvyukov/go-fuzz/go-fuzz-build
// $ go-fuzz-build -libfuzzer -func FuzzReduction -o lib.a // $ go-fuzz-build -libfuzzer -func FuzzReduction -o lib.a

34
vendor/github.com/cloudflare/circl/math/primes.go generated vendored Normal file
View File

@ -0,0 +1,34 @@
package math
import (
"crypto/rand"
"io"
"math/big"
)
// IsSafePrime reports whether p is (probably) a safe prime.
// The prime p=2*q+1 is safe prime if both p and q are primes.
// Note that ProbablyPrime is not suitable for judging primes
// that an adversary may have crafted to fool the test.
func IsSafePrime(p *big.Int) bool {
pdiv2 := new(big.Int).Rsh(p, 1)
return p.ProbablyPrime(20) && pdiv2.ProbablyPrime(20)
}
// SafePrime returns a number of the given bit length that is a safe prime with high probability.
// The number returned p=2*q+1 is a safe prime if both p and q are primes.
// SafePrime will return error for any error returned by rand.Read or if bits < 2.
func SafePrime(random io.Reader, bits int) (*big.Int, error) {
one := big.NewInt(1)
p := new(big.Int)
for {
q, err := rand.Prime(random, bits-1)
if err != nil {
return nil, err
}
p.Lsh(q, 1).Add(p, one)
if p.ProbablyPrime(20) {
return p, nil
}
}
}

View File

@ -1,7 +1,7 @@
// Package ed25519 implements Ed25519 signature scheme as described in RFC-8032. // Package ed25519 implements Ed25519 signature scheme as described in RFC-8032.
// //
// This package provides optimized implementations of the three signature // This package provides optimized implementations of the three signature
// variants and maintaining closer compatiblilty with crypto/ed25519. // variants and maintaining closer compatibility with crypto/ed25519.
// //
// | Scheme Name | Sign Function | Verification | Context | // | Scheme Name | Sign Function | Verification | Context |
// |-------------|-------------------|---------------|-------------------| // |-------------|-------------------|---------------|-------------------|
@ -20,7 +20,7 @@
// in this package. While Ed25519Ph accepts an empty context, Ed25519Ctx // in this package. While Ed25519Ph accepts an empty context, Ed25519Ctx
// enforces non-empty context strings. // enforces non-empty context strings.
// //
// Compatibility with crypto.ed25519 // # Compatibility with crypto.ed25519
// //
// These functions are compatible with the “Ed25519” function defined in // These functions are compatible with the “Ed25519” function defined in
// RFC-8032. However, unlike RFC 8032's formulation, this package's private // RFC-8032. However, unlike RFC 8032's formulation, this package's private

View File

@ -29,6 +29,7 @@ const (
// mLSBRecoding is the odd-only modified LSB-set. // mLSBRecoding is the odd-only modified LSB-set.
// //
// Reference: // Reference:
//
// "Efficient and secure algorithms for GLV-based scalar multiplication and // "Efficient and secure algorithms for GLV-based scalar multiplication and
// their implementation on GLVGLS curves" by (Faz-Hernandez et al.) // their implementation on GLVGLS curves" by (Faz-Hernandez et al.)
// http://doi.org/10.1007/s13389-014-0085-7. // http://doi.org/10.1007/s13389-014-0085-7.

View File

@ -18,9 +18,9 @@
// //
// References: // References:
// //
// - RFC8032 https://rfc-editor.org/rfc/rfc8032.txt // - RFC8032: https://rfc-editor.org/rfc/rfc8032.txt
// - EdDSA for more curves https://eprint.iacr.org/2015/677 // - EdDSA for more curves: https://eprint.iacr.org/2015/677
// - High-speed high-security signatures. https://doi.org/10.1007/s13389-012-0027-1 // - High-speed high-security signatures: https://doi.org/10.1007/s13389-012-0027-1
package ed448 package ed448
import ( import (

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