Compare commits

..

2 Commits

Author SHA1 Message Date
e467740c87 Merge pull request #738 from sv222/update-installing
Update installation instructions for Windows in INSTALLING.md
2023-12-13 12:20:24 -05:00
c52a9426b1 Update installation instructions for Windows in INSTALLING.md 2023-09-29 20:17:42 +02:00
1400 changed files with 31676 additions and 73039 deletions

View File

@ -9,20 +9,28 @@ On Unix-like systems, you may simply paste the following snippet into your termi
```sh ```sh
cd /tmp \ cd /tmp \
&& wget https://github.com/cheat/cheat/releases/download/4.4.2/cheat-linux-amd64.gz \ && wget https://github.com/cheat/cheat/releases/download/4.4.0/cheat-linux-amd64.gz \
&& gunzip cheat-linux-amd64.gz \ && gunzip cheat-linux-amd64.gz \
&& chmod +x cheat-linux-amd64 \ && chmod +x cheat-linux-amd64 \
&& sudo mv cheat-linux-amd64 /usr/local/bin/cheat && sudo mv cheat-linux-amd64 /usr/local/bin/cheat
``` ```
You may need to need to change the version number (`4.4.2`) and the archive You may need to need to change the version number (`4.4.0`) and the archive
(`cheat-linux-amd64.gz`) depending on your platform. (`cheat-linux-amd64.gz`) depending on your platform.
See the [releases page][releases] for a list of supported platforms. See the [releases page][releases] for a list of supported platforms.
#### Windows #### Windows
TODO: community support is requested here. Please open a PR if you'd like to To install "cheat" on Windows using PowerShell, follow these steps:
contribute installation instructions for Windows.
- Open PowerShell as an administrator. You can do this by right-clicking the Windows PowerShell or PowerShell application in the Start menu and selecting 'Run as administrator'.
- Copy and paste the following command into the elevated PowerShell window:
```sh
Invoke-WebRequest -Uri 'https://github.com/cheat/cheat/releases/download/4.4.0/cheat-windows-amd64.exe.zip' -OutFile cheat-windows-amd64.exe.zip ; Expand-Archive -Path .\cheat-windows-amd64.exe.zip -DestinationPath 'C:\Program Files\Cheat' -Force ; Rename-Item -Path 'C:\Program Files\Cheat\cheat-windows-amd64.exe' -NewName 'cheat.exe' ; [System.Environment]::SetEnvironmentVariable('Path', [System.Environment]::GetEnvironmentVariable('Path', [System.EnvironmentVariableTarget]::Machine) + ';C:\Program Files\Cheat', [System.EnvironmentVariableTarget]::Machine)
```
- Open the shell again for a new session and use "cheat".
### Install via `go install` ### Install via `go install`
If you have `go` version `>=1.17` available on your `PATH`, you can install If you have `go` version `>=1.17` available on your `PATH`, you can install
@ -35,13 +43,13 @@ go install github.com/cheat/cheat/cmd/cheat@latest
### Install via package manager ### Install via package manager
Several community-maintained packages are also available: Several community-maintained packages are also available:
Package manager | Package(s) | Package manager | Package(s) |
---------------- | ----------- |-----------------|--------------------------------------------------------|
aur | [cheat][pkg-aur-cheat], [cheat-bin][pkg-aur-cheat-bin] | aur | [cheat][pkg-aur-cheat], [cheat-bin][pkg-aur-cheat-bin] |
brew | [cheat][pkg-brew] | brew | [cheat][pkg-brew] |
docker | [docker-cheat][pkg-docker] | docker | [docker-cheat][pkg-docker] |
nix | [nixos.cheat][pkg-nix] | nix | [nixos.cheat][pkg-nix] |
snap | [cheat][pkg-snap] | snap | [cheat][pkg-snap] |
<!--[pacman][] |--> <!--[pacman][] |-->

View File

@ -39,6 +39,7 @@ releases := \
$(dist_dir)/cheat-linux-arm7 \ $(dist_dir)/cheat-linux-arm7 \
$(dist_dir)/cheat-netbsd-amd64 \ $(dist_dir)/cheat-netbsd-amd64 \
$(dist_dir)/cheat-openbsd-amd64 \ $(dist_dir)/cheat-openbsd-amd64 \
$(dist_dir)/cheat-plan9-amd64 \
$(dist_dir)/cheat-solaris-amd64 \ $(dist_dir)/cheat-solaris-amd64 \
$(dist_dir)/cheat-windows-amd64.exe $(dist_dir)/cheat-windows-amd64.exe

View File

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

View File

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

View File

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

View File

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

View File

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

41
go.mod
View File

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

193
go.sum
View File

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

View File

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

View File

@ -1,4 +1,3 @@
// Package config implements functions pertaining to configuration management.
package config package config
import ( import (
@ -10,7 +9,7 @@ import (
cp "github.com/cheat/cheat/internal/cheatpath" cp "github.com/cheat/cheat/internal/cheatpath"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v2"
) )
// Config encapsulates configuration parameters // Config encapsulates configuration parameters
@ -25,7 +24,7 @@ type Config struct {
} }
// New returns a new Config struct // New returns a new Config struct
func New(_ map[string]interface{}, confPath string, resolve bool) (Config, error) { func New(opts map[string]interface{}, confPath string, resolve bool) (Config, error) {
// read the config file // read the config file
buf, err := os.ReadFile(confPath) buf, err := os.ReadFile(confPath)
@ -40,7 +39,7 @@ func New(_ map[string]interface{}, confPath string, resolve bool) (Config, error
conf.Path = confPath conf.Path = confPath
// unmarshal the yaml // unmarshal the yaml
err = yaml.Unmarshal(buf, &conf) err = yaml.UnmarshalStrict(buf, &conf)
if err != nil { if err != nil {
return Config{}, fmt.Errorf("could not unmarshal yaml: %v", err) return Config{}, fmt.Errorf("could not unmarshal yaml: %v", err)
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@ import (
"github.com/cheat/cheat/internal/config" "github.com/cheat/cheat/internal/config"
"github.com/alecthomas/chroma/v2/quick" "github.com/alecthomas/chroma/quick"
) )
// Colorize applies syntax-highlighting to a cheatsheet's Text. // Colorize applies syntax-highlighting to a cheatsheet's Text.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,12 +16,11 @@ import (
"unsafe" "unsafe"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
"github.com/Microsoft/go-winio/internal/fs"
) )
//sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe //sys connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) = ConnectNamedPipe
//sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW //sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *syscall.SecurityAttributes) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateNamedPipeW
//sys createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) [failretval==syscall.InvalidHandle] = CreateFileW
//sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo //sys getNamedPipeInfo(pipe syscall.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo
//sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW //sys getNamedPipeHandleState(pipe syscall.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
//sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc //sys localAlloc(uFlags uint32, length uint32) (ptr uintptr) = LocalAlloc
@ -164,21 +163,19 @@ func (s pipeAddress) String() string {
} }
// tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout. // tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout.
func tryDialPipe(ctx context.Context, path *string, access fs.AccessMask) (syscall.Handle, error) { func tryDialPipe(ctx context.Context, path *string, access uint32) (syscall.Handle, error) {
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return syscall.Handle(0), ctx.Err() return syscall.Handle(0), ctx.Err()
default: default:
wh, err := fs.CreateFile(*path, h, err := createFile(*path,
access, access,
0, // mode 0,
nil, // security attributes nil,
fs.OPEN_EXISTING, syscall.OPEN_EXISTING,
fs.FILE_FLAG_OVERLAPPED|fs.SECURITY_SQOS_PRESENT|fs.SECURITY_ANONYMOUS, windows.FILE_FLAG_OVERLAPPED|windows.SECURITY_SQOS_PRESENT|windows.SECURITY_ANONYMOUS,
0, // template file handle 0)
)
h := syscall.Handle(wh)
if err == nil { if err == nil {
return h, nil return h, nil
} }
@ -222,7 +219,7 @@ func DialPipeContext(ctx context.Context, path string) (net.Conn, error) {
func DialPipeAccess(ctx context.Context, path string, access uint32) (net.Conn, error) { func DialPipeAccess(ctx context.Context, path string, access uint32) (net.Conn, error) {
var err error var err error
var h syscall.Handle var h syscall.Handle
h, err = tryDialPipe(ctx, &path, fs.AccessMask(access)) h, err = tryDialPipe(ctx, &path, access)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -282,7 +279,6 @@ func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (sy
} }
defer localFree(ntPath.Buffer) defer localFree(ntPath.Buffer)
oa.ObjectName = &ntPath oa.ObjectName = &ntPath
oa.Attributes = windows.OBJ_CASE_INSENSITIVE
// The security descriptor is only needed for the first pipe. // The security descriptor is only needed for the first pipe.
if first { if first {

View File

@ -63,6 +63,7 @@ var (
procBackupWrite = modkernel32.NewProc("BackupWrite") procBackupWrite = modkernel32.NewProc("BackupWrite")
procCancelIoEx = modkernel32.NewProc("CancelIoEx") procCancelIoEx = modkernel32.NewProc("CancelIoEx")
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe") procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
procCreateFileW = modkernel32.NewProc("CreateFileW")
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort") procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW") procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread") procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
@ -304,6 +305,24 @@ func connectNamedPipe(pipe syscall.Handle, o *syscall.Overlapped) (err error) {
return return
} }
func createFile(name string, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
var _p0 *uint16
_p0, err = syscall.UTF16PtrFromString(name)
if err != nil {
return
}
return _createFile(_p0, access, mode, sa, createmode, attrs, templatefile)
}
func _createFile(name *uint16, access uint32, mode uint32, sa *syscall.SecurityAttributes, createmode uint32, attrs uint32, templatefile syscall.Handle) (handle syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall9(procCreateFileW.Addr(), 7, uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile), 0, 0)
handle = syscall.Handle(r0)
if handle == syscall.InvalidHandle {
err = errnoErr(e1)
}
return
}
func createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) { func createIoCompletionPort(file syscall.Handle, port syscall.Handle, key uintptr, threadCount uint32) (newport syscall.Handle, err error) {
r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount), 0, 0) r0, _, e1 := syscall.Syscall6(procCreateIoCompletionPort.Addr(), 4, uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount), 0, 0)
newport = syscall.Handle(r0) newport = syscall.Handle(r0)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,14 +3,17 @@
package packet package packet
import ( import (
"bytes"
"crypto/cipher"
"crypto/rand"
"encoding/binary"
"io" "io"
"github.com/ProtonMail/go-crypto/openpgp/errors" "github.com/ProtonMail/go-crypto/openpgp/errors"
"github.com/ProtonMail/go-crypto/openpgp/internal/algorithm" "github.com/ProtonMail/go-crypto/openpgp/internal/algorithm"
) )
// AEADEncrypted represents an AEAD Encrypted Packet. // AEADEncrypted represents an AEAD Encrypted Packet (tag 20, RFC4880bis-5.16).
// See https://www.ietf.org/archive/id/draft-koch-openpgp-2015-rfc4880bis-00.html#name-aead-encrypted-data-packet-t
type AEADEncrypted struct { type AEADEncrypted struct {
cipher CipherFunction cipher CipherFunction
mode AEADMode mode AEADMode
@ -22,6 +25,33 @@ type AEADEncrypted struct {
// Only currently defined version // Only currently defined version
const aeadEncryptedVersion = 1 const aeadEncryptedVersion = 1
// An AEAD opener/sealer, its configuration, and data for en/decryption.
type aeadCrypter struct {
aead cipher.AEAD
chunkSize int
initialNonce []byte
associatedData []byte // Chunk-independent associated data
chunkIndex []byte // Chunk counter
bytesProcessed int // Amount of plaintext bytes encrypted/decrypted
buffer bytes.Buffer // Buffered bytes across chunks
}
// aeadEncrypter encrypts and writes bytes. It encrypts when necessary according
// to the AEAD block size, and buffers the extra encrypted bytes for next write.
type aeadEncrypter struct {
aeadCrypter // Embedded plaintext sealer
writer io.WriteCloser // 'writer' is a partialLengthWriter
}
// aeadDecrypter reads and decrypts bytes. It buffers extra decrypted bytes when
// necessary, similar to aeadEncrypter.
type aeadDecrypter struct {
aeadCrypter // Embedded ciphertext opener
reader io.Reader // 'reader' is a partialLengthReader
peekedBytes []byte // Used to detect last chunk
eof bool
}
func (ae *AEADEncrypted) parse(buf io.Reader) error { func (ae *AEADEncrypted) parse(buf io.Reader) error {
headerData := make([]byte, 4) headerData := make([]byte, 4)
if n, err := io.ReadFull(buf, headerData); n < 4 { if n, err := io.ReadFull(buf, headerData); n < 4 {
@ -29,14 +59,10 @@ func (ae *AEADEncrypted) parse(buf io.Reader) error {
} }
// Read initial nonce // Read initial nonce
mode := AEADMode(headerData[2]) mode := AEADMode(headerData[2])
nonceLen := mode.IvLength() nonceLen := mode.NonceLength()
if nonceLen == 0 {
// This packet supports only EAX and OCB
// https://www.ietf.org/archive/id/draft-koch-openpgp-2015-rfc4880bis-00.html#name-aead-encrypted-data-packet-t
if nonceLen == 0 || mode > AEADModeOCB {
return errors.AEADError("unknown mode") return errors.AEADError("unknown mode")
} }
initialNonce := make([]byte, nonceLen) initialNonce := make([]byte, nonceLen)
if n, err := io.ReadFull(buf, initialNonce); n < nonceLen { if n, err := io.ReadFull(buf, initialNonce); n < nonceLen {
return errors.AEADError("could not read aead nonce:" + err.Error()) return errors.AEADError("could not read aead nonce:" + err.Error())
@ -49,7 +75,7 @@ func (ae *AEADEncrypted) parse(buf io.Reader) error {
} }
ae.cipher = CipherFunction(c) ae.cipher = CipherFunction(c)
ae.mode = mode ae.mode = mode
ae.chunkSizeByte = headerData[3] ae.chunkSizeByte = byte(headerData[3])
return nil return nil
} }
@ -79,13 +105,225 @@ func (ae *AEADEncrypted) decrypt(key []byte) (io.ReadCloser, error) {
initialNonce: ae.initialNonce, initialNonce: ae.initialNonce,
associatedData: ae.associatedData(), associatedData: ae.associatedData(),
chunkIndex: make([]byte, 8), chunkIndex: make([]byte, 8),
packetTag: packetTypeAEADEncrypted,
}, },
reader: ae.Contents, reader: ae.Contents,
peekedBytes: peekedBytes}, nil peekedBytes: peekedBytes}, nil
} }
// associatedData for chunks: tag, version, cipher, mode, chunk size byte // 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
func (ae *AEADEncrypted) associatedData() []byte { func (ae *AEADEncrypted) associatedData() []byte {
return []byte{ return []byte{
0xD4, 0xD4,
@ -94,3 +332,33 @@ func (ae *AEADEncrypted) associatedData() []byte {
byte(ae.mode), byte(ae.mode),
ae.chunkSizeByte} ae.chunkSizeByte}
} }
// computeNonce takes the incremental index and computes an eXclusive OR with
// the least significant 8 bytes of the receivers' initial nonce (see sec.
// 5.16.1 and 5.16.2). It returns the resulting nonce.
func (wo *aeadCrypter) computeNextNonce() (nonce []byte) {
nonce = make([]byte, len(wo.initialNonce))
copy(nonce, wo.initialNonce)
offset := len(wo.initialNonce) - 8
for i := 0; i < 8; i++ {
nonce[i+offset] ^= wo.chunkIndex[i]
}
return
}
// incrementIndex performs an integer increment by 1 of the integer represented by the
// slice, modifying it accordingly.
func (wo *aeadCrypter) incrementIndex() error {
index := wo.chunkIndex
if len(index) == 0 {
return errors.AEADError("Index has length 0")
}
for i := len(index) - 1; i >= 0; i-- {
if index[i] < 255 {
index[i]++
return nil
}
index[i] = 0
}
return errors.AEADError("cannot further increment index")
}

View File

@ -10,8 +10,6 @@ import (
"io" "io"
"math/big" "math/big"
"time" "time"
"github.com/ProtonMail/go-crypto/openpgp/s2k"
) )
// Config collects a number of parameters along with sensible defaults. // Config collects a number of parameters along with sensible defaults.
@ -35,24 +33,16 @@ type Config struct {
DefaultCompressionAlgo CompressionAlgo DefaultCompressionAlgo CompressionAlgo
// CompressionConfig configures the compression settings. // CompressionConfig configures the compression settings.
CompressionConfig *CompressionConfig CompressionConfig *CompressionConfig
// S2K (String to Key) config, used for key derivation in the context of secret key encryption // S2KCount is only used for symmetric encryption. It
// and password-encrypted data. // determines the strength of the passphrase stretching when
// If nil, the default configuration is used
S2KConfig *s2k.Config
// Iteration count for Iterated S2K (String to Key).
// Only used if sk2.Mode is nil.
// This value is duplicated here from s2k.Config for backwards compatibility.
// It determines the strength of the passphrase stretching when
// the said passphrase is hashed to produce a key. S2KCount // the said passphrase is hashed to produce a key. S2KCount
// should be between 65536 and 65011712, inclusive. If Config // should be between 1024 and 65011712, inclusive. If Config
// is nil or S2KCount is 0, the value 16777216 used. Not all // is nil or S2KCount is 0, the value 65536 used. Not all
// values in the above range can be represented. S2KCount will // values in the above range can be represented. S2KCount will
// be rounded up to the next representable value if it cannot // be rounded up to the next representable value if it cannot
// be encoded exactly. When set, it is strongly encrouraged to // be encoded exactly. When set, it is strongly encrouraged to
// use a value that is at least 65536. See RFC 4880 Section // use a value that is at least 65536. See RFC 4880 Section
// 3.7.1.3. // 3.7.1.3.
//
// Deprecated: SK2Count should be configured in S2KConfig instead.
S2KCount int S2KCount int
// RSABits is the number of bits in new RSA keys made with NewEntity. // RSABits is the number of bits in new RSA keys made with NewEntity.
// If zero, then 2048 bit keys are created. // If zero, then 2048 bit keys are created.
@ -104,12 +94,6 @@ type Config struct {
// might be no other way than to tolerate the missing MDC. Setting this flag, allows this // might be no other way than to tolerate the missing MDC. Setting this flag, allows this
// mode of operation. It should be considered a measure of last resort. // mode of operation. It should be considered a measure of last resort.
InsecureAllowUnauthenticatedMessages bool InsecureAllowUnauthenticatedMessages bool
// KnownNotations is a map of Notation Data names to bools, which controls
// the notation names that are allowed to be present in critical Notation Data
// signature subpackets.
KnownNotations map[string]bool
// SignatureNotations is a list of Notations to be added to any signatures.
SignatureNotations []*Notation
} }
func (c *Config) Random() io.Reader { func (c *Config) Random() io.Reader {
@ -135,9 +119,9 @@ func (c *Config) Cipher() CipherFunction {
func (c *Config) Now() time.Time { func (c *Config) Now() time.Time {
if c == nil || c.Time == nil { if c == nil || c.Time == nil {
return time.Now().Truncate(time.Second) return time.Now()
} }
return c.Time().Truncate(time.Second) return c.Time()
} }
// KeyLifetime returns the validity period of the key. // KeyLifetime returns the validity period of the key.
@ -163,6 +147,13 @@ func (c *Config) Compression() CompressionAlgo {
return c.DefaultCompressionAlgo return c.DefaultCompressionAlgo
} }
func (c *Config) PasswordHashIterations() int {
if c == nil || c.S2KCount == 0 {
return 0
}
return c.S2KCount
}
func (c *Config) RSAModulusBits() int { func (c *Config) RSAModulusBits() int {
if c == nil || c.RSABits == 0 { if c == nil || c.RSABits == 0 {
return 2048 return 2048
@ -184,27 +175,6 @@ func (c *Config) CurveName() Curve {
return c.Curve return c.Curve
} }
// Deprecated: The hash iterations should now be queried via the S2K() method.
func (c *Config) PasswordHashIterations() int {
if c == nil || c.S2KCount == 0 {
return 0
}
return c.S2KCount
}
func (c *Config) S2K() *s2k.Config {
if c == nil {
return nil
}
// for backwards compatibility
if c != nil && c.S2KCount > 0 && c.S2KConfig == nil {
return &s2k.Config{
S2KCount: c.S2KCount,
}
}
return c.S2KConfig
}
func (c *Config) AEAD() *AEADConfig { func (c *Config) AEAD() *AEADConfig {
if c == nil { if c == nil {
return nil return nil
@ -232,17 +202,3 @@ func (c *Config) AllowUnauthenticatedMessages() bool {
} }
return c.InsecureAllowUnauthenticatedMessages return c.InsecureAllowUnauthenticatedMessages
} }
func (c *Config) KnownNotation(notationName string) bool {
if c == nil {
return false
}
return c.KnownNotations[notationName]
}
func (c *Config) Notations() []*Notation {
if c == nil {
return nil
}
return c.SignatureNotations
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,54 +5,36 @@
package packet package packet
import ( import (
"crypto/cipher"
"crypto/sha1"
"crypto/subtle"
"hash"
"io" "io"
"strconv"
"github.com/ProtonMail/go-crypto/openpgp/errors" "github.com/ProtonMail/go-crypto/openpgp/errors"
) )
const aeadSaltSize = 32
// SymmetricallyEncrypted represents a symmetrically encrypted byte string. The // SymmetricallyEncrypted represents a symmetrically encrypted byte string. The
// encrypted Contents will consist of more OpenPGP packets. See RFC 4880, // encrypted Contents will consist of more OpenPGP packets. See RFC 4880,
// sections 5.7 and 5.13. // sections 5.7 and 5.13.
type SymmetricallyEncrypted struct { type SymmetricallyEncrypted struct {
Version int MDC bool // true iff this is a type 18 packet and thus has an embedded MAC.
Contents io.Reader // contains tag for version 2 Contents io.Reader
IntegrityProtected bool // If true it is type 18 (with MDC or AEAD). False is packet type 9 prefix []byte
// Specific to version 1
prefix []byte
// Specific to version 2
Cipher CipherFunction
Mode AEADMode
ChunkSizeByte byte
Salt [aeadSaltSize]byte
} }
const ( const symmetricallyEncryptedVersion = 1
symmetricallyEncryptedVersionMdc = 1
symmetricallyEncryptedVersionAead = 2
)
func (se *SymmetricallyEncrypted) parse(r io.Reader) error { func (se *SymmetricallyEncrypted) parse(r io.Reader) error {
if se.IntegrityProtected { if se.MDC {
// See RFC 4880, section 5.13. // See RFC 4880, section 5.13.
var buf [1]byte var buf [1]byte
_, err := readFull(r, buf[:]) _, err := readFull(r, buf[:])
if err != nil { if err != nil {
return err return err
} }
if buf[0] != symmetricallyEncryptedVersion {
switch buf[0] {
case symmetricallyEncryptedVersionMdc:
se.Version = symmetricallyEncryptedVersionMdc
case symmetricallyEncryptedVersionAead:
se.Version = symmetricallyEncryptedVersionAead
if err := se.parseAead(r); err != nil {
return err
}
default:
return errors.UnsupportedError("unknown SymmetricallyEncrypted version") return errors.UnsupportedError("unknown SymmetricallyEncrypted version")
} }
} }
@ -64,27 +46,245 @@ func (se *SymmetricallyEncrypted) parse(r io.Reader) error {
// packet can be read. An incorrect key will only be detected after trying // packet can be read. An incorrect key will only be detected after trying
// to decrypt the entire data. // to decrypt the entire data.
func (se *SymmetricallyEncrypted) Decrypt(c CipherFunction, key []byte) (io.ReadCloser, error) { func (se *SymmetricallyEncrypted) Decrypt(c CipherFunction, key []byte) (io.ReadCloser, error) {
if se.Version == symmetricallyEncryptedVersionAead { keySize := c.KeySize()
return se.decryptAead(key) 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")
} }
return se.decryptMdc(c, 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
} }
// SerializeSymmetricallyEncrypted serializes a symmetrically encrypted packet // SerializeSymmetricallyEncrypted serializes a symmetrically encrypted packet
// to w and returns a WriteCloser to which the to-be-encrypted packets can be // to w and returns a WriteCloser to which the to-be-encrypted packets can be
// written. // written.
// If config is nil, sensible defaults will be used. // If config is nil, sensible defaults will be used.
func SerializeSymmetricallyEncrypted(w io.Writer, c CipherFunction, aeadSupported bool, cipherSuite CipherSuite, key []byte, config *Config) (Contents io.WriteCloser, err error) { 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")
}
writeCloser := noOpCloser{w} writeCloser := noOpCloser{w}
ciphertext, err := serializeStreamHeader(writeCloser, packetTypeSymmetricallyEncryptedIntegrityProtected) ciphertext, err := serializeStreamHeader(writeCloser, packetTypeSymmetricallyEncryptedMDC)
if err != nil { if err != nil {
return return
} }
if aeadSupported { _, err = ciphertext.Write([]byte{symmetricallyEncryptedVersion})
return serializeSymmetricallyEncryptedAead(ciphertext, cipherSuite, config.AEADConfig.ChunkSizeByte(), config.Random(), key) if err != nil {
return
} }
return serializeSymmetricallyEncryptedMdc(ciphertext, c, key, config) block := c.new(key)
blockSize := block.BlockSize()
iv := make([]byte, blockSize)
_, err = config.Random().Read(iv)
if err != nil {
return
}
s, prefix := NewOCFBEncrypter(block, iv, OCFBNoResync)
_, err = ciphertext.Write(prefix)
if err != nil {
return
}
plaintext := cipher.StreamWriter{S: s, W: ciphertext}
h := sha1.New()
h.Write(iv)
h.Write(iv[blockSize-2:])
Contents = &seMDCWriter{w: plaintext, h: h}
return
} }

View File

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

View File

@ -1,256 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package packet
import (
"crypto/cipher"
"crypto/sha1"
"crypto/subtle"
"hash"
"io"
"strconv"
"github.com/ProtonMail/go-crypto/openpgp/errors"
)
// seMdcReader wraps an io.Reader with a no-op Close method.
type seMdcReader struct {
in io.Reader
}
func (ser seMdcReader) Read(buf []byte) (int, error) {
return ser.in.Read(buf)
}
func (ser seMdcReader) Close() error {
return nil
}
func (se *SymmetricallyEncrypted) decryptMdc(c CipherFunction, key []byte) (io.ReadCloser, error) {
if !c.IsSupported() {
return nil, errors.UnsupportedError("unsupported cipher: " + strconv.Itoa(int(c)))
}
if len(key) != c.KeySize() {
return nil, errors.InvalidArgumentError("SymmetricallyEncrypted: incorrect key length")
}
if se.prefix == nil {
se.prefix = make([]byte, c.blockSize()+2)
_, err := readFull(se.Contents, se.prefix)
if err != nil {
return nil, err
}
} else if len(se.prefix) != c.blockSize()+2 {
return nil, errors.InvalidArgumentError("can't try ciphers with different block lengths")
}
ocfbResync := OCFBResync
if se.IntegrityProtected {
// MDC packets use a different form of OCFB mode.
ocfbResync = OCFBNoResync
}
s := NewOCFBDecrypter(c.new(key), se.prefix, ocfbResync)
plaintext := cipher.StreamReader{S: s, R: se.Contents}
if se.IntegrityProtected {
// IntegrityProtected packets have an embedded hash that we need to check.
h := sha1.New()
h.Write(se.prefix)
return &seMDCReader{in: plaintext, h: h}, nil
}
// Otherwise, we just need to wrap plaintext so that it's a valid ReadCloser.
return seMdcReader{plaintext}, nil
}
const mdcTrailerSize = 1 /* tag byte */ + 1 /* length byte */ + sha1.Size
// An seMDCReader wraps an io.Reader, maintains a running hash and keeps hold
// of the most recent 22 bytes (mdcTrailerSize). Upon EOF, those bytes form an
// MDC packet containing a hash of the previous Contents which is checked
// against the running hash. See RFC 4880, section 5.13.
type seMDCReader struct {
in io.Reader
h hash.Hash
trailer [mdcTrailerSize]byte
scratch [mdcTrailerSize]byte
trailerUsed int
error bool
eof bool
}
func (ser *seMDCReader) Read(buf []byte) (n int, err error) {
if ser.error {
err = io.ErrUnexpectedEOF
return
}
if ser.eof {
err = io.EOF
return
}
// If we haven't yet filled the trailer buffer then we must do that
// first.
for ser.trailerUsed < mdcTrailerSize {
n, err = ser.in.Read(ser.trailer[ser.trailerUsed:])
ser.trailerUsed += n
if err == io.EOF {
if ser.trailerUsed != mdcTrailerSize {
n = 0
err = io.ErrUnexpectedEOF
ser.error = true
return
}
ser.eof = true
n = 0
return
}
if err != nil {
n = 0
return
}
}
// If it's a short read then we read into a temporary buffer and shift
// the data into the caller's buffer.
if len(buf) <= mdcTrailerSize {
n, err = readFull(ser.in, ser.scratch[:len(buf)])
copy(buf, ser.trailer[:n])
ser.h.Write(buf[:n])
copy(ser.trailer[:], ser.trailer[n:])
copy(ser.trailer[mdcTrailerSize-n:], ser.scratch[:])
if n < len(buf) {
ser.eof = true
err = io.EOF
}
return
}
n, err = ser.in.Read(buf[mdcTrailerSize:])
copy(buf, ser.trailer[:])
ser.h.Write(buf[:n])
copy(ser.trailer[:], buf[n:])
if err == io.EOF {
ser.eof = true
}
return
}
// This is a new-format packet tag byte for a type 19 (Integrity Protected) packet.
const mdcPacketTagByte = byte(0x80) | 0x40 | 19
func (ser *seMDCReader) Close() error {
if ser.error {
return errors.ErrMDCMissing
}
for !ser.eof {
// We haven't seen EOF so we need to read to the end
var buf [1024]byte
_, err := ser.Read(buf[:])
if err == io.EOF {
break
}
if err != nil {
return errors.ErrMDCMissing
}
}
ser.h.Write(ser.trailer[:2])
final := ser.h.Sum(nil)
if subtle.ConstantTimeCompare(final, ser.trailer[2:]) != 1 {
return errors.ErrMDCHashMismatch
}
// The hash already includes the MDC header, but we still check its value
// to confirm encryption correctness
if ser.trailer[0] != mdcPacketTagByte || ser.trailer[1] != sha1.Size {
return errors.ErrMDCMissing
}
return nil
}
// An seMDCWriter writes through to an io.WriteCloser while maintains a running
// hash of the data written. On close, it emits an MDC packet containing the
// running hash.
type seMDCWriter struct {
w io.WriteCloser
h hash.Hash
}
func (w *seMDCWriter) Write(buf []byte) (n int, err error) {
w.h.Write(buf)
return w.w.Write(buf)
}
func (w *seMDCWriter) Close() (err error) {
var buf [mdcTrailerSize]byte
buf[0] = mdcPacketTagByte
buf[1] = sha1.Size
w.h.Write(buf[:2])
digest := w.h.Sum(nil)
copy(buf[2:], digest)
_, err = w.w.Write(buf[:])
if err != nil {
return
}
return w.w.Close()
}
// noOpCloser is like an ioutil.NopCloser, but for an io.Writer.
type noOpCloser struct {
w io.Writer
}
func (c noOpCloser) Write(data []byte) (n int, err error) {
return c.w.Write(data)
}
func (c noOpCloser) Close() error {
return nil
}
func serializeSymmetricallyEncryptedMdc(ciphertext io.WriteCloser, c CipherFunction, key []byte, config *Config) (Contents io.WriteCloser, err error) {
// Disallow old cipher suites
if !c.IsSupported() || c < CipherAES128 {
return nil, errors.InvalidArgumentError("invalid mdc cipher function")
}
if c.KeySize() != len(key) {
return nil, errors.InvalidArgumentError("error in mdc serialization: bad key length")
}
_, err = ciphertext.Write([]byte{symmetricallyEncryptedVersionMdc})
if err != nil {
return
}
block := c.new(key)
blockSize := block.BlockSize()
iv := make([]byte, blockSize)
_, err = config.Random().Read(iv)
if err != nil {
return
}
s, prefix := NewOCFBEncrypter(block, iv, OCFBNoResync)
_, err = ciphertext.Write(prefix)
if err != nil {
return
}
plaintext := cipher.StreamWriter{S: s, W: ciphertext}
h := sha1.New()
h.Write(iv)
h.Write(iv[blockSize-2:])
Contents = &seMDCWriter{w: plaintext, h: h}
return
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

42
vendor/github.com/acomagu/bufpipe/README.md generated vendored Normal file
View File

@ -0,0 +1,42 @@
# 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)

128
vendor/github.com/acomagu/bufpipe/bufpipe.go generated vendored Normal file
View File

@ -0,0 +1,128 @@
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
}

2
vendor/github.com/acomagu/bufpipe/doc.go generated vendored Normal file
View File

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

View File

@ -1,8 +1,4 @@
# Binaries for programs and plugins # Binaries for programs and plugins
.git
.idea
.vscode
.hermit
*.exe *.exe
*.dll *.dll
*.so *.so
@ -21,5 +17,3 @@
_models/ _models/
_examples/ _examples/
*.min.*
build/

View File

@ -36,19 +36,6 @@ linters:
- ifshort - ifshort
- wrapcheck - wrapcheck
- stylecheck - stylecheck
- thelper
- nonamedreturns
- revive
- dupword
- exhaustruct
- varnamelen
- forcetypeassert
- ireturn
- maintidx
- govet
- nosnakecase
- testableexamples
- musttag
linters-settings: linters-settings:
govet: govet:
@ -61,8 +48,8 @@ linters-settings:
min-len: 8 min-len: 8
min-occurrences: 3 min-occurrences: 3
forbidigo: forbidigo:
#forbid: forbid:
# - (Must)?NewLexer$ - (Must)?NewLexer
exclude_godoc_examples: false exclude_godoc_examples: false
@ -87,4 +74,3 @@ issues:
- 'methods on the same type should have the same receiver name' - 'methods on the same type should have the same receiver name'
- '_TokenType_name should be _TokenTypeName' - '_TokenType_name should be _TokenTypeName'
- '`_TokenType_map` should be `_TokenTypeMap`' - '`_TokenType_map` should be `_TokenTypeMap`'
- 'rewrite if-else to switch statement'

19
vendor/github.com/alecthomas/chroma/Makefile generated vendored Normal file
View File

@ -0,0 +1,19 @@
.PHONY: chromad upload all
VERSION ?= $(shell git describe --tags --dirty --always)
all: README.md tokentype_string.go
README.md: lexers/*/*.go
./table.py
tokentype_string.go: types.go
go generate
chromad:
rm -f chromad
(export CGOENABLED=0 GOOS=linux GOARCH=amd64; cd ./cmd/chromad && go build -ldflags="-X 'main.version=$(VERSION)'" -o ../../chromad .)
upload: chromad
scp chromad root@swapoff.org: && \
ssh root@swapoff.org 'install -m755 ./chromad /srv/http/swapoff.org/bin && service chromad restart'

285
vendor/github.com/alecthomas/chroma/README.md generated vendored Normal file
View File

@ -0,0 +1,285 @@
# Chroma — A general purpose syntax highlighter in pure Go
[![Golang Documentation](https://godoc.org/github.com/alecthomas/chroma?status.svg)](https://godoc.org/github.com/alecthomas/chroma) [![CI](https://github.com/alecthomas/chroma/actions/workflows/ci.yml/badge.svg)](https://github.com/alecthomas/chroma/actions/workflows/ci.yml) [![Slack chat](https://img.shields.io/static/v1?logo=slack&style=flat&label=slack&color=green&message=gophers)](https://invite.slack.golangbridge.org/)
> **NOTE:** As Chroma has just been released, its API is still in flux. That said, the high-level interface should not change significantly.
Chroma takes source code and other structured text and converts it into syntax
highlighted HTML, ANSI-coloured text, etc.
Chroma is based heavily on [Pygments](http://pygments.org/), and includes
translators for Pygments lexers and styles.
<a id="markdown-table-of-contents" name="table-of-contents"></a>
## Table of Contents
<!-- TOC -->
1. [Table of Contents](#table-of-contents)
2. [Supported languages](#supported-languages)
3. [Try it](#try-it)
4. [Using the library](#using-the-library)
1. [Quick start](#quick-start)
2. [Identifying the language](#identifying-the-language)
3. [Formatting the output](#formatting-the-output)
4. [The HTML formatter](#the-html-formatter)
5. [More detail](#more-detail)
1. [Lexers](#lexers)
2. [Formatters](#formatters)
3. [Styles](#styles)
6. [Command-line interface](#command-line-interface)
7. [What's missing compared to Pygments?](#whats-missing-compared-to-pygments)
<!-- /TOC -->
<a id="markdown-supported-languages" name="supported-languages"></a>
## Supported languages
Prefix | Language
:----: | --------
A | ABAP, ABNF, ActionScript, ActionScript 3, Ada, Angular2, ANTLR, ApacheConf, APL, AppleScript, Arduino, Awk
B | Ballerina, Base Makefile, Bash, Batchfile, BibTeX, Bicep, BlitzBasic, BNF, Brainfuck
C | C, C#, C++, Caddyfile, Caddyfile Directives, Cap'n Proto, Cassandra CQL, Ceylon, CFEngine3, cfstatement, ChaiScript, Cheetah, Clojure, CMake, COBOL, CoffeeScript, Common Lisp, Coq, Crystal, CSS, Cython
D | D, Dart, Diff, Django/Jinja, Docker, DTD, Dylan
E | EBNF, Elixir, Elm, EmacsLisp, Erlang
F | Factor, Fish, Forth, Fortran, FSharp
G | GAS, GDScript, Genshi, Genshi HTML, Genshi Text, Gherkin, GLSL, Gnuplot, Go, Go HTML Template, Go Text Template, GraphQL, Groff, Groovy
H | Handlebars, Haskell, Haxe, HCL, Hexdump, HLB, HTML, HTTP, Hy
I | Idris, Igor, INI, Io
J | J, Java, JavaScript, JSON, Julia, Jungle
K | Kotlin
L | Lighttpd configuration file, LLVM, Lua
M | Mako, markdown, Mason, Mathematica, Matlab, MiniZinc, MLIR, Modula-2, MonkeyC, MorrowindScript, Myghty, MySQL
N | NASM, Newspeak, Nginx configuration file, Nim, Nix
O | Objective-C, OCaml, Octave, OnesEnterprise, OpenEdge ABL, OpenSCAD, Org Mode
P | PacmanConf, Perl, PHP, PHTML, Pig, PkgConfig, PL/pgSQL, plaintext, Pony, PostgreSQL SQL dialect, PostScript, POVRay, PowerShell, Prolog, PromQL, Protocol Buffer, Puppet, Python 2, Python
Q | QBasic
R | R, Racket, Ragel, Raku, react, ReasonML, reg, reStructuredText, Rexx, Ruby, Rust
S | SAS, Sass, Scala, Scheme, Scilab, SCSS, Smalltalk, Smarty, Snobol, Solidity, SPARQL, SQL, SquidConf, Standard ML, Stylus, Svelte, Swift, SYSTEMD, systemverilog
T | TableGen, TASM, Tcl, Tcsh, Termcap, Terminfo, Terraform, TeX, Thrift, TOML, TradingView, Transact-SQL, Turing, Turtle, Twig, TypeScript, TypoScript, TypoScriptCssData, TypoScriptHtmlData
V | VB.net, verilog, VHDL, VimL, vue
W | WDTE
X | XML, Xorg
Y | YAML, YANG
Z | Zig
_I will attempt to keep this section up to date, but an authoritative list can be
displayed with `chroma --list`._
<a id="markdown-try-it" name="try-it"></a>
## Try it
Try out various languages and styles on the [Chroma Playground](https://swapoff.org/chroma/playground/).
<a id="markdown-using-the-library" name="using-the-library"></a>
## Using the library
Chroma, like Pygments, has the concepts of
[lexers](https://github.com/alecthomas/chroma/tree/master/lexers),
[formatters](https://github.com/alecthomas/chroma/tree/master/formatters) and
[styles](https://github.com/alecthomas/chroma/tree/master/styles).
Lexers convert source text into a stream of tokens, styles specify how token
types are mapped to colours, and formatters convert tokens and styles into
formatted output.
A package exists for each of these, containing a global `Registry` variable
with all of the registered implementations. There are also helper functions
for using the registry in each package, such as looking up lexers by name or
matching filenames, etc.
In all cases, if a lexer, formatter or style can not be determined, `nil` will
be returned. In this situation you may want to default to the `Fallback`
value in each respective package, which provides sane defaults.
<a id="markdown-quick-start" name="quick-start"></a>
### Quick start
A convenience function exists that can be used to simply format some source
text, without any effort:
```go
err := quick.Highlight(os.Stdout, someSourceCode, "go", "html", "monokai")
```
<a id="markdown-identifying-the-language" name="identifying-the-language"></a>
### Identifying the language
To highlight code, you'll first have to identify what language the code is
written in. There are three primary ways to do that:
1. Detect the language from its filename.
```go
lexer := lexers.Match("foo.go")
```
3. Explicitly specify the language by its Chroma syntax ID (a full list is available from `lexers.Names()`).
```go
lexer := lexers.Get("go")
```
3. Detect the language from its content.
```go
lexer := lexers.Analyse("package main\n\nfunc main()\n{\n}\n")
```
In all cases, `nil` will be returned if the language can not be identified.
```go
if lexer == nil {
lexer = lexers.Fallback
}
```
At this point, it should be noted that some lexers can be extremely chatty. To
mitigate this, you can use the coalescing lexer to coalesce runs of identical
token types into a single token:
```go
lexer = chroma.Coalesce(lexer)
```
<a id="markdown-formatting-the-output" name="formatting-the-output"></a>
### Formatting the output
Once a language is identified you will need to pick a formatter and a style (theme).
```go
style := styles.Get("swapoff")
if style == nil {
style = styles.Fallback
}
formatter := formatters.Get("html")
if formatter == nil {
formatter = formatters.Fallback
}
```
Then obtain an iterator over the tokens:
```go
contents, err := ioutil.ReadAll(r)
iterator, err := lexer.Tokenise(nil, string(contents))
```
And finally, format the tokens from the iterator:
```go
err := formatter.Format(w, style, iterator)
```
<a id="markdown-the-html-formatter" name="the-html-formatter"></a>
### The HTML formatter
By default the `html` registered formatter generates standalone HTML with
embedded CSS. More flexibility is available through the `formatters/html` package.
Firstly, the output generated by the formatter can be customised with the
following constructor options:
- `Standalone()` - generate standalone HTML with embedded CSS.
- `WithClasses()` - use classes rather than inlined style attributes.
- `ClassPrefix(prefix)` - prefix each generated CSS class.
- `TabWidth(width)` - Set the rendered tab width, in characters.
- `WithLineNumbers()` - Render line numbers (style with `LineNumbers`).
- `LinkableLineNumbers()` - Make the line numbers linkable and be a link to themselves.
- `HighlightLines(ranges)` - Highlight lines in these ranges (style with `LineHighlight`).
- `LineNumbersInTable()` - Use a table for formatting line numbers and code, rather than spans.
If `WithClasses()` is used, the corresponding CSS can be obtained from the formatter with:
```go
formatter := html.New(html.WithClasses())
err := formatter.WriteCSS(w, style)
```
<a id="markdown-more-detail" name="more-detail"></a>
## More detail
<a id="markdown-lexers" name="lexers"></a>
### Lexers
See the [Pygments documentation](http://pygments.org/docs/lexerdevelopment/)
for details on implementing lexers. Most concepts apply directly to Chroma,
but see existing lexer implementations for real examples.
In many cases lexers can be automatically converted directly from Pygments by
using the included Python 3 script `pygments2chroma.py`. I use something like
the following:
```sh
python3 _tools/pygments2chroma.py \
pygments.lexers.jvm.KotlinLexer \
> lexers/k/kotlin.go \
&& gofmt -s -w lexers/k/kotlin.go
```
See notes in [pygments-lexers.txt](https://github.com/alecthomas/chroma/blob/master/pygments-lexers.txt)
for a list of lexers, and notes on some of the issues importing them.
<a id="markdown-formatters" name="formatters"></a>
### Formatters
Chroma supports HTML output, as well as terminal output in 8 colour, 256 colour, and true-colour.
A `noop` formatter is included that outputs the token text only, and a `tokens`
formatter outputs raw tokens. The latter is useful for debugging lexers.
<a id="markdown-styles" name="styles"></a>
### Styles
Chroma styles use the [same syntax](http://pygments.org/docs/styles/) as Pygments.
All Pygments styles have been converted to Chroma using the `_tools/style.py` script.
When you work with one of [Chroma's styles](https://github.com/alecthomas/chroma/tree/master/styles), know that the `chroma.Background` token type provides the default style for tokens. It does so by defining a foreground color and background color.
For example, this gives each token name not defined in the style a default color of `#f8f8f8` and uses `#000000` for the highlighted code block's background:
~~~go
chroma.Background: "#f8f8f2 bg:#000000",
~~~
Also, token types in a style file are hierarchical. For instance, when `CommentSpecial` is not defined, Chroma uses the token style from `Comment`. So when several comment tokens use the same color, you'll only need to define `Comment` and override the one that has a different color.
For a quick overview of the available styles and how they look, check out the [Chroma Style Gallery](https://xyproto.github.io/splash/docs/).
<a id="markdown-command-line-interface" name="command-line-interface"></a>
## Command-line interface
A command-line interface to Chroma is included.
Binaries are available to install from [the releases page](https://github.com/alecthomas/chroma/releases).
The CLI can be used as a preprocessor to colorise output of `less(1)`,
see documentation for the `LESSOPEN` environment variable.
The `--fail` flag can be used to suppress output and return with exit status
1 to facilitate falling back to some other preprocessor in case chroma
does not resolve a specific lexer to use for the given file. For example:
```shell
export LESSOPEN='| p() { chroma --fail "$1" || cat "$1"; }; p "%s"'
```
Replace `cat` with your favourite fallback preprocessor.
When invoked as `.lessfilter`, the `--fail` flag is automatically turned
on under the hood for easy integration with [lesspipe shipping with
Debian and derivatives](https://manpages.debian.org/lesspipe#USER_DEFINED_FILTERS);
for that setup the `chroma` executable can be just symlinked to `~/.lessfilter`.
<a id="markdown-whats-missing-compared-to-pygments" name="whats-missing-compared-to-pygments"></a>
## What's missing compared to Pygments?
- Quite a few lexers, for various reasons (pull-requests welcome):
- Pygments lexers for complex languages often include custom code to
handle certain aspects, such as Raku's ability to nest code inside
regular expressions. These require time and effort to convert.
- I mostly only converted languages I had heard of, to reduce the porting cost.
- Some more esoteric features of Pygments are omitted for simplicity.
- Though the Chroma API supports content detection, very few languages support them.
I have plans to implement a statistical analyser at some point, but not enough time.

View File

@ -92,7 +92,7 @@ func (c Colour) Brighten(factor float64) Colour {
return NewColour(uint8(r), uint8(g), uint8(b)) return NewColour(uint8(r), uint8(g), uint8(b))
} }
// BrightenOrDarken brightens a colour if it is < 0.5 brightness or darkens if > 0.5 brightness. // BrightenOrDarken brightens a colour if it is < 0.5 brighteness or darkens if > 0.5 brightness.
func (c Colour) BrightenOrDarken(factor float64) Colour { func (c Colour) BrightenOrDarken(factor float64) Colour {
if c.Brightness() < 0.5 { if c.Brightness() < 0.5 {
return c.Brighten(factor) return c.Brighten(factor)
@ -100,35 +100,7 @@ func (c Colour) BrightenOrDarken(factor float64) Colour {
return c.Brighten(-factor) return c.Brighten(-factor)
} }
// ClampBrightness returns a copy of this colour with its brightness adjusted such that // Brightness of the colour (roughly) in the range 0.0 to 1.0
// it falls within the range [min, max] (or very close to it due to rounding errors).
// The supplied values use the same [0.0, 1.0] range as Brightness.
func (c Colour) ClampBrightness(min, max float64) Colour {
if !c.IsSet() {
return c
}
min = math.Max(min, 0)
max = math.Min(max, 1)
current := c.Brightness()
target := math.Min(math.Max(current, min), max)
if current == target {
return c
}
r := float64(c.Red())
g := float64(c.Green())
b := float64(c.Blue())
rgb := r + g + b
if target > current {
// Solve for x: target == ((255-r)*x + r + (255-g)*x + g + (255-b)*x + b) / 255 / 3
return c.Brighten((target*255*3 - rgb) / (255*3 - rgb))
}
// Solve for x: target == (r*(x+1) + g*(x+1) + b*(x+1)) / 255 / 3
return c.Brighten((target*255*3)/rgb - 1)
}
// Brightness of the colour (roughly) in the range 0.0 to 1.0.
func (c Colour) Brightness() float64 { func (c Colour) Brightness() float64 {
return (float64(c.Red()) + float64(c.Green()) + float64(c.Blue())) / 255.0 / 3.0 return (float64(c.Red()) + float64(c.Green()) + float64(c.Blue())) / 255.0 / 3.0
} }

View File

@ -24,21 +24,6 @@ func DelegatingLexer(root Lexer, language Lexer) Lexer {
} }
} }
func (d *delegatingLexer) AnalyseText(text string) float32 {
return d.root.AnalyseText(text)
}
func (d *delegatingLexer) SetAnalyser(analyser func(text string) float32) Lexer {
d.root.SetAnalyser(analyser)
return d
}
func (d *delegatingLexer) SetRegistry(r *LexerRegistry) Lexer {
d.root.SetRegistry(r)
d.language.SetRegistry(r)
return d
}
func (d *delegatingLexer) Config() *Config { func (d *delegatingLexer) Config() *Config {
return d.language.Config() return d.language.Config()
} }

View File

@ -4,9 +4,9 @@ import (
"io" "io"
"sort" "sort"
"github.com/alecthomas/chroma/v2" "github.com/alecthomas/chroma"
"github.com/alecthomas/chroma/v2/formatters/html" "github.com/alecthomas/chroma/formatters/html"
"github.com/alecthomas/chroma/v2/formatters/svg" "github.com/alecthomas/chroma/formatters/svg"
) )
var ( var (

View File

@ -7,7 +7,7 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/alecthomas/chroma/v2" "github.com/alecthomas/chroma"
) )
// Option sets an option of the HTML formatter. // Option sets an option of the HTML formatter.
@ -25,21 +25,12 @@ func WithClasses(b bool) Option { return func(f *Formatter) { f.Classes = b } }
// WithAllClasses disables an optimisation that omits redundant CSS classes. // WithAllClasses disables an optimisation that omits redundant CSS classes.
func WithAllClasses(b bool) Option { return func(f *Formatter) { f.allClasses = b } } func WithAllClasses(b bool) Option { return func(f *Formatter) { f.allClasses = b } }
// WithCustomCSS sets user's custom CSS styles.
func WithCustomCSS(css map[chroma.TokenType]string) Option {
return func(f *Formatter) {
f.customCSS = css
}
}
// TabWidth sets the number of characters for a tab. Defaults to 8. // TabWidth sets the number of characters for a tab. Defaults to 8.
func TabWidth(width int) Option { return func(f *Formatter) { f.tabWidth = width } } func TabWidth(width int) Option { return func(f *Formatter) { f.tabWidth = width } }
// PreventSurroundingPre prevents the surrounding pre tags around the generated code. // PreventSurroundingPre prevents the surrounding pre tags around the generated code.
func PreventSurroundingPre(b bool) Option { func PreventSurroundingPre(b bool) Option {
return func(f *Formatter) { return func(f *Formatter) {
f.preventSurroundingPre = b
if b { if b {
f.preWrapper = nopPreWrapper f.preWrapper = nopPreWrapper
} else { } else {
@ -48,29 +39,6 @@ func PreventSurroundingPre(b bool) Option {
} }
} }
// InlineCode creates inline code wrapped in a code tag.
func InlineCode(b bool) Option {
return func(f *Formatter) {
f.inlineCode = b
f.preWrapper = preWrapper{
start: func(code bool, styleAttr string) string {
if code {
return fmt.Sprintf(`<code%s>`, styleAttr)
}
return ``
},
end: func(code bool) string {
if code {
return `</code>`
}
return ``
},
}
}
}
// WithPreWrapper allows control of the surrounding pre tags. // WithPreWrapper allows control of the surrounding pre tags.
func WithPreWrapper(wrapper PreWrapper) Option { func WithPreWrapper(wrapper PreWrapper) Option {
return func(f *Formatter) { return func(f *Formatter) {
@ -100,9 +68,9 @@ func LineNumbersInTable(b bool) Option {
} }
} }
// WithLinkableLineNumbers decorates the line numbers HTML elements with an "id" // LinkableLineNumbers decorates the line numbers HTML elements with an "id"
// attribute so they can be linked. // attribute so they can be linked.
func WithLinkableLineNumbers(b bool, prefix string) Option { func LinkableLineNumbers(b bool, prefix string) Option {
return func(f *Formatter) { return func(f *Formatter) {
f.linkableLineNumbers = b f.linkableLineNumbers = b
f.lineNumbersIDPrefix = prefix f.lineNumbersIDPrefix = prefix
@ -171,10 +139,10 @@ var (
defaultPreWrapper = preWrapper{ defaultPreWrapper = preWrapper{
start: func(code bool, styleAttr string) string { start: func(code bool, styleAttr string) string {
if code { if code {
return fmt.Sprintf(`<pre%s><code>`, styleAttr) return fmt.Sprintf(`<pre tabindex="0"%s><code>`, styleAttr)
} }
return fmt.Sprintf(`<pre%s>`, styleAttr) return fmt.Sprintf(`<pre tabindex="0"%s>`, styleAttr)
}, },
end: func(code bool) string { end: func(code bool) string {
if code { if code {
@ -188,22 +156,19 @@ var (
// Formatter that generates HTML. // Formatter that generates HTML.
type Formatter struct { type Formatter struct {
standalone bool standalone bool
prefix string prefix string
Classes bool // Exported field to detect when classes are being used Classes bool // Exported field to detect when classes are being used
allClasses bool allClasses bool
customCSS map[chroma.TokenType]string preWrapper PreWrapper
preWrapper PreWrapper tabWidth int
inlineCode bool wrapLongLines bool
preventSurroundingPre bool lineNumbers bool
tabWidth int lineNumbersInTable bool
wrapLongLines bool linkableLineNumbers bool
lineNumbers bool lineNumbersIDPrefix string
lineNumbersInTable bool highlightRanges highlightRanges
linkableLineNumbers bool baseLineNumber int
lineNumbersIDPrefix string
highlightRanges highlightRanges
baseLineNumber int
} }
type highlightRanges [][2]int type highlightRanges [][2]int
@ -262,7 +227,7 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.
fmt.Fprintf(w, "<span%s>", f.styleAttr(css, chroma.LineHighlight)) fmt.Fprintf(w, "<span%s>", f.styleAttr(css, chroma.LineHighlight))
} }
fmt.Fprintf(w, "<span%s%s>%s\n</span>", f.styleAttr(css, chroma.LineNumbersTable), f.lineIDAttribute(line), f.lineTitleWithLinkIfNeeded(css, lineDigits, line)) fmt.Fprintf(w, "<span%s%s>%s\n</span>", f.styleAttr(css, chroma.LineNumbersTable), f.lineIDAttribute(line), f.lineTitleWithLinkIfNeeded(lineDigits, line))
if highlight { if highlight {
fmt.Fprintf(w, "</span>") fmt.Fprintf(w, "</span>")
@ -284,30 +249,27 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.
highlightIndex++ highlightIndex++
} }
if !(f.preventSurroundingPre || f.inlineCode) { // Start of Line
// Start of Line fmt.Fprint(w, `<span`)
fmt.Fprint(w, `<span`) if highlight {
// Line + LineHighlight
if highlight { if f.Classes {
// Line + LineHighlight fmt.Fprintf(w, ` class="%s %s"`, f.class(chroma.Line), f.class(chroma.LineHighlight))
if f.Classes {
fmt.Fprintf(w, ` class="%s %s"`, f.class(chroma.Line), f.class(chroma.LineHighlight))
} else {
fmt.Fprintf(w, ` style="%s %s"`, css[chroma.Line], css[chroma.LineHighlight])
}
fmt.Fprint(w, `>`)
} else { } else {
fmt.Fprintf(w, "%s>", f.styleAttr(css, chroma.Line)) fmt.Fprintf(w, ` style="%s %s"`, css[chroma.Line], css[chroma.LineHighlight])
} }
fmt.Fprint(w, `>`)
// Line number } else {
if f.lineNumbers && !wrapInTable { fmt.Fprintf(w, "%s>", f.styleAttr(css, chroma.Line))
fmt.Fprintf(w, "<span%s%s>%s</span>", f.styleAttr(css, chroma.LineNumbers), f.lineIDAttribute(line), f.lineTitleWithLinkIfNeeded(css, lineDigits, line))
}
fmt.Fprintf(w, `<span%s>`, f.styleAttr(css, chroma.CodeLine))
} }
// Line number
if f.lineNumbers && !wrapInTable {
fmt.Fprintf(w, "<span%s%s>%s</span>", f.styleAttr(css, chroma.LineNumbers), f.lineIDAttribute(line), f.lineTitleWithLinkIfNeeded(lineDigits, line))
}
fmt.Fprintf(w, `<span%s>`, f.styleAttr(css, chroma.CodeLine))
for _, token := range tokens { for _, token := range tokens {
html := html.EscapeString(token.String()) html := html.EscapeString(token.String())
attr := f.styleAttr(css, token.Type) attr := f.styleAttr(css, token.Type)
@ -317,12 +279,11 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.
fmt.Fprint(w, html) fmt.Fprint(w, html)
} }
if !(f.preventSurroundingPre || f.inlineCode) { fmt.Fprint(w, `</span>`) // End of CodeLine
fmt.Fprint(w, `</span>`) // End of CodeLine
fmt.Fprint(w, `</span>`) // End of Line fmt.Fprint(w, `</span>`) // End of Line
}
} }
fmt.Fprintf(w, f.preWrapper.End(true)) fmt.Fprintf(w, f.preWrapper.End(true))
if wrapInTable { if wrapInTable {
@ -345,12 +306,12 @@ func (f *Formatter) lineIDAttribute(line int) string {
return fmt.Sprintf(" id=\"%s\"", f.lineID(line)) return fmt.Sprintf(" id=\"%s\"", f.lineID(line))
} }
func (f *Formatter) lineTitleWithLinkIfNeeded(css map[chroma.TokenType]string, lineDigits, line int) string { func (f *Formatter) lineTitleWithLinkIfNeeded(lineDigits, line int) string {
title := fmt.Sprintf("%*d", lineDigits, line) title := fmt.Sprintf("%*d", lineDigits, line)
if !f.linkableLineNumbers { if !f.linkableLineNumbers {
return title return title
} }
return fmt.Sprintf("<a%s href=\"#%s\">%s</a>", f.styleAttr(css, chroma.LineLink), f.lineID(line), title) return fmt.Sprintf("<a style=\"outline: none; text-decoration:none; color:inherit\" href=\"#%s\">%s</a>", f.lineID(line), title)
} }
func (f *Formatter) lineID(line int) string { func (f *Formatter) lineID(line int) string {
@ -412,7 +373,7 @@ func (f *Formatter) styleAttr(styles map[chroma.TokenType]string, tt chroma.Toke
func (f *Formatter) tabWidthStyle() string { func (f *Formatter) tabWidthStyle() string {
if f.tabWidth != 0 && f.tabWidth != 8 { if f.tabWidth != 0 && f.tabWidth != 8 {
return fmt.Sprintf("-moz-tab-size: %[1]d; -o-tab-size: %[1]d; tab-size: %[1]d;", f.tabWidth) return fmt.Sprintf("; -moz-tab-size: %[1]d; -o-tab-size: %[1]d; tab-size: %[1]d", f.tabWidth)
} }
return "" return ""
} }
@ -474,53 +435,28 @@ func (f *Formatter) styleToCSS(style *chroma.Style) map[chroma.TokenType]string
if t != chroma.Background { if t != chroma.Background {
entry = entry.Sub(bg) entry = entry.Sub(bg)
} }
if !f.allClasses && entry.IsZero() {
// Inherit from custom CSS provided by user
tokenCategory := t.Category()
tokenSubCategory := t.SubCategory()
if t != tokenCategory {
if css, ok := f.customCSS[tokenCategory]; ok {
classes[t] = css
}
}
if tokenCategory != tokenSubCategory {
if css, ok := f.customCSS[tokenSubCategory]; ok {
classes[t] += css
}
}
// Add custom CSS provided by user
if css, ok := f.customCSS[t]; ok {
classes[t] += css
}
if !f.allClasses && entry.IsZero() && classes[t] == `` {
continue continue
} }
classes[t] = StyleEntryToCSS(entry)
styleEntryCSS := StyleEntryToCSS(entry)
if styleEntryCSS != `` && classes[t] != `` {
styleEntryCSS += `;`
}
classes[t] = styleEntryCSS + classes[t]
} }
classes[chroma.Background] += `;` + f.tabWidthStyle() classes[chroma.Background] += f.tabWidthStyle()
classes[chroma.PreWrapper] += classes[chroma.Background] classes[chroma.PreWrapper] += classes[chroma.Background] + `;`
// Make PreWrapper a grid to show highlight style with full width. // Make PreWrapper a grid to show highlight style with full width.
if len(f.highlightRanges) > 0 && f.customCSS[chroma.PreWrapper] == `` { if len(f.highlightRanges) > 0 {
classes[chroma.PreWrapper] += `display: grid;` classes[chroma.PreWrapper] += `display: grid;`
} }
// Make PreWrapper wrap long lines. // Make PreWrapper wrap long lines.
if f.wrapLongLines { if f.wrapLongLines {
classes[chroma.PreWrapper] += `white-space: pre-wrap; word-break: break-word;` classes[chroma.PreWrapper] += `white-space: pre-wrap; word-break: break-word;`
} }
lineNumbersStyle := `white-space: pre; -webkit-user-select: none; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;` lineNumbersStyle := `white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;`
// All rules begin with default rules followed by user provided rules // All rules begin with default rules followed by user provided rules
classes[chroma.Line] = `display: flex;` + classes[chroma.Line] classes[chroma.Line] = `display: flex;` + classes[chroma.Line]
classes[chroma.LineNumbers] = lineNumbersStyle + classes[chroma.LineNumbers] classes[chroma.LineNumbers] = lineNumbersStyle + classes[chroma.LineNumbers]
classes[chroma.LineNumbersTable] = lineNumbersStyle + classes[chroma.LineNumbersTable] classes[chroma.LineNumbersTable] = lineNumbersStyle + classes[chroma.LineNumbersTable]
classes[chroma.LineTable] = "border-spacing: 0; padding: 0; margin: 0; border: 0;" + classes[chroma.LineTable] classes[chroma.LineTable] = "border-spacing: 0; padding: 0; margin: 0; border: 0;" + classes[chroma.LineTable]
classes[chroma.LineTableTD] = "vertical-align: top; padding: 0; margin: 0; border: 0;" + classes[chroma.LineTableTD] classes[chroma.LineTableTD] = "vertical-align: top; padding: 0; margin: 0; border: 0;" + classes[chroma.LineTableTD]
classes[chroma.LineLink] = "outline: none; text-decoration: none; color: inherit" + classes[chroma.LineLink]
return classes return classes
} }

View File

@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/alecthomas/chroma/v2" "github.com/alecthomas/chroma"
) )
// JSON formatter outputs the raw token structures as JSON. // JSON formatter outputs the raw token structures as JSON.

View File

@ -6,11 +6,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"os" "io/ioutil"
"path" "path"
"strings" "strings"
"github.com/alecthomas/chroma/v2" "github.com/alecthomas/chroma"
) )
// Option sets an option of the SVG formatter. // Option sets an option of the SVG formatter.
@ -34,7 +34,7 @@ func EmbedFontFile(fontFamily string, fileName string) (option Option, err error
} }
var content []byte var content []byte
if content, err = os.ReadFile(fileName); err == nil { if content, err = ioutil.ReadFile(fileName); err == nil {
option = EmbedFont(fontFamily, base64.StdEncoding.EncodeToString(content), format) option = EmbedFont(fontFamily, base64.StdEncoding.EncodeToString(content), format)
} }
return return

View File

@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/alecthomas/chroma/v2" "github.com/alecthomas/chroma"
) )
// Tokens formatter outputs the raw token structures. // Tokens formatter outputs the raw token structures.

View File

@ -5,7 +5,7 @@ import (
"io" "io"
"math" "math"
"github.com/alecthomas/chroma/v2" "github.com/alecthomas/chroma"
) )
type ttyTable struct { type ttyTable struct {
@ -242,21 +242,12 @@ func (c *indexedTTYFormatter) Format(w io.Writer, style *chroma.Style, it chroma
theme := styleToEscapeSequence(c.table, style) theme := styleToEscapeSequence(c.table, style)
for token := it(); token != chroma.EOF; token = it() { for token := it(); token != chroma.EOF; token = it() {
clr, ok := theme[token.Type] clr, ok := theme[token.Type]
// This search mimics how styles.Get() is used in tty_truecolour.go.
if !ok { if !ok {
clr, ok = theme[token.Type.SubCategory()] clr, ok = theme[token.Type.SubCategory()]
if !ok { if !ok {
clr, ok = theme[token.Type.Category()] clr = theme[token.Type.Category()]
if !ok {
clr, ok = theme[chroma.Text]
if !ok {
clr = theme[chroma.Background]
}
}
} }
} }
if clr != "" { if clr != "" {
fmt.Fprint(w, clr) fmt.Fprint(w, clr)
} }

View File

@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/alecthomas/chroma/v2" "github.com/alecthomas/chroma"
) )
// TTY16m is a true-colour terminal formatter. // TTY16m is a true-colour terminal formatter.

View File

@ -15,30 +15,30 @@ var (
// Config for a lexer. // Config for a lexer.
type Config struct { type Config struct {
// Name of the lexer. // Name of the lexer.
Name string `xml:"name,omitempty"` Name string
// Shortcuts for the lexer // Shortcuts for the lexer
Aliases []string `xml:"alias,omitempty"` Aliases []string
// File name globs // File name globs
Filenames []string `xml:"filename,omitempty"` Filenames []string
// Secondary file name globs // Secondary file name globs
AliasFilenames []string `xml:"alias_filename,omitempty"` AliasFilenames []string
// MIME types // MIME types
MimeTypes []string `xml:"mime_type,omitempty"` MimeTypes []string
// Regex matching is case-insensitive. // Regex matching is case-insensitive.
CaseInsensitive bool `xml:"case_insensitive,omitempty"` CaseInsensitive bool
// Regex matches all characters. // Regex matches all characters.
DotAll bool `xml:"dot_all,omitempty"` DotAll bool
// Regex does not match across lines ($ matches EOL). // Regex does not match across lines ($ matches EOL).
// //
// Defaults to multiline. // Defaults to multiline.
NotMultiline bool `xml:"not_multiline,omitempty"` NotMultiline bool
// Don't strip leading and trailing newlines from the input. // Don't strip leading and trailing newlines from the input.
// DontStripNL bool // DontStripNL bool
@ -48,7 +48,7 @@ type Config struct {
// Make sure that the input ends with a newline. This // Make sure that the input ends with a newline. This
// is required for some lexers that consume input linewise. // is required for some lexers that consume input linewise.
EnsureNL bool `xml:"ensure_nl,omitempty"` EnsureNL bool
// If given and greater than 0, expand tabs in the input. // If given and greater than 0, expand tabs in the input.
// TabSize int // TabSize int
@ -56,27 +56,7 @@ type Config struct {
// Priority of lexer. // Priority of lexer.
// //
// If this is 0 it will be treated as a default of 1. // If this is 0 it will be treated as a default of 1.
Priority float32 `xml:"priority,omitempty"` Priority float32
// Analyse is a list of regexes to match against the input.
//
// If a match is found, the score is returned if single attribute is set to true,
// otherwise the sum of all the score of matching patterns will be
// used as the final score.
Analyse *AnalyseConfig `xml:"analyse,omitempty"`
}
// AnalyseConfig defines the list of regexes analysers.
type AnalyseConfig struct {
Regexes []RegexConfig `xml:"regex,omitempty"`
// If true, the first matching score is returned.
First bool `xml:"first,attr"`
}
// RegexConfig defines a single regex pattern and its score in case of match.
type RegexConfig struct {
Pattern string `xml:"pattern,attr"`
Score float32 `xml:"score,attr"`
} }
// Token output to formatter. // Token output to formatter.
@ -114,20 +94,6 @@ type Lexer interface {
Config() *Config Config() *Config
// Tokenise returns an Iterator over tokens in text. // Tokenise returns an Iterator over tokens in text.
Tokenise(options *TokeniseOptions, text string) (Iterator, error) Tokenise(options *TokeniseOptions, text string) (Iterator, error)
// SetRegistry sets the registry this Lexer is associated with.
//
// The registry should be used by the Lexer if it needs to look up other
// lexers.
SetRegistry(registry *LexerRegistry) Lexer
// SetAnalyser sets a function the Lexer should use for scoring how
// likely a fragment of text is to match this lexer, between 0.0 and 1.0.
// A value of 1 indicates high confidence.
//
// Lexers may ignore this if they implement their own analysers.
SetAnalyser(analyser func(text string) float32) Lexer
// AnalyseText scores how likely a fragment of text is to match
// this lexer, between 0.0 and 1.0. A value of 1 indicates high confidence.
AnalyseText(text string) float32
} }
// Lexers is a slice of lexers sortable by name. // Lexers is a slice of lexers sortable by name.

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