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
|
||||
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 \
|
||||
&& chmod +x cheat-linux-amd64 \
|
||||
&& 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.
|
||||
|
||||
See the [releases page][releases] for a list of supported platforms.
|
||||
|
|
|
@ -6,6 +6,6 @@ import (
|
|||
"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)
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
// 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
|
||||
var out bytes.Buffer
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
// 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
|
||||
cheatsheets, err := sheets.Load(conf.Cheatpaths)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// Package main serves as the executable entrypoint.
|
||||
package main
|
||||
|
||||
//go:generate go run ../../build/embed.go
|
||||
|
@ -16,7 +17,7 @@ import (
|
|||
"github.com/cheat/cheat/internal/installer"
|
||||
)
|
||||
|
||||
const version = "4.4.0"
|
||||
const version = "4.4.1"
|
||||
|
||||
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"
|
||||
.hy
|
||||
.SH NAME
|
||||
.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
|
||||
.PP
|
||||
\f[B]cheat\f[] [options] [\f[I]CHEATSHEET\f[]]
|
||||
\f[B]cheat\f[R] [options] [\f[I]CHEATSHEET\f[R]]
|
||||
.SH DESCRIPTION
|
||||
.PP
|
||||
\f[B]cheat\f[] allows you to create and view interactive cheatsheets on
|
||||
the command\-line.
|
||||
\f[B]cheat\f[R] allows you to create and view interactive cheatsheets on
|
||||
the command-line.
|
||||
It was designed to help remind *nix system administrators of options for
|
||||
commands that they use frequently, but not frequently enough to
|
||||
remember.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.B \[en]init
|
||||
\[en]init
|
||||
Print a config file to stdout.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-c, \[en]colorize
|
||||
-c, \[en]colorize
|
||||
Colorize output.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-d, \[en]directories
|
||||
-d, \[en]directories
|
||||
List cheatsheet directories.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-e, \[en]edit=\f[I]CHEATSHEET\f[]
|
||||
Open \f[I]CHEATSHEET\f[] for editing.
|
||||
.RS
|
||||
.RE
|
||||
-e, \[en]edit=\f[I]CHEATSHEET\f[R]
|
||||
Open \f[I]CHEATSHEET\f[R] for editing.
|
||||
.TP
|
||||
.B \-l, \[en]list
|
||||
-l, \[en]list
|
||||
List available cheatsheets.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-p, \[en]path=\f[I]PATH\f[]
|
||||
Filter only to sheets found on path \f[I]PATH\f[].
|
||||
.RS
|
||||
.RE
|
||||
-p, \[en]path=\f[I]PATH\f[R]
|
||||
Filter only to sheets found on path \f[I]PATH\f[R].
|
||||
.TP
|
||||
.B \-r, \[en]regex
|
||||
Treat search \f[I]PHRASE\f[] as a regular expression.
|
||||
.RS
|
||||
.RE
|
||||
-r, \[en]regex
|
||||
Treat search \f[I]PHRASE\f[R] as a regular expression.
|
||||
.TP
|
||||
.B \-s, \[en]search=\f[I]PHRASE\f[]
|
||||
Search cheatsheets for \f[I]PHRASE\f[].
|
||||
.RS
|
||||
.RE
|
||||
-s, \[en]search=\f[I]PHRASE\f[R]
|
||||
Search cheatsheets for \f[I]PHRASE\f[R].
|
||||
.TP
|
||||
.B \-t, \[en]tag=\f[I]TAG\f[]
|
||||
Filter only to sheets tagged with \f[I]TAG\f[].
|
||||
.RS
|
||||
.RE
|
||||
-t, \[en]tag=\f[I]TAG\f[R]
|
||||
Filter only to sheets tagged with \f[I]TAG\f[R].
|
||||
.TP
|
||||
.B \-T, \[en]tags
|
||||
-T, \[en]tags
|
||||
List all tags in use.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \-v, \[en]version
|
||||
-v, \[en]version
|
||||
Print the version number.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \[en]rm=\f[I]CHEATSHEET\f[]
|
||||
Remove (deletes) \f[I]CHEATSHEET\f[].
|
||||
.RS
|
||||
.RE
|
||||
\[en]rm=\f[I]CHEATSHEET\f[R]
|
||||
Remove (deletes) \f[I]CHEATSHEET\f[R].
|
||||
.SH EXAMPLES
|
||||
.TP
|
||||
.B To view the foo cheatsheet:
|
||||
cheat \f[I]foo\f[]
|
||||
.RS
|
||||
.RE
|
||||
To view the foo cheatsheet:
|
||||
cheat \f[I]foo\f[R]
|
||||
.TP
|
||||
.B To edit (or create) the foo cheatsheet:
|
||||
cheat \-e \f[I]foo\f[]
|
||||
.RS
|
||||
.RE
|
||||
To edit (or create) the foo cheatsheet:
|
||||
cheat -e \f[I]foo\f[R]
|
||||
.TP
|
||||
.B To edit (or create) the foo/bar cheatsheet on the `work' cheatpath:
|
||||
cheat \-p \f[I]work\f[] \-e \f[I]foo/bar\f[]
|
||||
.RS
|
||||
.RE
|
||||
To edit (or create) the foo/bar cheatsheet on the `work' cheatpath:
|
||||
cheat -p \f[I]work\f[R] -e \f[I]foo/bar\f[R]
|
||||
.TP
|
||||
.B To view all cheatsheet directories:
|
||||
cheat \-d
|
||||
.RS
|
||||
.RE
|
||||
To view all cheatsheet directories:
|
||||
cheat -d
|
||||
.TP
|
||||
.B To list all available cheatsheets:
|
||||
cheat \-l
|
||||
.RS
|
||||
.RE
|
||||
To list all available cheatsheets:
|
||||
cheat -l
|
||||
.TP
|
||||
.B To list all cheatsheets whose titles match `apt':
|
||||
cheat \-l \f[I]apt\f[]
|
||||
.RS
|
||||
.RE
|
||||
To list all cheatsheets whose titles match `apt':
|
||||
cheat -l \f[I]apt\f[R]
|
||||
.TP
|
||||
.B To list all tags in use:
|
||||
cheat \-T
|
||||
.RS
|
||||
.RE
|
||||
To list all tags in use:
|
||||
cheat -T
|
||||
.TP
|
||||
.B To list available cheatsheets that are tagged as `personal':
|
||||
cheat \-l \-t \f[I]personal\f[]
|
||||
.RS
|
||||
.RE
|
||||
To list available cheatsheets that are tagged as `personal':
|
||||
cheat -l -t \f[I]personal\f[R]
|
||||
.TP
|
||||
.B To search for `ssh' among all cheatsheets, and colorize matches:
|
||||
cheat \-c \-s \f[I]ssh\f[]
|
||||
.RS
|
||||
.RE
|
||||
To search for `ssh' among all cheatsheets, and colorize matches:
|
||||
cheat -c -s \f[I]ssh\f[R]
|
||||
.TP
|
||||
.B 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[]
|
||||
.RS
|
||||
.RE
|
||||
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[R]
|
||||
.TP
|
||||
.B To remove (delete) the foo/bar cheatsheet:
|
||||
cheat \[en]rm \f[I]foo/bar\f[]
|
||||
.RS
|
||||
.RE
|
||||
To remove (delete) the foo/bar cheatsheet:
|
||||
cheat \[en]rm \f[I]foo/bar\f[R]
|
||||
.SH FILES
|
||||
.SS Configuration
|
||||
.PP
|
||||
\f[B]cheat\f[] is configured via a YAML file that is conventionally
|
||||
named \f[I]conf.yaml\f[].
|
||||
\f[B]cheat\f[] will search for \f[I]conf.yaml\f[] in varying locations,
|
||||
depending upon your platform:
|
||||
\f[B]cheat\f[R] is configured via a YAML file that is conventionally
|
||||
named \f[I]conf.yaml\f[R].
|
||||
\f[B]cheat\f[R] will search for \f[I]conf.yaml\f[R] in varying
|
||||
locations, depending upon your platform:
|
||||
.SS Linux, OSX, and other Unixes
|
||||
.IP "1." 3
|
||||
\f[B]CHEAT_CONFIG_PATH\f[]
|
||||
\f[B]CHEAT_CONFIG_PATH\f[R]
|
||||
.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
|
||||
\f[B]$HOME\f[]/.config/cheat/conf.yml
|
||||
\f[B]$HOME\f[R]/.config/cheat/conf.yml
|
||||
.IP "4." 3
|
||||
\f[B]$HOME\f[]/.cheat/conf.yml
|
||||
\f[B]$HOME\f[R]/.cheat/conf.yml
|
||||
.SS Windows
|
||||
.IP "1." 3
|
||||
\f[B]CHEAT_CONFIG_PATH\f[]
|
||||
\f[B]CHEAT_CONFIG_PATH\f[R]
|
||||
.IP "2." 3
|
||||
\f[B]APPDATA\f[]/cheat/conf.yml
|
||||
\f[B]APPDATA\f[R]/cheat/conf.yml
|
||||
.IP "3." 3
|
||||
\f[B]PROGRAMDATA\f[]/cheat/conf.yml
|
||||
\f[B]PROGRAMDATA\f[R]/cheat/conf.yml
|
||||
.PP
|
||||
\f[B]cheat\f[] will search in the order specified above.
|
||||
The first \f[I]conf.yaml\f[] encountered will be respected.
|
||||
\f[B]cheat\f[R] will search in the order specified above.
|
||||
The first \f[I]conf.yaml\f[R] encountered will be respected.
|
||||
.PP
|
||||
If \f[B]cheat\f[] cannot locate a config file, it will ask if you'd like
|
||||
to generate one automatically.
|
||||
If \f[B]cheat\f[R] cannot locate a config file, it will ask if you\[cq]d
|
||||
like to generate one automatically.
|
||||
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.
|
||||
.SS Cheatpaths
|
||||
.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.
|
||||
Cheatpaths may be configured in \f[I]conf.yaml\f[], and viewed via
|
||||
\f[B]cheat \-d\f[].
|
||||
Cheatpaths may be configured in \f[I]conf.yaml\f[R], and viewed via
|
||||
\f[B]cheat -d\f[R].
|
||||
.PP
|
||||
For detailed instructions on how to configure cheatpaths, please refer
|
||||
to the comments in conf.yml.
|
||||
.SS Autocompletion
|
||||
.PP
|
||||
Autocompletion scripts for \f[B]bash\f[], \f[B]zsh\f[], and
|
||||
\f[B]fish\f[] are available for download:
|
||||
Autocompletion scripts for \f[B]bash\f[R], \f[B]zsh\f[R], and
|
||||
\f[B]fish\f[R] are available for download:
|
||||
.IP \[bu] 2
|
||||
<https://github.com/cheat/cheat/blob/master/scripts/cheat.bash>
|
||||
.IP \[bu] 2
|
||||
|
@ -184,25 +152,22 @@ Autocompletion scripts for \f[B]bash\f[], \f[B]zsh\f[], and
|
|||
.IP \[bu] 2
|
||||
<https://github.com/cheat/cheat/blob/master/scripts/cheat.zsh>
|
||||
.PP
|
||||
The \f[B]bash\f[] and \f[B]zsh\f[] scripts provide optional integration
|
||||
with \f[B]fzf\f[], if the latter is available on your \f[B]PATH\f[].
|
||||
The \f[B]bash\f[R] and \f[B]zsh\f[R] scripts provide optional
|
||||
integration with \f[B]fzf\f[R], if the latter is available on your
|
||||
\f[B]PATH\f[R].
|
||||
.PP
|
||||
The installation process will vary per system and shell configuration,
|
||||
and thus will not be discussed here.
|
||||
.SH ENVIRONMENT
|
||||
.TP
|
||||
.B \f[B]CHEAT_CONFIG_PATH\f[]
|
||||
\f[B]CHEAT_CONFIG_PATH\f[R]
|
||||
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.
|
||||
.RS
|
||||
.RE
|
||||
.TP
|
||||
.B \f[B]CHEAT_USE_FZF\f[]
|
||||
\f[B]CHEAT_USE_FZF\f[R]
|
||||
If set, autocompletion scripts will attempt to integrate with
|
||||
\f[B]fzf\f[].
|
||||
.RS
|
||||
.RE
|
||||
\f[B]fzf\f[R].
|
||||
.SH RETURN VALUES
|
||||
.IP "0." 3
|
||||
Successful termination
|
||||
|
@ -218,4 +183,4 @@ See GitHub issues: <https://github.com/cheat/cheat/issues>
|
|||
Christopher Allen Lane <chris@chris-allen-lane.com>
|
||||
.SH SEE ALSO
|
||||
.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/davecgh/go-spew v1.1.1
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
|
||||
github.com/go-git/go-git/v5 v5.4.2
|
||||
github.com/mattn/go-isatty v0.0.16
|
||||
github.com/go-git/go-git/v5 v5.11.0
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.3 // indirect
|
||||
github.com/cloudflare/circl v1.2.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.7.0 // indirect
|
||||
dario.cat/mergo v1.0.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c // indirect
|
||||
github.com/cloudflare/circl v1.3.6 // 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/go-git/gcfg v1.5.0 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.3.1 // indirect
|
||||
github.com/imdario/mergo v0.3.13 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.5.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/sergi/go-diff v1.2.0 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.2 // indirect
|
||||
golang.org/x/crypto v0.1.0 // indirect
|
||||
golang.org/x/mod v0.6.0 // indirect
|
||||
golang.org/x/net v0.1.0 // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
golang.org/x/tools v0.2.0 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/sergi/go-diff v1.3.1 // indirect
|
||||
github.com/skeema/knownhosts v1.2.1 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/crypto v0.16.0 // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/tools v0.16.1 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
|
182
go.sum
182
go.sum
|
@ -1,146 +1,144 @@
|
|||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
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.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=
|
||||
github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20221026131551-cf6655e29de4 h1:ra2OtmuW0AE5csawV4YXMNGNQQXvLRps3z2Z59OPO+I=
|
||||
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/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c h1:kMFnB0vCcX7IL/m9Y5LO+KQYv+t1CQOiFe6+SV2J7bE=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20230923063757-afb1ddc0824c/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
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/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
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.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
|
||||
github.com/cloudflare/circl v1.2.0 h1:NheeISPSUcYftKlfrLuOo4T62FkmD4t4jviLfFFYaec=
|
||||
github.com/cloudflare/circl v1.2.0/go.mod h1:Ch2UgYr6ti2KTtlejELlROl0YIYj7SLjAC8M+INXlMk=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cloudflare/circl v1.3.6 h1:/xbKIqSHbZXHwkhbrhrt2YOHIwYJlXH94E3tI/gDlUg=
|
||||
github.com/cloudflare/circl v1.3.6/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
|
||||
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||
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/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.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0=
|
||||
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/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/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
|
||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
|
||||
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
|
||||
github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||
github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
|
||||
github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
|
||||
github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
|
||||
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/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
|
||||
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||
github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4=
|
||||
github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
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/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/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.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
|
||||
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/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
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.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
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/xanzy/ssh-agent v0.3.2 h1:eKj4SX2Fe7mui28ZgnFW5fmTz1EIr7ugo5s6wDxdHBM=
|
||||
github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
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.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
|
||||
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
|
||||
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
|
||||
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-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.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
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-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-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-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-20211007075335-d3039528d8ac/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-20220520151302-bc2c85ada10a/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.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.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.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.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.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
|
||||
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
|
||||
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
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.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/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 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
|
||||
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// Package cheatpath implements functions pertaining to cheatsheet file path
|
||||
// management.
|
||||
package cheatpath
|
||||
|
||||
// Cheatpath encapsulates cheatsheet path information
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// Package config implements functions pertaining to configuration management.
|
||||
package config
|
||||
|
||||
import (
|
||||
|
@ -24,7 +25,7 @@ type 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
|
||||
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
|
||||
|
||||
import (
|
||||
|
@ -6,7 +8,8 @@ import (
|
|||
"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 {
|
||||
// make `str` faint only if colorization has been requested
|
||||
if conf.Colorize {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// Package installer implements functions that provide a first-time
|
||||
// installation wizard.
|
||||
package installer
|
||||
|
||||
import (
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// Package mock implements mock functions used in unit-tests.
|
||||
package mock
|
||||
|
||||
import (
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// Package repo implements functions pertaining to the management of git repos.
|
||||
package repo
|
||||
|
||||
import (
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// Package sheet implements functions pertaining to parsing, searching, and
|
||||
// displaying cheatsheets.
|
||||
package sheet
|
||||
|
||||
import (
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// Package sheets implements functions pertaining to loading, sorting,
|
||||
// filtering, and tagging cheatsheets.
|
||||
package sheets
|
||||
|
||||
import (
|
||||
|
|
|
@ -9,4 +9,4 @@ name = "go"
|
|||
enabled = true
|
||||
|
||||
[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
|
||||
|
||||
|
||||
[![GoDoc][3]][4]
|
||||
[![GitHub release][5]][6]
|
||||
[![GoCard][7]][8]
|
||||
[![Build Status][1]][2]
|
||||
[![Coverage Status][9]][10]
|
||||
[![Test status][1]][2]
|
||||
[![OpenSSF Scorecard][21]][22]
|
||||
[![OpenSSF Best Practices][19]][20]
|
||||
[![Coverage status][9]][10]
|
||||
[![Sourcegraph][11]][12]
|
||||
[![FOSSA Status][13]][14]
|
||||
[![Become my sponsor][15]][16]
|
||||
[![FOSSA status][13]][14]
|
||||
|
||||
[1]: https://travis-ci.org/imdario/mergo.png
|
||||
[2]: https://travis-ci.org/imdario/mergo
|
||||
[![GoDoc][3]][4]
|
||||
[![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
|
||||
[4]: https://godoc.org/github.com/imdario/mergo
|
||||
[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
|
||||
[15]: https://img.shields.io/github/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.
|
||||
|
||||
|
@ -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).
|
||||
|
||||
### 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.
|
||||
|
||||
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
|
||||
|
||||
|
@ -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
|
||||
|
||||
- [cli/cli](https://github.com/cli/cli)
|
||||
- [moby/moby](https://github.com/moby/moby)
|
||||
- [kubernetes/kubernetes](https://github.com/kubernetes/kubernetes)
|
||||
- [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
|
||||
|
||||
go get github.com/imdario/mergo
|
||||
go get dario.cat/mergo
|
||||
|
||||
// use in your .go code
|
||||
import (
|
||||
"github.com/imdario/mergo"
|
||||
"dario.cat/mergo"
|
||||
)
|
||||
|
||||
## Usage
|
||||
|
@ -144,7 +158,7 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/imdario/mergo"
|
||||
"dario.cat/mergo"
|
||||
)
|
||||
|
||||
type Foo struct {
|
||||
|
@ -180,9 +194,9 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/imdario/mergo"
|
||||
"reflect"
|
||||
"time"
|
||||
"dario.cat/mergo"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type timeTransformer struct {
|
||||
|
@ -231,5 +245,4 @@ Written by [Dario Castañé](http://dario.im).
|
|||
|
||||
[BSD 3-Clause](http://opensource.org/licenses/BSD-3-Clause) license, as [Go language](http://golang.org/LICENSE).
|
||||
|
||||
|
||||
[![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).
|
||||
|
||||
Status
|
||||
# Status
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
go get github.com/imdario/mergo
|
||||
go get dario.cat/mergo
|
||||
|
||||
// use in your .go code
|
||||
import (
|
||||
"github.com/imdario/mergo"
|
||||
)
|
||||
// use in your .go code
|
||||
import (
|
||||
"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).
|
||||
|
||||
|
@ -59,7 +65,7 @@ Here is a nice example:
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/imdario/mergo"
|
||||
"dario.cat/mergo"
|
||||
)
|
||||
|
||||
type Foo struct {
|
||||
|
@ -81,7 +87,7 @@ Here is a nice example:
|
|||
// {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?
|
||||
|
||||
|
@ -89,9 +95,9 @@ Transformers allow to merge specific types differently than in the default behav
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/imdario/mergo"
|
||||
"reflect"
|
||||
"time"
|
||||
"dario.cat/mergo"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
type timeTransformer struct {
|
||||
|
@ -127,17 +133,16 @@ Transformers allow to merge specific types differently than in the default behav
|
|||
// { 2018-01-12 01:15:00 +0000 UTC m=+0.000000001 }
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
About
|
||||
# About
|
||||
|
||||
Written by Dario Castañé: https://da.rio.hn
|
||||
|
||||
License
|
||||
# License
|
||||
|
||||
BSD 3-Clause license, as Go language.
|
||||
|
||||
*/
|
||||
package mergo
|
|
@ -44,7 +44,7 @@ func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, conf
|
|||
}
|
||||
}
|
||||
// Remember, remember...
|
||||
visited[h] = &visit{addr, typ, seen}
|
||||
visited[h] = &visit{typ, seen, addr}
|
||||
}
|
||||
zeroValue := reflect.Value{}
|
||||
switch dst.Kind() {
|
||||
|
@ -58,7 +58,7 @@ func deepMap(dst, src reflect.Value, visited map[uintptr]*visit, depth int, conf
|
|||
}
|
||||
fieldName := field.Name
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ func MapWithOverwrite(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 {
|
||||
return ErrNonPointerAgument
|
||||
return ErrNonPointerArgument
|
||||
}
|
||||
var (
|
||||
vDst, vSrc reflect.Value
|
|
@ -38,10 +38,11 @@ func isExportedComponent(field *reflect.StructField) bool {
|
|||
}
|
||||
|
||||
type Config struct {
|
||||
Transformers Transformers
|
||||
Overwrite bool
|
||||
ShouldNotDereference bool
|
||||
AppendSlice bool
|
||||
TypeCheck bool
|
||||
Transformers Transformers
|
||||
overwriteWithEmptyValue bool
|
||||
overwriteSliceWithEmptyValue bool
|
||||
sliceDeepCopy bool
|
||||
|
@ -76,7 +77,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
|
|||
}
|
||||
}
|
||||
// Remember, remember...
|
||||
visited[h] = &visit{addr, typ, seen}
|
||||
visited[h] = &visit{typ, seen, addr}
|
||||
}
|
||||
|
||||
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 {
|
||||
if dst.CanSet() && (isReflectNil(dst) || overwrite) && (!isEmptyValue(src) || overwriteWithEmptySrc) {
|
||||
if dst.CanSet() && (isReflectNil(dst) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc) {
|
||||
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 overwrite {
|
||||
if overwrite && dst.CanSet() {
|
||||
dst.Set(src)
|
||||
}
|
||||
return
|
||||
|
@ -162,7 +163,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
|
|||
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() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
if dstElement.IsValid() && !isEmptyValue(dstElement) && (reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map || reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice) {
|
||||
continue
|
||||
|
||||
if dstElement.IsValid() && !isEmptyValue(dstElement, !config.ShouldNotDereference) {
|
||||
if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Slice {
|
||||
continue
|
||||
}
|
||||
if reflect.TypeOf(srcElement.Interface()).Kind() == reflect.Map && reflect.TypeOf(dstElement.Interface()).Kind() == reflect.Map {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if srcElement.IsValid() && ((srcElement.Kind() != reflect.Ptr && overwrite) || !dstElement.IsValid() || isEmptyValue(dstElement)) {
|
||||
if srcElement.IsValid() && ((srcElement.Kind() != reflect.Ptr && overwrite) || !dstElement.IsValid() || isEmptyValue(dstElement, !config.ShouldNotDereference)) {
|
||||
if dst.IsNil() {
|
||||
dst.Set(reflect.MakeMap(dst.Type()))
|
||||
}
|
||||
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:
|
||||
if !dst.CanSet() {
|
||||
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)
|
||||
} else if config.AppendSlice {
|
||||
if src.Type() != dst.Type() {
|
||||
|
@ -244,12 +261,18 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
|
|||
|
||||
if src.Kind() != reflect.Interface {
|
||||
if dst.IsNil() || (src.Kind() != reflect.Ptr && overwrite) {
|
||||
if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
|
||||
if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) {
|
||||
dst.Set(src)
|
||||
}
|
||||
} else if src.Kind() == reflect.Ptr {
|
||||
if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
|
||||
return
|
||||
if !config.ShouldNotDereference {
|
||||
if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if overwriteWithEmptySrc || (overwrite && !src.IsNil()) || dst.IsNil() {
|
||||
dst.Set(src)
|
||||
}
|
||||
}
|
||||
} else if dst.Elem().Type() == src.Type() {
|
||||
if err = deepMerge(dst.Elem(), src, visited, depth+1, config); err != nil {
|
||||
|
@ -262,7 +285,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
|
|||
}
|
||||
|
||||
if dst.IsNil() || overwrite {
|
||||
if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
|
||||
if dst.CanSet() && (overwrite || isEmptyValue(dst, !config.ShouldNotDereference)) {
|
||||
dst.Set(src)
|
||||
}
|
||||
break
|
||||
|
@ -275,7 +298,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
|
|||
break
|
||||
}
|
||||
default:
|
||||
mustSet := (isEmptyValue(dst) || overwrite) && (!isEmptyValue(src) || overwriteWithEmptySrc)
|
||||
mustSet := (isEmptyValue(dst, !config.ShouldNotDereference) || overwrite) && (!isEmptyValue(src, !config.ShouldNotDereference) || overwriteWithEmptySrc)
|
||||
if mustSet {
|
||||
if dst.CanSet() {
|
||||
dst.Set(src)
|
||||
|
@ -326,6 +349,12 @@ func WithOverrideEmptySlice(config *Config) {
|
|||
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.
|
||||
func WithAppendSlice(config *Config) {
|
||||
config.AppendSlice = true
|
||||
|
@ -344,7 +373,7 @@ func WithSliceDeepCopy(config *Config) {
|
|||
|
||||
func merge(dst, src interface{}, opts ...func(*Config)) error {
|
||||
if dst != nil && reflect.ValueOf(dst).Kind() != reflect.Ptr {
|
||||
return ErrNonPointerAgument
|
||||
return ErrNonPointerArgument
|
||||
}
|
||||
var (
|
||||
vDst, vSrc reflect.Value
|
|
@ -20,7 +20,7 @@ var (
|
|||
ErrNotSupported = errors.New("only structs, maps, and slices are supported")
|
||||
ErrExpectedMapAsDestination = errors.New("dst was expected to be a map")
|
||||
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
|
||||
|
@ -28,13 +28,13 @@ var (
|
|||
// checks in progress are true when it reencounters them.
|
||||
// Visited are stored in a map indexed by 17 * a1 + a2;
|
||||
type visit struct {
|
||||
ptr uintptr
|
||||
typ reflect.Type
|
||||
next *visit
|
||||
ptr uintptr
|
||||
}
|
||||
|
||||
// From src/pkg/encoding/json/encode.go.
|
||||
func isEmptyValue(v reflect.Value) bool {
|
||||
func isEmptyValue(v reflect.Value, shouldDereference bool) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
|
@ -50,7 +50,10 @@ func isEmptyValue(v reflect.Value) bool {
|
|||
if v.IsNil() {
|
||||
return true
|
||||
}
|
||||
return isEmptyValue(v.Elem())
|
||||
if shouldDereference {
|
||||
return isEmptyValue(v.Elem(), shouldDereference)
|
||||
}
|
||||
return false
|
||||
case reflect.Func:
|
||||
return v.IsNil()
|
||||
case reflect.Invalid:
|
|
@ -8,12 +8,8 @@ linters:
|
|||
- containedctx # struct contains a context
|
||||
- dupl # duplicate code
|
||||
- errname # erorrs are named correctly
|
||||
- goconst # strings that should be constants
|
||||
- godot # comments end in a period
|
||||
- misspell
|
||||
- nolintlint # "//nolint" directives are properly explained
|
||||
- revive # golint replacement
|
||||
- stylecheck # golint replacement, less configurable than revive
|
||||
- unconvert # unnecessary conversions
|
||||
- wastedassign
|
||||
|
||||
|
@ -23,10 +19,7 @@ linters:
|
|||
- exhaustive # check exhaustiveness of enum switch statements
|
||||
- gofmt # files are gofmt'ed
|
||||
- gosec # security
|
||||
- nestif # deeply nested ifs
|
||||
- nilerr # returns nil even with non-nil error
|
||||
- prealloc # slices that can be pre-allocated
|
||||
- structcheck # unused struct fields
|
||||
- unparam # unused function params
|
||||
|
||||
issues:
|
||||
|
@ -42,6 +35,18 @@ issues:
|
|||
text: "^line-length-limit: "
|
||||
source: "^//(go:generate|sys) "
|
||||
|
||||
#TODO: remove after upgrading to go1.18
|
||||
# ignore comment spacing for nolint and sys directives
|
||||
- linters:
|
||||
- revive
|
||||
text: "^comment-spacings: no space between comment delimiter and comment text"
|
||||
source: "//(cspell:|nolint:|sys |todo)"
|
||||
|
||||
# not on go 1.18 yet, so no any
|
||||
- linters:
|
||||
- revive
|
||||
text: "^use-any: since GO 1.18 'interface{}' can be replaced by 'any'"
|
||||
|
||||
# allow unjustified ignores of error checks in defer statements
|
||||
- linters:
|
||||
- nolintlint
|
||||
|
@ -56,6 +61,8 @@ issues:
|
|||
|
||||
|
||||
linters-settings:
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: true
|
||||
govet:
|
||||
enable-all: true
|
||||
disable:
|
||||
|
@ -98,6 +105,8 @@ linters-settings:
|
|||
disabled: true
|
||||
- name: flag-parameter # excessive, and a common idiom we use
|
||||
disabled: true
|
||||
- name: unhandled-error # warns over common fmt.Print* and io.Close; rely on errcheck instead
|
||||
disabled: true
|
||||
# general config
|
||||
- name: line-length-limit
|
||||
arguments:
|
||||
|
@ -138,7 +147,3 @@ linters-settings:
|
|||
- VPCI
|
||||
- WCOW
|
||||
- WIM
|
||||
stylecheck:
|
||||
checks:
|
||||
- "all"
|
||||
- "-ST1003" # use revive's var naming
|
||||
|
|
|
@ -23,7 +23,7 @@ import (
|
|||
const afHVSock = 34 // AF_HYPERV
|
||||
|
||||
// Well known Service and VM IDs
|
||||
//https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/make-integration-service#vmid-wildcards
|
||||
// https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/make-integration-service#vmid-wildcards
|
||||
|
||||
// HvsockGUIDWildcard is the wildcard VmId for accepting connections from all partitions.
|
||||
func HvsockGUIDWildcard() guid.GUID { // 00000000-0000-0000-0000-000000000000
|
||||
|
@ -31,7 +31,7 @@ func HvsockGUIDWildcard() guid.GUID { // 00000000-0000-0000-0000-000000000000
|
|||
}
|
||||
|
||||
// HvsockGUIDBroadcast is the wildcard VmId for broadcasting sends to all partitions.
|
||||
func HvsockGUIDBroadcast() guid.GUID { //ffffffff-ffff-ffff-ffff-ffffffffffff
|
||||
func HvsockGUIDBroadcast() guid.GUID { // ffffffff-ffff-ffff-ffff-ffffffffffff
|
||||
return guid.GUID{
|
||||
Data1: 0xffffffff,
|
||||
Data2: 0xffff,
|
||||
|
@ -246,7 +246,7 @@ func (l *HvsockListener) Accept() (_ net.Conn, err error) {
|
|||
var addrbuf [addrlen * 2]byte
|
||||
|
||||
var bytes uint32
|
||||
err = syscall.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0 /*rxdatalen*/, addrlen, addrlen, &bytes, &c.o)
|
||||
err = syscall.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0 /* rxdatalen */, addrlen, addrlen, &bytes, &c.o)
|
||||
if _, err = l.sock.asyncIO(c, nil, bytes, err); err != nil {
|
||||
return nil, l.opErr("accept", os.NewSyscallError("acceptex", err))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -100,8 +100,8 @@ func (f *runtimeFunc) Load() error {
|
|||
(*byte)(unsafe.Pointer(&f.addr)),
|
||||
uint32(unsafe.Sizeof(f.addr)),
|
||||
&n,
|
||||
nil, //overlapped
|
||||
0, //completionRoutine
|
||||
nil, // overlapped
|
||||
0, // completionRoutine
|
||||
)
|
||||
})
|
||||
return f.err
|
||||
|
|
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"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
||||
"github.com/Microsoft/go-winio/internal/fs"
|
||||
)
|
||||
|
||||
//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 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 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
|
||||
|
@ -163,19 +164,21 @@ func (s pipeAddress) String() string {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return syscall.Handle(0), ctx.Err()
|
||||
default:
|
||||
h, err := createFile(*path,
|
||||
wh, err := fs.CreateFile(*path,
|
||||
access,
|
||||
0,
|
||||
nil,
|
||||
syscall.OPEN_EXISTING,
|
||||
windows.FILE_FLAG_OVERLAPPED|windows.SECURITY_SQOS_PRESENT|windows.SECURITY_ANONYMOUS,
|
||||
0)
|
||||
0, // mode
|
||||
nil, // security attributes
|
||||
fs.OPEN_EXISTING,
|
||||
fs.FILE_FLAG_OVERLAPPED|fs.SECURITY_SQOS_PRESENT|fs.SECURITY_ANONYMOUS,
|
||||
0, // template file handle
|
||||
)
|
||||
h := syscall.Handle(wh)
|
||||
if err == nil {
|
||||
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) {
|
||||
var err error
|
||||
var h syscall.Handle
|
||||
h, err = tryDialPipe(ctx, &path, access)
|
||||
h, err = tryDialPipe(ctx, &path, fs.AccessMask(access))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -279,6 +282,7 @@ func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (sy
|
|||
}
|
||||
defer localFree(ntPath.Buffer)
|
||||
oa.ObjectName = &ntPath
|
||||
oa.Attributes = windows.OBJ_CASE_INSENSITIVE
|
||||
|
||||
// The security descriptor is only needed for the first pipe.
|
||||
if first {
|
||||
|
|
|
@ -63,7 +63,6 @@ var (
|
|||
procBackupWrite = modkernel32.NewProc("BackupWrite")
|
||||
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
|
||||
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
|
||||
procCreateFileW = modkernel32.NewProc("CreateFileW")
|
||||
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
|
||||
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
|
||||
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
|
||||
|
@ -305,24 +304,6 @@ func connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) {
|
|||
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) {
|
||||
r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount), 0, 0)
|
||||
newport = syscall.Handle(r0)
|
||||
|
|
|
@ -191,7 +191,7 @@ func (bitCurve *BitCurve) doubleJacobian(x, y, z *big.Int) (*big.Int, *big.Int,
|
|||
return x3, y3, z3
|
||||
}
|
||||
|
||||
//TODO: double check if it is okay
|
||||
// TODO: double check if it is okay
|
||||
// ScalarMult returns k*(Bx,By) where k is a number in big-endian form.
|
||||
func (bitCurve *BitCurve) ScalarMult(Bx, By *big.Int, k []byte) (*big.Int, *big.Int) {
|
||||
// We have a slight problem in that the identity of the group (the
|
||||
|
@ -239,7 +239,7 @@ func (bitCurve *BitCurve) ScalarBaseMult(k []byte) (*big.Int, *big.Int) {
|
|||
|
||||
var mask = []byte{0xff, 0x1, 0x3, 0x7, 0xf, 0x1f, 0x3f, 0x7f}
|
||||
|
||||
//TODO: double check if it is okay
|
||||
// TODO: double check if it is okay
|
||||
// GenerateKey returns a public/private key pair. The private key is generated
|
||||
// using the given reader, which must return random data.
|
||||
func (bitCurve *BitCurve) GenerateKey(rand io.Reader) (priv []byte, x, y *big.Int, err error) {
|
||||
|
|
|
@ -80,4 +80,4 @@ func (curve *rcurve) ScalarMult(x1, y1 *big.Int, scalar []byte) (x, y *big.Int)
|
|||
|
||||
func (curve *rcurve) ScalarBaseMult(scalar []byte) (x, y *big.Int) {
|
||||
return curve.fromTwisted(curve.twisted.ScalarBaseMult(scalar))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ func (e *eax) Seal(dst, nonce, plaintext, adata []byte) []byte {
|
|||
if len(nonce) > e.nonceSize {
|
||||
panic("crypto/eax: Nonce too long for this instance")
|
||||
}
|
||||
ret, out := byteutil.SliceForAppend(dst, len(plaintext) + e.tagSize)
|
||||
ret, out := byteutil.SliceForAppend(dst, len(plaintext)+e.tagSize)
|
||||
omacNonce := e.omacT(0, nonce)
|
||||
omacAdata := e.omacT(1, adata)
|
||||
|
||||
|
@ -85,7 +85,7 @@ func (e *eax) Seal(dst, nonce, plaintext, adata []byte) []byte {
|
|||
return ret
|
||||
}
|
||||
|
||||
func (e* eax) Open(dst, nonce, ciphertext, adata []byte) ([]byte, error) {
|
||||
func (e *eax) Open(dst, nonce, ciphertext, adata []byte) ([]byte, error) {
|
||||
if len(nonce) > e.nonceSize {
|
||||
panic("crypto/eax: Nonce too long for this instance")
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ func ShiftNBytesLeft(dst, x []byte, n int) {
|
|||
bits := uint(n % 8)
|
||||
l := len(dst)
|
||||
for i := 0; i < l-1; i++ {
|
||||
dst[i] = (dst[i] << bits) | (dst[i+1] >> uint(8 - bits))
|
||||
dst[i] = (dst[i] << bits) | (dst[i+1] >> uint(8-bits))
|
||||
}
|
||||
dst[l-1] = dst[l-1] << bits
|
||||
|
||||
|
@ -56,7 +56,6 @@ func XorBytesMut(X, Y []byte) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// XorBytes assumes equal input length, puts X XOR Y into Z
|
||||
func XorBytes(Z, X, Y []byte) {
|
||||
for i := 0; i < len(X); i++ {
|
||||
|
@ -67,10 +66,10 @@ func XorBytes(Z, X, Y []byte) {
|
|||
// RightXor XORs smaller input (assumed Y) at the right of the larger input (assumed X)
|
||||
func RightXor(X, Y []byte) []byte {
|
||||
offset := len(X) - len(Y)
|
||||
xored := make([]byte, len(X));
|
||||
xored := make([]byte, len(X))
|
||||
copy(xored, X)
|
||||
for i := 0; i < len(Y); i++ {
|
||||
xored[offset + i] ^= Y[i]
|
||||
xored[offset+i] ^= Y[i]
|
||||
}
|
||||
return xored
|
||||
}
|
||||
|
@ -89,4 +88,3 @@ func SliceForAppend(in []byte, n int) (head, tail []byte) {
|
|||
tail = head[len(in):]
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -93,13 +93,13 @@ func NewOCBWithNonceAndTagSize(
|
|||
return nil, ocbError("Custom tag length exceeds blocksize")
|
||||
}
|
||||
return &ocb{
|
||||
block: block,
|
||||
tagSize: tagSize,
|
||||
nonceSize: nonceSize,
|
||||
mask: initializeMaskTable(block),
|
||||
block: block,
|
||||
tagSize: tagSize,
|
||||
nonceSize: nonceSize,
|
||||
mask: initializeMaskTable(block),
|
||||
reusableKtop: reusableKtop{
|
||||
noncePrefix: nil,
|
||||
Ktop: nil,
|
||||
Ktop: nil,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -4,21 +4,22 @@ package ocb
|
|||
var rfc7253TestVectorTaglen96 = struct {
|
||||
key, nonce, header, plaintext, ciphertext string
|
||||
}{"0F0E0D0C0B0A09080706050403020100",
|
||||
"BBAA9988776655443322110D",
|
||||
"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627",
|
||||
"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627",
|
||||
"1792A4E31E0755FB03E31B22116E6C2DDF9EFD6E33D536F1A0124B0A55BAE884ED93481529C76B6AD0C515F4D1CDD4FDAC4F02AA"}
|
||||
"BBAA9988776655443322110D",
|
||||
"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627",
|
||||
"000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F2021222324252627",
|
||||
"1792A4E31E0755FB03E31B22116E6C2DDF9EFD6E33D536F1A0124B0A55BAE884ED93481529C76B6AD0C515F4D1CDD4FDAC4F02AA"}
|
||||
|
||||
var rfc7253AlgorithmTest = []struct {
|
||||
KEYLEN, TAGLEN int
|
||||
OUTPUT string }{
|
||||
{128, 128, "67E944D23256C5E0B6C61FA22FDF1EA2"},
|
||||
{192, 128, "F673F2C3E7174AAE7BAE986CA9F29E17"},
|
||||
{256, 128, "D90EB8E9C977C88B79DD793D7FFA161C"},
|
||||
{128, 96, "77A3D8E73589158D25D01209"},
|
||||
{192, 96, "05D56EAD2752C86BE6932C5E"},
|
||||
{256, 96, "5458359AC23B0CBA9E6330DD"},
|
||||
{128, 64, "192C9B7BD90BA06A"},
|
||||
{192, 64, "0066BC6E0EF34E24"},
|
||||
{256, 64, "7D4EA5D445501CBE"},
|
||||
}
|
||||
OUTPUT string
|
||||
}{
|
||||
{128, 128, "67E944D23256C5E0B6C61FA22FDF1EA2"},
|
||||
{192, 128, "F673F2C3E7174AAE7BAE986CA9F29E17"},
|
||||
{256, 128, "D90EB8E9C977C88B79DD793D7FFA161C"},
|
||||
{128, 96, "77A3D8E73589158D25D01209"},
|
||||
{192, 96, "05D56EAD2752C86BE6932C5E"},
|
||||
{256, 96, "5458359AC23B0CBA9E6330DD"},
|
||||
{128, 64, "192C9B7BD90BA06A"},
|
||||
{192, 64, "0066BC6E0EF34E24"},
|
||||
{256, 64, "7D4EA5D445501CBE"},
|
||||
}
|
||||
|
|
|
@ -10,19 +10,22 @@ import (
|
|||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
||||
"io"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
||||
)
|
||||
|
||||
// A Block represents an OpenPGP armored structure.
|
||||
//
|
||||
// The encoded form is:
|
||||
// -----BEGIN Type-----
|
||||
// Headers
|
||||
//
|
||||
// base64-encoded Bytes
|
||||
// '=' base64 encoded checksum
|
||||
// -----END Type-----
|
||||
// -----BEGIN Type-----
|
||||
// Headers
|
||||
//
|
||||
// base64-encoded Bytes
|
||||
// '=' base64 encoded checksum
|
||||
// -----END Type-----
|
||||
//
|
||||
// where Headers is a possibly empty sequence of Key: Value lines.
|
||||
//
|
||||
// Since the armored data can be very large, this package presents a streaming
|
||||
|
@ -206,12 +209,16 @@ TryNextBlock:
|
|||
break
|
||||
}
|
||||
|
||||
i := bytes.Index(line, []byte(": "))
|
||||
i := bytes.Index(line, []byte(":"))
|
||||
if i == -1 {
|
||||
goto TryNextBlock
|
||||
}
|
||||
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
|
||||
|
|
|
@ -96,7 +96,8 @@ func (l *lineBreaker) Close() (err error) {
|
|||
// trailer.
|
||||
//
|
||||
// It's built into a stack of io.Writers:
|
||||
// encoding -> base64 encoder -> lineBreaker -> out
|
||||
//
|
||||
// encoding -> base64 encoder -> lineBreaker -> out
|
||||
type encoding struct {
|
||||
out io.Writer
|
||||
breaker *lineBreaker
|
||||
|
|
|
@ -34,7 +34,7 @@ type PrivateKey struct {
|
|||
|
||||
func NewPublicKey(curve ecc.ECDHCurve, kdfHash algorithm.Hash, kdfCipher algorithm.Cipher) *PublicKey {
|
||||
return &PublicKey{
|
||||
curve: curve,
|
||||
curve: curve,
|
||||
KDF: KDF{
|
||||
Hash: kdfHash,
|
||||
Cipher: kdfCipher,
|
||||
|
@ -167,7 +167,7 @@ func buildKey(pub *PublicKey, zb []byte, curveOID, fingerprint []byte, stripLead
|
|||
if _, err := param.Write(fingerprint[:20]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if param.Len() - len(curveOID) != 45 {
|
||||
if param.Len()-len(curveOID) != 45 {
|
||||
return nil, errors.New("ecdh: malformed KDF Param")
|
||||
}
|
||||
|
||||
|
@ -181,15 +181,19 @@ func buildKey(pub *PublicKey, zb []byte, curveOID, fingerprint []byte, stripLead
|
|||
j := zbLen - 1
|
||||
if stripLeading {
|
||||
// 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 {
|
||||
// Work around old OpenPGP.js bug where insignificant trailing zeros in
|
||||
// this little-endian number are missing.
|
||||
// (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
|
||||
}
|
||||
if _, err := h.Write(param.Bytes()); err != nil {
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
type PublicKey struct {
|
||||
X, Y *big.Int
|
||||
X, Y *big.Int
|
||||
curve ecc.ECDSACurve
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
)
|
||||
|
||||
type PublicKey struct {
|
||||
X []byte
|
||||
X []byte
|
||||
curve ecc.EdDSACurve
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
// 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
|
||||
// be used to break the cryptosystem. See ``Chosen Ciphertext Attacks
|
||||
// Against Protocols Based on the RSA Encryption Standard PKCS #1'', Daniel
|
||||
// be used to break the cryptosystem. See “Chosen Ciphertext Attacks
|
||||
// Against Protocols Based on the RSA Encryption Standard PKCS #1”, Daniel
|
||||
// Bleichenbacher, Advances in Cryptology (Crypto '98),
|
||||
func Decrypt(priv *PrivateKey, c1, c2 *big.Int) (msg []byte, err error) {
|
||||
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 (
|
||||
AEADModeEAX = AEADMode(1)
|
||||
AEADModeOCB = AEADMode(2)
|
||||
AEADModeGCM = AEADMode(100)
|
||||
AEADModeGCM = AEADMode(3)
|
||||
)
|
||||
|
||||
// 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.
|
||||
var (
|
||||
MD5 Hash = cryptoHash{1, crypto.MD5}
|
||||
SHA1 Hash = cryptoHash{2, crypto.SHA1}
|
||||
RIPEMD160 Hash = cryptoHash{3, crypto.RIPEMD160}
|
||||
SHA256 Hash = cryptoHash{8, crypto.SHA256}
|
||||
SHA384 Hash = cryptoHash{9, crypto.SHA384}
|
||||
SHA512 Hash = cryptoHash{10, crypto.SHA512}
|
||||
SHA224 Hash = cryptoHash{11, crypto.SHA224}
|
||||
SHA1 Hash = cryptoHash{2, crypto.SHA1}
|
||||
SHA256 Hash = cryptoHash{8, crypto.SHA256}
|
||||
SHA384 Hash = cryptoHash{9, crypto.SHA384}
|
||||
SHA512 Hash = cryptoHash{10, crypto.SHA512}
|
||||
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
|
||||
// http://www.iana.org/assignments/pgp-parameters/pgp-parameters.xhtml#pgp-parameters-14
|
||||
var (
|
||||
HashById = map[uint8]Hash{
|
||||
MD5.Id(): MD5,
|
||||
SHA1.Id(): SHA1,
|
||||
RIPEMD160.Id(): RIPEMD160,
|
||||
SHA256.Id(): SHA256,
|
||||
SHA384.Id(): SHA384,
|
||||
SHA512.Id(): SHA512,
|
||||
SHA224.Id(): SHA224,
|
||||
SHA256.Id(): SHA256,
|
||||
SHA384.Id(): SHA384,
|
||||
SHA512.Id(): SHA512,
|
||||
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{
|
||||
MD5.Id(): "MD5",
|
||||
SHA1.Id(): "SHA1",
|
||||
RIPEMD160.Id(): "RIPEMD160",
|
||||
SHA256.Id(): "SHA256",
|
||||
SHA384.Id(): "SHA384",
|
||||
SHA512.Id(): "SHA512",
|
||||
SHA224.Id(): "SHA224",
|
||||
SHA256.Id(): "SHA256",
|
||||
SHA384.Id(): "SHA384",
|
||||
SHA512.Id(): "SHA512",
|
||||
SHA224.Id(): "SHA224",
|
||||
SHA3_256.Id(): "SHA3-256",
|
||||
SHA3_512.Id(): "SHA3-512",
|
||||
}
|
||||
|
||||
func (h cryptoHash) String() string {
|
||||
|
@ -84,3 +82,62 @@ func (h cryptoHash) String() string {
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
x25519lib "github.com/cloudflare/circl/dh/x25519"
|
||||
)
|
||||
|
||||
type curve25519 struct {}
|
||||
type curve25519 struct{}
|
||||
|
||||
func NewCurve25519() *curve25519 {
|
||||
return &curve25519{}
|
||||
|
@ -21,14 +21,14 @@ func (c *curve25519) GetCurveName() string {
|
|||
|
||||
// MarshalBytePoint encodes the public point from native format, adding the prefix.
|
||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6
|
||||
func (c *curve25519) MarshalBytePoint(point [] byte) []byte {
|
||||
func (c *curve25519) MarshalBytePoint(point []byte) []byte {
|
||||
return append([]byte{0x40}, point...)
|
||||
}
|
||||
|
||||
// UnmarshalBytePoint decodes the public point to native format, removing the prefix.
|
||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6
|
||||
func (c *curve25519) UnmarshalBytePoint(point []byte) []byte {
|
||||
if len(point) != x25519lib.Size + 1 {
|
||||
if len(point) != x25519lib.Size+1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -11,76 +11,76 @@ import (
|
|||
|
||||
type CurveInfo struct {
|
||||
GenName string
|
||||
Oid *encoding.OID
|
||||
Curve Curve
|
||||
Oid *encoding.OID
|
||||
Curve Curve
|
||||
}
|
||||
|
||||
var Curves = []CurveInfo{
|
||||
{
|
||||
// NIST P-256
|
||||
GenName: "P256",
|
||||
Oid: encoding.NewOID([]byte{0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07}),
|
||||
Curve: NewGenericCurve(elliptic.P256()),
|
||||
Oid: encoding.NewOID([]byte{0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07}),
|
||||
Curve: NewGenericCurve(elliptic.P256()),
|
||||
},
|
||||
{
|
||||
// NIST P-384
|
||||
GenName: "P384",
|
||||
Oid: encoding.NewOID([]byte{0x2B, 0x81, 0x04, 0x00, 0x22}),
|
||||
Curve: NewGenericCurve(elliptic.P384()),
|
||||
Oid: encoding.NewOID([]byte{0x2B, 0x81, 0x04, 0x00, 0x22}),
|
||||
Curve: NewGenericCurve(elliptic.P384()),
|
||||
},
|
||||
{
|
||||
// NIST P-521
|
||||
GenName: "P521",
|
||||
Oid: encoding.NewOID([]byte{0x2B, 0x81, 0x04, 0x00, 0x23}),
|
||||
Curve: NewGenericCurve(elliptic.P521()),
|
||||
Oid: encoding.NewOID([]byte{0x2B, 0x81, 0x04, 0x00, 0x23}),
|
||||
Curve: NewGenericCurve(elliptic.P521()),
|
||||
},
|
||||
{
|
||||
// SecP256k1
|
||||
GenName: "SecP256k1",
|
||||
Oid: encoding.NewOID([]byte{0x2B, 0x81, 0x04, 0x00, 0x0A}),
|
||||
Curve: NewGenericCurve(bitcurves.S256()),
|
||||
Oid: encoding.NewOID([]byte{0x2B, 0x81, 0x04, 0x00, 0x0A}),
|
||||
Curve: NewGenericCurve(bitcurves.S256()),
|
||||
},
|
||||
{
|
||||
// Curve25519
|
||||
GenName: "Curve25519",
|
||||
Oid: encoding.NewOID([]byte{0x2B, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01}),
|
||||
Curve: NewCurve25519(),
|
||||
Oid: encoding.NewOID([]byte{0x2B, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01}),
|
||||
Curve: NewCurve25519(),
|
||||
},
|
||||
{
|
||||
// X448
|
||||
GenName: "Curve448",
|
||||
Oid: encoding.NewOID([]byte{0x2B, 0x65, 0x6F}),
|
||||
Curve: NewX448(),
|
||||
Oid: encoding.NewOID([]byte{0x2B, 0x65, 0x6F}),
|
||||
Curve: NewX448(),
|
||||
},
|
||||
{
|
||||
// Ed25519
|
||||
GenName: "Curve25519",
|
||||
Oid: encoding.NewOID([]byte{0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, 0x47, 0x0F, 0x01}),
|
||||
Curve: NewEd25519(),
|
||||
Oid: encoding.NewOID([]byte{0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, 0x47, 0x0F, 0x01}),
|
||||
Curve: NewEd25519(),
|
||||
},
|
||||
{
|
||||
// Ed448
|
||||
GenName: "Curve448",
|
||||
Oid: encoding.NewOID([]byte{0x2B, 0x65, 0x71}),
|
||||
Curve: NewEd448(),
|
||||
Oid: encoding.NewOID([]byte{0x2B, 0x65, 0x71}),
|
||||
Curve: NewEd448(),
|
||||
},
|
||||
{
|
||||
// BrainpoolP256r1
|
||||
GenName: "BrainpoolP256",
|
||||
Oid: encoding.NewOID([]byte{0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07}),
|
||||
Curve: NewGenericCurve(brainpool.P256r1()),
|
||||
Oid: encoding.NewOID([]byte{0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07}),
|
||||
Curve: NewGenericCurve(brainpool.P256r1()),
|
||||
},
|
||||
{
|
||||
// BrainpoolP384r1
|
||||
GenName: "BrainpoolP384",
|
||||
Oid: encoding.NewOID([]byte{0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B}),
|
||||
Curve: NewGenericCurve(brainpool.P384r1()),
|
||||
Oid: encoding.NewOID([]byte{0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B}),
|
||||
Curve: NewGenericCurve(brainpool.P384r1()),
|
||||
},
|
||||
{
|
||||
// BrainpoolP512r1
|
||||
GenName: "BrainpoolP512",
|
||||
Oid: encoding.NewOID([]byte{0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D}),
|
||||
Curve: NewGenericCurve(brainpool.P512r1()),
|
||||
Oid: encoding.NewOID([]byte{0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D}),
|
||||
Curve: NewGenericCurve(brainpool.P512r1()),
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ type EdDSACurve interface {
|
|||
type ECDHCurve interface {
|
||||
Curve
|
||||
MarshalBytePoint([]byte) (encoded []byte)
|
||||
UnmarshalBytePoint(encoded []byte) ([]byte)
|
||||
UnmarshalBytePoint(encoded []byte) []byte
|
||||
MarshalByteSecret(d []byte) []byte
|
||||
UnmarshalByteSecret(d []byte) []byte
|
||||
GenerateECDH(rand io.Reader) (point []byte, secret []byte, err error)
|
||||
|
|
|
@ -10,7 +10,8 @@ import (
|
|||
)
|
||||
|
||||
const ed25519Size = 32
|
||||
type ed25519 struct {}
|
||||
|
||||
type ed25519 struct{}
|
||||
|
||||
func NewEd25519() *ed25519 {
|
||||
return &ed25519{}
|
||||
|
@ -29,7 +30,7 @@ func (c *ed25519) MarshalBytePoint(x []byte) []byte {
|
|||
// UnmarshalBytePoint decodes a point from prefixed format to native.
|
||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
|
||||
func (c *ed25519) UnmarshalBytePoint(point []byte) (x []byte) {
|
||||
if len(point) != ed25519lib.PublicKeySize + 1 {
|
||||
if len(point) != ed25519lib.PublicKeySize+1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -52,7 +53,7 @@ func (c *ed25519) UnmarshalByteSecret(s []byte) (d []byte) {
|
|||
|
||||
// Handle stripped leading zeroes
|
||||
d = make([]byte, ed25519lib.SeedSize)
|
||||
copy(d[ed25519lib.SeedSize - len(s):], s)
|
||||
copy(d[ed25519lib.SeedSize-len(s):], s)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
ed448lib "github.com/cloudflare/circl/sign/ed448"
|
||||
)
|
||||
|
||||
type ed448 struct {}
|
||||
type ed448 struct{}
|
||||
|
||||
func NewEd448() *ed448 {
|
||||
return &ed448{}
|
||||
|
@ -29,7 +29,7 @@ func (c *ed448) MarshalBytePoint(x []byte) []byte {
|
|||
// UnmarshalBytePoint decodes a point from prefixed format to native.
|
||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
|
||||
func (c *ed448) UnmarshalBytePoint(point []byte) (x []byte) {
|
||||
if len(point) != ed448lib.PublicKeySize + 1 {
|
||||
if len(point) != ed448lib.PublicKeySize+1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ func (c *ed448) MarshalByteSecret(d []byte) []byte {
|
|||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.5
|
||||
func (c *ed448) UnmarshalByteSecret(s []byte) (d []byte) {
|
||||
// Check prefixed size
|
||||
if len(s) != ed448lib.SeedSize + 1 {
|
||||
if len(s) != ed448lib.SeedSize+1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ func (c *ed448) MarshalSignature(sig []byte) (r, s []byte) {
|
|||
// UnmarshalSignature decodes R and S in the native format. Only R is used, in prefixed native format.
|
||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.2.3.3.2
|
||||
func (c *ed448) UnmarshalSignature(r, s []byte) (sig []byte) {
|
||||
if len(r) != ed448lib.SignatureSize + 1 {
|
||||
if len(r) != ed448lib.SignatureSize+1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
x448lib "github.com/cloudflare/circl/dh/x448"
|
||||
)
|
||||
|
||||
type x448 struct {}
|
||||
type x448 struct{}
|
||||
|
||||
func NewX448() *x448 {
|
||||
return &x448{}
|
||||
|
@ -28,7 +28,7 @@ func (c *x448) MarshalBytePoint(point []byte) []byte {
|
|||
// UnmarshalBytePoint decodes a point from prefixed format to native.
|
||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6
|
||||
func (c *x448) UnmarshalBytePoint(point []byte) []byte {
|
||||
if len(point) != x448lib.Size + 1 {
|
||||
if len(point) != x448lib.Size+1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ func (c *x448) MarshalByteSecret(d []byte) []byte {
|
|||
// UnmarshalByteSecret decodes a scalar from prefixed format to native.
|
||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh-06#section-5.5.5.6.1.2
|
||||
func (c *x448) UnmarshalByteSecret(d []byte) []byte {
|
||||
if len(d) != x448lib.Size + 1 {
|
||||
if len(d) != x448lib.Size+1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,9 @@ func (c *x448) GenerateECDH(rand io.Reader) (point []byte, secret []byte, err er
|
|||
func (c *x448) Encaps(rand io.Reader, point []byte) (ephemeral, sharedSecret []byte, err error) {
|
||||
var pk, ss x448lib.Key
|
||||
seed, e, err := c.generateKeyPairBytes(rand)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
copy(pk[:], point)
|
||||
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
|
||||
|
||||
selfSignature := &packet.Signature{
|
||||
Version: primary.PublicKey.Version,
|
||||
SigType: packet.SigTypePositiveCert,
|
||||
PubKeyAlgo: primary.PublicKey.PubKeyAlgo,
|
||||
Hash: config.Hash(),
|
||||
CreationTime: creationTime,
|
||||
KeyLifetimeSecs: &keyLifetimeSecs,
|
||||
IssuerKeyId: &primary.PublicKey.KeyId,
|
||||
IssuerFingerprint: primary.PublicKey.Fingerprint,
|
||||
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,
|
||||
}
|
||||
selfSignature := createSignaturePacket(&primary.PublicKey, packet.SigTypePositiveCert, config)
|
||||
selfSignature.CreationTime = creationTime
|
||||
selfSignature.KeyLifetimeSecs = &keyLifetimeSecs
|
||||
selfSignature.IsPrimaryId = &isPrimaryId
|
||||
selfSignature.FlagsValid = true
|
||||
selfSignature.FlagSign = true
|
||||
selfSignature.FlagCertify = true
|
||||
selfSignature.SEIPDv1 = true // true by default, see 5.8 vs. 5.14
|
||||
selfSignature.SEIPDv2 = config.AEAD() != nil
|
||||
|
||||
// Set the PreferredHash for the SelfSignature from the packet.Config.
|
||||
// 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 {
|
||||
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.
|
||||
selfSignature.PreferredAEAD = []uint8{uint8(config.AEAD().Mode())}
|
||||
if config.AEAD().Mode() != packet.AEADModeEAX {
|
||||
selfSignature.PreferredAEAD = append(selfSignature.PreferredAEAD, uint8(packet.AEADModeEAX))
|
||||
modes := []uint8{uint8(config.AEAD().Mode())}
|
||||
if config.AEAD().Mode() != packet.AEADModeOCB {
|
||||
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
|
||||
|
@ -153,42 +157,30 @@ func (e *Entity) AddSigningSubkey(config *packet.Config) error {
|
|||
return err
|
||||
}
|
||||
sub := packet.NewSignerPrivateKey(creationTime, subPrivRaw)
|
||||
sub.IsSubkey = true
|
||||
if config != nil && config.V5Keys {
|
||||
sub.UpgradeToV5()
|
||||
}
|
||||
|
||||
subkey := Subkey{
|
||||
PublicKey: &sub.PublicKey,
|
||||
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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
subkey.PublicKey.IsSubkey = true
|
||||
subkey.PrivateKey.IsSubkey = true
|
||||
if err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey, config); err != nil {
|
||||
err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -210,30 +202,24 @@ func (e *Entity) addEncryptionSubkey(config *packet.Config, creationTime time.Ti
|
|||
return err
|
||||
}
|
||||
sub := packet.NewDecrypterPrivateKey(creationTime, subPrivRaw)
|
||||
sub.IsSubkey = true
|
||||
if config != nil && config.V5Keys {
|
||||
sub.UpgradeToV5()
|
||||
}
|
||||
|
||||
subkey := Subkey{
|
||||
PublicKey: &sub.PublicKey,
|
||||
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
|
||||
subkey.PrivateKey.IsSubkey = true
|
||||
if err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey, config); err != nil {
|
||||
err = subkey.Sig.SignKey(subkey.PublicKey, e.PrivateKey, config)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// If we don't have any candidate subkeys for encryption and
|
||||
// the primary key doesn't have any usage metadata then we
|
||||
// assume that the primary key is ok. Or, if the primary key is
|
||||
// marked as ok to encrypt with, then we can obviously use it.
|
||||
if !i.SelfSignature.FlagsValid || i.SelfSignature.FlagEncryptCommunications &&
|
||||
// If we don't have any subkeys for encryption and the primary key
|
||||
// is marked as OK to encrypt with, then we can use it.
|
||||
if i.SelfSignature.FlagsValid && i.SelfSignature.FlagEncryptCommunications &&
|
||||
e.PrimaryKey.PubKeyAlgo.CanEncrypt() {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
// CertificationKey return the best candidate Key for certifying a key with this
|
||||
// Entity.
|
||||
func (e *Entity) CertificationKey(now time.Time) (Key, bool) {
|
||||
|
@ -203,8 +200,8 @@ func (e *Entity) signingKeyByIdUsage(now time.Time, id uint64, flags int) (Key,
|
|||
var maxTime time.Time
|
||||
for idx, subkey := range e.Subkeys {
|
||||
if subkey.Sig.FlagsValid &&
|
||||
(flags & packet.KeyFlagCertify == 0 || subkey.Sig.FlagCertify) &&
|
||||
(flags & packet.KeyFlagSign == 0 || subkey.Sig.FlagSign) &&
|
||||
(flags&packet.KeyFlagCertify == 0 || subkey.Sig.FlagCertify) &&
|
||||
(flags&packet.KeyFlagSign == 0 || subkey.Sig.FlagSign) &&
|
||||
subkey.PublicKey.PubKeyAlgo.CanSign() &&
|
||||
!subkey.PublicKey.KeyExpired(subkey.Sig, now) &&
|
||||
!subkey.Sig.SigExpired(now) &&
|
||||
|
@ -221,12 +218,11 @@ func (e *Entity) signingKeyByIdUsage(now time.Time, id uint64, flags int) (Key,
|
|||
return Key{e, subkey.PublicKey, subkey.PrivateKey, subkey.Sig, subkey.Revocations}, true
|
||||
}
|
||||
|
||||
// If we have no candidate subkey then we assume that it's ok to sign
|
||||
// with the primary key. Or, if the primary key is marked as ok to
|
||||
// sign with, then we can use it.
|
||||
if !i.SelfSignature.FlagsValid || (
|
||||
(flags & packet.KeyFlagCertify == 0 || i.SelfSignature.FlagCertify) &&
|
||||
(flags & packet.KeyFlagSign == 0 || i.SelfSignature.FlagSign)) &&
|
||||
// If we don't have any subkeys for signing and the primary key
|
||||
// is marked as OK to sign with, then we can use it.
|
||||
if i.SelfSignature.FlagsValid &&
|
||||
(flags&packet.KeyFlagCertify == 0 || i.SelfSignature.FlagCertify) &&
|
||||
(flags&packet.KeyFlagSign == 0 || i.SelfSignature.FlagSign) &&
|
||||
e.PrimaryKey.PubKeyAlgo.CanSign() &&
|
||||
(id == 0 || e.PrimaryKey.KeyId == id) {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Note that third-party revocation signatures are not supported.
|
||||
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.
|
||||
func (el EntityList) KeysByIdUsage(id uint64, requiredUsage byte) (keys []Key) {
|
||||
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
|
||||
if key.SelfSignature.FlagCertify {
|
||||
usage |= packet.KeyFlagCertify
|
||||
|
@ -331,7 +369,7 @@ func (el EntityList) KeysByIdUsage(id uint64, requiredUsage byte) (keys []Key) {
|
|||
func (el EntityList) DecryptionKeys() (keys []Key) {
|
||||
for _, e := range el {
|
||||
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})
|
||||
}
|
||||
}
|
||||
|
@ -466,7 +504,7 @@ EachPacket:
|
|||
// Else, ignoring the signature as it does not follow anything
|
||||
// we would know to attach it to.
|
||||
case *packet.PrivateKey:
|
||||
if pkt.IsSubkey == false {
|
||||
if !pkt.IsSubkey {
|
||||
packets.Unread(p)
|
||||
break EachPacket
|
||||
}
|
||||
|
@ -475,7 +513,7 @@ EachPacket:
|
|||
return nil, err
|
||||
}
|
||||
case *packet.PublicKey:
|
||||
if pkt.IsSubkey == false {
|
||||
if !pkt.IsSubkey {
|
||||
packets.Unread(p)
|
||||
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")
|
||||
}
|
||||
|
||||
sig := &packet.Signature{
|
||||
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
|
||||
}
|
||||
sig := createSignaturePacket(certificationKey.PublicKey, packet.SigTypeGenericCert, config)
|
||||
|
||||
signingUserID := config.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).
|
||||
// If config is nil, sensible defaults will be used.
|
||||
func (e *Entity) RevokeKey(reason packet.ReasonForRevocation, reasonText string, config *packet.Config) error {
|
||||
revSig := &packet.Signature{
|
||||
Version: e.PrimaryKey.Version,
|
||||
CreationTime: config.Now(),
|
||||
SigType: packet.SigTypeKeyRevocation,
|
||||
PubKeyAlgo: e.PrimaryKey.PubKeyAlgo,
|
||||
Hash: config.Hash(),
|
||||
RevocationReason: &reason,
|
||||
RevocationReasonText: reasonText,
|
||||
IssuerKeyId: &e.PrimaryKey.KeyId,
|
||||
}
|
||||
revSig := createSignaturePacket(e.PrimaryKey, packet.SigTypeKeyRevocation, config)
|
||||
revSig.RevocationReason = &reason
|
||||
revSig.RevocationReasonText = reasonText
|
||||
|
||||
if err := revSig.RevokeKey(e.PrimaryKey, e.PrivateKey, config); err != nil {
|
||||
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")
|
||||
}
|
||||
|
||||
revSig := &packet.Signature{
|
||||
Version: e.PrimaryKey.Version,
|
||||
CreationTime: config.Now(),
|
||||
SigType: packet.SigTypeSubkeyRevocation,
|
||||
PubKeyAlgo: e.PrimaryKey.PubKeyAlgo,
|
||||
Hash: config.Hash(),
|
||||
RevocationReason: &reason,
|
||||
RevocationReasonText: reasonText,
|
||||
IssuerKeyId: &e.PrimaryKey.KeyId,
|
||||
}
|
||||
revSig := createSignaturePacket(e.PrimaryKey, packet.SigTypeSubkeyRevocation, config)
|
||||
revSig.RevocationReason = &reason
|
||||
revSig.RevocationReasonText = reasonText
|
||||
|
||||
if err := revSig.RevokeSubkey(sk.PublicKey, e.PrivateKey, config); err != nil {
|
||||
return err
|
||||
|
|
|
@ -518,3 +518,21 @@ XLCBln+wdewpU4ChEffMUDRBfqfQco/YsMqWV7bHJHAO0eC/DMKCjyU90xdH7R/d
|
|||
QgqsfguR1PqPuJxpXV4bSr6CGAAAAA==
|
||||
=MSvh
|
||||
-----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"
|
||||
|
||||
// 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.
|
||||
// A nil AEADConfig is valid and results in all default values.
|
||||
type AEADConfig struct {
|
||||
|
@ -15,12 +23,13 @@ type AEADConfig struct {
|
|||
|
||||
// Mode returns the AEAD mode of operation.
|
||||
func (conf *AEADConfig) Mode() AEADMode {
|
||||
// If no preference is specified, OCB is used (which is mandatory to implement).
|
||||
if conf == nil || conf.DefaultMode == 0 {
|
||||
return AEADModeEAX
|
||||
return AEADModeOCB
|
||||
}
|
||||
|
||||
mode := conf.DefaultMode
|
||||
if mode != AEADModeEAX && mode != AEADModeOCB &&
|
||||
mode != AEADModeExperimentalGCM {
|
||||
if mode != AEADModeEAX && mode != AEADModeOCB && mode != AEADModeGCM {
|
||||
panic("AEAD mode unsupported")
|
||||
}
|
||||
return mode
|
||||
|
@ -28,6 +37,8 @@ func (conf *AEADConfig) Mode() AEADMode {
|
|||
|
||||
// ChunkSizeByte returns the byte indicating the chunk size. The effective
|
||||
// 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 {
|
||||
if conf == nil || conf.ChunkSize == 0 {
|
||||
return 12 // 1 << (12 + 6) == 262144 bytes
|
||||
|
@ -38,8 +49,8 @@ func (conf *AEADConfig) ChunkSizeByte() byte {
|
|||
switch {
|
||||
case exponent < 6:
|
||||
exponent = 6
|
||||
case exponent > 27:
|
||||
exponent = 27
|
||||
case exponent > 16:
|
||||
exponent = 16
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
||||
"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 {
|
||||
cipher CipherFunction
|
||||
mode AEADMode
|
||||
|
@ -25,33 +22,6 @@ type AEADEncrypted struct {
|
|||
// Only currently defined version
|
||||
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 {
|
||||
headerData := make([]byte, 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
|
||||
mode := AEADMode(headerData[2])
|
||||
nonceLen := mode.NonceLength()
|
||||
if nonceLen == 0 {
|
||||
nonceLen := mode.IvLength()
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
initialNonce := make([]byte, nonceLen)
|
||||
if n, err := io.ReadFull(buf, initialNonce); n < nonceLen {
|
||||
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.mode = mode
|
||||
ae.chunkSizeByte = byte(headerData[3])
|
||||
ae.chunkSizeByte = headerData[3]
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -105,225 +79,13 @@ func (ae *AEADEncrypted) decrypt(key []byte) (io.ReadCloser, error) {
|
|||
initialNonce: ae.initialNonce,
|
||||
associatedData: ae.associatedData(),
|
||||
chunkIndex: make([]byte, 8),
|
||||
packetTag: packetTypeAEADEncrypted,
|
||||
},
|
||||
reader: ae.Contents,
|
||||
peekedBytes: peekedBytes}, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
// associatedData for chunks: tag, version, cipher, mode, chunk size byte
|
||||
func (ae *AEADEncrypted) associatedData() []byte {
|
||||
return []byte{
|
||||
0xD4,
|
||||
|
@ -332,33 +94,3 @@ func (ae *AEADEncrypted) associatedData() []byte {
|
|||
byte(ae.mode),
|
||||
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"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp/s2k"
|
||||
)
|
||||
|
||||
// Config collects a number of parameters along with sensible defaults.
|
||||
|
@ -33,16 +35,24 @@ type Config struct {
|
|||
DefaultCompressionAlgo CompressionAlgo
|
||||
// CompressionConfig configures the compression settings.
|
||||
CompressionConfig *CompressionConfig
|
||||
// S2KCount is only used for symmetric encryption. It
|
||||
// determines the strength of the passphrase stretching when
|
||||
// S2K (String to Key) config, used for key derivation in the context of secret key encryption
|
||||
// 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
|
||||
// should be between 1024 and 65011712, inclusive. If Config
|
||||
// is nil or S2KCount is 0, the value 65536 used. Not all
|
||||
// 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.
|
||||
//
|
||||
// Deprecated: SK2Count should be configured in S2KConfig instead.
|
||||
S2KCount int
|
||||
// RSABits is the number of bits in new RSA keys made with NewEntity.
|
||||
// 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
|
||||
// mode of operation. It should be considered a measure of last resort.
|
||||
InsecureAllowUnauthenticatedMessages bool
|
||||
// KnownNotations is a map of Notation Data names to bools, which controls
|
||||
// the notation names that are allowed to be present in critical Notation Data
|
||||
// signature subpackets.
|
||||
KnownNotations map[string]bool
|
||||
// SignatureNotations is a list of Notations to be added to any signatures.
|
||||
SignatureNotations []*Notation
|
||||
}
|
||||
|
||||
func (c *Config) Random() io.Reader {
|
||||
|
@ -119,9 +135,9 @@ func (c *Config) Cipher() CipherFunction {
|
|||
|
||||
func (c *Config) Now() time.Time {
|
||||
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.
|
||||
|
@ -147,13 +163,6 @@ func (c *Config) Compression() CompressionAlgo {
|
|||
return c.DefaultCompressionAlgo
|
||||
}
|
||||
|
||||
func (c *Config) PasswordHashIterations() int {
|
||||
if c == nil || c.S2KCount == 0 {
|
||||
return 0
|
||||
}
|
||||
return c.S2KCount
|
||||
}
|
||||
|
||||
func (c *Config) RSAModulusBits() int {
|
||||
if c == nil || c.RSABits == 0 {
|
||||
return 2048
|
||||
|
@ -175,6 +184,27 @@ func (c *Config) CurveName() 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 {
|
||||
if c == nil {
|
||||
return nil
|
||||
|
@ -202,3 +232,17 @@ func (c *Config) AllowUnauthenticatedMessages() bool {
|
|||
}
|
||||
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 {
|
||||
KeyId uint64
|
||||
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
|
||||
|
||||
encryptedMPI1, encryptedMPI2 encoding.Field
|
||||
|
@ -123,6 +123,10 @@ func (e *EncryptedKey) Decrypt(priv *PrivateKey, config *Config) error {
|
|||
}
|
||||
|
||||
e.CipherFunc = CipherFunction(b[0])
|
||||
if !e.CipherFunc.IsSupported() {
|
||||
return errors.UnsupportedError("unsupported encryption function")
|
||||
}
|
||||
|
||||
e.Key = b[1 : len(b)-2]
|
||||
expectedChecksum := uint16(b[len(b)-2])<<8 | uint16(b[len(b)-1])
|
||||
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"
|
||||
"encoding/binary"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/s2k"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
@ -37,7 +37,7 @@ func (ops *OnePassSignature) parse(r io.Reader) (err error) {
|
|||
}
|
||||
|
||||
var ok bool
|
||||
ops.Hash, ok = s2k.HashIdToHash(buf[2])
|
||||
ops.Hash, ok = algorithm.HashIdToHashWithSha1(buf[2])
|
||||
if !ok {
|
||||
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[1] = uint8(ops.SigType)
|
||||
var ok bool
|
||||
buf[2], ok = s2k.HashToHashId(ops.Hash)
|
||||
buf[2], ok = algorithm.HashToHashIdWithSha1(ops.Hash)
|
||||
if !ok {
|
||||
return errors.UnsupportedError("hash type: " + strconv.Itoa(int(ops.Hash)))
|
||||
}
|
||||
|
|
|
@ -302,21 +302,21 @@ func consumeAll(r io.Reader) (n int64, err error) {
|
|||
type packetType uint8
|
||||
|
||||
const (
|
||||
packetTypeEncryptedKey packetType = 1
|
||||
packetTypeSignature packetType = 2
|
||||
packetTypeSymmetricKeyEncrypted packetType = 3
|
||||
packetTypeOnePassSignature packetType = 4
|
||||
packetTypePrivateKey packetType = 5
|
||||
packetTypePublicKey packetType = 6
|
||||
packetTypePrivateSubkey packetType = 7
|
||||
packetTypeCompressed packetType = 8
|
||||
packetTypeSymmetricallyEncrypted packetType = 9
|
||||
packetTypeLiteralData packetType = 11
|
||||
packetTypeUserId packetType = 13
|
||||
packetTypePublicSubkey packetType = 14
|
||||
packetTypeUserAttribute packetType = 17
|
||||
packetTypeSymmetricallyEncryptedMDC packetType = 18
|
||||
packetTypeAEADEncrypted packetType = 20
|
||||
packetTypeEncryptedKey packetType = 1
|
||||
packetTypeSignature packetType = 2
|
||||
packetTypeSymmetricKeyEncrypted packetType = 3
|
||||
packetTypeOnePassSignature packetType = 4
|
||||
packetTypePrivateKey packetType = 5
|
||||
packetTypePublicKey packetType = 6
|
||||
packetTypePrivateSubkey packetType = 7
|
||||
packetTypeCompressed packetType = 8
|
||||
packetTypeSymmetricallyEncrypted packetType = 9
|
||||
packetTypeLiteralData packetType = 11
|
||||
packetTypeUserId packetType = 13
|
||||
packetTypePublicSubkey packetType = 14
|
||||
packetTypeUserAttribute packetType = 17
|
||||
packetTypeSymmetricallyEncryptedIntegrityProtected packetType = 18
|
||||
packetTypeAEADEncrypted packetType = 20
|
||||
)
|
||||
|
||||
// EncryptedDataPacket holds encrypted data. It is currently implemented by
|
||||
|
@ -361,9 +361,9 @@ func Read(r io.Reader) (p Packet, err error) {
|
|||
p = new(UserId)
|
||||
case packetTypeUserAttribute:
|
||||
p = new(UserAttribute)
|
||||
case packetTypeSymmetricallyEncryptedMDC:
|
||||
case packetTypeSymmetricallyEncryptedIntegrityProtected:
|
||||
se := new(SymmetricallyEncrypted)
|
||||
se.MDC = true
|
||||
se.IntegrityProtected = true
|
||||
p = se
|
||||
case packetTypeAEADEncrypted:
|
||||
p = new(AEADEncrypted)
|
||||
|
@ -384,18 +384,18 @@ func Read(r io.Reader) (p Packet, err error) {
|
|||
type SignatureType uint8
|
||||
|
||||
const (
|
||||
SigTypeBinary SignatureType = 0x00
|
||||
SigTypeText = 0x01
|
||||
SigTypeGenericCert = 0x10
|
||||
SigTypePersonaCert = 0x11
|
||||
SigTypeCasualCert = 0x12
|
||||
SigTypePositiveCert = 0x13
|
||||
SigTypeSubkeyBinding = 0x18
|
||||
SigTypePrimaryKeyBinding = 0x19
|
||||
SigTypeDirectSignature = 0x1F
|
||||
SigTypeKeyRevocation = 0x20
|
||||
SigTypeSubkeyRevocation = 0x28
|
||||
SigTypeCertificationRevocation = 0x30
|
||||
SigTypeBinary SignatureType = 0x00
|
||||
SigTypeText = 0x01
|
||||
SigTypeGenericCert = 0x10
|
||||
SigTypePersonaCert = 0x11
|
||||
SigTypeCasualCert = 0x12
|
||||
SigTypePositiveCert = 0x13
|
||||
SigTypeSubkeyBinding = 0x18
|
||||
SigTypePrimaryKeyBinding = 0x19
|
||||
SigTypeDirectSignature = 0x1F
|
||||
SigTypeKeyRevocation = 0x20
|
||||
SigTypeSubkeyRevocation = 0x28
|
||||
SigTypeCertificationRevocation = 0x30
|
||||
)
|
||||
|
||||
// PublicKeyAlgorithm represents the different public key system specified for
|
||||
|
@ -455,6 +455,11 @@ func (cipher CipherFunction) KeySize() int {
|
|||
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.
|
||||
func (cipher CipherFunction) blockSize() int {
|
||||
return algorithm.CipherFunction(cipher).BlockSize()
|
||||
|
@ -490,15 +495,16 @@ const (
|
|||
|
||||
// AEADMode represents the different Authenticated Encryption with Associated
|
||||
// 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
|
||||
|
||||
const (
|
||||
AEADModeEAX AEADMode = 1
|
||||
AEADModeOCB AEADMode = 2
|
||||
AEADModeExperimentalGCM AEADMode = 100
|
||||
AEADModeEAX AEADMode = 1
|
||||
AEADModeOCB AEADMode = 2
|
||||
AEADModeGCM AEADMode = 3
|
||||
)
|
||||
|
||||
func (mode AEADMode) NonceLength() int {
|
||||
func (mode AEADMode) IvLength() int {
|
||||
return algorithm.AEADMode(mode).NonceLength()
|
||||
}
|
||||
|
||||
|
@ -527,13 +533,19 @@ const (
|
|||
type Curve string
|
||||
|
||||
const (
|
||||
Curve25519 Curve = "Curve25519"
|
||||
Curve448 Curve = "Curve448"
|
||||
CurveNistP256 Curve = "P256"
|
||||
CurveNistP384 Curve = "P384"
|
||||
CurveNistP521 Curve = "P521"
|
||||
CurveSecP256k1 Curve = "SecP256k1"
|
||||
Curve25519 Curve = "Curve25519"
|
||||
Curve448 Curve = "Curve448"
|
||||
CurveNistP256 Curve = "P256"
|
||||
CurveNistP384 Curve = "P384"
|
||||
CurveNistP521 Curve = "P521"
|
||||
CurveSecP256k1 Curve = "SecP256k1"
|
||||
CurveBrainpoolP256 Curve = "BrainpoolP256"
|
||||
CurveBrainpoolP384 Curve = "BrainpoolP384"
|
||||
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
|
||||
|
|
|
@ -49,7 +49,7 @@ type PrivateKey struct {
|
|||
s2kParams *s2k.Params
|
||||
}
|
||||
|
||||
//S2KType s2k packet type
|
||||
// S2KType s2k packet type
|
||||
type S2KType uint8
|
||||
|
||||
const (
|
||||
|
@ -179,6 +179,9 @@ func (pk *PrivateKey) parse(r io.Reader) (err error) {
|
|||
return
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -367,8 +370,8 @@ func serializeECDHPrivateKey(w io.Writer, priv *ecdh.PrivateKey) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Decrypt decrypts an encrypted private key using a passphrase.
|
||||
func (pk *PrivateKey) Decrypt(passphrase []byte) error {
|
||||
// decrypt decrypts an encrypted private key using a decryption key.
|
||||
func (pk *PrivateKey) decrypt(decryptionKey []byte) error {
|
||||
if pk.Dummy() {
|
||||
return errors.ErrDummyPrivateKey("dummy key found")
|
||||
}
|
||||
|
@ -376,9 +379,7 @@ func (pk *PrivateKey) Decrypt(passphrase []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
key := make([]byte, pk.cipher.KeySize())
|
||||
pk.s2k(key, passphrase)
|
||||
block := pk.cipher.new(key)
|
||||
block := pk.cipher.new(decryptionKey)
|
||||
cfb := cipher.NewCFBDecrypter(block, pk.iv)
|
||||
|
||||
data := make([]byte, len(pk.encryptedData))
|
||||
|
@ -427,35 +428,79 @@ func (pk *PrivateKey) Decrypt(passphrase []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Encrypt encrypts an unencrypted private key using a passphrase.
|
||||
func (pk *PrivateKey) Encrypt(passphrase []byte) error {
|
||||
func (pk *PrivateKey) decryptWithCache(passphrase []byte, keyCache *s2k.Cache) 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)
|
||||
err := pk.serializePrivateKey(priv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//Default config of private key encryption
|
||||
pk.cipher = CipherAES256
|
||||
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.cipher = cipherFunction
|
||||
pk.s2kParams = params
|
||||
pk.s2k, err = pk.s2kParams.Function()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pk.s2k(key, passphrase)
|
||||
}
|
||||
|
||||
privateKeyBytes := priv.Bytes()
|
||||
pk.sha1Checksum = true
|
||||
block := pk.cipher.new(key)
|
||||
pk.iv = make([]byte, pk.cipher.blockSize())
|
||||
_, err = rand.Read(pk.iv)
|
||||
|
@ -486,6 +531,62 @@ func (pk *PrivateKey) Encrypt(passphrase []byte) error {
|
|||
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) {
|
||||
switch priv := pk.PrivateKey.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
|
|
|
@ -415,6 +415,10 @@ func (pk *PublicKey) parseEdDSA(r io.Reader) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
if len(pk.p.Bytes()) == 0 {
|
||||
return errors.StructuralError("empty EdDSA public key")
|
||||
}
|
||||
|
||||
pub := eddsa.NewPublicKey(c)
|
||||
|
||||
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)
|
||||
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")
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@ import (
|
|||
"github.com/ProtonMail/go-crypto/openpgp/ecdsa"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/eddsa"
|
||||
"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/s2k"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -66,11 +66,24 @@ type Signature struct {
|
|||
|
||||
SigLifetimeSecs, KeyLifetimeSecs *uint32
|
||||
PreferredSymmetric, PreferredHash, PreferredCompression []uint8
|
||||
PreferredAEAD []uint8
|
||||
PreferredCipherSuites [][2]uint8
|
||||
IssuerKeyId *uint64
|
||||
IssuerFingerprint []byte
|
||||
SignerUserId *string
|
||||
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
|
||||
// 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
|
||||
// indicating that the issuer implementation supports these features
|
||||
// (section 5.2.5.25).
|
||||
MDC, AEAD, V5Keys bool
|
||||
// see https://datatracker.ietf.org/doc/html/draft-ietf-openpgp-crypto-refresh#features-subpacket
|
||||
SEIPDv1, SEIPDv2 bool
|
||||
|
||||
// EmbeddedSignature, if non-nil, is a signature of the parent key, by
|
||||
// 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
|
||||
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 {
|
||||
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 {
|
||||
return
|
||||
}
|
||||
sig.buildHashSuffix(hashedSubpackets)
|
||||
err = sig.buildHashSuffix(hashedSubpackets)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = parseSignatureSubpackets(sig, hashedSubpackets, true)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -221,9 +244,12 @@ type signatureSubpacketType uint8
|
|||
const (
|
||||
creationTimeSubpacket signatureSubpacketType = 2
|
||||
signatureExpirationSubpacket signatureSubpacketType = 3
|
||||
trustSubpacket signatureSubpacketType = 5
|
||||
regularExpressionSubpacket signatureSubpacketType = 6
|
||||
keyExpirationSubpacket signatureSubpacketType = 9
|
||||
prefSymmetricAlgosSubpacket signatureSubpacketType = 11
|
||||
issuerSubpacket signatureSubpacketType = 16
|
||||
notationDataSubpacket signatureSubpacketType = 20
|
||||
prefHashAlgosSubpacket signatureSubpacketType = 21
|
||||
prefCompressionSubpacket signatureSubpacketType = 22
|
||||
primaryUserIdSubpacket signatureSubpacketType = 25
|
||||
|
@ -234,7 +260,7 @@ const (
|
|||
featuresSubpacket signatureSubpacketType = 30
|
||||
embeddedSignatureSubpacket signatureSubpacketType = 32
|
||||
issuerFingerprintSubpacket signatureSubpacketType = 33
|
||||
prefAeadAlgosSubpacket signatureSubpacketType = 34
|
||||
prefCipherSuitesSubpacket signatureSubpacketType = 39
|
||||
)
|
||||
|
||||
// parseSignatureSubpacket parses a single subpacket. len(subpacket) is >= 1.
|
||||
|
@ -245,6 +271,10 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
|||
packetType signatureSubpacketType
|
||||
isCritical bool
|
||||
)
|
||||
if len(subpacket) == 0 {
|
||||
err = errors.StructuralError("zero length signature subpacket")
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case subpacket[0] < 192:
|
||||
length = uint32(subpacket[0])
|
||||
|
@ -278,12 +308,14 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
|||
isCritical = subpacket[0]&0x80 == 0x80
|
||||
subpacket = subpacket[1:]
|
||||
sig.rawSubpackets = append(sig.rawSubpackets, outputSubpacket{isHashed, packetType, isCritical, subpacket})
|
||||
if !isHashed &&
|
||||
packetType != issuerSubpacket &&
|
||||
packetType != issuerFingerprintSubpacket &&
|
||||
packetType != embeddedSignatureSubpacket {
|
||||
return
|
||||
}
|
||||
switch packetType {
|
||||
case creationTimeSubpacket:
|
||||
if !isHashed {
|
||||
err = errors.StructuralError("signature creation time in non-hashed area")
|
||||
return
|
||||
}
|
||||
if len(subpacket) != 4 {
|
||||
err = errors.StructuralError("signature creation time not four bytes")
|
||||
return
|
||||
|
@ -292,20 +324,35 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
|||
sig.CreationTime = time.Unix(int64(t), 0)
|
||||
case signatureExpirationSubpacket:
|
||||
// Signature expiration time, section 5.2.3.10
|
||||
if !isHashed {
|
||||
return
|
||||
}
|
||||
if len(subpacket) != 4 {
|
||||
err = errors.StructuralError("expiration subpacket with bad length")
|
||||
return
|
||||
}
|
||||
sig.SigLifetimeSecs = new(uint32)
|
||||
*sig.SigLifetimeSecs = binary.BigEndian.Uint32(subpacket)
|
||||
case keyExpirationSubpacket:
|
||||
// Key expiration time, section 5.2.3.6
|
||||
if !isHashed {
|
||||
case trustSubpacket:
|
||||
if len(subpacket) != 2 {
|
||||
err = errors.StructuralError("trust subpacket with bad length")
|
||||
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 {
|
||||
err = errors.StructuralError("key expiration subpacket with bad length")
|
||||
return
|
||||
|
@ -314,41 +361,52 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
|||
*sig.KeyLifetimeSecs = binary.BigEndian.Uint32(subpacket)
|
||||
case prefSymmetricAlgosSubpacket:
|
||||
// Preferred symmetric algorithms, section 5.2.3.7
|
||||
if !isHashed {
|
||||
return
|
||||
}
|
||||
sig.PreferredSymmetric = make([]byte, len(subpacket))
|
||||
copy(sig.PreferredSymmetric, subpacket)
|
||||
case issuerSubpacket:
|
||||
// Issuer, section 5.2.3.5
|
||||
if sig.Version > 4 {
|
||||
err = errors.StructuralError("issuer subpacket found in v5 key")
|
||||
return
|
||||
}
|
||||
// Issuer, section 5.2.3.5
|
||||
if len(subpacket) != 8 {
|
||||
err = errors.StructuralError("issuer subpacket with bad length")
|
||||
return
|
||||
}
|
||||
sig.IssuerKeyId = new(uint64)
|
||||
*sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket)
|
||||
case prefHashAlgosSubpacket:
|
||||
// Preferred hash algorithms, section 5.2.3.8
|
||||
if !isHashed {
|
||||
case notationDataSubpacket:
|
||||
// Notation data, section 5.2.3.16
|
||||
if len(subpacket) < 8 {
|
||||
err = errors.StructuralError("notation data subpacket with bad length")
|
||||
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))
|
||||
copy(sig.PreferredHash, subpacket)
|
||||
case prefCompressionSubpacket:
|
||||
// Preferred compression algorithms, section 5.2.3.9
|
||||
if !isHashed {
|
||||
return
|
||||
}
|
||||
sig.PreferredCompression = make([]byte, len(subpacket))
|
||||
copy(sig.PreferredCompression, subpacket)
|
||||
case primaryUserIdSubpacket:
|
||||
// Primary User ID, section 5.2.3.19
|
||||
if !isHashed {
|
||||
return
|
||||
}
|
||||
if len(subpacket) != 1 {
|
||||
err = errors.StructuralError("primary user id subpacket with bad length")
|
||||
return
|
||||
|
@ -359,9 +417,6 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
|||
}
|
||||
case keyFlagsSubpacket:
|
||||
// Key flags, section 5.2.3.21
|
||||
if !isHashed {
|
||||
return
|
||||
}
|
||||
if len(subpacket) == 0 {
|
||||
err = errors.StructuralError("empty key flags subpacket")
|
||||
return
|
||||
|
@ -393,9 +448,6 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
|||
sig.SignerUserId = &userId
|
||||
case reasonForRevocationSubpacket:
|
||||
// Reason For Revocation, section 5.2.3.23
|
||||
if !isHashed {
|
||||
return
|
||||
}
|
||||
if len(subpacket) == 0 {
|
||||
err = errors.StructuralError("empty revocation reason subpacket")
|
||||
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
|
||||
// mechanism for OpenPGP implementations to signal support for new
|
||||
// features.
|
||||
if !isHashed {
|
||||
return
|
||||
}
|
||||
if len(subpacket) > 0 {
|
||||
if subpacket[0]&0x01 != 0 {
|
||||
sig.MDC = true
|
||||
sig.SEIPDv1 = true
|
||||
}
|
||||
if subpacket[0]&0x02 != 0 {
|
||||
sig.AEAD = true
|
||||
}
|
||||
if subpacket[0]&0x04 != 0 {
|
||||
sig.V5Keys = true
|
||||
// 0x02 and 0x04 are reserved
|
||||
if subpacket[0]&0x08 != 0 {
|
||||
sig.SEIPDv2 = true
|
||||
}
|
||||
}
|
||||
case embeddedSignatureSubpacket:
|
||||
|
@ -441,11 +488,12 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
|||
}
|
||||
case policyUriSubpacket:
|
||||
// Policy URI, section 5.2.3.20
|
||||
if !isHashed {
|
||||
return
|
||||
}
|
||||
sig.PolicyURI = string(subpacket)
|
||||
case issuerFingerprintSubpacket:
|
||||
if len(subpacket) == 0 {
|
||||
err = errors.StructuralError("empty issuer fingerprint subpacket")
|
||||
return
|
||||
}
|
||||
v, l := subpacket[0], len(subpacket[1:])
|
||||
if v == 5 && l != 32 || v != 5 && l != 20 {
|
||||
return nil, errors.StructuralError("bad fingerprint length")
|
||||
|
@ -458,13 +506,19 @@ func parseSignatureSubpacket(sig *Signature, subpacket []byte, isHashed bool) (r
|
|||
} else {
|
||||
*sig.IssuerKeyId = binary.BigEndian.Uint64(subpacket[13:21])
|
||||
}
|
||||
case prefAeadAlgosSubpacket:
|
||||
// Preferred symmetric algorithms, section 5.2.3.8
|
||||
if !isHashed {
|
||||
case prefCipherSuitesSubpacket:
|
||||
// Preferred AEAD cipher suites
|
||||
// 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
|
||||
}
|
||||
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:
|
||||
if isCritical {
|
||||
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.
|
||||
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 {
|
||||
sig.HashSuffix = nil
|
||||
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.SigType),
|
||||
uint8(sig.PubKeyAlgo),
|
||||
uint8(hash),
|
||||
uint8(hashId),
|
||||
uint8(len(hashedSubpackets) >> 8),
|
||||
uint8(len(hashedSubpackets)),
|
||||
})
|
||||
|
@ -842,7 +904,7 @@ func (sig *Signature) buildSubpackets(issuer PublicKey) (subpackets []outputSubp
|
|||
if sig.IssuerKeyId != nil && sig.Version == 4 {
|
||||
keyId := make([]byte, 8)
|
||||
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 {
|
||||
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}})
|
||||
}
|
||||
|
||||
for _, notation := range sig.Notations {
|
||||
subpackets = append(
|
||||
subpackets,
|
||||
outputSubpacket{
|
||||
true,
|
||||
notationDataSubpacket,
|
||||
notation.IsCritical,
|
||||
notation.getData(),
|
||||
})
|
||||
}
|
||||
|
||||
// The following subpackets may only appear in self-signatures.
|
||||
|
||||
var features = byte(0x00)
|
||||
if sig.MDC {
|
||||
if sig.SEIPDv1 {
|
||||
features |= 0x01
|
||||
}
|
||||
if sig.AEAD {
|
||||
features |= 0x02
|
||||
}
|
||||
if sig.V5Keys {
|
||||
features |= 0x04
|
||||
if sig.SEIPDv2 {
|
||||
features |= 0x08
|
||||
}
|
||||
|
||||
if features != 0x00 {
|
||||
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 {
|
||||
keyLifetime := make([]byte, 4)
|
||||
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)})
|
||||
}
|
||||
|
||||
if len(sig.PreferredAEAD) > 0 {
|
||||
subpackets = append(subpackets, outputSubpacket{true, prefAeadAlgosSubpacket, false, sig.PreferredAEAD})
|
||||
if len(sig.PreferredCipherSuites) > 0 {
|
||||
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.
|
||||
|
@ -971,7 +1055,7 @@ func (sig *Signature) AddMetadataToHashSuffix() {
|
|||
n := sig.HashSuffix[len(sig.HashSuffix)-8:]
|
||||
l := uint64(
|
||||
uint64(n[0])<<56 | uint64(n[1])<<48 | uint64(n[2])<<40 | uint64(n[3])<<32 |
|
||||
uint64(n[4])<<24 | uint64(n[5])<<16 | uint64(n[6])<<8 | uint64(n[7]))
|
||||
uint64(n[4])<<24 | uint64(n[5])<<16 | uint64(n[6])<<8 | uint64(n[7]))
|
||||
|
||||
suffix := bytes.NewBuffer(nil)
|
||||
suffix.Write(sig.HashSuffix[:l])
|
||||
|
|
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"
|
||||
)
|
||||
|
||||
// This is the largest session key that we'll support. Since no 512-bit cipher
|
||||
// has even been seriously used, this is comfortably large.
|
||||
// This is the largest session key that we'll support. Since at most 256-bit cipher
|
||||
// is supported in OpenPGP, this is large enough to contain also the auth tag.
|
||||
const maxSessionKeySizeInBytes = 64
|
||||
|
||||
// SymmetricKeyEncrypted represents a passphrase protected session key. See RFC
|
||||
|
@ -25,13 +25,16 @@ type SymmetricKeyEncrypted struct {
|
|||
CipherFunc CipherFunction
|
||||
Mode AEADMode
|
||||
s2k func(out, in []byte)
|
||||
aeadNonce []byte
|
||||
encryptedKey []byte
|
||||
iv []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 {
|
||||
// RFC 4880, section 5.3.
|
||||
var buf [2]byte
|
||||
var buf [1]byte
|
||||
|
||||
// Version
|
||||
if _, err := readFull(r, buf[:]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -39,17 +42,22 @@ func (ske *SymmetricKeyEncrypted) parse(r io.Reader) error {
|
|||
if ske.Version != 4 && ske.Version != 5 {
|
||||
return errors.UnsupportedError("unknown SymmetricKeyEncrypted version")
|
||||
}
|
||||
ske.CipherFunc = CipherFunction(buf[1])
|
||||
if ske.CipherFunc.KeySize() == 0 {
|
||||
return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(buf[1])))
|
||||
|
||||
// Cipher function
|
||||
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 {
|
||||
mode := make([]byte, 1)
|
||||
if _, err := r.Read(mode); err != nil {
|
||||
// AEAD mode
|
||||
if _, err := readFull(r, buf[:]); err != nil {
|
||||
return errors.StructuralError("cannot read AEAD octet from packet")
|
||||
}
|
||||
ske.Mode = AEADMode(mode[0])
|
||||
ske.Mode = AEADMode(buf[0])
|
||||
}
|
||||
|
||||
var err error
|
||||
|
@ -61,13 +69,14 @@ func (ske *SymmetricKeyEncrypted) parse(r io.Reader) error {
|
|||
}
|
||||
|
||||
if ske.Version == 5 {
|
||||
// AEAD nonce
|
||||
nonce := make([]byte, ske.Mode.NonceLength())
|
||||
_, err := readFull(r, nonce)
|
||||
if err != nil && err != io.ErrUnexpectedEOF {
|
||||
return err
|
||||
// AEAD IV
|
||||
iv := make([]byte, ske.Mode.IvLength())
|
||||
_, err := readFull(r, iv)
|
||||
if err != nil {
|
||||
return errors.StructuralError("cannot read AEAD IV")
|
||||
}
|
||||
ske.aeadNonce = nonce
|
||||
|
||||
ske.iv = iv
|
||||
}
|
||||
|
||||
encryptedKey := make([]byte, maxSessionKeySizeInBytes)
|
||||
|
@ -128,11 +137,10 @@ func (ske *SymmetricKeyEncrypted) decryptV4(key []byte) ([]byte, CipherFunction,
|
|||
}
|
||||
|
||||
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)}
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -142,17 +150,12 @@ func (ske *SymmetricKeyEncrypted) decryptV5(key []byte) ([]byte, error) {
|
|||
// SerializeSymmetricKeyEncrypted serializes a symmetric key packet to w.
|
||||
// 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
|
||||
// SerializeSymmetricallyEncrypted or SerializeAEADEncrypted, depending on
|
||||
// whether config.AEADConfig != nil.
|
||||
// SerializeSymmetricallyEncrypted.
|
||||
// If config is nil, sensible defaults will be used.
|
||||
func SerializeSymmetricKeyEncrypted(w io.Writer, passphrase []byte, config *Config) (key []byte, err error) {
|
||||
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)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -169,9 +172,8 @@ func SerializeSymmetricKeyEncrypted(w io.Writer, passphrase []byte, config *Conf
|
|||
|
||||
// SerializeSymmetricKeyEncryptedReuseKey serializes a symmetric key packet to w.
|
||||
// The packet contains the given session key, encrypted by a key derived from
|
||||
// the given passphrase. The session key must be passed to
|
||||
// SerializeSymmetricallyEncrypted or SerializeAEADEncrypted, depending on
|
||||
// whether config.AEADConfig != nil.
|
||||
// the given passphrase. The returned session key must be passed to
|
||||
// SerializeSymmetricallyEncrypted.
|
||||
// If config is nil, sensible defaults will be used.
|
||||
func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, passphrase []byte, config *Config) (err error) {
|
||||
var version int
|
||||
|
@ -181,16 +183,17 @@ func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, pass
|
|||
version = 4
|
||||
}
|
||||
cipherFunc := config.Cipher()
|
||||
keySize := cipherFunc.KeySize()
|
||||
if keySize == 0 {
|
||||
return errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(cipherFunc)))
|
||||
// cipherFunc must be AES
|
||||
if !cipherFunc.IsSupported() || cipherFunc < CipherAES128 || cipherFunc > CipherAES256 {
|
||||
return errors.UnsupportedError("unsupported cipher: " + strconv.Itoa(int(cipherFunc)))
|
||||
}
|
||||
|
||||
keySize := cipherFunc.KeySize()
|
||||
s2kBuf := new(bytes.Buffer)
|
||||
keyEncryptingKey := make([]byte, keySize)
|
||||
// s2k.Serialize salts and stretches the passphrase, and writes the
|
||||
// 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 {
|
||||
return
|
||||
}
|
||||
|
@ -201,20 +204,20 @@ func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, pass
|
|||
case 4:
|
||||
packetLength = 2 /* header */ + len(s2kBytes) + 1 /* cipher type */ + keySize
|
||||
case 5:
|
||||
nonceLen := config.AEAD().Mode().NonceLength()
|
||||
ivLen := config.AEAD().Mode().IvLength()
|
||||
tagLen := config.AEAD().Mode().TagLength()
|
||||
packetLength = 3 + len(s2kBytes) + nonceLen + keySize + tagLen
|
||||
packetLength = 3 + len(s2kBytes) + ivLen + keySize + tagLen
|
||||
}
|
||||
err = serializeHeader(w, packetTypeSymmetricKeyEncrypted, packetLength)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf := make([]byte, 2)
|
||||
// Symmetric Key Encrypted Version
|
||||
buf[0] = byte(version)
|
||||
buf := []byte{byte(version)}
|
||||
|
||||
// Cipher function
|
||||
buf[1] = byte(cipherFunc)
|
||||
buf = append(buf, byte(cipherFunc))
|
||||
|
||||
if version == 5 {
|
||||
// AEAD mode
|
||||
|
@ -241,19 +244,20 @@ func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, pass
|
|||
return
|
||||
}
|
||||
case 5:
|
||||
blockCipher := cipherFunc.new(keyEncryptingKey)
|
||||
mode := config.AEAD().Mode()
|
||||
aead := mode.new(blockCipher)
|
||||
// Sample nonce using random reader
|
||||
nonce := make([]byte, config.AEAD().Mode().NonceLength())
|
||||
_, err = io.ReadFull(config.Random(), nonce)
|
||||
adata := []byte{0xc3, byte(5), byte(cipherFunc), byte(mode)}
|
||||
aead := getEncryptedKeyAeadInstance(cipherFunc, mode, keyEncryptingKey, adata)
|
||||
|
||||
// Sample iv using random reader
|
||||
iv := make([]byte, config.AEAD().Mode().IvLength())
|
||||
_, err = io.ReadFull(config.Random(), iv)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Seal and write (encryptedData includes auth. tag)
|
||||
adata := []byte{0xc3, byte(5), byte(cipherFunc), byte(mode)}
|
||||
encryptedData := aead.Seal(nil, nonce, sessionKey, adata)
|
||||
_, err = w.Write(nonce)
|
||||
|
||||
encryptedData := aead.Seal(nil, iv, sessionKey, adata)
|
||||
_, err = w.Write(iv)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -265,3 +269,8 @@ func SerializeSymmetricKeyEncryptedReuseKey(w io.Writer, sessionKey []byte, pass
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
func getEncryptedKeyAeadInstance(c CipherFunction, mode AEADMode, inputKey, associatedData []byte) (aead cipher.AEAD) {
|
||||
blockCipher := c.new(inputKey)
|
||||
return mode.new(blockCipher)
|
||||
}
|
||||
|
|
274
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted.go
generated
vendored
274
vendor/github.com/ProtonMail/go-crypto/openpgp/packet/symmetrically_encrypted.go
generated
vendored
|
@ -5,36 +5,54 @@
|
|||
package packet
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/sha1"
|
||||
"crypto/subtle"
|
||||
"hash"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
||||
)
|
||||
|
||||
const aeadSaltSize = 32
|
||||
|
||||
// SymmetricallyEncrypted represents a symmetrically encrypted byte string. The
|
||||
// encrypted Contents will consist of more OpenPGP packets. See RFC 4880,
|
||||
// sections 5.7 and 5.13.
|
||||
type SymmetricallyEncrypted struct {
|
||||
MDC bool // true iff this is a type 18 packet and thus has an embedded MAC.
|
||||
Contents io.Reader
|
||||
prefix []byte
|
||||
Version int
|
||||
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
|
||||
|
||||
// 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 {
|
||||
if se.MDC {
|
||||
if se.IntegrityProtected {
|
||||
// See RFC 4880, section 5.13.
|
||||
var buf [1]byte
|
||||
_, err := readFull(r, buf[:])
|
||||
if err != nil {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
// to decrypt the entire data.
|
||||
func (se *SymmetricallyEncrypted) Decrypt(c CipherFunction, key []byte) (io.ReadCloser, error) {
|
||||
keySize := c.KeySize()
|
||||
if keySize == 0 {
|
||||
return nil, errors.UnsupportedError("unknown cipher: " + strconv.Itoa(int(c)))
|
||||
}
|
||||
if len(key) != keySize {
|
||||
return nil, errors.InvalidArgumentError("SymmetricallyEncrypted: incorrect key length")
|
||||
if se.Version == symmetricallyEncryptedVersionAead {
|
||||
return se.decryptAead(key)
|
||||
}
|
||||
|
||||
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.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
|
||||
return se.decryptMdc(c, key)
|
||||
}
|
||||
|
||||
// SerializeSymmetricallyEncrypted serializes a symmetrically encrypted packet
|
||||
// to w and returns a WriteCloser to which the to-be-encrypted packets can be
|
||||
// written.
|
||||
// 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) {
|
||||
if c.KeySize() != len(key) {
|
||||
return nil, errors.InvalidArgumentError("SymmetricallyEncrypted.Serialize: bad key length")
|
||||
}
|
||||
func SerializeSymmetricallyEncrypted(w io.Writer, c CipherFunction, aeadSupported bool, cipherSuite CipherSuite, key []byte, config *Config) (Contents io.WriteCloser, err error) {
|
||||
writeCloser := noOpCloser{w}
|
||||
ciphertext, err := serializeStreamHeader(writeCloser, packetTypeSymmetricallyEncryptedMDC)
|
||||
ciphertext, err := serializeStreamHeader(writeCloser, packetTypeSymmetricallyEncryptedIntegrityProtected)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = ciphertext.Write([]byte{symmetricallyEncryptedVersion})
|
||||
if err != nil {
|
||||
return
|
||||
if aeadSupported {
|
||||
return serializeSymmetricallyEncryptedAead(ciphertext, cipherSuite, config.AEADConfig.ChunkSizeByte(), config.Random(), key)
|
||||
}
|
||||
|
||||
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
|
||||
return serializeSymmetricallyEncryptedMdc(ciphertext, c, key, config)
|
||||
}
|
||||
|
|
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 (
|
||||
"crypto"
|
||||
_ "crypto/sha256"
|
||||
_ "crypto/sha512"
|
||||
"hash"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||
_ "golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
// SignatureType is the armor type for a PGP signature.
|
||||
|
@ -131,8 +134,8 @@ ParsePackets:
|
|||
}
|
||||
}
|
||||
case *packet.SymmetricallyEncrypted:
|
||||
if !p.MDC && !config.AllowUnauthenticatedMessages() {
|
||||
return nil, errors.UnsupportedError("message is not authenticated")
|
||||
if !p.IntegrityProtected && !config.AllowUnauthenticatedMessages() {
|
||||
return nil, errors.UnsupportedError("message is not integrity protected")
|
||||
}
|
||||
edp = p
|
||||
break ParsePackets
|
||||
|
@ -208,13 +211,11 @@ FindKey:
|
|||
if len(symKeys) != 0 && passphrase != nil {
|
||||
for _, s := range symKeys {
|
||||
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
|
||||
if err == nil {
|
||||
decrypted, err = edp.Decrypt(cipherFunc, key)
|
||||
// TODO: ErrKeyIncorrect is no longer thrown on SEIP decryption,
|
||||
// but it might still be relevant for when we implement AEAD decryption (otherwise, remove?)
|
||||
if err != nil && err != errors.ErrKeyIncorrect {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if decrypted != nil {
|
||||
|
@ -304,14 +305,14 @@ FindLiteralData:
|
|||
// 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
|
||||
// performs any needed preprocessing.
|
||||
func hashForSignature(hashId crypto.Hash, sigType packet.SignatureType) (hash.Hash, hash.Hash, error) {
|
||||
if hashId == crypto.MD5 {
|
||||
return nil, nil, errors.UnsupportedError("insecure hash algorithm: MD5")
|
||||
func hashForSignature(hashFunc crypto.Hash, sigType packet.SignatureType) (hash.Hash, hash.Hash, error) {
|
||||
if _, ok := algorithm.HashToHashIdWithSha1(hashFunc); !ok {
|
||||
return nil, nil, errors.UnsupportedError("unsupported hash function")
|
||||
}
|
||||
if !hashId.Available() {
|
||||
return nil, nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashId)))
|
||||
if !hashFunc.Available() {
|
||||
return nil, nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashFunc)))
|
||||
}
|
||||
h := hashId.New()
|
||||
h := hashFunc.New()
|
||||
|
||||
switch sigType {
|
||||
case packet.SigTypeBinary:
|
||||
|
@ -383,19 +384,7 @@ func (scr *signatureCheckReader) Read(buf []byte) (int, error) {
|
|||
key := scr.md.SignedBy
|
||||
signatureError := key.PublicKey.VerifySignature(scr.h, sig)
|
||||
if signatureError == nil {
|
||||
now := scr.config.Now()
|
||||
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
|
||||
}
|
||||
signatureError = checkSignatureDetails(key, sig, scr.config)
|
||||
}
|
||||
scr.md.Signature = sig
|
||||
scr.md.SignatureError = signatureError
|
||||
|
@ -434,8 +423,24 @@ func (scr *signatureCheckReader) Read(buf []byte) (int, error) {
|
|||
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
|
||||
// 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.
|
||||
func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader, config *packet.Config) (signer *Entity, err error) {
|
||||
var expectedHashes []crypto.Hash
|
||||
|
@ -445,6 +450,11 @@ func CheckDetachedSignature(keyring KeyRing, signed, signature io.Reader, config
|
|||
// CheckDetachedSignatureAndHash performs the same actions as
|
||||
// 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) {
|
||||
_, 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 hashFunc crypto.Hash
|
||||
var sigType packet.SignatureType
|
||||
|
@ -453,23 +463,22 @@ func CheckDetachedSignatureAndHash(keyring KeyRing, signed, signature io.Reader,
|
|||
|
||||
expectedHashesLen := len(expectedHashes)
|
||||
packets := packet.NewReader(signature)
|
||||
var sig *packet.Signature
|
||||
for {
|
||||
p, err = packets.Next()
|
||||
if err == io.EOF {
|
||||
return nil, errors.ErrUnknownIssuer
|
||||
return nil, nil, errors.ErrUnknownIssuer
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var ok bool
|
||||
sig, ok = p.(*packet.Signature)
|
||||
if !ok {
|
||||
return nil, errors.StructuralError("non signature packet found")
|
||||
return nil, nil, errors.StructuralError("non signature packet found")
|
||||
}
|
||||
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
|
||||
hashFunc = sig.Hash
|
||||
|
@ -480,7 +489,7 @@ func CheckDetachedSignatureAndHash(keyring KeyRing, signed, signature io.Reader,
|
|||
break
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(wrappedHash, signed); err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
err = key.PublicKey.VerifySignature(h, sig)
|
||||
if err == nil {
|
||||
now := config.Now()
|
||||
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 sig, key.Entity, checkSignatureDetails(&key, sig, config)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// CheckArmoredDetachedSignature performs the same actions as
|
||||
|
@ -536,3 +532,61 @@ func CheckArmoredDetachedSignature(keyring KeyRing, signed, signature io.Reader,
|
|||
|
||||
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
|
||||
|
||||
const testKey1KeyId = 0xA34D7E18C20C31BB
|
||||
const testKey3KeyId = 0x338934250CCC0360
|
||||
const testKeyP256KeyId = 0xd44a2c495918513e
|
||||
const testKey1KeyId uint64 = 0xA34D7E18C20C31BB
|
||||
const testKey3KeyId uint64 = 0x338934250CCC0360
|
||||
const testKeyP256KeyId uint64 = 0xd44a2c495918513e
|
||||
|
||||
const signedInput = "Signed message\nline 2\nline 3\n"
|
||||
const signedTextInput = "Signed message\r\nline 2\r\nline 3\r\n"
|
||||
|
@ -106,7 +106,7 @@ const unknownHashFunctionHex = `8a00000040040001990006050253863c24000a09103b4fe6
|
|||
|
||||
const rsaSignatureBadMPIlength = `8a00000040040001030006050253863c24000a09103b4fe6acc0b21f32ffff0101010101010101010101010101010101010101010101010101010101010101010101010101`
|
||||
|
||||
const missingHashFunctionHex = `8a00000040040001030006050253863c24000a09103b4fe6acc0b21f32ffff0101010101010101010101010101010101010101010101010101010101010101010101`
|
||||
const missingHashFunctionHex = `8a00000040040001030006050253863c24000a09103b4fe6acc0b21f32ffff0101010101010101010101010101010101010101010101010101010101010101010101010101`
|
||||
|
||||
const campbellQuine = `a0b001000300fcffa0b001000d00f2ff000300fcffa0b001000d00f2ff8270a01c00000500faff8270a01c00000500faff000500faff001400ebff8270a01c00000500faff000500faff001400ebff428821c400001400ebff428821c400001400ebff428821c400001400ebff428821c400001400ebff428821c400000000ffff000000ffff000b00f4ff428821c400000000ffff000000ffff000b00f4ff0233214c40000100feff000233214c40000100feff0000`
|
||||
|
||||
|
@ -171,3 +171,104 @@ y29VPonFXqi2zKkpZrvyvZxg+n5e8Nt9wNbuxeCd3QD/TtO2s+JvjrE4Siwv
|
|||
UQdl5MlBka1QSNbMq2Bz7XwNPg4=
|
||||
=6lbM
|
||||
-----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.
|
||||
|
||||
// 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"
|
||||
|
||||
import (
|
||||
|
@ -14,70 +15,47 @@ import (
|
|||
|
||||
"github.com/ProtonMail/go-crypto/openpgp/errors"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
|
||||
"golang.org/x/crypto/argon2"
|
||||
)
|
||||
|
||||
// Config collects configuration parameters for s2k key-stretching
|
||||
// transformations. A nil *Config is valid and results in all default
|
||||
// values. Currently, Config is used only by the Serialize function in
|
||||
// this package.
|
||||
type Config struct {
|
||||
// S2KMode is the mode of s2k function.
|
||||
// It can be 0 (simple), 1(salted), 3(iterated)
|
||||
// 2(reserved) 100-110(private/experimental).
|
||||
S2KMode uint8
|
||||
// Hash is the default hash function to be used. If
|
||||
// nil, SHA256 is used.
|
||||
Hash crypto.Hash
|
||||
// S2KCount is only used for symmetric encryption. 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. See RFC 4880 Section 3.7.1.3.
|
||||
S2KCount int
|
||||
}
|
||||
type Mode uint8
|
||||
|
||||
// Defines the default S2KMode constants
|
||||
//
|
||||
// 0 (simple), 1(salted), 3(iterated), 4(argon2)
|
||||
const (
|
||||
SimpleS2K Mode = 0
|
||||
SaltedS2K Mode = 1
|
||||
IteratedSaltedS2K Mode = 3
|
||||
Argon2S2K Mode = 4
|
||||
GnuS2K Mode = 101
|
||||
)
|
||||
|
||||
const Argon2SaltSize int = 16
|
||||
|
||||
// Params contains all the parameters of the s2k packet
|
||||
type Params struct {
|
||||
// mode is the mode of s2k function.
|
||||
// It can be 0 (simple), 1(salted), 3(iterated)
|
||||
// 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 byte
|
||||
// salt is a byte array to use as a salt in hashing process
|
||||
salt []byte
|
||||
// salt is a byte array to use as a salt in hashing process or argon2
|
||||
saltBytes [Argon2SaltSize]byte
|
||||
// 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.
|
||||
countByte byte
|
||||
}
|
||||
|
||||
func (c *Config) hash() crypto.Hash {
|
||||
if c == nil || uint(c.Hash) == 0 {
|
||||
return crypto.SHA256
|
||||
}
|
||||
|
||||
return c.Hash
|
||||
}
|
||||
|
||||
// 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)
|
||||
// passes is a parameter in Argon2 to determine the number of iterations
|
||||
// See RFC the crypto refresh Section 3.7.1.4.
|
||||
passes byte
|
||||
// parallelism is a parameter in Argon2 to determine the degree of paralellism
|
||||
// See RFC the crypto refresh Section 3.7.1.4.
|
||||
parallelism byte
|
||||
// memoryExp is a parameter in Argon2 to determine the memory usage
|
||||
// i.e., 2 ** memoryExp kibibytes
|
||||
// See RFC the crypto refresh Section 3.7.1.4.
|
||||
memoryExp byte
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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
|
||||
// 4880, section 3.7.1.1) using the given hash and input passphrase.
|
||||
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.
|
||||
// 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) {
|
||||
hashId, ok := HashToHashId(c.Hash)
|
||||
if !ok {
|
||||
return nil, errors.UnsupportedError("no such 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 {
|
||||
return nil, errors.UnsupportedError("no such hash")
|
||||
}
|
||||
|
||||
params := &Params{
|
||||
mode: 3, // Enforce iterared + salted method
|
||||
hashId: hashId,
|
||||
salt: make([]byte, 8),
|
||||
countByte: c.EncodedCount(),
|
||||
params = &Params{
|
||||
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,
|
||||
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 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
|
||||
// transformation from r and returns a struct describing the s2k parameters.
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
||||
params = &Params{
|
||||
mode: buf[0],
|
||||
hashId: buf[1],
|
||||
mode: Mode(buf[0]),
|
||||
}
|
||||
|
||||
switch params.mode {
|
||||
case 0:
|
||||
return params, nil
|
||||
case 1:
|
||||
_, err = io.ReadFull(r, buf[:8])
|
||||
case SimpleS2K:
|
||||
_, err = io.ReadFull(r, buf[:1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params.salt = buf[:8]
|
||||
params.hashId = buf[0]
|
||||
return params, nil
|
||||
case 3:
|
||||
case SaltedS2K:
|
||||
_, err = io.ReadFull(r, buf[:9])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params.salt = buf[:8]
|
||||
params.countByte = buf[8]
|
||||
params.hashId = buf[0]
|
||||
copy(params.salt(), buf[1:9])
|
||||
return params, nil
|
||||
case 101:
|
||||
// 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[:4]); err != nil {
|
||||
case IteratedSaltedS2K:
|
||||
_, err = io.ReadFull(r, buf[:10])
|
||||
if err != nil {
|
||||
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 nil, errors.UnsupportedError("GNU S2K extension")
|
||||
|
@ -255,39 +301,56 @@ func ParseIntoParams(r io.Reader) (params *Params, err error) {
|
|||
}
|
||||
|
||||
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) {
|
||||
if params.Dummy() {
|
||||
return nil, errors.ErrDummyPrivateKey("dummy key found")
|
||||
}
|
||||
hashObj, ok := HashIdToHash(params.hashId)
|
||||
if !ok {
|
||||
return nil, errors.UnsupportedError("hash for S2K function: " + strconv.Itoa(int(params.hashId)))
|
||||
}
|
||||
if !hashObj.Available() {
|
||||
return nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashObj)))
|
||||
var hashObj crypto.Hash
|
||||
if params.mode != Argon2S2K {
|
||||
var ok bool
|
||||
hashObj, ok = algorithm.HashIdToHashWithSha1(params.hashId)
|
||||
if !ok {
|
||||
return nil, errors.UnsupportedError("hash for S2K function: " + strconv.Itoa(int(params.hashId)))
|
||||
}
|
||||
if !hashObj.Available() {
|
||||
return nil, errors.UnsupportedError("hash not available: " + strconv.Itoa(int(hashObj)))
|
||||
}
|
||||
}
|
||||
|
||||
switch params.mode {
|
||||
case 0:
|
||||
case SimpleS2K:
|
||||
f := func(out, in []byte) {
|
||||
Simple(out, hashObj.New(), in)
|
||||
}
|
||||
|
||||
return f, nil
|
||||
case 1:
|
||||
case SaltedS2K:
|
||||
f := func(out, in []byte) {
|
||||
Salted(out, hashObj.New(), in, params.salt)
|
||||
Salted(out, hashObj.New(), in, params.salt())
|
||||
}
|
||||
|
||||
return f, nil
|
||||
case 3:
|
||||
case IteratedSaltedS2K:
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -295,23 +358,28 @@ func (params *Params) Function() (f func(out, in []byte), 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
|
||||
}
|
||||
if _, err = w.Write([]byte{params.hashId}); err != nil {
|
||||
return
|
||||
if params.mode != Argon2S2K {
|
||||
if _, err = w.Write([]byte{params.hashId}); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if params.Dummy() {
|
||||
_, err = w.Write(append([]byte("GNU"), 1))
|
||||
return
|
||||
}
|
||||
if params.mode > 0 {
|
||||
if _, err = w.Write(params.salt); err != nil {
|
||||
if _, err = w.Write(params.salt()); err != nil {
|
||||
return
|
||||
}
|
||||
if params.mode == 3 {
|
||||
if params.mode == IteratedSaltedS2K {
|
||||
_, err = w.Write([]byte{params.countByte})
|
||||
}
|
||||
if params.mode == Argon2S2K {
|
||||
_, err = w.Write([]byte{params.passes, params.parallelism, params.memoryExp})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -337,31 +405,3 @@ func Serialize(w io.Writer, key []byte, rand io.Reader, passphrase []byte, c *Co
|
|||
f(key, passphrase)
|
||||
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/errors"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
|
||||
"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
|
||||
|
@ -70,15 +70,11 @@ func detachSign(w io.Writer, signer *Entity, message io.Reader, sigType packet.S
|
|||
if signingKey.PrivateKey.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.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
|
||||
sig := createSignaturePacket(signingKey.PublicKey, sigType, config)
|
||||
|
||||
h, wrappedHash, err := hashForSignature(sig.Hash, sig.SigType)
|
||||
if err != nil {
|
||||
|
@ -125,16 +121,13 @@ func SymmetricallyEncrypt(ciphertext io.Writer, passphrase []byte, hints *FileHi
|
|||
}
|
||||
|
||||
var w io.WriteCloser
|
||||
if config.AEAD() != nil {
|
||||
w, err = packet.SerializeAEADEncrypted(ciphertext, key, config.Cipher(), config.AEAD().Mode(), config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
w, err = packet.SerializeSymmetricallyEncrypted(ciphertext, config.Cipher(), key, config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cipherSuite := packet.CipherSuite{
|
||||
Cipher: config.Cipher(),
|
||||
Mode: config.AEAD().Mode(),
|
||||
}
|
||||
w, err = packet.SerializeSymmetricallyEncrypted(ciphertext, config.Cipher(), config.AEAD() != nil, cipherSuite, key, config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
literalData := w
|
||||
|
@ -173,8 +166,25 @@ func intersectPreferences(a []uint8, b []uint8) (intersection []uint8) {
|
|||
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 {
|
||||
v, ok := s2k.HashToHashId(h)
|
||||
v, ok := algorithm.HashToHashId(h)
|
||||
if !ok {
|
||||
panic("tried to convert unknown hash")
|
||||
}
|
||||
|
@ -240,7 +250,7 @@ func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entit
|
|||
|
||||
var hash crypto.Hash
|
||||
for _, hashId := range candidateHashes {
|
||||
if h, ok := s2k.HashIdToHash(hashId); ok && h.Available() {
|
||||
if h, ok := algorithm.HashIdToHash(hashId); ok && h.Available() {
|
||||
hash = h
|
||||
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 configuredHash := config.Hash(); configuredHash.Available() {
|
||||
for _, hashId := range candidateHashes {
|
||||
if h, ok := s2k.HashIdToHash(hashId); ok && h == configuredHash {
|
||||
if h, ok := algorithm.HashIdToHash(hashId); ok && h == configuredHash {
|
||||
hash = h
|
||||
break
|
||||
}
|
||||
|
@ -258,7 +268,7 @@ func writeAndSign(payload io.WriteCloser, candidateHashes []uint8, signed *Entit
|
|||
|
||||
if hash == 0 {
|
||||
hashId := candidateHashes[0]
|
||||
name, ok := s2k.HashIdToString(hashId)
|
||||
name, ok := algorithm.HashIdToString(hashId)
|
||||
if !ok {
|
||||
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.
|
||||
candidateCiphers := []uint8{
|
||||
uint8(packet.CipherAES128),
|
||||
uint8(packet.CipherAES256),
|
||||
uint8(packet.CipherCAST5),
|
||||
uint8(packet.CipherAES128),
|
||||
}
|
||||
|
||||
// These are the possible hash functions that we'll use for the signature.
|
||||
candidateHashes := []uint8{
|
||||
hashToHashId(crypto.SHA256),
|
||||
hashToHashId(crypto.SHA384),
|
||||
hashToHashId(crypto.SHA512),
|
||||
hashToHashId(crypto.SHA1),
|
||||
hashToHashId(crypto.RIPEMD160),
|
||||
hashToHashId(crypto.SHA3_256),
|
||||
hashToHashId(crypto.SHA3_512),
|
||||
}
|
||||
candidateAeadModes := []uint8{
|
||||
uint8(packet.AEADModeEAX),
|
||||
uint8(packet.AEADModeOCB),
|
||||
uint8(packet.AEADModeExperimentalGCM),
|
||||
|
||||
// Prefer GCM if everyone supports it
|
||||
candidateCipherSuites := [][2]uint8{
|
||||
{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{
|
||||
uint8(packet.CompressionNone),
|
||||
uint8(packet.CompressionZIP),
|
||||
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))
|
||||
// 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 {
|
||||
var ok bool
|
||||
|
@ -371,38 +381,37 @@ func encrypt(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *En
|
|||
}
|
||||
|
||||
sig := to[i].PrimaryIdentity().SelfSignature
|
||||
if sig.AEAD == false {
|
||||
if !sig.SEIPDv2 {
|
||||
aeadSupported = false
|
||||
}
|
||||
|
||||
preferredSymmetric := sig.PreferredSymmetric
|
||||
if len(preferredSymmetric) == 0 {
|
||||
preferredSymmetric = defaultCiphers
|
||||
}
|
||||
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)
|
||||
candidateCiphers = intersectPreferences(candidateCiphers, sig.PreferredSymmetric)
|
||||
candidateHashes = intersectPreferences(candidateHashes, sig.PreferredHash)
|
||||
candidateCipherSuites = intersectCipherSuites(candidateCipherSuites, sig.PreferredCipherSuites)
|
||||
candidateCompression = intersectPreferences(candidateCompression, sig.PreferredCompression)
|
||||
}
|
||||
|
||||
if len(candidateCiphers) == 0 || len(candidateHashes) == 0 || len(candidateAeadModes) == 0 {
|
||||
return nil, errors.InvalidArgumentError("cannot encrypt because recipient set shares no common algorithms")
|
||||
// In the event that the intersection of supported algorithms is empty we use the ones
|
||||
// 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])
|
||||
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.
|
||||
configuredCipher := config.Cipher()
|
||||
for _, c := range candidateCiphers {
|
||||
|
@ -425,17 +434,11 @@ func encrypt(keyWriter io.Writer, dataWriter io.Writer, to []*Entity, signed *En
|
|||
}
|
||||
|
||||
var payload io.WriteCloser
|
||||
if config.AEAD() != nil && aeadSupported {
|
||||
payload, err = packet.SerializeAEADEncrypted(dataWriter, symKey, cipher, mode, config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
payload, err = packet.SerializeSymmetricallyEncrypted(dataWriter, cipher, symKey, config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
payload, err = packet.SerializeSymmetricallyEncrypted(dataWriter, cipher, aeadSupported, aeadCipherSuite, symKey, config)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
payload, err = handleCompression(payload, candidateCompression, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -458,8 +461,8 @@ func Sign(output io.Writer, signed *Entity, hints *FileHints, config *packet.Con
|
|||
hashToHashId(crypto.SHA256),
|
||||
hashToHashId(crypto.SHA384),
|
||||
hashToHashId(crypto.SHA512),
|
||||
hashToHashId(crypto.SHA1),
|
||||
hashToHashId(crypto.RIPEMD160),
|
||||
hashToHashId(crypto.SHA3_256),
|
||||
hashToHashId(crypto.SHA3_512),
|
||||
}
|
||||
defaultHashes := candidateHashes[0:1]
|
||||
preferredHashes := signed.PrimaryIdentity().SelfSignature.PreferredHash
|
||||
|
@ -502,15 +505,9 @@ func (s signatureWriter) Write(data []byte) (int, error) {
|
|||
}
|
||||
|
||||
func (s signatureWriter) Close() error {
|
||||
sig := &packet.Signature{
|
||||
Version: s.signer.Version,
|
||||
SigType: s.sigType,
|
||||
PubKeyAlgo: s.signer.PubKeyAlgo,
|
||||
Hash: s.hashType,
|
||||
CreationTime: s.config.Now(),
|
||||
IssuerKeyId: &s.signer.KeyId,
|
||||
Metadata: s.metadata,
|
||||
}
|
||||
sig := createSignaturePacket(&s.signer.PublicKey, s.sigType, s.config)
|
||||
sig.Hash = s.hashType
|
||||
sig.Metadata = s.metadata
|
||||
|
||||
if err := sig.Sign(s.h, s.signer, s.config); err != nil {
|
||||
return err
|
||||
|
@ -524,6 +521,21 @@ func (s signatureWriter) Close() error {
|
|||
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.
|
||||
// TODO: we have two of these in OpenPGP packages alone. This probably needs
|
||||
// to be promoted somewhere more common.
|
||||
|
@ -545,6 +557,9 @@ func handleCompression(compressed io.WriteCloser, candidateCompression []uint8,
|
|||
if confAlgo == packet.CompressionNone {
|
||||
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
|
||||
// if compression specified by config available we will use it
|
||||
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
|
|
@ -11,10 +11,9 @@ internally and returns false when the public key is invalid (i.e., it
|
|||
is a low-order point).
|
||||
|
||||
References:
|
||||
- [1] RFC7748 by Langley, Hamburg, Turner (https://rfc-editor.org/rfc/rfc7748.txt)
|
||||
- [2] Curve25519 by Bernstein (https://cr.yp.to/ecdh.html)
|
||||
- [3] Bernstein (https://cr.yp.to/ecdh.html#validate)
|
||||
- [4] Cremers&Jackson (https://eprint.iacr.org/2019/526)
|
||||
|
||||
- [1] RFC7748 by Langley, Hamburg, Turner (https://rfc-editor.org/rfc/rfc7748.txt)
|
||||
- [2] Curve25519 by Bernstein (https://cr.yp.to/ecdh.html)
|
||||
- [3] Bernstein (https://cr.yp.to/ecdh.html#validate)
|
||||
- [4] Cremers&Jackson (https://eprint.iacr.org/2019/526)
|
||||
*/
|
||||
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.
|
||||
func (k *Key) isValidPubKey() bool {
|
||||
fp.Modp((*fp.Elt)(k))
|
||||
isLowOrder := false
|
||||
var isLowOrder int
|
||||
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.
|
||||
|
|
|
@ -3,7 +3,9 @@ package x25519
|
|||
import "github.com/cloudflare/circl/math/fp25519"
|
||||
|
||||
// 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
|
||||
// Size = (256)*(256/8) = 8192 bytes.
|
||||
var tableGenerator = [256 * fp25519.Size]byte{
|
||||
|
|
|
@ -11,10 +11,9 @@ internally and returns false when the public key is invalid (i.e., it
|
|||
is a low-order point).
|
||||
|
||||
References:
|
||||
- [1] RFC7748 by Langley, Hamburg, Turner (https://rfc-editor.org/rfc/rfc7748.txt)
|
||||
- [2] Curve25519 by Bernstein (https://cr.yp.to/ecdh.html)
|
||||
- [3] Bernstein (https://cr.yp.to/ecdh.html#validate)
|
||||
- [4] Cremers&Jackson (https://eprint.iacr.org/2019/526)
|
||||
|
||||
- [1] RFC7748 by Langley, Hamburg, Turner (https://rfc-editor.org/rfc/rfc7748.txt)
|
||||
- [2] Curve25519 by Bernstein (https://cr.yp.to/ecdh.html)
|
||||
- [3] Bernstein (https://cr.yp.to/ecdh.html#validate)
|
||||
- [4] Cremers&Jackson (https://eprint.iacr.org/2019/526)
|
||||
*/
|
||||
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.
|
||||
func (k *Key) isValidPubKey() bool {
|
||||
fp.Modp((*fp.Elt)(k))
|
||||
isLowOrder := false
|
||||
var isLowOrder int
|
||||
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.
|
||||
|
|
|
@ -3,7 +3,9 @@ package x448
|
|||
import fp "github.com/cloudflare/circl/math/fp448"
|
||||
|
||||
// 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
|
||||
// Size = (448)*(448/8) = 25088 bytes.
|
||||
var tableGenerator = [448 * fp.Size]byte{
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
// ScalarSize is the size (in bytes) of scalars.
|
||||
const ScalarSize = 56 // 448 / 8
|
||||
|
||||
//_N is the number of 64-bit words to store scalars.
|
||||
// _N is the number of 64-bit words to store scalars.
|
||||
const _N = 7 // 448 / 64
|
||||
|
||||
// Scalar represents a positive integer stored in little-endian order.
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
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{}
|
||||
|
||||
// Identity returns the identity point.
|
||||
|
|
|
@ -8,8 +8,7 @@
|
|||
// Both types of hash function use the "sponge" construction and the Keccak
|
||||
// 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
|
||||
// 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
|
||||
// output.
|
||||
//
|
||||
//
|
||||
// Security strengths
|
||||
// # Security strengths
|
||||
//
|
||||
// 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"
|
||||
|
@ -31,8 +29,7 @@
|
|||
// is used. Requesting more than 64 or 32 bytes of output, respectively, does
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// 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
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !amd64 || appengine || gccgo
|
||||
// +build !amd64 appengine gccgo
|
||||
|
||||
package sha3
|
||||
|
||||
// KeccakF1600 applies the Keccak permutation to a 1600b-wide
|
||||
// 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
|
||||
// in the keccak reference code.
|
||||
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.
|
||||
// Unrolls 4 rounds per loop and spreads some steps across rounds.
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ type State struct {
|
|||
// Specific to SHA-3 and SHAKE.
|
||||
outputLen int // the default output size in bytes
|
||||
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.
|
||||
|
@ -86,11 +87,11 @@ func (d *State) permute() {
|
|||
xorIn(d, d.buf())
|
||||
d.bufe = 0
|
||||
d.bufo = 0
|
||||
KeccakF1600(&d.a)
|
||||
KeccakF1600(&d.a, d.turbo)
|
||||
case spongeSqueezing:
|
||||
// If we're squeezing, we need to apply the permutation before
|
||||
// copying more output.
|
||||
KeccakF1600(&d.a)
|
||||
KeccakF1600(&d.a, d.turbo)
|
||||
d.bufe = d.rate
|
||||
d.bufo = 0
|
||||
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.
|
||||
xorIn(d, p[:d.rate])
|
||||
p = p[d.rate:]
|
||||
KeccakF1600(&d.a)
|
||||
KeccakF1600(&d.a, d.turbo)
|
||||
} else {
|
||||
// The slow path; buffer the input until we can fill the sponge, and then xor it in.
|
||||
todo := d.rate - bufl
|
||||
|
@ -193,3 +194,7 @@ func (d *State) Sum(in []byte) []byte {
|
|||
_, _ = dup.Read(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}
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Its generic security strength is 256 bits against all attacks if
|
||||
// at least 64 bytes of its output are used.
|
||||
|
@ -64,6 +75,17 @@ func NewShake256() State {
|
|||
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.
|
||||
func ShakeSum128(hash, data []byte) {
|
||||
h := NewShake128()
|
||||
|
@ -77,3 +99,21 @@ func ShakeSum256(hash, data []byte) {
|
|||
_, _ = h.Write(data)
|
||||
_, _ = 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
|
||||
// Instr: x86_64, bmi2, adx
|
||||
#define integerMulAdx(z,x,y) \
|
||||
MOVL $0,R15; \
|
||||
MOVQ 0+y, DX; XORL AX, AX; \
|
||||
MULXQ 0+x, AX, R8; MOVQ AX, 0+z; \
|
||||
MULXQ 8+x, AX, R9; ADCXQ AX, R8; \
|
||||
|
|
|
@ -158,6 +158,7 @@
|
|||
// Uses: AX, DX, R8-R15, FLAGS
|
||||
// Instr: x86_64, bmi2, adx
|
||||
#define integerMulAdx(z,x,y) \
|
||||
MOVL $0,R15; \
|
||||
MOVQ 0+y, DX; XORL AX, AX; MOVQ $0, R8; \
|
||||
MULXQ 0+x, AX, R9; MOVQ AX, 0+z; \
|
||||
MULXQ 8+x, AX, R10; ADCXQ AX, R9; \
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue