mirror of https://github.com/cheat/cheat.git
commit
2294f40ee0
|
@ -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.
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
||||||
|
|
211
doc/cheat.1
211
doc/cheat.1
|
@ -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
37
go.mod
|
@ -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
182
go.sum
|
@ -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=
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Package installer implements functions that provide a first-time
|
||||||
|
// installation wizard.
|
||||||
package installer
|
package installer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// Package mock implements mock functions used in unit-tests.
|
||||||
package mock
|
package mock
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// Package repo implements functions pertaining to the management of git repos.
|
||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Package sheet implements functions pertaining to parsing, searching, and
|
||||||
|
// displaying cheatsheets.
|
||||||
package sheet
|
package sheet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// Package sheets implements functions pertaining to loading, sorting,
|
||||||
|
// filtering, and tagging cheatsheets.
|
||||||
package sheets
|
package sheets
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
@ -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"
|
|
@ -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)!
|
|
@ -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)
|
|
@ -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.
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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:
|
|
@ -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
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
// This package contains Win32 filesystem functionality.
|
||||||
|
package fs
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
)
|
64
vendor/github.com/Microsoft/go-winio/internal/fs/zsyscall_windows.go
generated
vendored
Normal file
64
vendor/github.com/Microsoft/go-winio/internal/fs/zsyscall_windows.go
generated
vendored
Normal 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
|
||||||
|
}
|
132
vendor/github.com/Microsoft/go-winio/internal/stringbuffer/wstring.go
generated
vendored
Normal file
132
vendor/github.com/Microsoft/go-winio/internal/stringbuffer/wstring.go
generated
vendored
Normal 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 }
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,7 +66,7 @@ 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]
|
||||||
|
@ -89,4 +88,3 @@ func SliceForAppend(in []byte, n int) (head, tail []byte) {
|
||||||
tail = head[len(in):]
|
tail = head[len(in):]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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"},
|
||||||
|
|
|
@ -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
|
||||||
|
@ -211,7 +214,11 @@ TryNextBlock:
|
||||||
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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -181,13 +181,17 @@ 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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const ed25519Size = 32
|
const ed25519Size = 32
|
||||||
|
|
||||||
type ed25519 struct{}
|
type ed25519 struct{}
|
||||||
|
|
||||||
func NewEd25519() *ed25519 {
|
func NewEd25519() *ed25519 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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-----
|
||||||
|
`
|
||||||
|
|
|
@ -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)
|
||||||
|
|
264
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/aead_crypter.go
generated
vendored
Normal file
264
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/aead_crypter.go
generated
vendored
Normal 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
|
||||||
|
}
|
|
@ -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")
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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)))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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, ¬ation)
|
||||||
|
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.
|
||||||
|
|
107
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetric_key_encrypted.go
generated
vendored
107
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetric_key_encrypted.go
generated
vendored
|
@ -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)
|
||||||
|
}
|
||||||
|
|
272
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted.go
generated
vendored
272
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted.go
generated
vendored
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
156
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted_aead.go
generated
vendored
Normal file
156
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted_aead.go
generated
vendored
Normal 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
|
||||||
|
}
|
256
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted_mdc.go
generated
vendored
Normal file
256
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted_mdc.go
generated
vendored
Normal 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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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-----`
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
// Package bufpipe provides a IO pipe, has variable-sized buffer.
|
|
||||||
package bufpipe
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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; \
|
||||||
|
|
|
@ -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; \
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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 GLV–GLS curves" by (Faz-Hernandez et al.)
|
// their implementation on GLV–GLS curves" by (Faz-Hernandez et al.)
|
||||||
// http://doi.org/10.1007/s13389-014-0085-7.
|
// http://doi.org/10.1007/s13389-014-0085-7.
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
|
||||||
|
Copyright (C) 2017 SUSE LLC. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,79 @@
|
||||||
|
## `filepath-securejoin` ##
|
||||||
|
|
||||||
|
[![Build Status](https://github.com/cyphar/filepath-securejoin/actions/workflows/ci.yml/badge.svg)](https://github.com/cyphar/filepath-securejoin/actions/workflows/ci.yml)
|
||||||
|
|
||||||
|
An implementation of `SecureJoin`, a [candidate for inclusion in the Go
|
||||||
|
standard library][go#20126]. The purpose of this function is to be a "secure"
|
||||||
|
alternative to `filepath.Join`, and in particular it provides certain
|
||||||
|
guarantees that are not provided by `filepath.Join`.
|
||||||
|
|
||||||
|
> **NOTE**: This code is *only* safe if you are not at risk of other processes
|
||||||
|
> modifying path components after you've used `SecureJoin`. If it is possible
|
||||||
|
> for a malicious process to modify path components of the resolved path, then
|
||||||
|
> you will be vulnerable to some fairly trivial TOCTOU race conditions. [There
|
||||||
|
> are some Linux kernel patches I'm working on which might allow for a better
|
||||||
|
> solution.][lwn-obeneath]
|
||||||
|
>
|
||||||
|
> In addition, with a slightly modified API it might be possible to use
|
||||||
|
> `O_PATH` and verify that the opened path is actually the resolved one -- but
|
||||||
|
> I have not done that yet. I might add it in the future as a helper function
|
||||||
|
> to help users verify the path (we can't just return `/proc/self/fd/<foo>`
|
||||||
|
> because that doesn't always work transparently for all users).
|
||||||
|
|
||||||
|
This is the function prototype:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func SecureJoin(root, unsafePath string) (string, error)
|
||||||
|
```
|
||||||
|
|
||||||
|
This library **guarantees** the following:
|
||||||
|
|
||||||
|
* If no error is set, the resulting string **must** be a child path of
|
||||||
|
`root` and will not contain any symlink path components (they will all be
|
||||||
|
expanded).
|
||||||
|
|
||||||
|
* When expanding symlinks, all symlink path components **must** be resolved
|
||||||
|
relative to the provided root. In particular, this can be considered a
|
||||||
|
userspace implementation of how `chroot(2)` operates on file paths. Note that
|
||||||
|
these symlinks will **not** be expanded lexically (`filepath.Clean` is not
|
||||||
|
called on the input before processing).
|
||||||
|
|
||||||
|
* Non-existent path components are unaffected by `SecureJoin` (similar to
|
||||||
|
`filepath.EvalSymlinks`'s semantics).
|
||||||
|
|
||||||
|
* The returned path will always be `filepath.Clean`ed and thus not contain any
|
||||||
|
`..` components.
|
||||||
|
|
||||||
|
A (trivial) implementation of this function on GNU/Linux systems could be done
|
||||||
|
with the following (note that this requires root privileges and is far more
|
||||||
|
opaque than the implementation in this library, and also requires that
|
||||||
|
`readlink` is inside the `root` path):
|
||||||
|
|
||||||
|
```go
|
||||||
|
package securejoin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SecureJoin(root, unsafePath string) (string, error) {
|
||||||
|
unsafePath = string(filepath.Separator) + unsafePath
|
||||||
|
cmd := exec.Command("chroot", root,
|
||||||
|
"readlink", "--canonicalize-missing", "--no-newline", unsafePath)
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
expanded := string(output)
|
||||||
|
return filepath.Join(root, expanded), nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[lwn-obeneath]: https://lwn.net/Articles/767547/
|
||||||
|
[go#20126]: https://github.com/golang/go/issues/20126
|
||||||
|
|
||||||
|
### License ###
|
||||||
|
|
||||||
|
The license of this project is the same as Go, which is a BSD 3-clause license
|
||||||
|
available in the `LICENSE` file.
|
|
@ -0,0 +1 @@
|
||||||
|
0.2.4
|
|
@ -0,0 +1,125 @@
|
||||||
|
// Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved.
|
||||||
|
// Copyright (C) 2017 SUSE LLC. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package securejoin is an implementation of the hopefully-soon-to-be-included
|
||||||
|
// SecureJoin helper that is meant to be part of the "path/filepath" package.
|
||||||
|
// The purpose of this project is to provide a PoC implementation to make the
|
||||||
|
// SecureJoin proposal (https://github.com/golang/go/issues/20126) more
|
||||||
|
// tangible.
|
||||||
|
package securejoin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsNotExist tells you if err is an error that implies that either the path
|
||||||
|
// accessed does not exist (or path components don't exist). This is
|
||||||
|
// effectively a more broad version of os.IsNotExist.
|
||||||
|
func IsNotExist(err error) bool {
|
||||||
|
// Check that it's not actually an ENOTDIR, which in some cases is a more
|
||||||
|
// convoluted case of ENOENT (usually involving weird paths).
|
||||||
|
return errors.Is(err, os.ErrNotExist) || errors.Is(err, syscall.ENOTDIR) || errors.Is(err, syscall.ENOENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecureJoinVFS joins the two given path components (similar to Join) except
|
||||||
|
// that the returned path is guaranteed to be scoped inside the provided root
|
||||||
|
// path (when evaluated). Any symbolic links in the path are evaluated with the
|
||||||
|
// given root treated as the root of the filesystem, similar to a chroot. The
|
||||||
|
// filesystem state is evaluated through the given VFS interface (if nil, the
|
||||||
|
// standard os.* family of functions are used).
|
||||||
|
//
|
||||||
|
// Note that the guarantees provided by this function only apply if the path
|
||||||
|
// components in the returned string are not modified (in other words are not
|
||||||
|
// replaced with symlinks on the filesystem) after this function has returned.
|
||||||
|
// Such a symlink race is necessarily out-of-scope of SecureJoin.
|
||||||
|
//
|
||||||
|
// Volume names in unsafePath are always discarded, regardless if they are
|
||||||
|
// provided via direct input or when evaluating symlinks. Therefore:
|
||||||
|
//
|
||||||
|
// "C:\Temp" + "D:\path\to\file.txt" results in "C:\Temp\path\to\file.txt"
|
||||||
|
func SecureJoinVFS(root, unsafePath string, vfs VFS) (string, error) {
|
||||||
|
// Use the os.* VFS implementation if none was specified.
|
||||||
|
if vfs == nil {
|
||||||
|
vfs = osVFS{}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafePath = filepath.FromSlash(unsafePath)
|
||||||
|
var path bytes.Buffer
|
||||||
|
n := 0
|
||||||
|
for unsafePath != "" {
|
||||||
|
if n > 255 {
|
||||||
|
return "", &os.PathError{Op: "SecureJoin", Path: root + string(filepath.Separator) + unsafePath, Err: syscall.ELOOP}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := filepath.VolumeName(unsafePath); v != "" {
|
||||||
|
unsafePath = unsafePath[len(v):]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next path component, p.
|
||||||
|
i := strings.IndexRune(unsafePath, filepath.Separator)
|
||||||
|
var p string
|
||||||
|
if i == -1 {
|
||||||
|
p, unsafePath = unsafePath, ""
|
||||||
|
} else {
|
||||||
|
p, unsafePath = unsafePath[:i], unsafePath[i+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a cleaned path, using the lexical semantics of /../a, to
|
||||||
|
// create a "scoped" path component which can safely be joined to fullP
|
||||||
|
// for evaluation. At this point, path.String() doesn't contain any
|
||||||
|
// symlink components.
|
||||||
|
cleanP := filepath.Clean(string(filepath.Separator) + path.String() + p)
|
||||||
|
if cleanP == string(filepath.Separator) {
|
||||||
|
path.Reset()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fullP := filepath.Clean(root + cleanP)
|
||||||
|
|
||||||
|
// Figure out whether the path is a symlink.
|
||||||
|
fi, err := vfs.Lstat(fullP)
|
||||||
|
if err != nil && !IsNotExist(err) {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Treat non-existent path components the same as non-symlinks (we
|
||||||
|
// can't do any better here).
|
||||||
|
if IsNotExist(err) || fi.Mode()&os.ModeSymlink == 0 {
|
||||||
|
path.WriteString(p)
|
||||||
|
path.WriteRune(filepath.Separator)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only increment when we actually dereference a link.
|
||||||
|
n++
|
||||||
|
|
||||||
|
// It's a symlink, expand it by prepending it to the yet-unparsed path.
|
||||||
|
dest, err := vfs.Readlink(fullP)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// Absolute symlinks reset any work we've already done.
|
||||||
|
if filepath.IsAbs(dest) {
|
||||||
|
path.Reset()
|
||||||
|
}
|
||||||
|
unsafePath = dest + string(filepath.Separator) + unsafePath
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have to clean path.String() here because it may contain '..'
|
||||||
|
// components that are entirely lexical, but would be misleading otherwise.
|
||||||
|
// And finally do a final clean to ensure that root is also lexically
|
||||||
|
// clean.
|
||||||
|
fullP := filepath.Clean(string(filepath.Separator) + path.String())
|
||||||
|
return filepath.Clean(root + fullP), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecureJoin is a wrapper around SecureJoinVFS that just uses the os.* library
|
||||||
|
// of functions as the VFS. If in doubt, use this function over SecureJoinVFS.
|
||||||
|
func SecureJoin(root, unsafePath string) (string, error) {
|
||||||
|
return SecureJoinVFS(root, unsafePath, nil)
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright (C) 2017 SUSE LLC. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package securejoin
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// In future this should be moved into a separate package, because now there
|
||||||
|
// are several projects (umoci and go-mtree) that are using this sort of
|
||||||
|
// interface.
|
||||||
|
|
||||||
|
// VFS is the minimal interface necessary to use SecureJoinVFS. A nil VFS is
|
||||||
|
// equivalent to using the standard os.* family of functions. This is mainly
|
||||||
|
// used for the purposes of mock testing, but also can be used to otherwise use
|
||||||
|
// SecureJoin with VFS-like system.
|
||||||
|
type VFS interface {
|
||||||
|
// Lstat returns a FileInfo describing the named file. If the file is a
|
||||||
|
// symbolic link, the returned FileInfo describes the symbolic link. Lstat
|
||||||
|
// makes no attempt to follow the link. These semantics are identical to
|
||||||
|
// os.Lstat.
|
||||||
|
Lstat(name string) (os.FileInfo, error)
|
||||||
|
|
||||||
|
// Readlink returns the destination of the named symbolic link. These
|
||||||
|
// semantics are identical to os.Readlink.
|
||||||
|
Readlink(name string) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// osVFS is the "nil" VFS, in that it just passes everything through to the os
|
||||||
|
// module.
|
||||||
|
type osVFS struct{}
|
||||||
|
|
||||||
|
// Lstat returns a FileInfo describing the named file. If the file is a
|
||||||
|
// symbolic link, the returned FileInfo describes the symbolic link. Lstat
|
||||||
|
// makes no attempt to follow the link. These semantics are identical to
|
||||||
|
// os.Lstat.
|
||||||
|
func (o osVFS) Lstat(name string) (os.FileInfo, error) { return os.Lstat(name) }
|
||||||
|
|
||||||
|
// Readlink returns the destination of the named symbolic link. These
|
||||||
|
// semantics are identical to os.Readlink.
|
||||||
|
func (o osVFS) Readlink(name string) (string, error) { return os.Readlink(name) }
|
|
@ -92,6 +92,66 @@ if isMatch, _ := re.MatchString(`Something to match`); isMatch {
|
||||||
|
|
||||||
This feature is a work in progress and I'm open to ideas for more things to put here (maybe more relaxed character escaping rules?).
|
This feature is a work in progress and I'm open to ideas for more things to put here (maybe more relaxed character escaping rules?).
|
||||||
|
|
||||||
|
## Catastrophic Backtracking and Timeouts
|
||||||
|
|
||||||
|
`regexp2` supports features that can lead to catastrophic backtracking.
|
||||||
|
`Regexp.MatchTimeout` can be set to to limit the impact of such behavior; the
|
||||||
|
match will fail with an error after approximately MatchTimeout. No timeout
|
||||||
|
checks are done by default.
|
||||||
|
|
||||||
|
Timeout checking is not free. The current timeout checking implementation starts
|
||||||
|
a background worker that updates a clock value approximately once every 100
|
||||||
|
milliseconds. The matching code compares this value against the precomputed
|
||||||
|
deadline for the match. The performance impact is as follows.
|
||||||
|
|
||||||
|
1. A match with a timeout runs almost as fast as a match without a timeout.
|
||||||
|
2. If any live matches have a timeout, there will be a background CPU load
|
||||||
|
(`~0.15%` currently on a modern machine). This load will remain constant
|
||||||
|
regardless of the number of matches done including matches done in parallel.
|
||||||
|
3. If no live matches are using a timeout, the background load will remain
|
||||||
|
until the longest deadline (match timeout + the time when the match started)
|
||||||
|
is reached. E.g., if you set a timeout of one minute the load will persist
|
||||||
|
for approximately a minute even if the match finishes quickly.
|
||||||
|
|
||||||
|
See [PR #58](https://github.com/dlclark/regexp2/pull/58) for more details and
|
||||||
|
alternatives considered.
|
||||||
|
|
||||||
|
## Goroutine leak error
|
||||||
|
If you're using a library during unit tests (e.g. https://github.com/uber-go/goleak) that validates all goroutines are exited then you'll likely get an error if you or any of your dependencies use regex's with a MatchTimeout.
|
||||||
|
To remedy the problem you'll need to tell the unit test to wait until the backgroup timeout goroutine is exited.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func TestSomething(t *testing.T) {
|
||||||
|
defer goleak.VerifyNone(t)
|
||||||
|
defer regexp2.StopTimeoutClock()
|
||||||
|
|
||||||
|
// ... test
|
||||||
|
}
|
||||||
|
|
||||||
|
//or
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
// setup
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// run
|
||||||
|
m.Run()
|
||||||
|
|
||||||
|
//tear down
|
||||||
|
regexp2.StopTimeoutClock()
|
||||||
|
goleak.VerifyNone(t)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This will add ~100ms runtime to each test (or TestMain). If that's too much time you can set the clock cycle rate of the timeout goroutine in an init function in a test file. `regexp2.SetTimeoutCheckPeriod` isn't threadsafe so it must be setup before starting any regex's with Timeouts.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func init() {
|
||||||
|
//speed up testing by making the timeout clock 1ms
|
||||||
|
regexp2.SetTimeoutCheckPeriod(time.Millisecond)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## ECMAScript compatibility mode
|
## ECMAScript compatibility mode
|
||||||
In this mode the engine provides compatibility with the [regex engine](https://tc39.es/ecma262/multipage/text-processing.html#sec-regexp-regular-expression-objects) described in the ECMAScript specification.
|
In this mode the engine provides compatibility with the [regex engine](https://tc39.es/ecma262/multipage/text-processing.html#sec-regexp-regular-expression-objects) described in the ECMAScript specification.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
package regexp2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fasttime holds a time value (ticks since clock initialization)
|
||||||
|
type fasttime int64
|
||||||
|
|
||||||
|
// fastclock provides a fast clock implementation.
|
||||||
|
//
|
||||||
|
// A background goroutine periodically stores the current time
|
||||||
|
// into an atomic variable.
|
||||||
|
//
|
||||||
|
// A deadline can be quickly checked for expiration by comparing
|
||||||
|
// its value to the clock stored in the atomic variable.
|
||||||
|
//
|
||||||
|
// The goroutine automatically stops once clockEnd is reached.
|
||||||
|
// (clockEnd covers the largest deadline seen so far + some
|
||||||
|
// extra time). This ensures that if regexp2 with timeouts
|
||||||
|
// stops being used we will stop background work.
|
||||||
|
type fastclock struct {
|
||||||
|
// instances of atomicTime must be at the start of the struct (or at least 64-bit aligned)
|
||||||
|
// otherwise 32-bit architectures will panic
|
||||||
|
|
||||||
|
current atomicTime // Current time (approximate)
|
||||||
|
clockEnd atomicTime // When clock updater is supposed to stop (>= any existing deadline)
|
||||||
|
|
||||||
|
// current and clockEnd can be read via atomic loads.
|
||||||
|
// Reads and writes of other fields require mu to be held.
|
||||||
|
mu sync.Mutex
|
||||||
|
start time.Time // Time corresponding to fasttime(0)
|
||||||
|
running bool // Is a clock updater running?
|
||||||
|
}
|
||||||
|
|
||||||
|
var fast fastclock
|
||||||
|
|
||||||
|
// reached returns true if current time is at or past t.
|
||||||
|
func (t fasttime) reached() bool {
|
||||||
|
return fast.current.read() >= t
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeDeadline returns a time that is approximately time.Now().Add(d)
|
||||||
|
func makeDeadline(d time.Duration) fasttime {
|
||||||
|
// Increase the deadline since the clock we are reading may be
|
||||||
|
// just about to tick forwards.
|
||||||
|
end := fast.current.read() + durationToTicks(d+clockPeriod)
|
||||||
|
|
||||||
|
// Start or extend clock if necessary.
|
||||||
|
if end > fast.clockEnd.read() {
|
||||||
|
extendClock(end)
|
||||||
|
}
|
||||||
|
return end
|
||||||
|
}
|
||||||
|
|
||||||
|
// extendClock ensures that clock is live and will run until at least end.
|
||||||
|
func extendClock(end fasttime) {
|
||||||
|
fast.mu.Lock()
|
||||||
|
defer fast.mu.Unlock()
|
||||||
|
|
||||||
|
if fast.start.IsZero() {
|
||||||
|
fast.start = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extend the running time to cover end as well as a bit of slop.
|
||||||
|
if shutdown := end + durationToTicks(time.Second); shutdown > fast.clockEnd.read() {
|
||||||
|
fast.clockEnd.write(shutdown)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start clock if necessary
|
||||||
|
if !fast.running {
|
||||||
|
fast.running = true
|
||||||
|
go runClock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop the timeout clock in the background
|
||||||
|
// should only used for unit tests to abandon the background goroutine
|
||||||
|
func stopClock() {
|
||||||
|
fast.mu.Lock()
|
||||||
|
if fast.running {
|
||||||
|
fast.clockEnd.write(fasttime(0))
|
||||||
|
}
|
||||||
|
fast.mu.Unlock()
|
||||||
|
|
||||||
|
// pause until not running
|
||||||
|
// get and release the lock
|
||||||
|
isRunning := true
|
||||||
|
for isRunning {
|
||||||
|
time.Sleep(clockPeriod / 2)
|
||||||
|
fast.mu.Lock()
|
||||||
|
isRunning = fast.running
|
||||||
|
fast.mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func durationToTicks(d time.Duration) fasttime {
|
||||||
|
// Downscale nanoseconds to approximately a millisecond so that we can avoid
|
||||||
|
// overflow even if the caller passes in math.MaxInt64.
|
||||||
|
return fasttime(d) >> 20
|
||||||
|
}
|
||||||
|
|
||||||
|
const DefaultClockPeriod = 100 * time.Millisecond
|
||||||
|
|
||||||
|
// clockPeriod is the approximate interval between updates of approximateClock.
|
||||||
|
var clockPeriod = DefaultClockPeriod
|
||||||
|
|
||||||
|
func runClock() {
|
||||||
|
fast.mu.Lock()
|
||||||
|
defer fast.mu.Unlock()
|
||||||
|
|
||||||
|
for fast.current.read() <= fast.clockEnd.read() {
|
||||||
|
// Unlock while sleeping.
|
||||||
|
fast.mu.Unlock()
|
||||||
|
time.Sleep(clockPeriod)
|
||||||
|
fast.mu.Lock()
|
||||||
|
|
||||||
|
newTime := durationToTicks(time.Since(fast.start))
|
||||||
|
fast.current.write(newTime)
|
||||||
|
}
|
||||||
|
fast.running = false
|
||||||
|
}
|
||||||
|
|
||||||
|
type atomicTime struct{ v int64 } // Should change to atomic.Int64 when we can use go 1.19
|
||||||
|
|
||||||
|
func (t *atomicTime) read() fasttime { return fasttime(atomic.LoadInt64(&t.v)) }
|
||||||
|
func (t *atomicTime) write(v fasttime) { atomic.StoreInt64(&t.v, int64(v)) }
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue