Compare commits

...

141 commits

Author SHA1 Message Date
Daniel
4c4b4471d8 Add support for json file signing 2024-11-08 14:28:09 +01:00
Daniel
4fbce7d649 Switch from portbase to structures lib, update build and version system 2024-06-24 09:48:48 +02:00
Daniel
393206b5e2 Bump version 2023-12-19 15:48:11 +01:00
Daniel
c475557cd1 Update deps 2023-12-19 15:48:05 +01:00
Daniel Hovie
2a3994208b
Merge pull request from safing/feature/blake3
Add support for BLAKE3, add Suites with BLAKE3
2023-12-19 15:42:34 +01:00
Daniel
a93f8142fc Remove CodeQL workflow 2023-12-18 20:28:07 +01:00
Daniel
914459cb99 Add support for BLAKE3, add Suites with BLAKE3 2023-11-22 14:37:23 +01:00
Daniel
e907a26493 Bump version 2023-10-25 14:10:15 +02:00
Daniel Hovie
d57426d89b
Merge pull request from safing/feature/checksums
Add support for embedded text, json and yaml checksums
2023-10-25 14:08:11 +02:00
Daniel
a1424bb197 Update deps 2023-10-25 14:07:45 +02:00
Daniel
fa3982d07a Fix linter warnings 2023-09-26 13:26:44 +02:00
Daniel
1125f87df4 Update go workflow 2023-09-26 13:07:44 +02:00
Daniel
863e870b23 Update golangci config 2023-09-26 13:06:16 +02:00
Daniel
9c7f6954e6 Add support for embedded text, json and yaml checksums 2023-09-26 13:05:31 +02:00
Daniel
ed928f9a3e Issue Mgmt: Add label actions config 2023-08-30 14:01:27 +02:00
Daniel
6c91e759a1 Update issue management workflows 2023-08-30 13:01:01 +02:00
Daniel
5dacdd9eaf Bump version 2022-10-11 13:49:25 +02:00
Daniel Hovie
788a04e6bd
Merge pull request from safing/fix/fs-error-handling
Fix fs error handling
2022-10-11 13:48:45 +02:00
Daniel
e3fcadaa4d Update deps 2022-10-11 13:43:59 +02:00
Daniel
ce0d08ff18 Fix fs error handling 2022-10-11 12:48:24 +02:00
Daniel
93e6672b0c Update pack script 2022-10-10 16:55:35 +02:00
Daniel
d209a94a7d Bump version 2022-09-29 10:50:19 +02:00
Daniel
7437606c81 Update deps 2022-09-29 10:50:09 +02:00
Daniel Hovie
3a520a98b8
Merge pull request from safing/dependabot/go_modules/github.com/AlecAivazis/survey/v2-2.3.6
Bump github.com/AlecAivazis/survey/v2 from 2.3.5 to 2.3.6
2022-09-29 10:11:23 +02:00
dependabot[bot]
904b079876
Bump github.com/AlecAivazis/survey/v2 from 2.3.5 to 2.3.6
Bumps [github.com/AlecAivazis/survey/v2](https://github.com/AlecAivazis/survey) from 2.3.5 to 2.3.6.
- [Release notes](https://github.com/AlecAivazis/survey/releases)
- [Commits](https://github.com/AlecAivazis/survey/compare/v2.3.5...v2.3.6)

---
updated-dependencies:
- dependency-name: github.com/AlecAivazis/survey/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-29 08:08:22 +00:00
Daniel Hovie
b78d2aacd5
Merge pull request from safing/dependabot/go_modules/github.com/safing/portbase-0.15.2
Bump github.com/safing/portbase from 0.14.5 to 0.15.2
2022-09-29 10:07:57 +02:00
dependabot[bot]
a3759454fd
Bump github.com/safing/portbase from 0.14.5 to 0.15.2
Bumps [github.com/safing/portbase](https://github.com/safing/portbase) from 0.14.5 to 0.15.2.
- [Release notes](https://github.com/safing/portbase/releases)
- [Commits](https://github.com/safing/portbase/compare/v0.14.5...v0.15.2)

---
updated-dependencies:
- dependency-name: github.com/safing/portbase
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-29 08:05:53 +00:00
Daniel Hovie
a63e42986a
Merge pull request from safing/feature/file-sigs
Add file signature support
2022-09-29 10:05:06 +02:00
Daniel
9ba2af21ac Update go workflow 2022-09-28 20:09:09 +02:00
Daniel
88e4c92633 Add missing and ignored files 2022-09-28 20:03:11 +02:00
Daniel
678c558e7f Implement review suggestions and fix tests and linter errors 2022-09-28 19:56:41 +02:00
Daniel
19f008e701 Update golang-ci lint config 2022-09-28 14:45:08 +02:00
Daniel
0fdd07b0e2 Improve labeled hash helpers 2022-09-28 14:45:01 +02:00
Daniel
7566eefcd7 Expose raw hasher and add raw hash equality function 2022-08-12 13:16:43 +02:00
Daniel
12f5da337c Add support for file signing suite in manager 2022-08-12 13:16:16 +02:00
Daniel
3e3a8c29b4 Fix and improve recent additions 2022-08-12 13:15:58 +02:00
Daniel
663e8fcfc7 Build jess cli with correct binary name 2022-08-12 13:14:19 +02:00
Daniel
127c0b4f8d Compile for arm64 2022-08-08 14:47:35 +02:00
Daniel
f91ea4bc19 Add option to immediately export/backup generated signet 2022-08-08 14:47:20 +02:00
Daniel
48163a56ce Add deps 2022-08-08 14:46:34 +02:00
Daniel
d4c5d7a4d4 Fix linter 2022-08-08 14:46:20 +02:00
Daniel
93372c3219 Increase re-key message limit 2022-08-08 14:46:13 +02:00
Daniel
c0d050e34c Add support for system keyring as trust store 2022-08-08 14:45:53 +02:00
Daniel
d4d06574b8 Add text format for signets and envelopes and support for import/export 2022-08-08 14:45:06 +02:00
Daniel
74d41194cc Add support for file signatures in cli 2022-07-11 17:04:48 +02:00
Daniel
345ceb01e4 Stop saving keys to disk in envelopes 2022-07-11 17:04:01 +02:00
Daniel
028a0b0d20 Fix build script 2022-07-11 17:03:35 +02:00
Daniel
4556eda901 Save signature to file data for access outside helper functions 2022-07-11 17:03:12 +02:00
Daniel
c346404d62 Add signature to existing file by default and remove other jess sigs 2022-07-11 17:02:46 +02:00
Daniel
a33fe9b9cf Add support for verifying files from stdin 2022-07-11 17:02:17 +02:00
Daniel
d398ae6956 Fix filesig package name 2022-07-11 17:01:17 +02:00
Daniel
8e4665a639 Expose labeled hash algorithm names 2022-07-11 17:00:04 +02:00
Daniel
9f017c0f9e Add suite for file signing 2022-07-11 16:59:25 +02:00
Daniel
0bb5f33c4a Add filesig package for signing files in a common way 2022-07-08 22:33:30 +02:00
Daniel
8ad0eabf82 Fix linter errors 2022-07-08 22:32:53 +02:00
Daniel
55378bda85 Add signet import/export functions 2022-07-08 22:32:43 +02:00
Daniel
62077eefb5 Add labeled hash helper functions for hashing data 2022-07-08 22:32:13 +02:00
Daniel
6889bbd62e Add reference from hashtools to their labeled hash version 2022-07-08 22:31:52 +02:00
Daniel
9dffde4dfa Add Integrity attribute to Signing Purpose 2022-07-08 22:30:55 +02:00
Daniel
d74b387f23
Create codeql-analysis.yml 2022-06-30 11:04:19 +02:00
Daniel
50d38337bc
Delete renovate.json 2022-06-30 11:00:55 +02:00
Daniel
da7dd18f34
Create dependabot.yml 2022-06-30 11:00:44 +02:00
renovate[bot]
a19f1cad54 Update module github.com/safing/portbase to v0.14.5 2022-06-29 14:47:07 +00:00
renovate[bot]
288ad45650 Update module github.com/spf13/cobra to v1.5.0 2022-06-28 02:44:40 +00:00
renovate[bot]
d2c6a24f35 Update golang.org/x/crypto digest to 0559593 2022-06-23 01:14:49 +00:00
Renovate Bot
e87e047cca Update module github.com/AlecAivazis/survey/v2 to v2.3.5 2022-06-07 20:37:53 +00:00
Renovate Bot
482b8f31b7 Update golang.org/x/crypto digest to 793ad66 2022-05-26 03:49:25 +00:00
Renovate Bot
79e0f72e60 Update golang.org/x/crypto digest to 6f7dac9 2022-05-18 10:04:26 +00:00
Renovate Bot
81a184e55a Update golang.org/x/crypto digest to 85d78b3 2022-05-17 05:04:18 +00:00
Renovate Bot
d3990a89a7 Update golang.org/x/crypto digest to 403b017 2022-05-16 23:11:31 +00:00
Renovate Bot
9849aa1661 Update module github.com/safing/portbase to v0.14.4 2022-05-16 13:18:28 +00:00
Renovate Bot
ca77a526ed Update golang.org/x/crypto digest to 4661260 2022-05-14 01:30:55 +00:00
Renovate Bot
451ca92a90 Update golang.org/x/crypto digest to c6db032 2022-05-11 23:48:52 +00:00
Renovate Bot
97ffd39ab5 Update golang.org/x/crypto digest to 2cf3ade 2022-05-10 18:31:21 +00:00
Daniel
8dfd0738aa
Merge pull request from safing/fix/go-workflow
Update Go workflow
2022-05-10 16:00:23 +02:00
Daniel
514628d7f4 Update Go workflow 2022-05-10 15:58:35 +02:00
Renovate Bot
ba57dade51 Update module github.com/AlecAivazis/survey/v2 to v2.3.4 2022-03-30 19:09:48 +00:00
Renovate Bot
afb5c70f02 Update module github.com/safing/portbase to v0.14.1 2022-03-28 16:06:00 +00:00
Renovate Bot
1e3b61e772 Update golang.org/x/crypto digest to 2c7772b 2022-03-21 19:44:03 +00:00
Renovate Bot
4815a17844 Update module github.com/spf13/cobra to v1.4.0 2022-03-18 15:16:44 +00:00
Renovate Bot
d57733fb15 Update golang.org/x/crypto digest to 3147a52 2022-03-15 21:31:54 +00:00
Renovate Bot
0845543d87 Update golang.org/x/crypto digest to 5d542ad 2022-03-15 04:15:44 +00:00
Renovate Bot
aa823947f0 Update golang.org/x/crypto digest to b769efc 2022-03-13 04:06:09 +00:00
Renovate Bot
50b6d67009 Update golang.org/x/crypto commit hash to 6068a2e 2022-03-12 17:57:03 +00:00
Renovate Bot
677e39b9b0 Update module github.com/safing/portbase to v0.14.0 2022-03-08 17:03:13 +00:00
Renovate Bot
d175768c7f Update golang.org/x/crypto commit hash to efcb850 2022-03-08 00:51:05 +00:00
Renovate Bot
ae8a4033cb Update module github.com/safing/portbase to v0.13.6 2022-02-21 16:03:30 +00:00
Daniel
5c81928db6 Add issue manager workflow 2022-02-15 14:50:31 +01:00
Renovate Bot
883cac9291 Update golang.org/x/crypto commit hash to 8634188 2022-02-14 23:19:47 +00:00
Renovate Bot
70d95eaefd Update module github.com/safing/portbase to v0.13.5 2022-02-14 14:41:09 +00:00
Renovate Bot
e12728be37 Update golang.org/x/crypto commit hash to 1e6e349 2022-02-13 21:50:29 +00:00
Renovate Bot
8737b37214 Update golang.org/x/crypto commit hash to f4118a5 2022-02-10 18:27:15 +00:00
Renovate Bot
b4bc135a7a Update golang.org/x/crypto commit hash to db63837 2022-02-09 22:35:35 +00:00
Renovate Bot
91cab2773c Update golang.org/x/crypto commit hash to dad3315 2022-02-09 18:54:09 +00:00
Renovate Bot
83d276f996 Update golang.org/x/crypto commit hash to bba287d 2022-02-09 05:24:20 +00:00
Renovate Bot
e90a193cf4 Update golang.org/x/crypto commit hash to 20e1d8d 2022-02-08 09:09:36 +00:00
Daniel
ceecfb0a8d
Merge pull request from safing/feature/update-linters
Update linters and fix warnings
2022-02-03 15:34:11 +01:00
Daniel
632d2ecdfd Update linters and fix warnings 2022-02-02 15:21:58 +01:00
Renovate Bot
c7e209cd81 Update golang.org/x/crypto commit hash to 30dcbda 2022-01-31 23:28:01 +00:00
Renovate Bot
61278cfcd4 Update golang.org/x/crypto commit hash to 198e437 2022-01-28 23:34:07 +00:00
Renovate Bot
d1dadca916 Update module github.com/safing/portbase to v0.13.4 2022-01-27 12:25:34 +00:00
Renovate Bot
b154fd0201 Update golang.org/x/crypto commit hash to aa10faf 2022-01-27 03:14:23 +00:00
Renovate Bot
30c8dc460f Update golang.org/x/crypto commit hash to e04a857 2022-01-26 21:37:41 +00:00
Renovate Bot
f0cf6ddbb9 Update golang.org/x/crypto commit hash to 5e0467b 2022-01-12 21:26:32 +00:00
Renovate Bot
6763513a6e Update module github.com/spf13/cobra to v1.3.0 2021-12-29 13:25:58 +00:00
Renovate Bot
373c4d105e Update module github.com/safing/portbase to v0.13.2 2021-12-29 11:19:22 +00:00
Renovate Bot
d3eb0506a8 Update golang.org/x/crypto commit hash to e495a2d 2021-12-15 19:41:34 +00:00
Renovate Bot
1a25a728af Update golang.org/x/crypto commit hash to 4570a08 2021-12-09 22:01:17 +00:00
Renovate Bot
3583f246fa Update module github.com/safing/portbase to v0.13.1 2021-12-07 09:43:15 +00:00
Renovate Bot
ca9505e106 Update golang.org/x/crypto commit hash to 5770296 2021-12-02 23:44:06 +00:00
Renovate Bot
0034e04c34 Update module github.com/safing/portbase to v0.12.4 2021-11-24 15:48:06 +00:00
Renovate Bot
cc8f77434e Update golang.org/x/crypto commit hash to ae814b3 2021-11-17 21:50:20 +00:00
Renovate Bot
d57fc16a56 Update golang.org/x/crypto commit hash to b4de73f 2021-11-16 02:38:41 +00:00
Renovate Bot
8d8217a712 Update golang.org/x/crypto commit hash to ceb1ce7 2021-11-15 13:02:49 +00:00
Daniel
5fdc14db2c Add stabilityDays option to renovate 2021-11-10 15:49:04 +01:00
Renovate Bot
87ecc7d370 Update module github.com/safing/portbase to v0.12.3 2021-10-15 17:19:56 +00:00
Renovate Bot
2948b71e0b Update module github.com/safing/portbase to v0.12.2 2021-10-12 17:04:25 +00:00
Renovate Bot
55d45eac42 Update module github.com/safing/portbase to v0.12.1 2021-10-06 18:16:24 +00:00
Daniel
a51f718566
Merge pull request from safing/feature/lint-upgrade
Linter upgrade
2021-10-04 17:08:30 +02:00
Daniel
8a0851a726
Merge pull request from safing/renovate/github.com-tevino-abool-2.x
Update module github.com/tevino/abool to v2
2021-10-04 17:08:16 +02:00
Renovate Bot
e5a0bec29b
Update module github.com/tevino/abool to v2 2021-10-04 12:09:29 +00:00
Renovate Bot
3324760d0a Update golang.org/x/sys commit hash to 2c5d950 2021-10-04 12:08:56 +00:00
Daniel
4e06dfb226 Add comment with link to docs for golangci-lint 2021-10-04 13:40:30 +02:00
Renovate Bot
711f939913 Update golang.org/x/sys commit hash to b1ebd4e 2021-10-03 16:14:09 +00:00
Daniel
5059051610 Fix linter errors 2021-10-02 23:00:01 +02:00
Daniel
70f411c597 Update golangci-lint to v1.42.1 2021-10-02 22:59:26 +02:00
Renovate Bot
89f70739ea Update golang.org/x/sys commit hash to 808efd9 2021-10-02 12:37:38 +00:00
Renovate Bot
06354da1b3 Update module github.com/spf13/cobra to v1.2.1 2021-10-01 18:54:35 +00:00
Renovate Bot
4aed44485c Update module github.com/safing/portbase to v0.12.0 2021-10-01 17:48:33 +00:00
Renovate Bot
0c6106e090 Update module github.com/AlecAivazis/survey/v2 to v2.3.2 2021-10-01 16:36:03 +00:00
Renovate Bot
b95c07ea68 Update module github.com/mattn/go-colorable to v0.1.11 2021-10-01 14:45:24 +00:00
Renovate Bot
b43cf3068c Update golang.org/x/sys commit hash to 39dca11 2021-10-01 13:56:20 +00:00
Renovate Bot
e2642de2bd Update golang.org/x/crypto commit hash to 089bfa5 2021-10-01 12:23:43 +00:00
Renovate Bot
4ea1811147 Update github.com/mgutz/ansi commit hash to d51e80e 2021-10-01 12:04:42 +00:00
Daniel
30c3c87962
Merge pull request from safing/fix/actions
Fix test script and linter errors
2021-10-01 14:00:52 +02:00
Daniel
1e91e2b9f7 Fix test script and linter errors 2021-10-01 13:58:26 +02:00
Daniel
b4f443f7c2
Merge pull request from safing/renovate/configure
Configure Renovate
2021-10-01 08:44:29 +02:00
Daniel
ac933d788a Add Go lint and test action 2021-10-01 08:35:18 +02:00
Daniel
6ff3d72bb1 Move renovate config to .github dir 2021-10-01 08:32:00 +02:00
Daniel
39eaf8bc69
Add renovate automerge config 2021-09-30 13:24:56 +02:00
Renovate Bot
1106e578ac
Add renovate.json 2021-09-30 10:35:51 +00:00
110 changed files with 4603 additions and 1141 deletions

11
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,11 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "daily"

40
.github/label-actions.yml vendored Normal file
View file

@ -0,0 +1,40 @@
# Configuration for Label Actions - https://github.com/dessant/label-actions
community support:
comment: |
Hey @{issue-author}, thank you for raising this issue with us.
After a first review we noticed that this does not seem to be a technical issue, but rather a configuration issue or general question about how Portmaster works.
Thus, we invite the community to help with configuration and/or answering this questions.
If you are in a hurry or haven't received an answer, a good place to ask is in [our Discord community](https://discord.gg/safing).
If your problem or question has been resolved or answered, please come back and give an update here for other users encountering the same and then close this issue.
If you are a paying subscriber and want this issue to be checked out by Safing, please send us a message [on Discord](https://discord.gg/safing) or [via Email](mailto:support@safing.io) with your username and the link to this issue, so we can prioritize accordingly.
needs debug info:
comment: |
Hey @{issue-author}, thank you for raising this issue with us.
After a first review we noticed that we will require the Debug Info for further investigation. However, you haven't supplied any Debug Info in your report.
Please [collect Debug Info](https://wiki.safing.io/en/FAQ/DebugInfo) from Portmaster _while_ the reported issue is present.
in/compatibility:
comment: |
Hey @{issue-author}, thank you for reporting on a compatibility.
We keep a list of compatible software and user provided guides for improving compatibility [in the wiki - please have a look there](https://wiki.safing.io/en/Portmaster/App/Compatibility).
If you can't find your software in the list, then a good starting point is our guide on [How do I make software compatible with Portmaster](https://wiki.safing.io/en/FAQ/MakeSoftwareCompatibleWithPortmaster).
If you have managed to establish compatibility with an application, please share your findings here. This will greatly help other users encountering the same issues.
fixed:
comment: |
This issue has been fixed by the recently referenced commit or PR.
However, the fix is not released yet.
It is expected to go into the [Beta Release Channel](https://wiki.safing.io/en/FAQ/SwitchReleaseChannel) for testing within the next two weeks and will be available for everyone within the next four weeks. While this is the typical timeline we work with, things are subject to change.

55
.github/workflows/go.yml vendored Normal file
View file

@ -0,0 +1,55 @@
name: Go
on:
push:
branches:
- master
- develop
pull_request:
branches:
- master
- develop
jobs:
lint:
name: Linter
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: '^1.19'
- name: Get dependencies
run: go mod download
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.52.2
only-new-issues: true
args: -c ./.golangci.yml --timeout 15m
- name: Run go vet
run: go vet ./...
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: '^1.19'
- name: Get dependencies
run: go mod download
- name: Run tests
run: ./test --test-only

View file

@ -0,0 +1,26 @@
# This workflow responds to first time posters with a greeting message.
# Docs: https://github.com/actions/first-interaction
name: Greet New Users
# This workflow is triggered when a new issue is created.
on:
issues:
types: opened
permissions:
contents: read
issues: write
jobs:
greet:
runs-on: ubuntu-latest
steps:
- uses: actions/first-interaction@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
# Respond to first time issue raisers.
issue-message: |
Greetings and welcome to our community! As this is the first issue you opened here, we wanted to share some useful infos with you:
- 🗣️ Our community on [Discord](https://discord.gg/safing) is super helpful and active. We also have an AI-enabled support bot that knows Portmaster well and can give you immediate help.
- 📖 The [Wiki](https://wiki.safing.io/) answers all common questions and has many important details. If you can't find an answer there, let us know, so we can add anything that's missing.

View file

@ -0,0 +1,22 @@
# This workflow responds with a message when certain labels are added to an issue or PR.
# Docs: https://github.com/dessant/label-actions
name: Label Actions
# This workflow is triggered when a label is added to an issue.
on:
issues:
types: labeled
permissions:
contents: read
issues: write
jobs:
action:
runs-on: ubuntu-latest
steps:
- uses: dessant/label-actions@v3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
config-path: ".github/label-actions.yml"
process-only: "issues"

42
.github/workflows/issues-stale.yml vendored Normal file
View file

@ -0,0 +1,42 @@
# This workflow warns and then closes stale issues and PRs.
# Docs: https://github.com/actions/stale
name: Close Stale Issues
on:
schedule:
- cron: "17 5 * * 1-5" # run at 5:17 (UTC) on Monday to Friday
workflow_dispatch:
permissions:
contents: read
issues: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v8
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
# Increase max operations.
# When using GITHUB_TOKEN, the rate limit is 1,000 requests per hour per repository.
operations-per-run: 500
# Handle stale issues
stale-issue-label: 'stale'
# Exemptions
exempt-all-issue-assignees: true
exempt-issue-labels: 'support,dependencies,pinned,security'
# Mark as stale
days-before-issue-stale: 63 # 2 months / 9 weeks
stale-issue-message: |
This issue has been automatically marked as inactive because it has not had activity in the past two months.
If no further activity occurs, this issue will be automatically closed in one week in order to increase our focus on active topics.
# Close
days-before-issue-close: 7 # 1 week
close-issue-message: |
This issue has been automatically closed because it has not had recent activity. Thank you for your contributions.
If the issue has not been resolved, you can [find more information in our Wiki](https://wiki.safing.io/) or [continue the conversation on our Discord](https://discord.gg/safing).
# TODO: Handle stale PRs
days-before-pr-stale: 36500 # 100 years - effectively disabled.

3
.gitignore vendored
View file

@ -1,8 +1,7 @@
cpu.out
vendor
cmd/cmd*
cmd/jess*
dist
# Custom dev deops
# Custom dev deps
go.mod.*

View file

@ -1,16 +1,72 @@
# Docs:
# https://golangci-lint.run/usage/linters/
linters:
enable-all: true
disable:
- lll
- gochecknoinits
- gochecknoglobals
- containedctx
- contextcheck
- cyclop
- depguard
- exhaustivestruct
- exhaustruct
- forbidigo
- funlen
- gochecknoglobals
- gochecknoinits
- gocognit
- gocyclo
- goerr113
- gomnd
- ifshort
- interfacebloat
- interfacer
- ireturn
- lll
- musttag
- nestif
- nilnil
- nlreturn
- noctx
- nolintlint
- nonamedreturns
- nosnakecase
- revive
- tagliatelle
- testpackage
- varnamelen
- whitespace
- wrapcheck
- wsl
linters-settings:
revive:
# See https://github.com/mgechev/revive#available-rules for details.
enable-all-rules: true
gci:
# put imports beginning with prefix after 3rd-party packages;
# only support one prefix
# if not set, use goimports.local-prefixes
local-prefixes: github.com/safing
godox:
# report any comments starting with keywords, this is useful for TODO or FIXME comments that
# might be left in the code accidentally and should be resolved before merging
keywords:
- FIXME
gosec:
# To specify a set of rules to explicitly exclude.
# Available rules: https://github.com/securego/gosec#available-rules
excludes:
- G204 # Variables in commands.
- G304 # Variables in file paths.
- G505 # We need crypto/sha1 for non-security stuff. Using `nolint:` triggers another linter.
issues:
exclude-use-default: false
exclude-rules:
- text: "a blank import .*"
linters:
- golint
- text: "ST1000: at least one file in a package should have a package comment.*"
linters:
- stylecheck

View file

@ -1,55 +1,29 @@
#!/bin/bash
set -eo pipefail
baseDir="$( cd "$(dirname "$0")" && pwd )"
cd "$baseDir"
# get build data
if [[ "$BUILD_COMMIT" == "" ]]; then
BUILD_COMMIT=$(git describe --all --long --abbrev=99 --dirty 2>/dev/null)
fi
if [[ "$BUILD_USER" == "" ]]; then
BUILD_USER=$(id -un)
fi
if [[ "$BUILD_HOST" == "" ]]; then
BUILD_HOST=$(hostname)
fi
if [[ "$BUILD_DATE" == "" ]]; then
BUILD_DATE=$(date +%d.%m.%Y)
fi
if [[ "$BUILD_SOURCE" == "" ]]; then
BUILD_SOURCE=$(git remote -v | grep origin | cut -f2 | cut -d" " -f1 | head -n 1)
fi
if [[ "$BUILD_SOURCE" == "" ]]; then
BUILD_SOURCE=$(git remote -v | cut -f2 | cut -d" " -f1 | head -n 1)
fi
BUILD_BUILDOPTIONS=$(echo $* | sed "s/ /§/g")
# check
if [[ "$BUILD_COMMIT" == "" ]]; then
echo "could not automatically determine BUILD_COMMIT, please supply manually as environment variable."
exit 1
fi
if [[ "$BUILD_USER" == "" ]]; then
echo "could not automatically determine BUILD_USER, please supply manually as environment variable."
exit 1
fi
if [[ "$BUILD_HOST" == "" ]]; then
echo "could not automatically determine BUILD_HOST, please supply manually as environment variable."
exit 1
fi
if [[ "$BUILD_DATE" == "" ]]; then
echo "could not automatically determine BUILD_DATE, please supply manually as environment variable."
exit 1
fi
if [[ "$BUILD_SOURCE" == "" ]]; then
echo "could not automatically determine BUILD_SOURCE, please supply manually as environment variable."
exit 1
fi
echo "Please notice, that this build script includes metadata into the build."
echo "This information is useful for debugging and license compliance."
echo "Run the compiled binary with the version command to see the information included."
# build
BUILD_PATH="github.com/safing/jess/vendor/github.com/safing/portbase/info"
go build -ldflags "-X ${BUILD_PATH}.commit=${BUILD_COMMIT} -X ${BUILD_PATH}.buildOptions=${BUILD_BUILDOPTIONS} -X ${BUILD_PATH}.buildUser=${BUILD_USER} -X ${BUILD_PATH}.buildHost=${BUILD_HOST} -X ${BUILD_PATH}.buildDate=${BUILD_DATE} -X ${BUILD_PATH}.buildSource=${BUILD_SOURCE}" $@
# Get version.
VERSION="$(git tag --points-at)" || true
test -z "$VERSION" && DEV_VERSION="$(git describe --tags --first-parent --abbrev=0)" || true
test -n "$DEV_VERSION" && VERSION="${DEV_VERSION}_dev_build"
test -z "$VERSION" && VERSION="dev_build"
BUILD_SOURCE=$( ( git remote -v | cut -f2 | cut -d" " -f1 | head -n 1 ) || echo "unknown" )
BUILD_TIME=$(date -u "+%Y-%m-%dT%H:%M:%SZ" || echo "unknown")
LDFLAGS="-X main.Version=${VERSION} -X main.BuildSource=${BUILD_SOURCE} -X main.BuildTime=${BUILD_TIME}"
# build output name
BIN_NAME="jess"
if [[ "$GOOS" == "windows" ]]; then
BIN_NAME="${BIN_NAME}.exe"
fi
# Build.
export CGO_ENABLED=0
go build -o "${BIN_NAME}" -ldflags "$LDFLAGS" "$@"

View file

@ -6,6 +6,7 @@ import (
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/safing/jess"
)
@ -29,7 +30,8 @@ func newEnvelope(name string) (*jess.Envelope, error) {
"Encrypt with key",
"Encrypt for someone and sign",
"Encrypt for someone but don't sign",
"Sign a file",
"Sign a file (wrapped)",
"Sign a file (separate sig)",
},
}
err := survey.AskOne(prompt, &preset, nil)
@ -53,9 +55,12 @@ func newEnvelope(name string) (*jess.Envelope, error) {
case "Encrypt for someone but don't sign":
envelope.SuiteID = jess.SuiteRcptOnly
err = selectSignets(envelope, "recipient")
case "Sign a file":
case "Sign a file (wrapped)":
envelope.SuiteID = jess.SuiteSign
err = selectSignets(envelope, "sender")
case "Sign a file (separate sig)":
envelope.SuiteID = jess.SuiteSignFile
err = selectSignets(envelope, "sender")
}
if err != nil {
return nil, err
@ -92,6 +97,7 @@ func editEnvelope(envelope *jess.Envelope) error {
{"Recipients", formatSignetNames(envelope.Recipients)},
{"Senders", formatSignetNames(envelope.Senders)},
{""},
{"Export", "export to text format"},
{"Abort", "discard changes and return"},
{"Delete", "delete and return"},
}),
@ -104,8 +110,28 @@ func editEnvelope(envelope *jess.Envelope) error {
switch {
case strings.HasPrefix(submenu, "Done"):
// save
// Check if the envelope is valid.
if envelope.SecurityLevel == 0 {
fmt.Println("Envelope is invalid, please fix before saving.")
continue
}
// Remove and keys and save.
envelope.CleanSignets()
return trustStore.StoreEnvelope(envelope)
case strings.HasPrefix(submenu, "Export"):
// Check if the envelope is valid.
if envelope.SecurityLevel == 0 {
fmt.Println("Envelope is invalid, please fix before exporting.")
continue
}
// Remove keys and export.
envelope.CleanSignets()
text, err := envelope.Export(false)
if err != nil {
return fmt.Errorf("failed to export: %w", err)
}
fmt.Println("Exported envelope:")
fmt.Println(text)
case strings.HasPrefix(submenu, "Abort"):
return nil
case strings.HasPrefix(submenu, "Delete"):

View file

@ -6,12 +6,13 @@ import (
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/safing/jess"
"github.com/safing/jess/tools"
)
//nolint:gocognit
func newSignet(name, scheme string) (*jess.Signet, error) {
func newSignet(name, scheme string, saveToTrustStore bool) (*jess.Signet, error) {
// get name
name = strings.TrimSpace(name)
if name == "" {
@ -109,28 +110,30 @@ func newSignet(name, scheme string) (*jess.Signet, error) {
Created: time.Now(),
}
// write signet
err = trustStore.StoreSignet(signet)
if err != nil {
return nil, err
}
if saveToTrustStore {
// write signet
err = trustStore.StoreSignet(signet)
if err != nil {
return nil, err
}
// export as recipient
switch scheme {
case jess.SignetSchemePassword, jess.SignetSchemeKey:
// is secret, no recipient
default:
rcpt, err := signet.AsRecipient()
if err != nil {
return nil, err
}
err = rcpt.StoreKey()
if err != nil {
return nil, err
}
err = trustStore.StoreSignet(rcpt)
if err != nil {
return nil, err
// export as recipient
switch scheme {
case jess.SignetSchemePassword, jess.SignetSchemeKey:
// is secret, no recipient
default:
rcpt, err := signet.AsRecipient()
if err != nil {
return nil, err
}
err = rcpt.StoreKey()
if err != nil {
return nil, err
}
err = trustStore.StoreSignet(rcpt)
if err != nil {
return nil, err
}
}
}

View file

@ -4,11 +4,10 @@ import (
"fmt"
"strings"
"github.com/safing/jess/hashtools"
"github.com/safing/jess/tools"
"github.com/AlecAivazis/survey/v2"
"github.com/safing/jess/hashtools"
"github.com/safing/jess/tools"
)
func pickTools(toolNames []string, promptMsg string) ([]string, error) { //nolint:unused,deadcode // TODO

111
cmd/cmd-checksum.go Normal file
View file

@ -0,0 +1,111 @@
package main
import (
"errors"
"fmt"
"os"
"path/filepath"
"github.com/spf13/cobra"
"github.com/safing/jess/filesig"
)
func init() {
rootCmd.AddCommand(checksum)
checksum.AddCommand(checksumAdd)
checksum.AddCommand(checksumVerify)
}
var (
checksum = &cobra.Command{
Use: "checksum",
Short: "add or verify embedded checksums",
}
checksumAddUsage = "usage: checksum add <file>"
checksumAdd = &cobra.Command{
Use: "add <file>",
Short: "add an embedded checksum to a file",
Long: "add an embedded checksum to a file (support file types: txt, json, yaml)",
RunE: handleChecksumAdd,
}
checksumVerifyUsage = "usage: checksum verify <file>"
checksumVerify = &cobra.Command{
Use: "verify <file>",
Short: "verify the embedded checksum of a file",
Long: "verify the embedded checksum of a file (support file types: txt, json, yaml)",
RunE: handleChecksumVerify,
}
)
func handleChecksumAdd(cmd *cobra.Command, args []string) error {
// Check args.
if len(args) != 1 {
return errors.New(checksumAddUsage)
}
filename := args[0]
data, err := os.ReadFile(filename)
if err != nil {
return fmt.Errorf("failed to read file: %w", err)
}
switch filepath.Ext(filename) {
case ".json":
data, err = filesig.AddJSONChecksum(data)
case ".yml", ".yaml":
data, err = filesig.AddYAMLChecksum(data, filesig.TextPlacementAfterComment)
case ".txt":
data, err = filesig.AddTextFileChecksum(data, "#", filesig.TextPlacementAfterComment)
default:
return errors.New("unsupported file format")
}
if err != nil {
return err
}
// Write back to disk.
fileInfo, err := os.Stat(filename)
if err != nil {
return fmt.Errorf("failed to stat file: %w", err)
}
err = os.WriteFile(filename, data, fileInfo.Mode().Perm())
if err != nil {
return fmt.Errorf("failed to write back file with checksum: %w", err)
}
fmt.Println("checksum added")
return nil
}
func handleChecksumVerify(cmd *cobra.Command, args []string) error {
// Check args.
if len(args) != 1 {
return errors.New(checksumVerifyUsage)
}
filename := args[0]
data, err := os.ReadFile(filename)
if err != nil {
return fmt.Errorf("failed to read file: %w", err)
}
switch filepath.Ext(filename) {
case ".json":
err = filesig.VerifyJSONChecksum(data)
case ".yml", ".yaml":
err = filesig.VerifyYAMLChecksum(data)
case ".txt":
err = filesig.VerifyTextFileChecksum(data, "#")
default:
return errors.New("unsupported file format")
}
if err != nil {
return err
}
fmt.Println("checksum verified")
return nil
}

View file

@ -3,7 +3,8 @@ package main
import (
"errors"
"fmt"
"io/ioutil"
"io"
"io/fs"
"os"
"strings"
@ -12,7 +13,7 @@ import (
func init() {
rootCmd.AddCommand(closeCmd)
closeCmd.Flags().StringVarP(&closeFlagOutput, "output", "o", "", "specify output file (`-` for stdout")
closeCmd.Flags().StringVarP(&closeFlagOutput, "output", "o", "", "specify output file (`-` for stdout)")
}
var (
@ -49,10 +50,10 @@ var (
filename := args[0]
outputFilename := closeFlagOutput
if outputFilename == "" {
if strings.HasSuffix(filename, ".letter") {
if strings.HasSuffix(filename, letterFileExtension) {
return errors.New("cannot automatically derive output filename, please specify with --output")
}
outputFilename = filename + ".letter"
outputFilename = filename + letterFileExtension
}
// check input file
if filename != "-" {
@ -81,17 +82,17 @@ var (
if !confirmed {
return nil
}
} else if !os.IsNotExist(err) {
return fmt.Errorf("failed to access output file: %s", err)
} else if !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("failed to access output file: %w", err)
}
}
// load file
var data []byte
if filename == "-" {
data, err = ioutil.ReadAll(os.Stdin)
data, err = io.ReadAll(os.Stdin)
} else {
data, err = ioutil.ReadFile(filename)
data, err = os.ReadFile(filename)
}
if err != nil {
return err
@ -114,7 +115,11 @@ var (
if outputFilename == "-" {
file = os.Stdout
} else {
file, err = os.OpenFile(outputFilename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
file, err = os.OpenFile(
outputFilename,
os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
0o0600,
)
if err != nil {
return err
}
@ -123,7 +128,7 @@ var (
// write
err = c.WriteAllTo(file)
if err != nil {
file.Close()
_ = file.Close()
return err
}
return file.Close()

View file

@ -3,52 +3,49 @@ package main
import (
"errors"
"github.com/safing/jess/truststores"
"github.com/spf13/cobra"
"github.com/safing/jess"
"github.com/spf13/cobra"
"github.com/safing/jess/truststores"
)
func init() {
rootCmd.AddCommand(configureCmd)
}
var (
configureCmd = &cobra.Command{
Use: "configure <envelope name>",
Short: "configure (and create) envelope",
DisableFlagsInUseLine: true,
Args: cobra.MaximumNArgs(1),
PreRunE: requireTrustStore,
RunE: func(cmd *cobra.Command, args []string) (err error) {
// check envelope name existence
if len(args) == 0 {
return errors.New("please specify an envelope name")
}
envelopeName := args[0]
var configureCmd = &cobra.Command{
Use: "configure <envelope name>",
Short: "configure (and create) envelope",
DisableFlagsInUseLine: true,
Args: cobra.MaximumNArgs(1),
PreRunE: requireTrustStore,
RunE: func(cmd *cobra.Command, args []string) (err error) {
// check envelope name existence
if len(args) == 0 {
return errors.New("please specify an envelope name")
}
envelopeName := args[0]
// check envelope name
if !truststores.NamePlaysNiceWithFS(envelopeName) {
return errors.New("please only use alphanumeric characters and `- ._+@` for best compatibility with various systems")
}
// check envelope name
if !truststores.NamePlaysNiceWithFS(envelopeName) {
return errors.New("please only use alphanumeric characters and `- ._+@` for best compatibility with various systems")
}
// get envelope from trust store
envelope, err := trustStore.GetEnvelope(envelopeName)
if err != nil && err != jess.ErrEnvelopeNotFound {
// get envelope from trust store
envelope, err := trustStore.GetEnvelope(envelopeName)
if err != nil && !errors.Is(err, jess.ErrEnvelopeNotFound) {
return err
}
// create
if envelope == nil {
envelope, err = newEnvelope(envelopeName)
if err != nil {
return err
}
}
// create
if envelope == nil {
envelope, err = newEnvelope(envelopeName)
if err != nil {
return err
}
}
// edit (and save)
return editEnvelope(envelope)
},
}
)
// edit (and save)
return editEnvelope(envelope)
},
}

View file

@ -1,6 +1,8 @@
package main
import (
"fmt"
"github.com/spf13/cobra"
)
@ -8,11 +10,13 @@ func init() {
rootCmd.AddCommand(generateCmd)
generateCmd.Flags().StringVarP(&generateFlagName, "name", "l", "", "specify signet name/label")
generateCmd.Flags().StringVarP(&generateFlagScheme, "scheme", "t", "", "specify signet scheme/tool")
generateCmd.Flags().BoolVarP(&generateFlagTextOnly, "textonly", "", false, "do not save to trust store and only output directly as text")
}
var (
generateFlagName string
generateFlagScheme string
generateFlagName string
generateFlagScheme string
generateFlagTextOnly bool
generateCmd = &cobra.Command{
Use: "generate",
@ -21,8 +25,43 @@ var (
Args: cobra.NoArgs,
PreRunE: requireTrustStore,
RunE: func(cmd *cobra.Command, args []string) error {
_, err := newSignet(generateFlagName, generateFlagScheme)
return err
// Generate new signet
signet, err := newSignet(generateFlagName, generateFlagScheme, !generateFlagTextOnly)
if err != nil {
return err
}
// Output as text if not saved to trust store.
if generateFlagTextOnly {
// Make text backup.
backup, err := signet.Backup(false)
if err != nil {
return err
}
// Convert to recipient and serialize key.
rcpt, err := signet.AsRecipient()
if err != nil {
return err
}
err = rcpt.StoreKey()
if err != nil {
return err
}
// Make text export.
export, err := rcpt.Export(false)
if err != nil {
return err
}
// Write output.
fmt.Printf("Generated %s key with ID %s and name %q\n", signet.Scheme, signet.ID, signet.Info.Name)
fmt.Printf("Backup (private key): %s\n", backup)
fmt.Printf("Export (public key): %s\n", export)
}
return nil
},
}
)

170
cmd/cmd-import-export.go Normal file
View file

@ -0,0 +1,170 @@
package main
import (
"errors"
"fmt"
"strings"
"github.com/spf13/cobra"
"github.com/safing/jess"
)
func init() {
rootCmd.AddCommand(exportCmd)
rootCmd.AddCommand(backupCmd)
rootCmd.AddCommand(importCmd)
}
var (
exportCmdHelp = "usage: export <id>"
exportCmd = &cobra.Command{
Use: "export <id>",
Short: "export a signet or envelope",
Long: "export a signet (as a recipient - the public key only) or an envelope (configuration)",
RunE: handleExport,
}
backupCmdHelp = "usage: backup <id"
backupCmd = &cobra.Command{
Use: "backup <id>",
Short: "backup a signet",
Long: "backup a signet (the private key - do not share!)",
RunE: handleBackup,
}
importCmdHelp = "usage: import <text>"
importCmd = &cobra.Command{
Use: "import <text>",
Short: "import a signet or an enveleope",
Long: "import a signet (any kind) or an enveleope",
RunE: handleImport,
}
)
func handleExport(cmd *cobra.Command, args []string) error {
// Check args.
if len(args) != 1 {
return errors.New(exportCmdHelp)
}
id := args[0]
// Get Recipient.
recipient, err := trustStore.GetSignet(id, true)
if err == nil {
text, err := recipient.Export(false)
if err != nil {
return fmt.Errorf("failed to export recipient %s: %w", id, err)
}
fmt.Println(text)
return nil
}
// Check if there is a signet instead.
signet, err := trustStore.GetSignet(id, false)
if err == nil {
recipient, err := signet.AsRecipient()
if err != nil {
return fmt.Errorf("failed convert signet %s to recipient for export: %w", id, err)
}
text, err := recipient.Export(false)
if err != nil {
return fmt.Errorf("failed to export recipient %s: %w", id, err)
}
fmt.Println(text)
return nil
}
// Check for an envelope.
env, err := trustStore.GetEnvelope(id)
if err == nil {
text, err := env.Export(false)
if err != nil {
return fmt.Errorf("failed to export envelope %s: %w", id, err)
}
fmt.Println(text)
return nil
}
return errors.New("no recipient or envelope found with the given ID")
}
func handleBackup(cmd *cobra.Command, args []string) error {
// Check args.
if len(args) != 1 {
return errors.New(backupCmdHelp)
}
id := args[0]
// Check if there is a signet instead.
signet, err := trustStore.GetSignet(id, false)
if err != nil {
text, err := signet.Backup(false)
if err != nil {
return fmt.Errorf("failed to backup signet %s: %w", id, err)
}
fmt.Println(text)
return nil
}
return errors.New("no signet found with the given ID")
}
func handleImport(cmd *cobra.Command, args []string) error {
// Check args.
if len(args) != 1 {
return errors.New(importCmdHelp)
}
text := args[0]
// First, check if it's an envelope.
if strings.HasPrefix(text, jess.ExportEnvelopePrefix) {
env, err := jess.EnvelopeFromTextFormat(text)
if err != nil {
return fmt.Errorf("failed to parse envelope: %w", err)
}
err = trustStore.StoreEnvelope(env)
if err != nil {
return fmt.Errorf("failed to import envelope into trust store: %w", err)
}
fmt.Printf("imported envelope %q intro trust store\n", env.Name)
return nil
}
// Then handle all signet types together.
var (
signetType string
parseFunc func(textFormat string) (*jess.Signet, error)
)
switch {
case strings.HasPrefix(text, jess.ExportSenderPrefix):
signetType = jess.ExportSenderKeyword
parseFunc = jess.SenderFromTextFormat
case strings.HasPrefix(text, jess.ExportRecipientPrefix):
signetType = jess.ExportRecipientKeyword
parseFunc = jess.RecipientFromTextFormat
case strings.HasPrefix(text, jess.ExportKeyPrefix):
signetType = jess.ExportKeyKeyword
parseFunc = jess.KeyFromTextFormat
default:
return fmt.Errorf(
"invalid format or unknown type, expected one of %s, %s, %s, %s",
jess.ExportKeyKeyword,
jess.ExportSenderKeyword,
jess.ExportRecipientKeyword,
jess.ExportEnvelopeKeyword,
)
}
// Parse and import
signet, err := parseFunc(text)
if err != nil {
return fmt.Errorf("failed to parse %s: %w", signetType, err)
}
err = trustStore.StoreSignet(signet)
if err != nil {
return fmt.Errorf("failed to import %s into trust store: %w", signetType, err)
}
fmt.Printf("imported %s %s intro trust store\n", signetType, signet.ID)
return nil
}

View file

@ -6,8 +6,9 @@ import (
"strings"
"github.com/AlecAivazis/survey/v2"
"github.com/safing/jess"
"github.com/spf13/cobra"
"github.com/safing/jess"
)
const (
@ -94,7 +95,7 @@ func manageSignets() error {
case "Delete":
err = trustStore.DeleteSignet(selectedSignet.ID, selectedSignet.Public)
if err != nil {
return nil
return err
}
case "Back to list":
continue

View file

@ -4,15 +4,14 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"io/fs"
"os"
"strings"
"github.com/safing/portbase/container"
"github.com/spf13/cobra"
"github.com/safing/jess"
"github.com/spf13/cobra"
"github.com/safing/structures/container"
)
func init() {
@ -72,17 +71,17 @@ var (
if !confirmed {
return nil
}
} else if !os.IsNotExist(err) {
return fmt.Errorf("failed to access output file: %s", err)
} else if !errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("failed to access output file: %w", err)
}
}
// load file
var data []byte
if filename == "-" {
data, err = ioutil.ReadAll(os.Stdin)
data, err = io.ReadAll(os.Stdin)
} else {
data, err = ioutil.ReadFile(filename)
data, err = os.ReadFile(filename)
}
if err != nil {
return err
@ -94,6 +93,11 @@ var (
return err
}
// Create default requirements if not set.
if requirements == nil {
requirements = jess.NewRequirements()
}
// decrypt (and verify)
plainText, err := letter.Open(requirements, trustStore)
if err != nil {
@ -105,7 +109,11 @@ var (
if outputFilename == "-" {
file = os.Stdout
} else {
file, err = os.OpenFile(outputFilename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
file, err = os.OpenFile(
outputFilename,
os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
0o0600,
)
if err != nil {
return err
}
@ -114,11 +122,11 @@ var (
// write
n, err := file.Write(plainText)
if err != nil {
file.Close()
_ = file.Close()
return err
}
if n < len(plainText) {
file.Close()
_ = file.Close()
return io.ErrShortWrite
}
return file.Close()

62
cmd/cmd-sign.go Normal file
View file

@ -0,0 +1,62 @@
package main
import (
"errors"
"fmt"
"strings"
"github.com/spf13/cobra"
"github.com/safing/jess/filesig"
)
func init() {
rootCmd.AddCommand(signCmd)
signCmd.Flags().StringVarP(&closeFlagOutput, "output", "o", "", "specify output file (`-` for stdout")
signCmd.Flags().StringToStringVarP(&metaDataFlag, "metadata", "m", nil, "specify file metadata to sign")
}
var (
metaDataFlag map[string]string
signCmdHelp = "usage: jess sign <file> with <envelope name>"
signCmd = &cobra.Command{
Use: "sign <file> with <envelope name>",
Short: "sign file",
Long: "sign file with the given envelope. Use `-` to use stdin",
DisableFlagsInUseLine: true,
PreRunE: requireTrustStore,
RunE: func(cmd *cobra.Command, args []string) error {
registerPasswordCallbacks()
// check args
if len(args) != 3 || args[1] != "with" {
return errors.New(signCmdHelp)
}
// get envelope
envelope, err := trustStore.GetEnvelope(args[2])
if err != nil {
return err
}
// check filenames
filename := args[0]
outputFilename := closeFlagOutput
if outputFilename == "" {
if strings.HasSuffix(filename, filesig.Extension) {
return errors.New("cannot automatically derive output filename, please specify with --output")
}
outputFilename = filename + filesig.Extension
}
fd, err := filesig.SignFile(filename, outputFilename, metaDataFlag, envelope, trustStore)
if err != nil {
return err
}
fmt.Print(formatSignatures(filename, outputFilename, []*filesig.FileData{fd}))
return nil
},
}
)

View file

@ -3,86 +3,260 @@ package main
import (
"errors"
"fmt"
"io/ioutil"
"io"
"io/fs"
"os"
"github.com/safing/portbase/container"
"github.com/safing/jess"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"github.com/safing/jess"
"github.com/safing/jess/filesig"
"github.com/safing/structures/container"
)
func init() {
rootCmd.AddCommand(verifyCmd)
verifyCmd.Flags().StringToStringVarP(&metaDataFlag, "metadata", "m", nil, "specify file metadata to verify (.sig only)")
}
var (
verifyCmdHelp = "usage: jess verify <file>"
var verifyCmd = &cobra.Command{
Use: "verify <files and directories>",
Short: "verify signed files and files in directories",
DisableFlagsInUseLine: true,
Args: cobra.MinimumNArgs(1),
PreRunE: requireTrustStore,
RunE: func(cmd *cobra.Command, args []string) (err error) {
var verificationFails, verificationWarnings int
verifyCmd = &cobra.Command{
Use: "verify <file>",
Short: "verify file",
DisableFlagsInUseLine: true,
RunE: func(cmd *cobra.Command, args []string) (err error) {
// check args
if len(args) != 1 {
return errors.New(verifyCmdHelp)
// Check if we are only verifying a single file.
if len(args) == 1 {
matches, err := filepath.Glob(args[0])
if err != nil {
return err
}
// check filenames
filename := args[0]
// check input file
if filename != "-" {
fileInfo, err := os.Stat(filename)
switch len(matches) {
case 0:
return errors.New("file not found")
case 1:
// Check if the single match is a file.
fileInfo, err := os.Stat(matches[0])
if err != nil {
return err
}
if fileInfo.Size() > warnFileSize {
confirmed, err := confirm("Input file is really big (%s) and jess needs to load it fully to memory, continue?", true)
if err != nil {
return err
}
if !confirmed {
return nil
}
// Verify file if it is not a directory.
if !fileInfo.IsDir() {
return verify(matches[0], false)
}
}
}
// load file
var data []byte
if filename == "-" {
data, err = ioutil.ReadAll(os.Stdin)
} else {
data, err = ioutil.ReadFile(filename)
}
// Resolve globs.
files := make([]string, 0, len(args))
for _, arg := range args {
matches, err := filepath.Glob(arg)
if err != nil {
return err
}
files = append(files, matches...)
}
// parse file
letter, err := jess.LetterFromFileFormat(container.New(data))
// Go through all files.
for _, file := range files {
fileInfo, err := os.Stat(file)
if err != nil {
return err
verificationWarnings++
fmt.Printf("[WARN] %s failed to read: %s\n", file, err)
continue
}
// adjust requirements
if requirements == nil {
requirements = jess.NewRequirements().
Remove(jess.Confidentiality).
Remove(jess.Integrity).
Remove(jess.RecipientAuthentication)
// Walk directories.
if fileInfo.IsDir() {
err := filepath.Walk(file, func(path string, info fs.FileInfo, err error) error {
// Log walking errors.
if err != nil {
verificationWarnings++
fmt.Printf("[WARN] %s failed to read: %s\n", path, err)
return nil
}
// Only verify if .sig or .letter.
if strings.HasSuffix(path, filesig.Extension) ||
strings.HasSuffix(path, letterFileExtension) {
if err := verify(path, true); err != nil {
verificationFails++
}
}
return nil
})
if err != nil {
verificationWarnings++
fmt.Printf("[WARN] %s failed to walk directory: %s\n", file, err)
}
continue
}
// verify
err = letter.Verify(requirements, trustStore)
if err != nil {
return err
if err := verify(file, true); err != nil {
verificationFails++
}
}
// success
fmt.Println("ok")
return nil
},
// End with error status if any verification failed.
if verificationFails > 0 {
return fmt.Errorf("%d verification failures", verificationFails)
}
if verificationWarnings > 0 {
return fmt.Errorf("%d warnings", verificationWarnings)
}
return nil
},
}
var verifiedSigs = make(map[string]struct{})
func verify(filename string, bulkMode bool) error {
// Check if file was already verified.
if _, alreadyVerified := verifiedSigs[filename]; alreadyVerified {
return nil
}
)
var (
signame string
signedBy []string
err error
)
// Get correct files and verify.
switch {
case filename == stdInOutFilename:
signedBy, err = verifyLetter(filename, bulkMode)
case strings.HasSuffix(filename, letterFileExtension):
signedBy, err = verifyLetter(filename, bulkMode)
case strings.HasSuffix(filename, filesig.Extension):
filename = strings.TrimSuffix(filename, filesig.Extension)
fallthrough
default:
signame = filename + filesig.Extension
signedBy, err = verifySig(filename, signame, bulkMode)
}
// Remember the files already verified.
verifiedSigs[filename] = struct{}{}
if signame != "" {
verifiedSigs[signame] = struct{}{}
}
// Output result in bulk mode.
if bulkMode {
if err == nil {
fmt.Printf("[ OK ] %s signed by %s\n", filename, strings.Join(signedBy, ", "))
} else {
fmt.Printf("[FAIL] %s failed to verify: %s\n", filename, err)
}
}
return err
}
func verifyLetter(filename string, silent bool) (signedBy []string, err error) {
if len(metaDataFlag) > 0 {
return nil, errors.New("metadata flag only valid for verifying .sig files")
}
if filename != "-" {
fileInfo, err := os.Stat(filename)
if err != nil {
return nil, err
}
if fileInfo.Size() > warnFileSize {
confirmed, err := confirm("Input file is really big (%s) and jess needs to load it fully to memory, continue?", true)
if err != nil {
return nil, err
}
if !confirmed {
return nil, nil
}
}
}
// load file
var data []byte
if filename == "-" {
data, err = io.ReadAll(os.Stdin)
} else {
data, err = os.ReadFile(filename)
}
if err != nil {
return nil, err
}
// parse file
letter, err := jess.LetterFromFileFormat(container.New(data))
if err != nil {
return nil, err
}
// Create default requirements if not set.
if requirements == nil {
requirements = jess.NewRequirements().
Remove(jess.Confidentiality).
Remove(jess.RecipientAuthentication)
}
// verify
err = letter.Verify(requirements, trustStore)
if err != nil {
return nil, err
}
// get signers
signedBy = make([]string, 0, len(letter.Signatures))
for _, seal := range letter.Signatures {
if signet, err := trustStore.GetSignet(seal.ID, true); err == nil {
signedBy = append(signedBy, fmt.Sprintf("%s (%s)", signet.Info.Name, seal.ID))
} else {
signedBy = append(signedBy, seal.ID)
}
}
// success
if !silent {
if err == nil {
fmt.Println("Verification: OK")
fmt.Printf("Signed By: %s\n", strings.Join(signedBy, ", "))
} else {
fmt.Printf("Verification FAILED: %s\n\n", err)
}
}
return signedBy, nil
}
func verifySig(filename, signame string, silent bool) (signedBy []string, err error) {
fds, err := filesig.VerifyFile(filename, signame, metaDataFlag, trustStore)
if err != nil {
return nil, err
}
if !silent {
fmt.Print(formatSignatures(filename, signame, fds))
return nil, nil
}
signedBy = make([]string, 0, len(fds))
for _, fd := range fds {
if sig := fd.Signature(); sig != nil {
for _, seal := range sig.Signatures {
if signet, err := trustStore.GetSignet(seal.ID, true); err == nil {
signedBy = append(signedBy, fmt.Sprintf("%s (%s)", signet.Info.Name, seal.ID))
} else {
signedBy = append(signedBy, seal.ID)
}
}
}
}
return signedBy, nil
}

View file

@ -2,20 +2,85 @@ package main
import (
"fmt"
"runtime"
"runtime/debug"
"strings"
"github.com/spf13/cobra"
"github.com/safing/portbase/info"
)
func init() {
rootCmd.AddCommand(versionCmd)
}
var versionCmd = &cobra.Command{
Use: "version",
Short: "print version information",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(info.FullVersion())
},
var (
// Version is the version of this command.
Version = "dev build"
// BuildSource holds the primary source repo used to build.
BuildSource = "unknown"
// BuildTime holds the time when the binary was built.
BuildTime = "unknown"
)
func init() {
// Convert version string space placeholders.
Version = strings.ReplaceAll(Version, "_", " ")
BuildSource = strings.ReplaceAll(BuildSource, "_", " ")
BuildTime = strings.ReplaceAll(BuildTime, "_", " ")
// Get build info.
buildInfo, _ := debug.ReadBuildInfo()
buildSettings := make(map[string]string)
for _, setting := range buildInfo.Settings {
buildSettings[setting.Key] = setting.Value
}
// Add "dev build" to version if repo is dirty.
if buildSettings["vcs.modified"] == "true" &&
!strings.HasSuffix(Version, "dev build") {
Version += " dev build"
}
rootCmd.AddCommand(versionCmd)
}
var versionCmd = &cobra.Command{
Use: "version",
Run: version,
}
func version(cmd *cobra.Command, args []string) {
builder := new(strings.Builder)
// Get build info.
buildInfo, _ := debug.ReadBuildInfo()
buildSettings := make(map[string]string)
for _, setting := range buildInfo.Settings {
buildSettings[setting.Key] = setting.Value
}
// Print version info.
builder.WriteString(fmt.Sprintf("Jess %s\n", Version))
// Build info.
cgoInfo := "-cgo"
if buildSettings["CGO_ENABLED"] == "1" {
cgoInfo = "+cgo"
}
builder.WriteString(fmt.Sprintf("\nbuilt with %s (%s %s) for %s/%s\n", runtime.Version(), runtime.Compiler, cgoInfo, runtime.GOOS, runtime.GOARCH))
builder.WriteString(fmt.Sprintf(" at %s\n", BuildTime))
// Commit info.
dirtyInfo := "clean"
if buildSettings["vcs.modified"] == "true" {
dirtyInfo = "dirty"
}
builder.WriteString(fmt.Sprintf("\ncommit %s (%s)\n", buildSettings["vcs.revision"], dirtyInfo))
builder.WriteString(fmt.Sprintf(" at %s\n", buildSettings["vcs.time"]))
builder.WriteString(fmt.Sprintf(" from %s\n", BuildSource))
// License info.
builder.WriteString("\nLicensed under the GPLv3 license.")
_, _ = fmt.Println(builder.String())
}

View file

@ -3,6 +3,7 @@ package main
import (
"bufio"
"bytes"
"errors"
"fmt"
"strings"
"text/tabwriter"
@ -23,7 +24,7 @@ func formatColumns(table [][]string) []string {
}
fmt.Fprint(tabWriter, strings.Join(table[i], "\t"))
}
tabWriter.Flush()
_ = tabWriter.Flush()
// parse to []string
var lines []string
@ -111,7 +112,7 @@ func formatSignetSecurityLevel(signet *jess.Signet) string {
securityLevel, err := tool.StaticLogic.SecurityLevel(signet)
if err != nil {
if err == tools.ErrProtected {
if errors.Is(err, tools.ErrProtected) {
return "[protected]"
}
return failPlaceholder

79
cmd/format_sig.go Normal file
View file

@ -0,0 +1,79 @@
package main
import (
"encoding/hex"
"fmt"
"sort"
"strings"
"github.com/safing/jess/filesig"
)
func formatSignatures(filename, signame string, fds []*filesig.FileData) string {
b := &strings.Builder{}
switch len(fds) {
case 0:
case 1:
formatSignature(b, fds[0])
case 2:
for _, fd := range fds {
fmt.Fprintf(b, "%d Signatures:\n\n\n", len(fds))
formatSignature(b, fd)
b.WriteString("\n\n")
}
}
if filename != "" || signame != "" {
b.WriteString("\n")
fmt.Fprintf(b, "File: %s\n", filename)
fmt.Fprintf(b, "Sig: %s\n", signame)
}
return b.String()
}
func formatSignature(b *strings.Builder, fd *filesig.FileData) {
if fd.VerificationError() == nil {
b.WriteString("Verification: OK\n")
} else {
fmt.Fprintf(b, "Verification FAILED: %s\n", fd.VerificationError())
}
if letter := fd.Signature(); letter != nil {
b.WriteString("\n")
for _, sig := range letter.Signatures {
signet, err := trustStore.GetSignet(sig.ID, true)
if err == nil {
fmt.Fprintf(b, "Signed By: %s (%s)\n", signet.Info.Name, sig.ID)
} else {
fmt.Fprintf(b, "Signed By: %s\n", sig.ID)
}
}
}
if fileHash := fd.FileHash(); fileHash != nil {
b.WriteString("\n")
fmt.Fprintf(b, "Hash Alg: %s\n", fileHash.Algorithm())
fmt.Fprintf(b, "Hash Sum: %s\n", hex.EncodeToString(fileHash.Sum()))
}
if len(fd.MetaData) > 0 {
b.WriteString("\nMetadata:\n")
sortedMetaData := make([][]string, 0, len(fd.MetaData))
for k, v := range fd.MetaData {
sortedMetaData = append(sortedMetaData, []string{k, v})
}
sort.Sort(sortByMetaDataKey(sortedMetaData))
for _, entry := range sortedMetaData {
fmt.Fprintf(b, " %s: %s\n", entry[0], entry[1])
}
}
}
type sortByMetaDataKey [][]string
func (a sortByMetaDataKey) Len() int { return len(a) }
func (a sortByMetaDataKey) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a sortByMetaDataKey) Less(i, j int) bool { return a[i][0] < a[j][0] }

View file

@ -2,21 +2,19 @@ package main
import (
"errors"
"fmt"
"os"
"github.com/safing/jess/truststores"
"github.com/safing/jess"
"github.com/spf13/cobra"
"github.com/safing/portbase/info"
// import all tools
"github.com/safing/jess"
_ "github.com/safing/jess/tools/all"
"github.com/safing/jess/truststores"
)
const (
stdInOutFilename = "-"
letterFileExtension = ".letter"
warnFileSize = 12000000 // 120MB
)
@ -31,25 +29,21 @@ var (
}
trustStoreDir string
trustStoreKeyring string
noSpec string
minimumSecurityLevel = 0
defaultSymmetricKeySize = 0
trustStore truststores.ExtendedTrustStore
requirements = jess.NewRequirements()
requirements *jess.Requirements
)
func main() {
info.Set("jess", "0.2", "GPLv3", true)
err := info.CheckVersion()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
rootCmd.PersistentFlags().StringVarP(&trustStoreDir, "tsdir", "d", "",
"specify a truststore directory (default loaded from JESS_TSDIR env variable)",
"specify a truststore directory (default loaded from JESS_TS_DIR env variable)",
)
rootCmd.PersistentFlags().StringVarP(&trustStoreKeyring, "tskeyring", "r", "",
"specify a truststore keyring namespace (default loaded from JESS_TS_KEYRING env variable) - lower priority than tsdir",
)
rootCmd.PersistentFlags().StringVarP(&noSpec, "no", "n", "",
"remove requirements using the abbreviations C, I, R, S",
@ -58,26 +52,43 @@ func main() {
rootCmd.PersistentFlags().IntVarP(&minimumSecurityLevel, "seclevel", "s", 0, "specify a minimum security level")
rootCmd.PersistentFlags().IntVarP(&defaultSymmetricKeySize, "symkeysize", "k", 0, "specify a default symmetric key size (only applies in certain conditions, use when prompted)")
err = rootCmd.Execute()
if err != nil {
if rootCmd.Execute() != nil {
os.Exit(1)
}
os.Exit(0)
}
func initGlobalFlags(cmd *cobra.Command, args []string) (err error) {
// trust store
// trust store directory
if trustStoreDir == "" {
trustStoreDir, _ = os.LookupEnv("JESS_TSDIR")
trustStoreDir, _ = os.LookupEnv("JESS_TS_DIR")
if trustStoreDir == "" {
trustStoreDir, _ = os.LookupEnv("JESS_TSDIR")
}
}
if trustStoreDir != "" {
var err error
trustStore, err = truststores.NewDirTrustStore(trustStoreDir)
if err != nil {
return err
}
}
// trust store keyring
if trustStore == nil {
if trustStoreKeyring == "" {
trustStoreKeyring, _ = os.LookupEnv("JESS_TS_KEYRING")
if trustStoreKeyring == "" {
trustStoreKeyring, _ = os.LookupEnv("JESS_TSKEYRING")
}
}
if trustStoreKeyring != "" {
trustStore, err = truststores.NewKeyringTrustStore(trustStoreKeyring)
if err != nil {
return err
}
}
}
// requirements
if noSpec != "" {
requirements, err = jess.ParseRequirementsFromNoSpec(noSpec)

View file

@ -2,7 +2,7 @@ package main
import (
"bufio"
"crypto/sha1" //nolint:gosec // required for HIBP API
"crypto/sha1"
"encoding/hex"
"errors"
"fmt"
@ -10,9 +10,9 @@ import (
"net/http"
"strings"
"github.com/safing/jess"
"github.com/AlecAivazis/survey/v2"
"github.com/safing/jess"
)
func registerPasswordCallbacks() {
@ -115,9 +115,11 @@ func checkForWeakPassword(pw string) error {
// request hash list
resp, err := http.Get(fmt.Sprintf("https://api.pwnedpasswords.com/range/%s", prefix))
if err != nil {
return fmt.Errorf("failed to contact HIBP service: %s", err)
return fmt.Errorf("failed to contact HIBP service: %w", err)
}
defer resp.Body.Close()
defer func() {
_ = resp.Body.Close()
}()
// check if password is in hash list
bodyReader := bufio.NewReader(resp.Body)
@ -139,7 +141,7 @@ func checkForWeakPassword(pw string) error {
}
// fmt.Printf("checked %d leaked passwords\n", cnt)
if err := scanner.Err(); err != nil {
return fmt.Errorf("failed to read HIBP response: %s", err)
return fmt.Errorf("failed to read HIBP response: %w", err)
}
return nil

View file

@ -6,6 +6,8 @@ import (
//nolint:unused,deadcode // tested manually
func testCfWP(t *testing.T, password string, expectedError string) {
t.Helper()
var errMsg string
err := checkForWeakPassword(password)
if err != nil {
@ -17,7 +19,9 @@ func testCfWP(t *testing.T, password string, expectedError string) {
}
func TestCheckForWeakPassword(t *testing.T) {
// TODO: only run these manually, es they actually require the live HIBP API.
t.Parallel()
// TODO: only run these manually, as they actually require the live HIBP API.
// testCfWP(t, "asdfasdfasdf", "")
// testCfWP(t, "mfJLiQH9O9V9zXYrkNeYvGLvE14HcPyW7/sWWGfBX2nBU7c", "")
}

View file

@ -0,0 +1,14 @@
J{
"Version": 1,
"ID": "3911c84c-78f7-4354-a7f5-0e115aa2903c",
"Scheme": "Ed25519",
"Key": "ATYVZjmhR1Zwe0KAPV99pzbzI+6zWgKvELNhFwolRdnv",
"Public": true,
"Info": {
"Name": "Safing Code Signing Cert 1",
"Owner": "",
"Created": "2022-07-11T10:23:31.705715613+02:00",
"Expires": "0001-01-01T00:00:00Z",
"Details": null
}
}

View file

@ -0,0 +1,13 @@
J{
"Version": 1,
"ID": "3911c84c-78f7-4354-a7f5-0e115aa2903c",
"Scheme": "Ed25519",
"Key": "Aee5n/V1wJM8aNpaNEPBEPeN6S0Tl41OJP0rHwtsGcZcNhVmOaFHVnB7QoA9X32nNvMj7rNaAq8Qs2EXCiVF2e8=",
"Info": {
"Name": "Safing Code Signing Cert 1",
"Owner": "",
"Created": "2022-07-11T10:23:31.705715613+02:00",
"Expires": "0001-01-01T00:00:00Z",
"Details": null
}
}

View file

@ -0,0 +1,23 @@
J{
"Version": 1,
"Name": "safing-codesign-1",
"SuiteID": "signfile_v1",
"Secrets": null,
"Senders": [
{
"Version": 1,
"ID": "3911c84c-78f7-4354-a7f5-0e115aa2903c",
"Scheme": "Ed25519",
"Key": null,
"Info": {
"Name": "Safing Code Signing Cert 1",
"Owner": "",
"Created": "2022-07-11T10:23:31.705715613+02:00",
"Expires": "0001-01-01T00:00:00Z",
"Details": null
}
}
],
"Recipients": null,
"SecurityLevel": 128
}

1
cmd/testdata/test.txt vendored Normal file
View file

@ -0,0 +1 @@
hello world!

13
cmd/testdata/test.txt.letter vendored Normal file
View file

@ -0,0 +1,13 @@
J{
"Version": 1,
"SuiteID": "signfile_v1",
"Nonce": "pKOQBQ==",
"Signatures": [
{
"Scheme": "Ed25519",
"ID": "3911c84c-78f7-4354-a7f5-0e115aa2903c",
"Value": "ftsIINZ9oApKiXYQTcLIdAZDSflp6nRN/y8Gm0rdQC+3/wal6Q+7N3N8HEAxpoxWseSQNaRVCT9hSnRQStHYBA=="
}
]
}
hello world!

9
cmd/testdata/test.txt.sig vendored Normal file
View file

@ -0,0 +1,9 @@
-----BEGIN JESS SIGNATURE-----
Q6VnVmVyc2lvbgFnU3VpdGVJRGtzaWduZmlsZV92MWVOb25jZUQb+MqAZERhdGFY
d02Dq0xhYmVsZWRIYXNoxCIJIOz3Afcn2eLXfEqkmsb7vMmXJ4rKAQvd7rlhwQz1
TUNaqFNpZ25lZEF01v9iy+uLqE1ldGFEYXRhgqd2ZXJzaW9upTAuMC4xqmlkZW50
aWZpZXKyd2luZG93cy9jb2RlL3RoaW5nalNpZ25hdHVyZXOBo2ZTY2hlbWVnRWQy
NTUxOWJJRHgkMzkxMWM4NGMtNzhmNy00MzU0LWE3ZjUtMGUxMTVhYTI5MDNjZVZh
bHVlWECJZFbIifczUGAJkmATXCHy/MiQZkiktM99X7U/cPgw3IKpKAxQsJ5LobgZ
4P2ecv0IlN4gQb+x+lycxl93E9sJ
-----END JESS SIGNATURE-----

1
cmd/testdata/test3.txt vendored Normal file
View file

@ -0,0 +1 @@
hello world!!

9
cmd/testdata/test3.txt.sig vendored Normal file
View file

@ -0,0 +1,9 @@
-----BEGIN JESS SIGNATURE-----
Q6VnVmVyc2lvbgFnU3VpdGVJRGtzaWduZmlsZV92MWVOb25jZUQJ9s/nZERhdGFY
d02Dq0xhYmVsZWRIYXNoxCIJILtKnL1AHj7YubrWdLu1D+voud8Ky04vh756eTae
rWQwqFNpZ25lZEF01v9izC6hqE1ldGFEYXRhgqd2ZXJzaW9upTAuMC4xqmlkZW50
aWZpZXKyd2luZG93cy9jb2RlL3RoaW5nalNpZ25hdHVyZXOBo2ZTY2hlbWVnRWQy
NTUxOWJJRHgkMzkxMWM4NGMtNzhmNy00MzU0LWE3ZjUtMGUxMTVhYTI5MDNjZVZh
bHVlWEBLsd2QbM7VmEsnW60hHn/V6EP2mGFauWZgbEOlKTiqumVFbWU4K7Fi91KL
Zgvwj+CNdZJ7Xv2qR7etviRDCmwC
-----END JESS SIGNATURE-----

1
cmd/testdata/test4.txt vendored Normal file
View file

@ -0,0 +1 @@
hello world!

1
cmd/testdata/testdir/test2.txt vendored Normal file
View file

@ -0,0 +1 @@
hello world!

9
cmd/testdata/testdir/test2.txt.sig vendored Normal file
View file

@ -0,0 +1,9 @@
-----BEGIN JESS SIGNATURE-----
Q6VnVmVyc2lvbgFnU3VpdGVJRGtzaWduZmlsZV92MWVOb25jZUThzxO6ZERhdGFY
d02Dq0xhYmVsZWRIYXNoxCIJIOz3Afcn2eLXfEqkmsb7vMmXJ4rKAQvd7rlhwQz1
TUNaqFNpZ25lZEF01v9izC3SqE1ldGFEYXRhgqd2ZXJzaW9upTAuMC4xqmlkZW50
aWZpZXKyd2luZG93cy9jb2RlL3RoaW5nalNpZ25hdHVyZXOBo2ZTY2hlbWVnRWQy
NTUxOWJJRHgkMzkxMWM4NGMtNzhmNy00MzU0LWE3ZjUtMGUxMTVhYTI5MDNjZVZh
bHVlWEAGLkIoej0+ilJrIyb+BzX8+Yw2LY0zkoL9vwI02/2KqKVT7/pH+LTDX1Hl
h1epYkF8ICdwa1iVNDx6P7iNmWkL
-----END JESS SIGNATURE-----

View file

@ -26,7 +26,7 @@ func (w *WireSession) sendHandshakeAndInitKDF(letter *Letter) error {
case wireStateInit: // client
keyMaterial, err = w.session.setupClosingKeyMaterial(letter)
if err != nil {
return fmt.Errorf("failed to setup initial sending handshake key material: %s", err)
return fmt.Errorf("failed to setup initial sending handshake key material: %w", err)
}
fallthrough
@ -34,12 +34,12 @@ func (w *WireSession) sendHandshakeAndInitKDF(letter *Letter) error {
if w.msgNo == 0 || (!w.server && w.reKeyNeeded()) {
err = w.generateLocalKeyExchangeSignets(letter)
if err != nil {
return fmt.Errorf("failed to generate local key exchange signets for initiating handshake: %s", err)
return fmt.Errorf("failed to generate local key exchange signets for initiating handshake: %w", err)
}
err = w.generateLocalKeyEncapsulationSignets(letter)
if err != nil {
return fmt.Errorf("failed to generate local key encapsulation signets for initiating handshake: %s", err)
return fmt.Errorf("failed to generate local key encapsulation signets for initiating handshake: %w", err)
}
w.handshakeState = wireStateAwaitKey
@ -49,7 +49,7 @@ func (w *WireSession) sendHandshakeAndInitKDF(letter *Letter) error {
err = w.generateLocalKeyExchangeSignets(letter)
if err != nil {
return fmt.Errorf("failed to generate local key exchange signets for completing handshake: %s", err)
return fmt.Errorf("failed to generate local key exchange signets for completing handshake: %w", err)
}
// debugging:
@ -67,17 +67,17 @@ func (w *WireSession) sendHandshakeAndInitKDF(letter *Letter) error {
keyMaterial, err = w.makeSharedKeys(keyMaterial)
if err != nil {
return fmt.Errorf("failed to create shared keys for completing handshake: %s", err)
return fmt.Errorf("failed to create shared keys for completing handshake: %w", err)
}
err = w.generateLocalKeyEncapsulationSignets(letter)
if err != nil {
return fmt.Errorf("failed to generate local key encapsulation signets for completing handshake: %s", err)
return fmt.Errorf("failed to generate local key encapsulation signets for completing handshake: %w", err)
}
keyMaterial, err = w.makeAndEncapsulateNewKeys(letter, keyMaterial)
if err != nil {
return fmt.Errorf("failed to encapsulate keys for completing handshake: %s", err)
return fmt.Errorf("failed to encapsulate keys for completing handshake: %w", err)
}
w.newKeyMaterial = copyKeyMaterial(keyMaterial)
@ -102,13 +102,13 @@ func (w *WireSession) sendHandshakeAndInitKDF(letter *Letter) error {
// init KDF
err = w.session.kdf.InitKeyDerivation(letter.Nonce, keyMaterial...)
if err != nil {
return fmt.Errorf("failed to init %s kdf: %s", w.session.kdf.Info().Name, err)
return fmt.Errorf("failed to init %s kdf: %w", w.session.kdf.Info().Name, err)
}
// derive new carryover key
err = w.session.kdf.DeriveKeyWriteTo(w.sendKeyCarryover)
if err != nil {
return fmt.Errorf("failed to iterate session key with %s: %s", w.session.kdf.Info().Name, err)
return fmt.Errorf("failed to iterate session key with %s: %w", w.session.kdf.Info().Name, err)
}
if w.msgNo == 0 {
// copy initial sendkey to recvkey
@ -137,7 +137,7 @@ func (w *WireSession) recvHandshakeAndInitKDF(letter *Letter) error {
case wireStateInit: // server
keyMaterial, err = w.session.setupOpeningKeyMaterial(letter)
if err != nil {
return fmt.Errorf("failed to setup initial receiving handshake key material: %s", err)
return fmt.Errorf("failed to setup initial receiving handshake key material: %w", err)
}
fallthrough
@ -246,13 +246,13 @@ func (w *WireSession) recvHandshakeAndInitKDF(letter *Letter) error {
// init KDF
err = w.session.kdf.InitKeyDerivation(letter.Nonce, keyMaterial...)
if err != nil {
return fmt.Errorf("failed to init %s kdf: %s", w.session.kdf.Info().Name, err)
return fmt.Errorf("failed to init %s kdf: %w", w.session.kdf.Info().Name, err)
}
// derive new carryover key
err = w.session.kdf.DeriveKeyWriteTo(w.recvKeyCarryover)
if err != nil {
return fmt.Errorf("failed to iterate session key with %s: %s", w.session.kdf.Info().Name, err)
return fmt.Errorf("failed to iterate session key with %s: %w", w.session.kdf.Info().Name, err)
}
if w.msgNo == 0 {
// copy initial recvkey to sendkey
@ -456,11 +456,11 @@ func (w *WireSession) burnEphemeralKeys() error {
}
func copyKeyMaterial(keyMaterial [][]byte) [][]byte {
new := make([][]byte, len(keyMaterial))
copied := make([][]byte, len(keyMaterial))
for index, part := range keyMaterial {
newPart := make([]byte, len(part))
copy(newPart, part)
new[index] = newPart
copiedPart := make([]byte, len(part))
copy(copiedPart, part)
copied[index] = copiedPart
}
return new
return copied
}

View file

@ -5,10 +5,12 @@ import (
"testing"
"time"
"github.com/safing/portbase/container"
"github.com/safing/structures/container"
)
func TestWire(t *testing.T) {
t.Parallel()
wireReKeyAfterMsgs = 100
// current suites recommendation
@ -21,6 +23,8 @@ func TestWire(t *testing.T) {
}
func testWireCorrespondence(t *testing.T, suite *Suite, testData string) {
t.Helper()
wtr := &wireTestRange{t: t}
wtr.init(suite, testData)
fmt.Printf("\n\nsimulating %v\n", suite.ID)

82
core.go
View file

@ -5,7 +5,7 @@ import (
"errors"
"fmt"
"github.com/safing/portbase/container"
"github.com/safing/structures/container"
)
// Close encrypts (and possibly signs) the given data and returns a Letter. Storyline: Close takes an envelope, inserts the message and closes it, resulting in a letter.
@ -28,14 +28,14 @@ func (s *Session) Close(data []byte) (*Letter, error) { //nolint:gocognit
data = copiedData
}
/////////////////
// ==============
// key management
/////////////////
// ==============
// create nonce
nonce, err := RandomBytes(s.NonceSize())
if err != nil {
return nil, fmt.Errorf("failed to get nonce: %s", err)
return nil, fmt.Errorf("failed to get nonce: %w", err)
}
letter.Nonce = nonce
@ -57,13 +57,13 @@ func (s *Session) Close(data []byte) (*Letter, error) { //nolint:gocognit
// init KDF
err = s.kdf.InitKeyDerivation(letter.Nonce, keyMaterial...)
if err != nil {
return nil, fmt.Errorf("failed to init %s kdf: %s", s.kdf.Info().Name, err)
return nil, fmt.Errorf("failed to init %s kdf: %w", s.kdf.Info().Name, err)
}
}
/////////////
// ==========
// encryption
/////////////
// ==========
// setup tools
err = s.setup()
@ -76,7 +76,7 @@ func (s *Session) Close(data []byte) (*Letter, error) { //nolint:gocognit
for _, tool := range s.ciphers {
data, err = tool.Encrypt(data)
if err != nil {
return nil, fmt.Errorf("failed to encrypt with %s: %s", tool.Info().Name, err)
return nil, fmt.Errorf("failed to encrypt with %s: %w", tool.Info().Name, err)
}
}
@ -89,7 +89,7 @@ func (s *Session) Close(data []byte) (*Letter, error) { //nolint:gocognit
for _, tool := range s.integratedCiphers {
data, err = tool.AuthenticatedEncrypt(data, associatedData)
if err != nil {
return nil, fmt.Errorf("failed to auth-encrypt with %s: %s", tool.Info().Name, err)
return nil, fmt.Errorf("failed to auth-encrypt with %s: %w", tool.Info().Name, err)
}
}
@ -109,7 +109,7 @@ func (s *Session) Close(data []byte) (*Letter, error) { //nolint:gocognit
for _, tool := range s.macs {
mac, err := tool.MAC(data, associatedData)
if err != nil {
return nil, fmt.Errorf("failed to calculate MAC with %s: %s", tool.Info().Name, err)
return nil, fmt.Errorf("failed to calculate MAC with %s: %w", tool.Info().Name, err)
}
allMacs.Append(mac)
}
@ -142,10 +142,9 @@ func (s *Session) Close(data []byte) (*Letter, error) { //nolint:gocognit
for _, tool := range s.signers {
//nolint:scopelint // function is executed immediately within loop
err = s.envelope.LoopSenders(tool.Info().Name, func(signet *Signet) error {
sig, err := tool.Sign(data, associatedSigningData, signet)
if err != nil {
return fmt.Errorf("failed to sign with %s: %s", tool.Info().Name, err)
return fmt.Errorf("failed to sign with %s: %w", tool.Info().Name, err)
}
letter.Signatures = append(letter.Signatures, &Seal{
@ -181,9 +180,9 @@ func (s *Session) Open(letter *Letter) ([]byte, error) { //nolint:gocognit,gocyc
return nil, fmt.Errorf("unsupported letter version: %d", letter.Version)
}
/////////
// ======
// verify
/////////
// ======
// TODO: signature verification is run before tool setup. Currently, this is ok, but might change in the future. This might break additional signing algorithms that actually need setup.
@ -218,10 +217,9 @@ func (s *Session) Open(letter *Letter) ([]byte, error) { //nolint:gocognit,gocyc
for _, tool := range s.signers {
//nolint:scopelint // function is executed immediately within loop
err = s.envelope.LoopSenders(tool.Info().Name, func(signet *Signet) error {
err := tool.Verify(data, associatedSigningData, letter.Signatures[sigIndex].Value, signet)
if err != nil {
return fmt.Errorf("failed to verify signature (%s) with ID %s: %s", tool.Info().Name, letter.Signatures[sigIndex].ID, err)
return fmt.Errorf("failed to verify signature (%s) with ID %s: %w", tool.Info().Name, letter.Signatures[sigIndex].ID, err)
}
sigIndex++
@ -242,9 +240,9 @@ func (s *Session) Open(letter *Letter) ([]byte, error) { //nolint:gocognit,gocyc
return data, nil
}
/////////////////
// ==============
// key management
/////////////////
// ==============
// key establishment
if s.wire != nil {
@ -261,13 +259,13 @@ func (s *Session) Open(letter *Letter) ([]byte, error) { //nolint:gocognit,gocyc
// init KDF
err = s.kdf.InitKeyDerivation(letter.Nonce, keyMaterial...)
if err != nil {
return nil, fmt.Errorf("failed to init %s kdf: %s", s.kdf.Info().Name, err)
return nil, fmt.Errorf("failed to init %s kdf: %w", s.kdf.Info().Name, err)
}
}
/////////////
// ==========
// decryption
/////////////
// ==========
// setup tools
err = s.setup()
@ -293,7 +291,7 @@ func (s *Session) Open(letter *Letter) ([]byte, error) { //nolint:gocognit,gocyc
for _, tool := range s.macs {
mac, err := tool.MAC(data, associatedData)
if err != nil {
return nil, fmt.Errorf("failed to calculate MAC with %s: %s", tool.Info().Name, err)
return nil, fmt.Errorf("failed to calculate MAC with %s: %w", tool.Info().Name, err)
}
allMacs.Append(mac)
}
@ -306,7 +304,7 @@ func (s *Session) Open(letter *Letter) ([]byte, error) { //nolint:gocognit,gocyc
for i := len(s.integratedCiphers) - 1; i >= 0; i-- {
data, err = s.integratedCiphers[i].AuthenticatedDecrypt(data, associatedData)
if err != nil {
return nil, fmt.Errorf("%w: [%s] %s", ErrIntegrityViolation, s.integratedCiphers[i].Info().Name, err)
return nil, fmt.Errorf("%w: [%s] %w", ErrIntegrityViolation, s.integratedCiphers[i].Info().Name, err)
}
}
@ -314,7 +312,7 @@ func (s *Session) Open(letter *Letter) ([]byte, error) { //nolint:gocognit,gocyc
for i := len(s.ciphers) - 1; i >= 0; i-- {
data, err = s.ciphers[i].Decrypt(data)
if err != nil {
return nil, fmt.Errorf("%w: decryption failed: [%s] %s", ErrIntegrityViolation, s.ciphers[i].Info().Name, err)
return nil, fmt.Errorf("%w: decryption failed: [%s] %w", ErrIntegrityViolation, s.ciphers[i].Info().Name, err)
}
}
@ -323,7 +321,6 @@ func (s *Session) Open(letter *Letter) ([]byte, error) { //nolint:gocognit,gocyc
// Verify verifies signatures of the given letter.
func (s *Session) Verify(letter *Letter) error {
// debugging:
/*
fmt.Printf("opening: %+v\n", letter)
@ -337,9 +334,9 @@ func (s *Session) Verify(letter *Letter) error {
return fmt.Errorf("unsupported letter version: %d", letter.Version)
}
/////////
// ======
// verify
/////////
// ======
// TODO: signature verification is run before tool setup. Currently, this is ok, but might change in the future. This might break additional signing algorithms that actually need setup.
@ -374,10 +371,9 @@ func (s *Session) Verify(letter *Letter) error {
for _, tool := range s.signers {
//nolint:scopelint // function is executed immediately within loop
err = s.envelope.LoopSenders(tool.Info().Name, func(signet *Signet) error {
err := tool.Verify(data, associatedSigningData, letter.Signatures[sigIndex].Value, signet)
if err != nil {
return fmt.Errorf("failed to verify signature (%s) with ID %s: %s", tool.Info().Name, letter.Signatures[sigIndex].ID, err)
return fmt.Errorf("failed to verify signature (%s) with ID %s: %w", tool.Info().Name, letter.Signatures[sigIndex].ID, err)
}
sigIndex++
@ -417,7 +413,7 @@ func (s *Session) setupClosingKeyMaterial(letter *Letter) ([][]byte, error) {
}
pwKey, err := s.passDerivator.DeriveKeyFromPassword(signet.Key, letter.Nonce)
if err != nil {
return fmt.Errorf("failed to get derive key from password with %s: %s", s.passDerivator.Info().Name, err)
return fmt.Errorf("failed to get derive key from password with %s: %w", s.passDerivator.Info().Name, err)
}
letter.Keys = append(letter.Keys, &Seal{
Scheme: SignetSchemePassword,
@ -440,23 +436,23 @@ func (s *Session) setupClosingKeyMaterial(letter *Letter) ([][]byte, error) {
senderSignet := NewSignetBase(tool.Definition())
err := senderSignet.GenerateKey()
if err != nil {
return fmt.Errorf("failed to generate new sender signet for %s: %s", tool.Info().Name, err)
return fmt.Errorf("failed to generate new sender signet for %s: %w", tool.Info().Name, err)
}
// create exchange and add to letter
exchKey, err := tool.MakeSharedKey(senderSignet, recipient)
if err != nil {
return fmt.Errorf("failed to make managed key with %s: %s", tool.Info().Name, err)
return fmt.Errorf("failed to make managed key with %s: %w", tool.Info().Name, err)
}
// add to letter
senderRcpt, err := senderSignet.AsRecipient() // convert to public signet
if err != nil {
return fmt.Errorf("failed to get public sender signet for %s: %s", tool.Info().Name, err)
return fmt.Errorf("failed to get public sender signet for %s: %w", tool.Info().Name, err)
}
err = senderRcpt.StoreKey()
if err != nil {
return fmt.Errorf("failed to serialize sender public key for %s: %s", tool.Info().Name, err)
return fmt.Errorf("failed to serialize sender public key for %s: %w", tool.Info().Name, err)
}
letter.Keys = append(letter.Keys, &Seal{
ID: recipient.ID,
@ -496,13 +492,13 @@ func (s *Session) setupClosingKeyMaterial(letter *Letter) ([][]byte, error) {
// generate new key
newKey, err := RandomBytes(tool.Helper().DefaultSymmetricKeySize())
if err != nil {
return fmt.Errorf("failed to generate new key for %s: %s", tool.Info().Name, err)
return fmt.Errorf("failed to generate new key for %s: %w", tool.Info().Name, err)
}
// encapsulate key
wrappedKey, err := tool.EncapsulateKey(newKey, recipient)
if err != nil {
return fmt.Errorf("failed to encapsulate key with %s: %s", tool.Info().Name, err)
return fmt.Errorf("failed to encapsulate key with %s: %w", tool.Info().Name, err)
}
// add to letter
@ -557,7 +553,7 @@ func (s *Session) setupOpeningKeyMaterial(letter *Letter) ([][]byte, error) {
}
pwKey, err := s.passDerivator.DeriveKeyFromPassword(signet.Key, letter.Nonce)
if err != nil {
return fmt.Errorf("failed to get derive key from password with %s: %s", s.passDerivator.Info().Name, err)
return fmt.Errorf("failed to get derive key from password with %s: %w", s.passDerivator.Info().Name, err)
}
keyMaterial = append(keyMaterial, pwKey)
@ -583,7 +579,7 @@ func (s *Session) setupOpeningKeyMaterial(letter *Letter) ([][]byte, error) {
// load key
err := peerSignet.LoadKey()
if err != nil {
return fmt.Errorf("failed to load ephermal signet for key exchange: %s", err)
return fmt.Errorf("failed to load ephermal signet for key exchange: %w", err)
}
// save to state
if s.wire != nil {
@ -596,7 +592,7 @@ func (s *Session) setupOpeningKeyMaterial(letter *Letter) ([][]byte, error) {
// make shared key
exchKey, err := tool.MakeSharedKey(signet, peerSignet)
if err != nil {
return fmt.Errorf("failed to make shared key with %s: %s", tool.Info().Name, err)
return fmt.Errorf("failed to make shared key with %s: %w", tool.Info().Name, err)
}
// add key
@ -642,7 +638,7 @@ func (s *Session) setup() error {
for _, tool := range s.toolsWithState {
err := tool.Setup()
if err != nil {
return fmt.Errorf("failed to run tool %s setup: %s", tool.Info().Name, err)
return fmt.Errorf("failed to run tool %s setup: %w", tool.Info().Name, err)
}
}
@ -655,7 +651,7 @@ func (s *Session) reset() error {
for _, tool := range s.toolsWithState {
err := tool.Reset()
if err != nil {
return fmt.Errorf("failed to run tool %s reset: %s", tool.Info().Name, err)
return fmt.Errorf("failed to run tool %s reset: %w", tool.Info().Name, err)
}
}
@ -666,7 +662,7 @@ func (s *Session) feedManagedHashers(managedHashers map[string]*managedHasher, d
for _, mngdHasher := range managedHashers {
n, err := mngdHasher.hash.Write(data)
if err != nil {
return fmt.Errorf("failed to write data to managed hasher %s: %s", mngdHasher.tool.Name, err)
return fmt.Errorf("failed to write data to managed hasher %s: %w", mngdHasher.tool.Name, err)
}
if n != len(data) {
return fmt.Errorf("failed to fully write data to managed hasher %s", mngdHasher.tool.Name)
@ -674,7 +670,7 @@ func (s *Session) feedManagedHashers(managedHashers map[string]*managedHasher, d
n, err = mngdHasher.hash.Write(associatedData)
if err != nil {
return fmt.Errorf("failed to write associated data to managed hasher %s: %s", mngdHasher.tool.Name, err)
return fmt.Errorf("failed to write associated data to managed hasher %s: %w", mngdHasher.tool.Name, err)
}
if n != len(associatedData) {
return fmt.Errorf("failed to fully write associated data to managed hasher %s", mngdHasher.tool.Name)

View file

@ -19,7 +19,7 @@ const (
Qui voluptates quod omnis rerum. Soluta dolore quia eius quo similique accusamus. Quisquam fugiat sed voluptatibus eos earum sed. Numquam quia at commodi aut esse ducimus enim.
Enim nihil architecto architecto. Reprehenderit at assumenda labore. Et ut sed ut inventore tenetur autem. Iusto et neque ab dolores eum. Praesentium amet sint ut voluptate impedit sit.
A accusantium ullam voluptatibus. Adipisci architecto minus dolore tenetur eos. Id illum quo neque laborum numquam laborum animi libero.
Debitis voluptatem non aut ex. Et et quis qui aut aut fugit accusantium. Est dolor quia accusantium culpa.
Debitis voluptatem non aut ex. Et et quis qui aut fugit accusantium. Est dolor quia accusantium culpa.
Facere iste dolor a qui. Earum aut facilis maxime repudiandae magnam. Laborum illum distinctio quo libero corrupti maxime. Eum nam officiis culpa nobis.
Et repellat qui ut quaerat error explicabo. Distinctio repudiandae sit dolores nam at. Suscipit aliquam alias ullam id.`
@ -46,6 +46,8 @@ var (
)
func tErrorf(t *testing.T, msg string, args ...interface{}) {
t.Helper()
t.Errorf(msg, args...)
if runTestsInDebugStyleActive {
debugStyleErrorCnt++
@ -111,23 +113,26 @@ func init() {
defaultSecurityLevel = 128
// init special test config
if RunComprehensiveTests == "true" { //nolint:goconst
if RunComprehensiveTests == "true" {
runComprehensiveTestsActive = true
}
if RunTestsInDebugStyle == "true" { //nolint:goconst
if RunTestsInDebugStyle == "true" {
runTestsInDebugStyleActive = true
}
}
func TestCoreBasic(t *testing.T) {
t.Parallel()
for _, suite := range Suites() {
testStorage(t, suite)
}
}
//nolint:gocognit
// TestCoreAllCombinations tests all tools in all combinations and every tool
// should be tested when placed before and after every other tool.
func TestCoreAllCombinations(t *testing.T) {
// This shall test all tools in all combinations and every tool should be tested when placed before and after every other tool.
t.Parallel()
// skip in short tests and when not running comprehensive
if testing.Short() || !runComprehensiveTestsActive {
@ -220,8 +225,8 @@ func TestCoreAllCombinations(t *testing.T) {
t.Logf("of these, %d were successfully detected as invalid", combinationsDetectedInvalid)
}
func testStorage(t *testing.T, suite *Suite) (detectedInvalid bool) {
// t.Logf("testing storage with %s", suite.ID)
func testStorage(t *testing.T, suite *Suite) (detectedInvalid bool) { //nolint:thelper
t.Logf("testing storage with %s", suite.ID)
e, err := setupEnvelopeAndTrustStore(t, suite)
if err != nil {
@ -291,6 +296,8 @@ func testStorage(t *testing.T, suite *Suite) (detectedInvalid bool) {
//nolint:gocognit,gocyclo
func setupEnvelopeAndTrustStore(t *testing.T, suite *Suite) (*Envelope, error) {
t.Helper()
// check if suite is registered
if suite.ID == "" {
// register as test suite
@ -369,6 +376,7 @@ func setupEnvelopeAndTrustStore(t *testing.T, suite *Suite) (*Envelope, error) {
case tools.PurposeKeyEncapsulation:
e.suite.Provides.Add(RecipientAuthentication)
case tools.PurposeSigning:
e.suite.Provides.Add(Integrity)
e.suite.Provides.Add(SenderAuthentication)
case tools.PurposeIntegratedCipher:
e.suite.Provides.Add(Confidentiality)
@ -394,9 +402,7 @@ func setupEnvelopeAndTrustStore(t *testing.T, suite *Suite) (*Envelope, error) {
}
// check if we are missing key derivation - this is only ok if we are merely signing
if !keyDerPresent &&
(len(e.suite.Provides.all) != 1 ||
!e.suite.Provides.Has(SenderAuthentication)) {
if !keyDerPresent && len(e.Senders) != len(e.suite.Tools) {
return nil, testInvalidToolset(e, "omitting a key derivation tool is only allowed when merely signing")
}
@ -456,6 +462,7 @@ func testInvalidToolset(e *Envelope, whyInvalid string) error {
}
func getOrMakeSignet(t *testing.T, tool tools.ToolLogic, recipient bool, signetID string) (*Signet, error) {
t.Helper()
// check if signet already exists
signet, err := testTrustStore.GetSignet(signetID, recipient)
@ -469,24 +476,24 @@ func getOrMakeSignet(t *testing.T, tool tools.ToolLogic, recipient bool, signetI
}
// create new signet
new := NewSignetBase(tool.Definition())
new.ID = signetID
newSignet := NewSignetBase(tool.Definition())
newSignet.ID = signetID
// generate signet and log time taken
start := time.Now()
err = tool.GenerateKey(new)
err = tool.GenerateKey(newSignet)
if err != nil {
return nil, err
}
t.Logf("generated %s signet %s in %s", new.Scheme, new.ID, time.Since(start))
t.Logf("generated %s signet %s in %s", newSignet.Scheme, newSignet.ID, time.Since(start))
// store signet
err = testTrustStore.StoreSignet(new)
err = testTrustStore.StoreSignet(newSignet)
if err != nil {
return nil, err
}
// store recipient
newRcpt, err := new.AsRecipient()
newRcpt, err := newSignet.AsRecipient()
if err != nil {
return nil, err
}
@ -499,13 +506,14 @@ func getOrMakeSignet(t *testing.T, tool tools.ToolLogic, recipient bool, signetI
if recipient {
return newRcpt, nil
}
return new, nil
return newSignet, nil
}
// generateCombinations returns all possible combinations of the given []string slice.
// Forked from https://github.com/mxschmitt/golang-combinations/blob/a887187146560effd2677e987b069262f356297f/combinations.go
// Copyright (c) 2018 Max Schmitt
// MIT License
//
// Forked from https://github.com/mxschmitt/golang-combinations/blob/a887187146560effd2677e987b069262f356297f/combinations.go
// Copyright (c) 2018 Max Schmitt,
// MIT License.
func generateCombinations(set []string) (subsets [][]string) {
length := uint(len(set))

View file

@ -1,7 +1,7 @@
package jess
var (
// must be var in order decrease for testing for better speed
// Must be var in order decrease for testing for better speed.
defaultSecurityLevel = 128
minimumSecurityLevel = 0
@ -10,7 +10,7 @@ var (
minimumSymmetricKeySize = 0
)
// Currently recommended toolsets
// Currently recommended toolsets.
var (
RecommendedNetwork = []string{"ECDH-X25519", "HKDF(SHA2-256)", "CHACHA20-POLY1305"}
RecommendedStoragePassword = []string{"PBKDF2-SHA2-256", "HKDF(SHA2-256)", "CHACHA20-POLY1305"}

View file

@ -3,6 +3,10 @@ package jess
import (
"errors"
"fmt"
"github.com/mr-tron/base58"
"github.com/safing/structures/dsd"
)
// Envelope holds configuration for jess to put data into a letter.
@ -181,7 +185,7 @@ func (e *Envelope) prepSignets(signets []*Signet, recipients bool, storage Trust
if signet.Scheme == SignetSchemePassword {
err := fillPassword(signet, !recipients, storage, e.suite.SecurityLevel)
if err != nil {
return fmt.Errorf(`failed to get password for "%s": %s`, signet.ID, err)
return fmt.Errorf(`failed to get password for "%s": %w`, signet.ID, err)
}
continue
}
@ -202,19 +206,19 @@ func (e *Envelope) prepSignets(signets []*Signet, recipients bool, storage Trust
}
// get signet from trust store
new, err := storage.GetSignet(signet.ID, recipients)
newSignet, err := storage.GetSignet(signet.ID, recipients)
if err != nil {
return fmt.Errorf(`failed to get signet with ID "%s" from truststore: %s`, signet.ID, err)
return fmt.Errorf(`failed to get signet with ID "%s" from truststore: %w`, signet.ID, err)
}
// check for scheme mismatch
if signet.Scheme != "" && signet.Scheme != new.Scheme {
return fmt.Errorf(`failed to apply signet with ID "%s" from truststore: was expected to be of type %s, but is %s`, signet.ID, signet.Scheme, new.Scheme)
if signet.Scheme != "" && signet.Scheme != newSignet.Scheme {
return fmt.Errorf(`failed to apply signet with ID "%s" from truststore: was expected to be of type %s, but is %s`, signet.ID, signet.Scheme, newSignet.Scheme)
}
// apply signet back into envelope
signet = new
signets[i] = new
signet = newSignet
signets[i] = newSignet
}
// unwrap protection
@ -252,12 +256,12 @@ func fillPassword(signet *Signet, createPassword bool, storage TrustStore, minSe
// check trust store for name
if len(signet.ID) > 0 && storage != nil {
// get signet from trust store
new, err := storage.GetSignet(signet.ID, false)
if err == nil && new.Info != nil {
newSignet, err := storage.GetSignet(signet.ID, false)
if err == nil && newSignet.Info != nil {
if signet.Info == nil {
signet.Info = new.Info
signet.Info = newSignet.Info
} else {
signet.Info.Name = new.Info.Name
signet.Info.Name = newSignet.Info.Name
}
}
}
@ -268,3 +272,80 @@ func fillPassword(signet *Signet, createPassword bool, storage TrustStore, minSe
}
return getPasswordCallback(signet)
}
// CleanSignets cleans all the signets from all the non-necessary data as well
// as key material.
// This is for preparing for serializing and saving the signet.
func (e *Envelope) CleanSignets() {
for i, signet := range e.Secrets {
e.Secrets[i] = &Signet{
Version: signet.Version,
ID: signet.ID,
Scheme: signet.Scheme,
}
}
for i, signet := range e.Senders {
e.Senders[i] = &Signet{
Version: signet.Version,
ID: signet.ID,
Scheme: signet.Scheme,
}
}
for i, signet := range e.Recipients {
e.Recipients[i] = &Signet{
Version: signet.Version,
ID: signet.ID,
Scheme: signet.Scheme,
}
}
}
// ToBytes serializes the envelope to a byte slice.
func (e *Envelope) ToBytes() ([]byte, error) {
// Minimize data and remove any key material.
e.CleanSignets()
// Serialize envelope.
data, err := dsd.Dump(e, dsd.CBOR)
if err != nil {
return nil, fmt.Errorf("failed to serialize the envelope: %w", err)
}
return data, nil
}
// EnvelopeFromBytes parses and loads a serialized envelope.
func EnvelopeFromBytes(data []byte) (*Envelope, error) {
e := &Envelope{}
// Parse envelope from data.
if _, err := dsd.Load(data, e); err != nil {
return nil, fmt.Errorf("failed to parse data format: %w", err)
}
return e, nil
}
// ToBase58 serializes the envelope and encodes it with base58.
func (e *Envelope) ToBase58() (string, error) {
// Serialize Signet.
data, err := e.ToBytes()
if err != nil {
return "", err
}
// Encode and return.
return base58.Encode(data), nil
}
// EnvelopeFromBase58 parses and loads a base58 encoded serialized envelope.
func EnvelopeFromBase58(base58Encoded string) (*Envelope, error) {
// Decode string.
data, err := base58.Decode(base58Encoded)
if err != nil {
return nil, fmt.Errorf("failed to decode base58: %w", err)
}
// Parse and return.
return EnvelopeFromBytes(data)
}

123
filesig/format_armor.go Normal file
View file

@ -0,0 +1,123 @@
package filesig
import (
"bytes"
"encoding/base64"
"fmt"
"regexp"
"github.com/safing/jess"
"github.com/safing/structures/dsd"
)
const (
sigFileArmorStart = "-----BEGIN JESS SIGNATURE-----"
sigFileArmorEnd = "-----END JESS SIGNATURE-----"
sigFileLineLength = 64
)
var (
sigFileArmorFindMatcher = regexp.MustCompile(`(?ms)` + sigFileArmorStart + `(.+?)` + sigFileArmorEnd)
sigFileArmorRemoveMatcher = regexp.MustCompile(`(?ms)` + sigFileArmorStart + `.+?` + sigFileArmorEnd + `\r?\n?`)
whitespaceMatcher = regexp.MustCompile(`(?ms)\s`)
)
// ParseSigFile parses a signature file and extracts any jess signatures from it.
// If signatures are returned along with an error, the error should be treated
// as a warning, but the result should also not be treated as a full success,
// as there might be missing signatures.
func ParseSigFile(fileData []byte) (signatures []*jess.Letter, err error) {
var warning error
captured := make([][]byte, 0, 1)
// Find any signature blocks.
matches := sigFileArmorFindMatcher.FindAllSubmatch(fileData, -1)
for _, subMatches := range matches {
if len(subMatches) >= 2 {
// First entry is the whole match, second the submatch.
captured = append(
captured,
bytes.TrimPrefix(
bytes.TrimSuffix(
whitespaceMatcher.ReplaceAll(subMatches[1], nil),
[]byte(sigFileArmorEnd),
),
[]byte(sigFileArmorStart),
),
)
}
}
// Parse any found signatures.
signatures = make([]*jess.Letter, 0, len(captured))
for _, sigBase64Data := range captured {
// Decode from base64
sigData := make([]byte, base64.RawStdEncoding.DecodedLen(len(sigBase64Data)))
_, err = base64.RawStdEncoding.Decode(sigData, sigBase64Data)
if err != nil {
warning = err
continue
}
// Parse signature.
var letter *jess.Letter
letter, err = jess.LetterFromDSD(sigData)
if err != nil {
warning = err
} else {
signatures = append(signatures, letter)
}
}
return signatures, warning
}
// MakeSigFileSection creates a new section for a signature file.
func MakeSigFileSection(signature *jess.Letter) ([]byte, error) {
// Serialize.
data, err := signature.ToDSD(dsd.CBOR)
if err != nil {
return nil, fmt.Errorf("failed to serialize signature: %w", err)
}
// Encode to base64
encodedData := make([]byte, base64.RawStdEncoding.EncodedLen(len(data)))
base64.RawStdEncoding.Encode(encodedData, data)
// Split into lines and add armor.
splittedData := make([][]byte, 0, (len(encodedData)/sigFileLineLength)+3)
splittedData = append(splittedData, []byte(sigFileArmorStart))
for len(encodedData) > 0 {
if len(encodedData) > sigFileLineLength {
splittedData = append(splittedData, encodedData[:sigFileLineLength])
encodedData = encodedData[sigFileLineLength:]
} else {
splittedData = append(splittedData, encodedData)
encodedData = nil
}
}
splittedData = append(splittedData, []byte(sigFileArmorEnd))
linedData := bytes.Join(splittedData, []byte("\n"))
return linedData, nil
}
// AddToSigFile adds the given signature to the signature file.
func AddToSigFile(signature *jess.Letter, sigFileData []byte, removeExistingJessSignatures bool) (newFileData []byte, err error) {
// Create new section for new sig.
newSigSection, err := MakeSigFileSection(signature)
if err != nil {
return nil, err
}
// Remove any existing jess signature sections.
if removeExistingJessSignatures {
sigFileData = sigFileArmorRemoveMatcher.ReplaceAll(sigFileData, nil)
}
// Append new signature section to end of file with a newline.
sigFileData = append(sigFileData, []byte("\n")...)
sigFileData = append(sigFileData, newSigSection...)
return sigFileData, nil
}

View file

@ -0,0 +1,197 @@
package filesig
import (
"bytes"
"testing"
"github.com/safing/jess"
"github.com/safing/jess/lhash"
)
var (
testFileSigOneKey = "7KoUBdrRfF6drrPvKianoGfEXTQFCS5wDbfQyc87VQnYApPckRS8SfrrmAXZhV1JgKfnh44ib9nydQVEDRJiZArV22RqMfPrJmQdoAsE7zuzPRSrku8yF7zfnEv46X5GsmgfdSDrFMdG7XJd3fdaxStYCXTYDS5R"
testFileSigOneData = []byte("The quick brown fox jumps over the lazy dog")
testFileSigOneMetaData = map[string]string{
"id": "resource/path",
"version": "0.0.1",
}
testFileSigOneSignature = []byte(`
-----BEGIN JESS SIGNATURE-----
Q6VnVmVyc2lvbgFnU3VpdGVJRGdzaWduX3YxZU5vbmNlRA40a/BkRGF0YVhqTYOr
TGFiZWxlZEhhc2jEIhkgAXGM7DXNPXlt0AAg4L/stHOtI0V9Bjt17/KcD/ouWKmo
U2lnbmVkQXTW/2LH/ueoTWV0YURhdGGComlkrXJlc291cmNlL3BhdGindmVyc2lv
bqUwLjAuMWpTaWduYXR1cmVzgaNmU2NoZW1lZ0VkMjU1MTliSURwZmlsZXNpZy10
ZXN0LWtleWVWYWx1ZVhA4b1kfIJF7do6OcJnemQ5mtj/ZyMFJWWTmD1W5KvkpZac
2AP5f+dDJhzWBHsoSXTCl6uA3DA3+RbABMYAZn6eDg
-----END JESS SIGNATURE-----
`)
)
func TestFileSigFormat(t *testing.T) {
t.Parallel()
// Load test key.
signet, err := jess.SignetFromBase58(testFileSigOneKey)
if err != nil {
t.Fatal(err)
}
// Store signet.
if err := testTrustStore.StoreSignet(signet); err != nil {
t.Fatal(err)
}
// Store public key for verification.
recipient, err := signet.AsRecipient()
if err != nil {
t.Fatal(err)
}
if err := testTrustStore.StoreSignet(recipient); err != nil {
t.Fatal(err)
}
// Create envelope.
envelope := jess.NewUnconfiguredEnvelope()
envelope.SuiteID = jess.SuiteSignV1
envelope.Senders = []*jess.Signet{signet}
// Hash and sign file.
hash := lhash.Digest(lhash.BLAKE2b_256, testFileSigOneData)
letter, _, err := SignFileData(hash, testFileSigOneMetaData, envelope, testTrustStore)
if err != nil {
t.Fatal(err)
}
// Serialize signature.
sigFile, err := MakeSigFileSection(letter)
if err != nil {
t.Fatal(err)
}
// fmt.Println("Signature:")
// fmt.Println(string(sigFile))
// Parse signature again.
sigs, err := ParseSigFile(sigFile)
if err != nil {
t.Fatal(err)
}
if len(sigs) != 1 {
t.Fatalf("one sig expected, got %d", len(sigs))
}
// Verify Signature.
fileData, err := VerifyFileData(sigs[0], testFileSigOneMetaData, testTrustStore)
if err != nil {
t.Fatal(err)
}
// Verify File.
if !fileData.FileHash().MatchesData(testFileSigOneData) {
t.Fatal("file hash does not match")
}
// Verify the saved version of the signature.
// Parse the saved signature.
sigs, err = ParseSigFile(testFileSigOneSignature)
if err != nil {
t.Fatal(err)
}
if len(sigs) != 1 {
t.Fatalf("only one sig expected, got %d", len(sigs))
}
// Verify Signature.
fileData, err = VerifyFileData(sigs[0], testFileSigOneMetaData, testTrustStore)
if err != nil {
t.Fatal(err)
}
// Verify File.
if !fileData.FileHash().MatchesData(testFileSigOneData) {
t.Fatal("file hash does not match")
}
}
var (
testFileSigFormat1 = []byte(`TGFiZWxlZEhhc2jEIhkgAXGM7DXNPXlt0AAg4L
-----BEGIN JESS SIGNATURE-----
Q6VnVmVyc2lvbgFnU3VpdGVJRGdzaWduX3YxZU5vbmNlRA40a/BkRGF0YVhqTYOr
TGFiZWxlZEhhc2jEIhkgAXGM7DXNPXlt0AAg4L/stHOtI0V9Bjt17/KcD/ouWKmo
U2lnbmVkQXTW/2LH/ueoTWV0YURhdGGComlkrXJlc291cmNlL3BhdGindmVyc2lv
bqUwLjAuMWpTaWduYXR1cmVzgaNmU2NoZW1lZ0VkMjU1MTliSURwZmlsZXNpZy10
ZXN0LWtleWVWYWx1ZVhA4b1kfIJF7do6OcJnemQ5mtj/ZyMFJWWTmD1W5KvkpZac
2AP5f+dDJhzWBHsoSXTCl6uA3DA3+RbABMYAZn6eDg
-----END JESS SIGNATURE-----
-----END JESS SIGNATURE-----
-----BEGIN JESS SIGNATURE-----
Q6VnVmVyc2lvbgFnU3VpdGVJRGdzaWduX3YxZU5vbmNlRA40a/BkRGF0YVhqTYOr
TGFiZWxlZEhhc2jEIhkgAXGM7DXNPXlt0AAg4L/stHOtI0V9Bjt17/KcD/ouWKmo
U2lnbmVkQXTW/2LH/ueoTWV0YURhdGGComlk
rXJlc291cmNlL3BhdGindmVyc2lvbqUwLjAuMWpTaWduYXR1cmVzgaNmU2NoZW1lZ0VkMjU1MTliSURwZmlsZXNpZy10
ZXN0LWtleWVWYWx1ZVhA4b1kfIJF7do6OcJnemQ5mtj/ZyMFJWWTmD1W5KvkpZac
2AP5f+dDJhzWBHsoSXTCl6uA3DA3+RbABMYAZn6eDg
-----END JESS SIGNATURE-----
end`)
testFileSigFormat2 = []byte(`test data 1
-----BEGIN JESS SIGNATURE-----
invalid sig
-----END JESS SIGNATURE-----
test data 2`)
testFileSigFormat3 = []byte(`test data 1
-----BEGIN JESS SIGNATURE-----
invalid sig
-----END JESS SIGNATURE-----
test data 2
-----BEGIN JESS SIGNATURE-----
Q6VnVmVyc2lvbgFnU3VpdGVJRGdzaWduX3YxZU5vbmNlRA40a/BkRGF0YVhqTYOr
TGFiZWxlZEhhc2jEIhkgAXGM7DXNPXlt0AAg4L/stHOtI0V9Bjt17/KcD/ouWKmo
U2lnbmVkQXTW/2LH/ueoTWV0YURhdGGComlkrXJlc291cmNlL3BhdGindmVyc2lv
bqUwLjAuMWpTaWduYXR1cmVzgaNmU2NoZW1lZ0VkMjU1MTliSURwZmlsZXNpZy10
ZXN0LWtleWVWYWx1ZVhA4b1kfIJF7do6OcJnemQ5mtj/ZyMFJWWTmD1W5KvkpZac
2AP5f+dDJhzWBHsoSXTCl6uA3DA3+RbABMYAZn6eDg
-----END JESS SIGNATURE-----`)
testFileSigFormat4 = []byte(`test data 1
test data 2
-----BEGIN JESS SIGNATURE-----
Q6VnVmVyc2lvbgFnU3VpdGVJRGdzaWduX3YxZU5vbmNlRA40a/BkRGF0YVhqTYOr
TGFiZWxlZEhhc2jEIhkgAXGM7DXNPXlt0AAg4L/stHOtI0V9Bjt17/KcD/ouWKmo
U2lnbmVkQXTW/2LH/ueoTWV0YURhdGGComlkrXJlc291cmNlL3BhdGindmVyc2lv
bqUwLjAuMWpTaWduYXR1cmVzgaNmU2NoZW1lZ0VkMjU1MTliSURwZmlsZXNpZy10
ZXN0LWtleWVWYWx1ZVhA4b1kfIJF7do6OcJnemQ5mtj/ZyMFJWWTmD1W5KvkpZac
2AP5f+dDJhzWBHsoSXTCl6uA3DA3+RbABMYAZn6eDg
-----END JESS SIGNATURE-----`)
)
func TestFileSigFormatParsing(t *testing.T) {
t.Parallel()
sigs, err := ParseSigFile(testFileSigFormat1)
if err != nil {
t.Fatal(err)
}
if len(sigs) != 2 {
t.Fatalf("expected two signatures, got %d", 1)
}
newFile, err := AddToSigFile(sigs[0], testFileSigFormat2, false)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(newFile, testFileSigFormat3) {
t.Fatalf("unexpected output:\n%s", string(newFile))
}
newFile, err = AddToSigFile(sigs[0], testFileSigFormat2, true)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(newFile, testFileSigFormat4) {
t.Fatalf("unexpected output:\n%s", string(newFile))
}
}

147
filesig/helpers.go Normal file
View file

@ -0,0 +1,147 @@
package filesig
import (
"errors"
"fmt"
"io/fs"
"os"
"strings"
"github.com/safing/jess"
"github.com/safing/jess/hashtools"
"github.com/safing/jess/lhash"
)
// SignFile signs a file and replaces the signature file with a new one.
// If the dataFilePath is "-", the file data is read from stdin.
// Existing jess signatures in the signature file are removed.
func SignFile(dataFilePath, signatureFilePath string, metaData map[string]string, envelope *jess.Envelope, trustStore jess.TrustStore) (fileData *FileData, err error) {
// Load encryption suite.
if err := envelope.LoadSuite(); err != nil {
return nil, err
}
// Extract the used hashing algorithm from the suite.
var hashTool *hashtools.HashTool
for _, toolID := range envelope.Suite().Tools {
if strings.Contains(toolID, "(") {
hashToolID := strings.Trim(strings.Split(toolID, "(")[1], "()")
hashTool, _ = hashtools.Get(hashToolID)
break
}
}
if hashTool == nil {
return nil, errors.New("suite not suitable for file signing")
}
// Hash the data file.
var fileHash *lhash.LabeledHash
if dataFilePath == "-" {
fileHash, err = hashTool.LabeledHasher().DigestFromReader(os.Stdin)
} else {
fileHash, err = hashTool.LabeledHasher().DigestFile(dataFilePath)
}
if err != nil {
return nil, fmt.Errorf("failed to hash file: %w", err)
}
// Sign the file data.
signature, fileData, err := SignFileData(fileHash, metaData, envelope, trustStore)
if err != nil {
return nil, fmt.Errorf("failed to sign file: %w", err)
}
sigFileData, err := os.ReadFile(signatureFilePath)
var newSigFileData []byte
switch {
case err == nil:
// Add signature to existing file.
newSigFileData, err = AddToSigFile(signature, sigFileData, true)
if err != nil {
return nil, fmt.Errorf("failed to add signature to file: %w", err)
}
case errors.Is(err, fs.ErrNotExist):
// Make signature section for saving to disk.
newSigFileData, err = MakeSigFileSection(signature)
if err != nil {
return nil, fmt.Errorf("failed to format signature for file: %w", err)
}
default:
return nil, fmt.Errorf("failed to open existing signature file: %w", err)
}
// Write the signature to file.
if err := os.WriteFile(signatureFilePath, newSigFileData, 0o0644); err != nil { //nolint:gosec
return nil, fmt.Errorf("failed to write signature to file: %w", err)
}
return fileData, nil
}
// VerifyFile verifies the given files and returns the verified file data.
// If the dataFilePath is "-", the file data is read from stdin.
// If an error is returned, there was an error in at least some part of the process.
// Any returned file data struct must be checked for an verification error.
func VerifyFile(dataFilePath, signatureFilePath string, metaData map[string]string, trustStore jess.TrustStore) (verifiedFileData []*FileData, err error) {
var lastErr error
// Read signature from file.
sigFileData, err := os.ReadFile(signatureFilePath)
if err != nil {
return nil, fmt.Errorf("failed to read signature file: %w", err)
}
// Extract all signatures.
sigs, err := ParseSigFile(sigFileData)
switch {
case len(sigs) == 0 && err != nil:
return nil, fmt.Errorf("failed to parse signature file: %w", err)
case len(sigs) == 0:
return nil, errors.New("no signatures found in signature file")
case err != nil:
lastErr = fmt.Errorf("failed to parse signature file: %w", err)
}
// Verify all signatures.
goodFileData := make([]*FileData, 0, len(sigs))
var badFileData []*FileData
for _, sigLetter := range sigs {
// Verify signature.
fileData, err := VerifyFileData(sigLetter, metaData, trustStore)
if err != nil {
lastErr = err
if fileData != nil {
fileData.verificationError = err
badFileData = append(badFileData, fileData)
}
continue
}
// Hash the file.
var fileHash *lhash.LabeledHash
if dataFilePath == "-" {
fileHash, err = fileData.FileHash().Algorithm().DigestFromReader(os.Stdin)
} else {
fileHash, err = fileData.FileHash().Algorithm().DigestFile(dataFilePath)
}
if err != nil {
lastErr = err
fileData.verificationError = err
badFileData = append(badFileData, fileData)
continue
}
// Check if the hash matches.
if !fileData.FileHash().Equal(fileHash) {
lastErr = errors.New("signature invalid: file was modified")
fileData.verificationError = lastErr
badFileData = append(badFileData, fileData)
continue
}
// Add verified file data to list for return.
goodFileData = append(goodFileData, fileData)
}
return append(goodFileData, badFileData...), lastErr
}

279
filesig/json.go Normal file
View file

@ -0,0 +1,279 @@
package filesig
import (
"encoding/base64"
"errors"
"fmt"
"github.com/tidwall/gjson"
"github.com/tidwall/pretty"
"github.com/tidwall/sjson"
"golang.org/x/exp/slices"
"github.com/safing/jess"
"github.com/safing/jess/lhash"
"github.com/safing/structures/dsd"
)
// JSON file metadata keys.
const (
JSONKeyPrefix = "_jess-"
JSONChecksumKey = JSONKeyPrefix + "checksum"
JSONSignatureKey = JSONKeyPrefix + "signature"
)
// AddJSONChecksum adds a checksum to a text file.
func AddJSONChecksum(data []byte) ([]byte, error) {
// Extract content and metadata from json.
content, checksums, signatures, err := jsonSplit(data)
if err != nil {
return nil, err
}
// Calculate checksum.
h := lhash.BLAKE2b_256.Digest(content)
checksums = append(checksums, h.Base58())
// Sort and deduplicate checksums and sigs.
slices.Sort(checksums)
checksums = slices.Compact(checksums)
slices.Sort(signatures)
signatures = slices.Compact(signatures)
// Add metadata and return.
return jsonAddMeta(content, checksums, signatures)
}
// VerifyJSONChecksum checks a checksum in a text file.
func VerifyJSONChecksum(data []byte) error {
// Extract content and metadata from json.
content, checksums, _, err := jsonSplit(data)
if err != nil {
return err
}
// Verify all checksums.
var checksumsVerified int
for _, checksum := range checksums {
// Parse checksum.
h, err := lhash.FromBase58(checksum)
if err != nil {
return fmt.Errorf("%w: failed to parse labeled hash: %w", ErrChecksumFailed, err)
}
// Verify checksum.
if !h.Matches(content) {
return ErrChecksumFailed
}
checksumsVerified++
}
// Fail when no checksums were verified.
if checksumsVerified == 0 {
return ErrChecksumMissing
}
return nil
}
func AddJSONSignature(data []byte, envelope *jess.Envelope, trustStore jess.TrustStore) (signedData []byte, err error) {
// Create session.
session, err := envelope.Correspondence(trustStore)
if err != nil {
return nil, fmt.Errorf("invalid signing envelope: %w", err)
}
// Check if the envelope is suitable for signing.
if err := envelope.Suite().Provides.CheckComplianceTo(fileSigRequirements); err != nil {
return nil, fmt.Errorf("envelope not suitable for signing: %w", err)
}
// Extract content and metadata from json.
content, checksums, signatures, err := jsonSplit(data)
if err != nil {
return nil, fmt.Errorf("invalid json structure: %w", err)
}
// Sign data.
letter, err := session.Close(content)
if err != nil {
return nil, fmt.Errorf("sign: %w", err)
}
// Serialize signature and add it.
letter.Data = nil
sig, err := letter.ToDSD(dsd.CBOR)
if err != nil {
return nil, fmt.Errorf("serialize sig: %w", err)
}
signatures = append(signatures, base64.RawURLEncoding.EncodeToString(sig))
// Sort and deduplicate checksums and sigs.
slices.Sort(checksums)
checksums = slices.Compact(checksums)
slices.Sort(signatures)
signatures = slices.Compact(signatures)
// Add metadata and return.
return jsonAddMeta(data, checksums, signatures)
}
func VerifyJSONSignature(data []byte, trustStore jess.TrustStore) (err error) {
// Extract content and metadata from json.
content, _, signatures, err := jsonSplit(data)
if err != nil {
return fmt.Errorf("invalid json structure: %w", err)
}
var signaturesVerified int
for i, sig := range signatures {
// Deserialize signature.
sigData, err := base64.RawURLEncoding.DecodeString(sig)
if err != nil {
return fmt.Errorf("signature %d malformed: %w", i+1, err)
}
letter := &jess.Letter{}
_, err = dsd.Load(sigData, letter)
if err != nil {
return fmt.Errorf("signature %d malformed: %w", i+1, err)
}
// Verify signature.
letter.Data = content
err = letter.Verify(fileSigRequirements, trustStore)
if err != nil {
return fmt.Errorf("signature %d invalid: %w", i+1, err)
}
signaturesVerified++
}
// Fail when no signatures were verified.
if signaturesVerified == 0 {
return ErrSignatureMissing
}
return nil
}
func jsonSplit(data []byte) (
content []byte,
checksums []string,
signatures []string,
err error,
) {
// Check json.
if !gjson.ValidBytes(data) {
return nil, nil, nil, errors.New("invalid json")
}
content = data
// Get checksums.
result := gjson.GetBytes(content, JSONChecksumKey)
if result.Exists() {
if result.IsArray() {
array := result.Array()
checksums = make([]string, 0, len(array))
for _, result := range array {
if result.Type == gjson.String {
checksums = append(checksums, result.String())
}
}
} else if result.Type == gjson.String {
checksums = []string{result.String()}
}
// Delete key.
content, err = sjson.DeleteBytes(content, JSONChecksumKey)
if err != nil {
return nil, nil, nil, err
}
}
// Get signatures.
result = gjson.GetBytes(content, JSONSignatureKey)
if result.Exists() {
if result.IsArray() {
array := result.Array()
signatures = make([]string, 0, len(array))
for _, result := range array {
if result.Type == gjson.String {
signatures = append(signatures, result.String())
}
}
} else if result.Type == gjson.String {
signatures = []string{result.String()}
}
// Delete key.
content, err = sjson.DeleteBytes(content, JSONSignatureKey)
if err != nil {
return nil, nil, nil, err
}
}
// Format for reproducible checksums and signatures.
content = pretty.PrettyOptions(content, &pretty.Options{
Width: 200, // Must not change!
Prefix: "", // Must not change!
Indent: " ", // Must not change!
SortKeys: true, // Must not change!
})
return content, checksums, signatures, nil
}
func jsonAddMeta(data []byte, checksums, signatures []string) ([]byte, error) {
var (
err error
opts = &sjson.Options{
ReplaceInPlace: true,
}
)
// Add checksums.
switch len(checksums) {
case 0:
// Skip
case 1:
// Add single checksum.
data, err = sjson.SetBytesOptions(
data, JSONChecksumKey, checksums[0], opts,
)
default:
// Add multiple checksums.
data, err = sjson.SetBytesOptions(
data, JSONChecksumKey, checksums, opts,
)
}
if err != nil {
return nil, err
}
// Add signatures.
switch len(signatures) {
case 0:
// Skip
case 1:
// Add single signature.
data, err = sjson.SetBytesOptions(
data, JSONSignatureKey, signatures[0], opts,
)
default:
// Add multiple signatures.
data, err = sjson.SetBytesOptions(
data, JSONSignatureKey, signatures, opts,
)
}
if err != nil {
return nil, err
}
// Final pretty print.
data = pretty.PrettyOptions(data, &pretty.Options{
Width: 200, // Must not change!
Prefix: "", // Must not change!
Indent: " ", // Must not change!
})
return data, nil
}

226
filesig/json_test.go Normal file
View file

@ -0,0 +1,226 @@
package filesig
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/safing/jess"
"github.com/safing/jess/tools"
)
func TestJSONChecksums(t *testing.T) {
t.Parallel()
// Base test text file.
json := `{"a": "b", "c": 1}`
// Test with checksum after comment.
jsonWithChecksum := `{
"_jess-checksum": "ZwtAd75qvioh6uf1NAq64KRgTbqeehFVYmhLmrwu1s7xJo",
"a": "b",
"c": 1
}
`
testJSONWithChecksum, err := AddJSONChecksum([]byte(json))
require.NoError(t, err, "should be able to add checksum")
assert.Equal(t, jsonWithChecksum, string(testJSONWithChecksum), "should match")
require.NoError(t,
VerifyJSONChecksum(testJSONWithChecksum),
"checksum should be correct",
)
jsonWithChecksum = `{
"c": 1, "a":"b",
"_jess-checksum": "ZwtAd75qvioh6uf1NAq64KRgTbqeehFVYmhLmrwu1s7xJo"
}`
require.NoError(t,
VerifyJSONChecksum([]byte(jsonWithChecksum)),
"checksum should be correct",
)
jsonWithMultiChecksum := `{
"_jess-checksum": [
"PTV7S3Ca81aRk2kdNw7q2RfjLfEdPPT5Px5d211nhZedZC",
"PTV7S3Ca81aRk2kdNw7q2RfjLfEdPPT5Px5d211nhZedZC",
"CyDGH55DZUwa556DiYztMXaKZVBDjzWeFETiGmABMbvC3V"
],
"a": "b",
"c": 1
}
`
require.NoError(t,
VerifyJSONChecksum([]byte(jsonWithMultiChecksum)),
"checksum should be correct",
)
jsonWithMultiChecksumOutput := `{
"_jess-checksum": ["CyDGH55DZUwa556DiYztMXaKZVBDjzWeFETiGmABMbvC3V", "PTV7S3Ca81aRk2kdNw7q2RfjLfEdPPT5Px5d211nhZedZC", "ZwtAd75qvioh6uf1NAq64KRgTbqeehFVYmhLmrwu1s7xJo"],
"a": "b",
"c": 1
}
`
testJSONWithMultiChecksum, err := AddJSONChecksum([]byte(jsonWithMultiChecksum))
require.NoError(t, err, "should be able to add checksum")
assert.Equal(t, jsonWithMultiChecksumOutput, string(testJSONWithMultiChecksum), "should match")
require.NoError(t,
VerifyJSONChecksum(testJSONWithMultiChecksum),
"checksum should be correct",
)
// // Test with multiple checksums.
// textWithMultiChecksum := `# jess-checksum: PTNktssvYCYjZXLFL2QoBk7DYoSz1qF7DJd5XNvtptd41B
// #!/bin/bash
// # Initial
// # Comment
// # Block
// # jess-checksum: Cy2TyVDjEStUqX3wCzCCKTfy228KaQK25ZDbHNmKiF8SPf
// do_something()
// # jess-checksum: YdgJFzuvFduk1MwRjZ2JkWQ6tCE1wkjn9xubSggKAdJSX5
// `
// assert.NoError(t,
// VerifyTextFileChecksum([]byte(textWithMultiChecksum), "#"),
// "checksum should be correct",
// )
// textWithMultiChecksumOutput := `#!/bin/bash
// # Initial
// # Comment
// # Block
// # jess-checksum: Cy2TyVDjEStUqX3wCzCCKTfy228KaQK25ZDbHNmKiF8SPf
// # jess-checksum: PTNktssvYCYjZXLFL2QoBk7DYoSz1qF7DJd5XNvtptd41B
// # jess-checksum: YdgJFzuvFduk1MwRjZ2JkWQ6tCE1wkjn9xubSggKAdJSX5
// # jess-checksum: ZwngYUfUBeUn99HSdrNxkWSNjqrgZuSpVrexeEYttBso5o
// do_something()
// `
// testTextWithMultiChecksumOutput, err := AddTextFileChecksum([]byte(textWithMultiChecksum), "#", AfterComment)
// assert.NoError(t, err, "should be able to add checksum")
// assert.Equal(t, textWithMultiChecksumOutput, string(testTextWithMultiChecksumOutput), "should match")
// // Test failing checksums.
// textWithFailingChecksums := `#!/bin/bash
// # Initial
// # Comment
// # Block
// # jess-checksum: Cy2TyVDjEStUqX3wCzCCKTfy228KaQK25ZDbHNmKiF8SPf
// # jess-checksum: PTNktssvYCYjZXLFL2QoBk7DYoSz1qF7DJd5XNvtptd41B
// # jess-checksum: YdgJFzuvFduk1MwRjZ2JkWQ6tCE1wkjn9xubSggKAdJSX5
// # jess-checksum: ZwngYUfUBeUn99HSdrNxkWSNjaaaaaaaaaaaaaaaaaaaaa
// do_something()
// `
//
// assert.Error(t, VerifyTextFileChecksum([]byte(textWithFailingChecksums), "#"), "should fail")
}
func TestJSONSignatures(t *testing.T) {
t.Parallel()
// Get tool for key generation.
tool, err := tools.Get("Ed25519")
if err != nil {
t.Fatal(err)
}
// Generate key pair.
s, err := getOrMakeSignet(t, tool.StaticLogic, false, "test-key-jsonsig-1")
if err != nil {
t.Fatal(err)
}
// sBackup, err := s.Backup(true)
// if err != nil {
// t.Fatal(err)
// }
// t.Logf("signet: %s", sBackup)
// Make envelope.
envelope := jess.NewUnconfiguredEnvelope()
envelope.SuiteID = jess.SuiteSignV1
envelope.Senders = []*jess.Signet{s}
// Test 1: Simple json.
json := `{"a": "b", "c": 1}`
testJSONWithSignature, err := AddJSONSignature([]byte(json), envelope, testTrustStore)
require.NoError(t, err, "should be able to add signature")
require.NoError(t,
VerifyJSONSignature(testJSONWithSignature, testTrustStore),
"signature should be valid",
)
// Test 2: Prepared json with signature.
// Load signing key into trust store.
signingKey2, err := jess.SenderFromTextFormat(
"sender:2ZxXzzL3mc3mLPizTUe49zi8Z3NMbDrmmqJ4V9mL4AxefZ1o8pM8wPMuK2uW12Mvd3EJL9wsKTn14BDuqH2AtucvHTAkjDdZZ5YA9Azmji5tLRXmypvSxEj2mxXU3MFXBVdpzPdwRcE4WauLo9ZfQWebznvnatVLwuxmeo17tU2pL7",
)
if err != nil {
t.Fatal(err)
}
rcptKey2, err := signingKey2.AsRecipient()
if err != nil {
t.Fatal(err)
}
if err := testTrustStore.StoreSignet(rcptKey2); err != nil {
t.Fatal(err)
}
// Verify data.
jsonWithSignature := `{
"c":1,"a":"b",
"_jess-signature": "Q6RnVmVyc2lvbgFnU3VpdGVJRGdzaWduX3YxZU5vbmNlRK6e7JhqU2lnbmF0dXJlc4GjZlNjaGVtZWdFZDI1NTE5YklEeBl0ZXN0LXN0YXRpYy1rZXktanNvbnNpZy0xZVZhbHVlWEBPEbeM4_CTl3OhNT2z74h38jIZG5R7BBLDFd6npJ3E-4JqM6TaSMa-2pPEBf3fDNuikR3ak45SekC6Z10uWiEB"
}`
require.NoError(t,
VerifyJSONSignature([]byte(jsonWithSignature), testTrustStore),
"signature should be valid",
)
// Test 3: Add signature to prepared json.
testJSONWithSignature, err = AddJSONSignature([]byte(jsonWithSignature), envelope, testTrustStore)
require.NoError(t, err, "should be able to add signature")
require.NoError(t,
VerifyJSONSignature(testJSONWithSignature, testTrustStore),
"signatures should be valid",
)
// Test 4: Prepared json with multiple signatures.
// Load signing key into trust store.
signingKey3, err := jess.SenderFromTextFormat(
"sender:2ZxXzzL3mc3mLPizTUe49zi8Z3NMbDrmmqJ4V9mL4AxefZ1o8pM8wPMuRAXdZNaPX3B96bhGCpww6TbXJ6WXLHoLwLV196cgdm1BurfTMdjUPa4PUj1KgHuM82b1p8ezQeryzj1CsjeM8KRQdh9YP87gwKpXNmLW5GmUyWG5KxzZ7W",
)
if err != nil {
t.Fatal(err)
}
rcptKey3, err := signingKey3.AsRecipient()
if err != nil {
t.Fatal(err)
}
if err := testTrustStore.StoreSignet(rcptKey3); err != nil {
t.Fatal(err)
}
jsonWithMultiSig := `{
"_jess-signature": [
"Q6RnVmVyc2lvbgFnU3VpdGVJRGdzaWduX3YxZU5vbmNlRK6e7JhqU2lnbmF0dXJlc4GjZlNjaGVtZWdFZDI1NTE5YklEeBl0ZXN0LXN0YXRpYy1rZXktanNvbnNpZy0xZVZhbHVlWEBPEbeM4_CTl3OhNT2z74h38jIZG5R7BBLDFd6npJ3E-4JqM6TaSMa-2pPEBf3fDNuikR3ak45SekC6Z10uWiEB",
"Q6RnVmVyc2lvbgFnU3VpdGVJRGdzaWduX3YxZU5vbmNlRC32oylqU2lnbmF0dXJlc4GjZlNjaGVtZWdFZDI1NTE5YklEeBl0ZXN0LXN0YXRpYy1rZXktanNvbnNpZy0yZVZhbHVlWEDYVHeKaJvzZPOkgC6Tie6x70bNm2jtmJmAwDFDcBL1ddK7pVSefyAPg47xMO7jeucP5bw754P6CdrR5gyANJkM"
],
"a": "b",
"c": 1
}
`
assert.NoError(t,
VerifyJSONSignature([]byte(jsonWithMultiSig), testTrustStore),
"signatures should be valid",
)
}

123
filesig/main.go Normal file
View file

@ -0,0 +1,123 @@
package filesig
import (
"fmt"
"time"
"github.com/safing/jess"
"github.com/safing/jess/lhash"
"github.com/safing/structures/dsd"
)
// Extension holds the default file extension to be used for signature files.
const Extension = ".sig"
var fileSigRequirements = jess.NewRequirements().
Remove(jess.RecipientAuthentication).
Remove(jess.Confidentiality)
// FileData describes a file that is signed.
type FileData struct {
LabeledHash []byte
fileHash *lhash.LabeledHash
SignedAt time.Time
MetaData map[string]string
signature *jess.Letter
verificationError error
}
// FileHash returns the labeled hash of the file that was signed.
func (fd *FileData) FileHash() *lhash.LabeledHash {
return fd.fileHash
}
// Signature returns the signature, if present.
func (fd *FileData) Signature() *jess.Letter {
return fd.signature
}
// VerificationError returns the error encountered during verification.
func (fd *FileData) VerificationError() error {
return fd.verificationError
}
// SignFileData signs the given file checksum and metadata.
func SignFileData(fileHash *lhash.LabeledHash, metaData map[string]string, envelope *jess.Envelope, trustStore jess.TrustStore) (letter *jess.Letter, fd *FileData, err error) {
// Create session.
session, err := envelope.Correspondence(trustStore)
if err != nil {
return nil, nil, err
}
// Check if the envelope is suitable for signing.
if err := envelope.Suite().Provides.CheckComplianceTo(fileSigRequirements); err != nil {
return nil, nil, fmt.Errorf("envelope not suitable for signing: %w", err)
}
// Create struct and transform data into serializable format to be signed.
fd = &FileData{
SignedAt: time.Now().Truncate(time.Second),
fileHash: fileHash,
MetaData: metaData,
}
fd.LabeledHash = fd.fileHash.Bytes()
// Serialize file signature.
fileData, err := dsd.Dump(fd, dsd.MsgPack)
if err != nil {
return nil, nil, fmt.Errorf("failed to serialize file signature data: %w", err)
}
// Sign data.
letter, err = session.Close(fileData)
if err != nil {
return nil, nil, fmt.Errorf("failed to sign: %w", err)
}
return letter, fd, nil
}
// VerifyFileData verifies the given signed file data and returns the file data.
// If an error is returned, there was an error in at least some part of the process.
// Any returned file data struct must be checked for an verification error.
func VerifyFileData(letter *jess.Letter, requiredMetaData map[string]string, trustStore jess.TrustStore) (fd *FileData, err error) {
// Parse data.
fd = &FileData{
signature: letter,
}
_, err = dsd.Load(letter.Data, fd)
if err != nil {
return nil, fmt.Errorf("failed to parse file signature data: %w", err)
}
// Verify signature and get data.
_, err = letter.Open(fileSigRequirements, trustStore)
if err != nil {
fd.verificationError = fmt.Errorf("failed to verify file signature: %w", err)
return fd, fd.verificationError
}
// Check if the required metadata matches.
for reqKey, reqValue := range requiredMetaData {
sigMetaValue, ok := fd.MetaData[reqKey]
if !ok {
fd.verificationError = fmt.Errorf("missing required metadata key %q", reqKey)
return fd, fd.verificationError
}
if sigMetaValue != reqValue {
fd.verificationError = fmt.Errorf("required metadata %q=%q does not match the file's value %q", reqKey, reqValue, sigMetaValue)
return fd, fd.verificationError
}
}
// Parse labeled hash.
fd.fileHash, err = lhash.Load(fd.LabeledHash)
if err != nil {
fd.verificationError = fmt.Errorf("failed to parse file checksum: %w", err)
return fd, fd.verificationError
}
return fd, nil
}

130
filesig/main_test.go Normal file
View file

@ -0,0 +1,130 @@
package filesig
import (
"errors"
"testing"
"time"
"github.com/safing/jess"
"github.com/safing/jess/lhash"
"github.com/safing/jess/tools"
)
var (
testTrustStore = jess.NewMemTrustStore()
testData1 = "The quick brown fox jumps over the lazy dog. "
testFileSigMetaData1 = map[string]string{
"key1": "value1",
"key2": "value2",
}
testFileSigMetaData1x = map[string]string{
"key1": "value1x",
}
testFileSigMetaData2 = map[string]string{
"key3": "value3",
"key4": "value4",
}
testFileSigMetaData3 = map[string]string{}
)
func TestFileSigs(t *testing.T) {
t.Parallel()
testFileSigningWithOptions(t, testFileSigMetaData1, testFileSigMetaData1, true)
testFileSigningWithOptions(t, testFileSigMetaData1, testFileSigMetaData1x, false)
testFileSigningWithOptions(t, testFileSigMetaData2, testFileSigMetaData2, true)
testFileSigningWithOptions(t, testFileSigMetaData1, testFileSigMetaData2, false)
testFileSigningWithOptions(t, testFileSigMetaData2, testFileSigMetaData1, false)
testFileSigningWithOptions(t, testFileSigMetaData1, testFileSigMetaData3, true)
testFileSigningWithOptions(t, testFileSigMetaData3, testFileSigMetaData1, false)
}
func testFileSigningWithOptions(t *testing.T, signingMetaData, verifyingMetaData map[string]string, shouldSucceed bool) {
t.Helper()
// Get tool for key generation.
tool, err := tools.Get("Ed25519")
if err != nil {
t.Fatal(err)
}
// Generate key pair.
s, err := getOrMakeSignet(t, tool.StaticLogic, false, "test-key-filesig-1")
if err != nil {
t.Fatal(err)
}
// Hash "file".
fileHash := lhash.BLAKE2b_256.Digest([]byte(testData1))
// Make envelope.
envelope := jess.NewUnconfiguredEnvelope()
envelope.SuiteID = jess.SuiteSignV1
envelope.Senders = []*jess.Signet{s}
// Sign data.
letter, fileData, err := SignFileData(fileHash, signingMetaData, envelope, testTrustStore)
if err != nil {
t.Fatal(err)
}
// Check if the checksum made it.
if len(fileData.LabeledHash) == 0 {
t.Fatal("missing labeled hash")
}
// Verify signature.
_, err = VerifyFileData(letter, verifyingMetaData, testTrustStore)
if (err == nil) != shouldSucceed {
t.Fatal(err)
}
}
func getOrMakeSignet(t *testing.T, tool tools.ToolLogic, recipient bool, signetID string) (*jess.Signet, error) {
t.Helper()
// check if signet already exists
signet, err := testTrustStore.GetSignet(signetID, recipient)
if err == nil {
return signet, nil
}
// handle special cases
if tool == nil {
return nil, errors.New("bad parameters")
}
// create new signet
newSignet := jess.NewSignetBase(tool.Definition())
newSignet.ID = signetID
// generate signet and log time taken
start := time.Now()
err = tool.GenerateKey(newSignet)
if err != nil {
return nil, err
}
t.Logf("generated %s signet %s in %s", newSignet.Scheme, newSignet.ID, time.Since(start))
// store signet
err = testTrustStore.StoreSignet(newSignet)
if err != nil {
return nil, err
}
// store recipient
newRcpt, err := newSignet.AsRecipient()
if err != nil {
return nil, err
}
err = testTrustStore.StoreSignet(newRcpt)
if err != nil {
return nil, err
}
// return
if recipient {
return newRcpt, nil
}
return newSignet, nil
}

232
filesig/text.go Normal file
View file

@ -0,0 +1,232 @@
package filesig
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"strings"
"golang.org/x/exp/slices"
"github.com/safing/jess/lhash"
)
// Text file metadata keys.
const (
TextKeyPrefix = "jess-"
TextChecksumKey = TextKeyPrefix + "checksum"
TextSignatureKey = TextKeyPrefix + "signature"
)
// Text Operation Errors.
var (
ErrChecksumMissing = errors.New("no checksum found")
ErrChecksumFailed = errors.New("checksum does not match")
ErrSignatureMissing = errors.New("signature not found")
ErrSignatureFailed = errors.New("signature does not match")
)
// TextPlacement signifies where jess metadata is put in text files.
type TextPlacement string
const (
// TextPlacementTop places the metadata at end of file.
TextPlacementTop TextPlacement = "top"
// TextPlacementBottom places the metadata at end of file.
TextPlacementBottom TextPlacement = "bottom"
// TextPlacementAfterComment places the metadata at end of the top comment
// block, or at the top, if the first line is not a comment.
TextPlacementAfterComment TextPlacement = "after-comment"
defaultMetaPlacement = TextPlacementAfterComment
)
// AddTextFileChecksum adds a checksum to a text file.
func AddTextFileChecksum(data []byte, commentSign string, placement TextPlacement) ([]byte, error) {
// Split text file into content and jess metadata lines.
content, metaLines, err := textSplit(data, commentSign)
if err != nil {
return nil, err
}
// Calculate checksum.
h := lhash.BLAKE2b_256.Digest(content)
metaLines = append(metaLines, TextChecksumKey+": "+h.Base58())
// Sort and deduplicate meta lines.
slices.Sort[[]string, string](metaLines)
metaLines = slices.Compact[[]string, string](metaLines)
// Add meta lines and return.
return textAddMeta(content, metaLines, commentSign, placement)
}
// VerifyTextFileChecksum checks a checksum in a text file.
func VerifyTextFileChecksum(data []byte, commentSign string) error {
// Split text file into content and jess metadata lines.
content, metaLines, err := textSplit(data, commentSign)
if err != nil {
return err
}
// Verify all checksums.
var checksumsVerified int
for _, line := range metaLines {
if strings.HasPrefix(line, TextChecksumKey) {
// Clean key, delimiters and space.
line = strings.TrimPrefix(line, TextChecksumKey)
line = strings.TrimSpace(line) // Spaces and newlines.
line = strings.Trim(line, ":= ") // Delimiters and spaces.
// Parse checksum.
h, err := lhash.FromBase58(line)
if err != nil {
return fmt.Errorf("%w: failed to parse labeled hash: %w", ErrChecksumFailed, err)
}
// Verify checksum.
if !h.Matches(content) {
return ErrChecksumFailed
}
checksumsVerified++
}
}
// Fail when no checksums were verified.
if checksumsVerified == 0 {
return ErrChecksumMissing
}
return nil
}
func textSplit(data []byte, commentSign string) (content []byte, metaLines []string, err error) {
metaLinePrefix := commentSign + " " + TextKeyPrefix
contentBuf := bytes.NewBuffer(make([]byte, 0, len(data)))
metaLines = make([]string, 0, 1)
// Find jess metadata lines.
s := bufio.NewScanner(bytes.NewReader(data))
s.Split(scanRawLines)
for s.Scan() {
if strings.HasPrefix(s.Text(), metaLinePrefix) {
metaLines = append(metaLines, strings.TrimSpace(strings.TrimPrefix(s.Text(), commentSign)))
} else {
_, _ = contentBuf.Write(s.Bytes())
}
}
if s.Err() != nil {
return nil, nil, s.Err()
}
return bytes.TrimSpace(contentBuf.Bytes()), metaLines, nil
}
func detectLineEndFormat(data []byte) (lineEnd string) {
i := bytes.IndexByte(data, '\n')
switch i {
case -1:
// Default to just newline.
return "\n"
case 0:
// File start with a newline.
return "\n"
default:
// First newline is at second byte or later.
if bytes.Equal(data[i-1:i+1], []byte("\r\n")) {
return "\r\n"
}
return "\n"
}
}
func textAddMeta(data []byte, metaLines []string, commentSign string, position TextPlacement) ([]byte, error) {
// Prepare new buffer.
requiredSize := len(data)
for _, line := range metaLines {
requiredSize += len(line) + len(commentSign) + 3 // space + CRLF
}
contentBuf := bytes.NewBuffer(make([]byte, 0, requiredSize))
// Find line ending.
lineEnd := detectLineEndFormat(data)
// Find jess metadata lines.
if position == "" {
position = defaultMetaPlacement
}
switch position {
case TextPlacementTop:
textWriteMetaLines(metaLines, commentSign, lineEnd, contentBuf)
contentBuf.Write(data)
// Add final newline.
contentBuf.WriteString(lineEnd)
case TextPlacementBottom:
contentBuf.Write(data)
// Add to newlines when appending, as content is first whitespace-stripped.
contentBuf.WriteString(lineEnd)
contentBuf.WriteString(lineEnd)
textWriteMetaLines(metaLines, commentSign, lineEnd, contentBuf)
case TextPlacementAfterComment:
metaWritten := false
s := bufio.NewScanner(bytes.NewReader(data))
s.Split(scanRawLines)
for s.Scan() {
switch {
case metaWritten:
_, _ = contentBuf.Write(s.Bytes())
case strings.HasPrefix(s.Text(), commentSign):
_, _ = contentBuf.Write(s.Bytes())
default:
textWriteMetaLines(metaLines, commentSign, lineEnd, contentBuf)
metaWritten = true
_, _ = contentBuf.Write(s.Bytes())
}
}
if s.Err() != nil {
return nil, s.Err()
}
// If we have scanned through the file, and meta was not written, write it now.
if !metaWritten {
textWriteMetaLines(metaLines, commentSign, lineEnd, contentBuf)
}
// Add final newline.
contentBuf.WriteString(lineEnd)
}
return contentBuf.Bytes(), nil
}
func textWriteMetaLines(metaLines []string, commentSign string, lineEnd string, writer io.StringWriter) {
for _, line := range metaLines {
_, _ = writer.WriteString(commentSign)
_, _ = writer.WriteString(" ")
_, _ = writer.WriteString(line)
_, _ = writer.WriteString(lineEnd)
}
}
// scanRawLines is a split function for a Scanner that returns each line of
// text, including any trailing end-of-line marker. The returned line may
// be empty. The end-of-line marker is one optional carriage return followed
// by one mandatory newline. In regular expression notation, it is `\r?\n`.
// The last non-empty line of input will be returned even if it has no
// newline.
func scanRawLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
if atEOF && len(data) == 0 {
return 0, nil, nil
}
if i := bytes.IndexByte(data, '\n'); i >= 0 {
// We have a full newline-terminated line.
return i + 1, data[0 : i+1], nil
}
// If we're at EOF, we have a final, non-terminated line. Return it.
if atEOF {
return len(data), data, nil
}
// Request more data.
return 0, nil, nil
}

180
filesig/text_test.go Normal file
View file

@ -0,0 +1,180 @@
package filesig
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestTextChecksums(t *testing.T) {
t.Parallel()
// Base test text file.
text := `#!/bin/bash
# Initial
# Comment
# Block
do_something()`
// Test with checksum after comment.
textWithChecksumAfterComment := `#!/bin/bash
# Initial
# Comment
# Block
# jess-checksum: ZwngYUfUBeUn99HSdrNxkWSNjqrgZuSpVrexeEYttBso5o
do_something()
`
testTextWithChecksumAfterComment, err := AddTextFileChecksum([]byte(text), "#", TextPlacementAfterComment)
require.NoError(t, err, "should be able to add checksum")
assert.Equal(t, textWithChecksumAfterComment, string(testTextWithChecksumAfterComment), "should match")
require.NoError(t,
VerifyTextFileChecksum(testTextWithChecksumAfterComment, "#"),
"checksum should be correct",
)
require.NoError(t,
VerifyTextFileChecksum(append(
[]byte("\n\n \r\n"),
testTextWithChecksumAfterComment...,
), "#"),
"checksum should be correct",
)
require.NoError(t,
VerifyTextFileChecksum(append(
testTextWithChecksumAfterComment,
[]byte("\r\n \n \n")...,
), "#"),
"checksum should be correct",
)
// Test with checksum at top.
textWithChecksumAtTop := `# jess-checksum: ZwngYUfUBeUn99HSdrNxkWSNjqrgZuSpVrexeEYttBso5o
#!/bin/bash
# Initial
# Comment
# Block
do_something()
`
testTextWithChecksumAtTop, err := AddTextFileChecksum([]byte(text), "#", TextPlacementTop)
require.NoError(t, err, "should be able to add checksum")
assert.Equal(t, textWithChecksumAtTop, string(testTextWithChecksumAtTop), "should match")
require.NoError(t,
VerifyTextFileChecksum(testTextWithChecksumAtTop, "#"),
"checksum should be correct",
)
// Test with checksum at bottom.
textWithChecksumAtBottom := `#!/bin/bash
# Initial
# Comment
# Block
do_something()
# jess-checksum: ZwngYUfUBeUn99HSdrNxkWSNjqrgZuSpVrexeEYttBso5o
`
testTextWithChecksumAtBottom, err := AddTextFileChecksum([]byte(text), "#", TextPlacementBottom)
require.NoError(t, err, "should be able to add checksum")
assert.Equal(t, textWithChecksumAtBottom, string(testTextWithChecksumAtBottom), "should match")
require.NoError(t,
VerifyTextFileChecksum(testTextWithChecksumAtBottom, "#"),
"checksum should be correct",
)
// Test with multiple checksums.
textWithMultiChecksum := `# jess-checksum: PTNktssvYCYjZXLFL2QoBk7DYoSz1qF7DJd5XNvtptd41B
#!/bin/bash
# Initial
# Comment
# Block
# jess-checksum: Cy2TyVDjEStUqX3wCzCCKTfy228KaQK25ZDbHNmKiF8SPf
do_something()
# jess-checksum: YdgJFzuvFduk1MwRjZ2JkWQ6tCE1wkjn9xubSggKAdJSX5
`
assert.NoError(t,
VerifyTextFileChecksum([]byte(textWithMultiChecksum), "#"),
"checksum should be correct",
)
textWithMultiChecksumOutput := `#!/bin/bash
# Initial
# Comment
# Block
# jess-checksum: Cy2TyVDjEStUqX3wCzCCKTfy228KaQK25ZDbHNmKiF8SPf
# jess-checksum: PTNktssvYCYjZXLFL2QoBk7DYoSz1qF7DJd5XNvtptd41B
# jess-checksum: YdgJFzuvFduk1MwRjZ2JkWQ6tCE1wkjn9xubSggKAdJSX5
# jess-checksum: ZwngYUfUBeUn99HSdrNxkWSNjqrgZuSpVrexeEYttBso5o
do_something()
`
testTextWithMultiChecksumOutput, err := AddTextFileChecksum([]byte(textWithMultiChecksum), "#", TextPlacementAfterComment)
require.NoError(t, err, "should be able to add checksum")
assert.Equal(t, textWithMultiChecksumOutput, string(testTextWithMultiChecksumOutput), "should match")
// Test failing checksums.
textWithFailingChecksums := `#!/bin/bash
# Initial
# Comment
# Block
# jess-checksum: Cy2TyVDjEStUqX3wCzCCKTfy228KaQK25ZDbHNmKiF8SPf
# jess-checksum: PTNktssvYCYjZXLFL2QoBk7DYoSz1qF7DJd5XNvtptd41B
# jess-checksum: YdgJFzuvFduk1MwRjZ2JkWQ6tCE1wkjn9xubSggKAdJSX5
# jess-checksum: ZwngYUfUBeUn99HSdrNxkWSNjaaaaaaaaaaaaaaaaaaaaa
do_something()
`
require.Error(t, VerifyTextFileChecksum([]byte(textWithFailingChecksums), "#"), "should fail")
}
func TestLineEndDetection(t *testing.T) {
t.Parallel()
assert.Equal(t,
"\n",
detectLineEndFormat(nil),
"empty data should default to simple lf ending",
)
assert.Equal(t,
"\n",
detectLineEndFormat([]byte("\n")),
"shoud detect lf ending with empty first line",
)
assert.Equal(t,
"\r\n",
detectLineEndFormat([]byte("\r\n")),
"shoud detect crlf ending with empty first line",
)
assert.Equal(t,
"\n",
detectLineEndFormat([]byte("abc\n")),
"shoud detect lf ending with data on single line",
)
assert.Equal(t,
"\r\n",
detectLineEndFormat([]byte("abc\r\n")),
"shoud detect crlf ending with data on single line",
)
assert.Equal(t,
"\n",
detectLineEndFormat([]byte("abc\nabc\r\n")),
"shoud detect lf ending with data on first line",
)
assert.Equal(t,
"\r\n",
detectLineEndFormat([]byte("abc\r\nabc\n")),
"shoud detect crlf ending with data on first line",
)
}

11
filesig/text_yaml.go Normal file
View file

@ -0,0 +1,11 @@
package filesig
// AddYAMLChecksum adds a checksum to a yaml file.
func AddYAMLChecksum(data []byte, placement TextPlacement) ([]byte, error) {
return AddTextFileChecksum(data, "#", placement)
}
// VerifyYAMLChecksum checks a checksum in a yaml file.
func VerifyYAMLChecksum(data []byte) error {
return VerifyTextFileChecksum(data, "#")
}

53
go.mod
View file

@ -1,21 +1,50 @@
module github.com/safing/jess
go 1.15
go 1.21.1
toolchain go1.22.3
require (
github.com/AlecAivazis/survey/v2 v2.0.7
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/aead/ecdh v0.2.0
github.com/inconshreveable/mousetrap v1.0.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/mattn/go-colorable v0.1.4
github.com/mattn/go-isatty v0.0.11
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
github.com/mr-tron/base58 v1.2.0
github.com/safing/portbase v0.11.0
github.com/safing/structures v1.1.0
github.com/satori/go.uuid v1.2.0
github.com/spf13/cobra v1.1.3
github.com/spf13/pflag v1.0.5
github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.8.4
github.com/tevino/abool v1.2.0
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
github.com/tidwall/gjson v1.17.1
github.com/tidwall/pretty v1.2.1
github.com/tidwall/sjson v1.2.5
github.com/zalando/go-keyring v0.2.5
github.com/zeebo/blake3 v0.2.3
golang.org/x/crypto v0.24.0
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8
)
require (
github.com/alessio/shellescape v1.4.2 // indirect
github.com/danieljoos/wincred v1.2.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

497
go.sum
View file

@ -1,443 +1,138 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/AlecAivazis/survey/v2 v2.0.7 h1:+f825XHLse/hWd2tE/V5df04WFGimk34Eyg/z35w/rc=
github.com/AlecAivazis/survey/v2 v2.0.7/go.mod h1:mlizQTaPjnR4jcpwRSaSlkbsRfYFEyKgLQvYTzxxiHA=
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
github.com/VictoriaMetrics/metrics v1.15.2/go.mod h1:Z1tSfPfngDn12bTfZSCqArT3OPY3u88J12hSoOhuiRE=
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/aead/ecdh v0.2.0 h1:pYop54xVaq/CEREFEcukHRZfTdjiWvYIsZDXXrBapQQ=
github.com/aead/ecdh v0.2.0/go.mod h1:a9HHtXuSo8J1Js1MwLQx2mBhkXMT6YwUmVVEY4tTB8U=
github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6 h1:5L8Mj9Co9sJVgW3TpYk2gxGJnDjsYuboNTcRmbtGKGs=
github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6/go.mod h1:3HgLJ9d18kXMLQlJvIY3+FszZYMxCz8WfE2MQ7hDY0w=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0=
github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs=
github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE=
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgraph-io/ristretto v0.0.3/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ=
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/safing/portbase v0.4.1 h1:N/R1YBvyXMrjOCLPm9U5i+9VQLTPYcX61NXluxP1f04=
github.com/safing/portbase v0.4.1/go.mod h1:ZuWxXI5bgNC1/2vLEIxWajgGz8RsP5N4wNRwO+OCJiA=
github.com/safing/portbase v0.11.0 h1:POouPUkS6TOFENmWEl5FZBhqGj9CIpW9GaXRQJLbac0=
github.com/safing/portbase v0.11.0/go.mod h1:Xb0htNFFEb6BlkloWyVbQoUguUMGrTVmEDwwpAAMS5g=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/safing/structures v1.1.0 h1:QzHBQBjaZSLzw2f6PM4ibSmPcfBHAOB5CKJ+k4FYkhQ=
github.com/safing/structures v1.1.0/go.mod h1:QUrB74FcU41ahQ5oy3YNFCoSq+twE/n3+vNZc2K35II=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/seehuhn/fortuna v1.0.1 h1:lu9+CHsmR0bZnx5Ay646XvCSRJ8PJTi5UYJwDBX68H0=
github.com/seehuhn/fortuna v1.0.1/go.mod h1:LX8ubejCnUoT/hX+1aKUtbKls2H6DRkqzkc7TdR3iis=
github.com/seehuhn/sha256d v1.0.0 h1:TXTsAuEWr02QjRm153Fnvvb6fXXDo7Bmy1FizxarGYw=
github.com/seehuhn/sha256d v1.0.0/go.mod h1:PEuxg9faClSveVuFXacQmi+NtDI/PX8bpKjtNzf2+s4=
github.com/shirou/gopsutil v3.21.2+incompatible h1:U+YvJfjCh6MslYlIAXvPtzhW3YZEtc9uncueUNpD/0A=
github.com/shirou/gopsutil v3.21.2+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
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.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5 h1:hNna6Fi0eP1f2sMBe/rJicDmaHmoXGe1Ta84FPYHLuE=
github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5/go.mod h1:f1SCnEOt6sc3fOJfPQDRDzHOtSXuTtnz0ImG9kPRDV0=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
github.com/tidwall/gjson v1.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w=
github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8=
github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/sjson v1.1.5 h1:wsUceI/XDyZk3J1FUvuuYlK62zJv2HO2Pzb8A5EWdUE=
github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE=
github.com/tklauser/go-sysconf v0.3.4/go.mod h1:Cl2c8ZRWfHD5IrfHo9VN+FX9kCFjIOyVklgXycLB6ek=
github.com/tklauser/numcpus v0.2.1/go.mod h1:9aU+wOc6WjUIZEwWMP62PL/41d65P+iks1gBkr4QyP8=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
github.com/valyala/histogram v1.1.2/go.mod h1:CZAr6gK9dbD7hYx2s8WSPh0p5x5wETjC+2b3PJVtEdg=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zalando/go-keyring v0.2.5 h1:Bc2HHpjALryKD62ppdEzaFG6VxL6Bc+5v0LYpN8Lba8=
github.com/zalando/go-keyring v0.2.5/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk=
github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY=
github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg=
github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ=
github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo=
github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708 h1:pXVtWnwHkrWD9ru3sDxY/qFK/bfc0egRovX91EjWjf4=
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
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/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056 h1:dHtDnRWQtSx0Hjq9kvKFpBh9uPPKfQN70NZZmvssGwk=
golang.org/x/sys v0.0.0-20191113165036-4c7a9d0fe056/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/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-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/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-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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/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.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -3,9 +3,11 @@ package hashtools
import (
"crypto"
// register BLAKE2 in Go's internal registry
// Register BLAKE2 in Go's internal registry.
_ "golang.org/x/crypto/blake2b"
_ "golang.org/x/crypto/blake2s"
"github.com/safing/jess/lhash"
)
func init() {
@ -16,31 +18,39 @@ func init() {
Register(blake2bBase.With(&HashTool{
Name: "BLAKE2s-256",
Hash: crypto.BLAKE2s_256,
NewHash: crypto.BLAKE2s_256.New,
CryptoHashID: crypto.BLAKE2b_256,
DigestSize: crypto.BLAKE2s_256.Size(),
BlockSize: crypto.BLAKE2s_256.New().BlockSize(),
SecurityLevel: 128,
Comment: "RFC 7693, successor of SHA3 finalist, optimized for 8-32 bit software",
labeledAlg: lhash.BLAKE2s_256,
}))
Register(blake2bBase.With(&HashTool{
Name: "BLAKE2b-256",
Hash: crypto.BLAKE2b_256,
NewHash: crypto.BLAKE2b_256.New,
CryptoHashID: crypto.BLAKE2b_256,
DigestSize: crypto.BLAKE2b_256.Size(),
BlockSize: crypto.BLAKE2b_256.New().BlockSize(),
SecurityLevel: 128,
labeledAlg: lhash.BLAKE2b_256,
}))
Register(blake2bBase.With(&HashTool{
Name: "BLAKE2b-384",
Hash: crypto.BLAKE2b_384,
NewHash: crypto.BLAKE2b_384.New,
CryptoHashID: crypto.BLAKE2b_384,
DigestSize: crypto.BLAKE2b_384.Size(),
BlockSize: crypto.BLAKE2b_384.New().BlockSize(),
SecurityLevel: 192,
labeledAlg: lhash.BLAKE2b_384,
}))
Register(blake2bBase.With(&HashTool{
Name: "BLAKE2b-512",
Hash: crypto.BLAKE2b_512,
NewHash: crypto.BLAKE2b_512.New,
CryptoHashID: crypto.BLAKE2b_512,
DigestSize: crypto.BLAKE2b_512.Size(),
BlockSize: crypto.BLAKE2b_512.New().BlockSize(),
SecurityLevel: 256,
labeledAlg: lhash.BLAKE2b_512,
}))
}

26
hashtools/blake3.go Normal file
View file

@ -0,0 +1,26 @@
package hashtools
import (
"hash"
"github.com/zeebo/blake3"
"github.com/safing/jess/lhash"
)
func init() {
Register(&HashTool{
Name: "BLAKE3",
NewHash: newBlake3,
DigestSize: newBlake3().Size(),
BlockSize: newBlake3().BlockSize(),
SecurityLevel: 128,
Comment: "cryptographic hash function based on Bao and BLAKE2",
Author: "Jean-Philippe Aumasson et al., 2020",
labeledAlg: lhash.BLAKE3,
})
}
func newBlake3() hash.Hash {
return blake3.New()
}

View file

@ -3,12 +3,16 @@ package hashtools
import (
"crypto"
"hash"
"github.com/safing/jess/lhash"
)
// HashTool holds generic information about a hash tool.
type HashTool struct {
Name string
Hash crypto.Hash
NewHash func() hash.Hash
CryptoHashID crypto.Hash
DigestSize int // in bytes
BlockSize int // in bytes
@ -16,11 +20,13 @@ type HashTool struct {
Comment string
Author string
labeledAlg lhash.Algorithm
}
// New returns a new hash.Hash instance of the hash tool.
func (ht *HashTool) New() hash.Hash {
return ht.Hash.New()
return ht.NewHash()
}
// With uses the original HashTool as a template for a new HashTool and returns the new HashTool.
@ -28,8 +34,11 @@ func (ht *HashTool) With(changes *HashTool) *HashTool {
if changes.Name == "" {
changes.Name = ht.Name
}
if changes.Hash == 0 {
changes.Hash = ht.Hash
if changes.NewHash == nil {
changes.NewHash = ht.NewHash
}
if changes.CryptoHashID == 0 {
changes.CryptoHashID = ht.CryptoHashID
}
if changes.DigestSize == 0 {
changes.DigestSize = ht.DigestSize
@ -46,6 +55,14 @@ func (ht *HashTool) With(changes *HashTool) *HashTool {
if changes.Author == "" {
changes.Author = ht.Author
}
if changes.labeledAlg == 0 {
changes.labeledAlg = ht.labeledAlg
}
return changes
}
// LabeledHasher returns the corresponding labeled hashing algorithm.
func (ht *HashTool) LabeledHasher() lhash.Algorithm {
return ht.labeledAlg
}

View file

@ -2,13 +2,14 @@ package hashtools
import (
"crypto"
// register SHA2 in Go's internal registry
// Register SHA2 in Go's internal registry.
_ "crypto/sha256"
_ "crypto/sha512"
// register SHA3 in Go's internal registry
// Register SHA3 in Go's internal registry.
_ "golang.org/x/crypto/sha3"
"github.com/safing/jess/lhash"
)
func init() {
@ -19,46 +20,58 @@ func init() {
}
Register(sha2Base.With(&HashTool{
Name: "SHA2-224",
Hash: crypto.SHA224,
NewHash: crypto.SHA224.New,
CryptoHashID: crypto.SHA224,
DigestSize: crypto.SHA224.Size(),
BlockSize: crypto.SHA224.New().BlockSize(),
SecurityLevel: 112,
Author: "NSA, 2004",
labeledAlg: lhash.SHA2_224,
}))
Register(sha2Base.With(&HashTool{
Name: "SHA2-256",
Hash: crypto.SHA256,
NewHash: crypto.SHA256.New,
CryptoHashID: crypto.SHA256,
DigestSize: crypto.SHA256.Size(),
BlockSize: crypto.SHA256.New().BlockSize(),
SecurityLevel: 128,
labeledAlg: lhash.SHA2_256,
}))
Register(sha2Base.With(&HashTool{
Name: "SHA2-384",
Hash: crypto.SHA384,
NewHash: crypto.SHA384.New,
CryptoHashID: crypto.SHA384,
DigestSize: crypto.SHA384.Size(),
BlockSize: crypto.SHA384.New().BlockSize(),
SecurityLevel: 192,
labeledAlg: lhash.SHA2_384,
}))
Register(sha2Base.With(&HashTool{
Name: "SHA2-512",
Hash: crypto.SHA512,
NewHash: crypto.SHA512.New,
CryptoHashID: crypto.SHA512,
DigestSize: crypto.SHA512.Size(),
BlockSize: crypto.SHA512.New().BlockSize(),
SecurityLevel: 256,
labeledAlg: lhash.SHA2_512,
}))
Register(sha2Base.With(&HashTool{
Name: "SHA2-512-224",
Hash: crypto.SHA512_224,
NewHash: crypto.SHA512_224.New,
CryptoHashID: crypto.SHA512_224,
DigestSize: crypto.SHA512_224.Size(),
BlockSize: crypto.SHA512_224.New().BlockSize(),
SecurityLevel: 112,
labeledAlg: lhash.SHA2_512_224,
}))
Register(sha2Base.With(&HashTool{
Name: "SHA2-512-256",
Hash: crypto.SHA512_256,
NewHash: crypto.SHA512_256.New,
CryptoHashID: crypto.SHA512_256,
DigestSize: crypto.SHA512_256.Size(),
BlockSize: crypto.SHA512_256.New().BlockSize(),
SecurityLevel: 128,
labeledAlg: lhash.SHA2_512_256,
}))
// SHA3
@ -68,30 +81,38 @@ func init() {
}
Register(sha3Base.With(&HashTool{
Name: "SHA3-224",
Hash: crypto.SHA3_224,
NewHash: crypto.SHA3_224.New,
CryptoHashID: crypto.SHA3_224,
DigestSize: crypto.SHA3_224.Size(),
BlockSize: crypto.SHA3_224.New().BlockSize(),
SecurityLevel: 112,
labeledAlg: lhash.SHA3_224,
}))
Register(sha3Base.With(&HashTool{
Name: "SHA3-256",
Hash: crypto.SHA3_256,
NewHash: crypto.SHA3_256.New,
CryptoHashID: crypto.SHA3_256,
DigestSize: crypto.SHA3_256.Size(),
BlockSize: crypto.SHA3_256.New().BlockSize(),
SecurityLevel: 128,
labeledAlg: lhash.SHA3_256,
}))
Register(sha3Base.With(&HashTool{
Name: "SHA3-384",
Hash: crypto.SHA3_384,
NewHash: crypto.SHA3_384.New,
CryptoHashID: crypto.SHA3_384,
DigestSize: crypto.SHA3_384.Size(),
BlockSize: crypto.SHA3_384.New().BlockSize(),
SecurityLevel: 192,
labeledAlg: lhash.SHA3_384,
}))
Register(sha3Base.With(&HashTool{
Name: "SHA3-512",
Hash: crypto.SHA3_512,
NewHash: crypto.SHA3_512.New,
CryptoHashID: crypto.SHA3_512,
DigestSize: crypto.SHA3_512.Size(),
BlockSize: crypto.SHA3_512.New().BlockSize(),
SecurityLevel: 256,
labeledAlg: lhash.SHA3_512,
}))
}

View file

@ -31,7 +31,7 @@ func Get(name string) (*HashTool, error) {
return hashTool, nil
}
// New returns a new hash.Hash with the given Name
// New returns a new hash.Hash with the given name.
func New(name string) (hash.Hash, error) {
hashTool, err := Get(name)
if err != nil {

View file

@ -1,15 +1,18 @@
package hashtools
import "testing"
import (
"encoding/hex"
"testing"
)
func TestAll(t *testing.T) {
t.Parallel()
testData := []byte("The quick brown fox jumps over the lazy dog. ")
testData := []byte("The quick brown fox jumps over the lazy dog.")
all := AsList()
for _, hashTool := range all {
// take detour in getting hash.Hash for testing
// Test hash usage.
hash, err := New(hashTool.Name)
if err != nil {
t.Fatalf("failed to get HashTool %s", hashTool.Name)
@ -29,5 +32,97 @@ func TestAll(t *testing.T) {
t.Errorf("hashTool %s is broken or reports invalid digest size. Expected %d, got %d.", hashTool.Name, hashTool.DigestSize, len(sum))
}
// Check hash outputs.
expectedOutputs, ok := testOutputs[hashTool.Name]
if !ok {
t.Errorf("no test outputs available for %s", hashTool.Name)
continue
}
// Test empty string.
hash.Reset()
_, _ = hash.Write(testInputEmpty)
hexSum := hex.EncodeToString(hash.Sum(nil))
if hexSum != expectedOutputs[0] {
t.Errorf("hash tool %s: test empty: digest mismatch, expected %+v, got %+v",
hashTool.Name, expectedOutputs[0], hexSum)
}
// Test fox string.
hash.Reset()
_, _ = hash.Write(testInputFox)
hexSum = hex.EncodeToString(hash.Sum(nil))
if hexSum != expectedOutputs[1] {
t.Errorf("hash tool %s: test empty: digest mismatch, expected %+v, got %+v",
hashTool.Name, expectedOutputs[1], hexSum)
}
}
}
var (
testInputEmpty = []byte("")
testInputFox = []byte("The quick brown fox jumps over the lazy dog.")
)
var testOutputs = map[string][2]string{
"SHA2-224": {
"d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f",
"619cba8e8e05826e9b8c519c0a5c68f4fb653e8a3d8aa04bb2c8cd4c",
},
"SHA2-256": {
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c",
},
"SHA2-384": {
"38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b",
"ed892481d8272ca6df370bf706e4d7bc1b5739fa2177aae6c50e946678718fc67a7af2819a021c2fc34e91bdb63409d7",
},
"SHA2-512": {
"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e",
"91ea1245f20d46ae9a037a989f54f1f790f0a47607eeb8a14d12890cea77a1bbc6c7ed9cf205e67b7f2b8fd4c7dfd3a7a8617e45f3c463d481c7e586c39ac1ed",
},
"SHA2-512-224": {
"6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4",
"6d6a9279495ec4061769752e7ff9c68b6b0b3c5a281b7917ce0572de",
},
"SHA2-512-256": {
"c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a",
"1546741840f8a492b959d9b8b2344b9b0eb51b004bba35c0aebaac86d45264c3",
},
"SHA3-224": {
"6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7",
"2d0708903833afabdd232a20201176e8b58c5be8a6fe74265ac54db0",
},
"SHA3-256": {
"a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a",
"a80f839cd4f83f6c3dafc87feae470045e4eb0d366397d5c6ce34ba1739f734d",
},
"SHA3-384": {
"0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004",
"1a34d81695b622df178bc74df7124fe12fac0f64ba5250b78b99c1273d4b080168e10652894ecad5f1f4d5b965437fb9",
},
"SHA3-512": {
"a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26",
"18f4f4bd419603f95538837003d9d254c26c23765565162247483f65c50303597bc9ce4d289f21d1c2f1f458828e33dc442100331b35e7eb031b5d38ba6460f8",
},
"BLAKE2s-256": {
"69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9",
"95bca6e1b761dca1323505cc629949a0e03edf11633cc7935bd8b56f393afcf2",
},
"BLAKE2b-256": {
"0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8",
"69d7d3b0afba81826d27024c17f7f183659ed0812cf27b382eaef9fdc29b5712",
},
"BLAKE2b-384": {
"b32811423377f52d7862286ee1a72ee540524380fda1724a6f25d7978c6fd3244a6caf0498812673c5e05ef583825100",
"16d65de1a3caf1c26247234c39af636284c7e19ca448c0de788272081410778852c94d9cef6b939968d4f872c7f78337",
},
"BLAKE2b-512": {
"786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce",
"87af9dc4afe5651b7aa89124b905fd214bf17c79af58610db86a0fb1e0194622a4e9d8e395b352223a8183b0d421c0994b98286cbf8c68a495902e0fe6e2bda2",
},
"BLAKE3": {
"af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262",
"4c9bd68d7f0baa2e167cef98295eb1ec99a3ec8f0656b33dbae943b387f31d5d",
},
}

195
import_export.go Normal file
View file

@ -0,0 +1,195 @@
package jess
import (
"errors"
"fmt"
"regexp"
"strings"
)
// Keywords and Prefixes for the export text format.
const (
ExportSenderKeyword = "sender"
ExportSenderPrefix = "sender:"
ExportRecipientKeyword = "recipient"
ExportRecipientPrefix = "recipient:"
ExportKeyKeyword = "secret"
ExportKeyPrefix = "secret:"
ExportEnvelopeKeyword = "envelope"
ExportEnvelopePrefix = "envelope:"
)
// Export exports the public part of a signet in text format.
func (signet *Signet) Export(short bool) (textFormat string, err error) {
// Make public if needed.
if !signet.Public {
signet, err = signet.AsRecipient()
if err != nil {
return "", err
}
}
// Transform to text format.
return signet.toTextFormat(short)
}
// Backup exports the private part of a signet in text format.
func (signet *Signet) Backup(short bool) (textFormat string, err error) {
// Abprt if public.
if signet.Public {
return "", errors.New("cannot backup (only export) a recipient")
}
// Transform to text format.
return signet.toTextFormat(short)
}
func (signet *Signet) toTextFormat(short bool) (textFormat string, err error) {
// Serialize to base58.
base58data, err := signet.ToBase58()
if err != nil {
return "", err
}
// Define keywords.
var keyword, typeComment string
switch {
case signet.Scheme == SignetSchemePassword:
return "", errors.New("cannot backup or export passwords")
case signet.Scheme == SignetSchemeKey:
// Check if the signet is marked as "public".
if signet.Public {
return "", errors.New("cannot export keys")
}
keyword = ExportKeyKeyword
typeComment = "symmetric-key"
case signet.Public:
keyword = ExportRecipientKeyword
typeComment = fmt.Sprintf(
"public-%s-key", toTextFormatString(signet.Scheme),
)
default:
keyword = ExportSenderKeyword
typeComment = fmt.Sprintf(
"private-%s-key", toTextFormatString(signet.Scheme),
)
}
// Transform to text format.
if short {
return fmt.Sprintf(
"%s:%s",
keyword,
base58data,
), nil
}
return fmt.Sprintf(
"%s:%s:%s:%s",
keyword,
typeComment,
toTextFormatString(signet.Info.Name),
base58data,
), nil
}
// Export exports the envelope in text format.
func (e *Envelope) Export(short bool) (textFormat string, err error) {
// Remove and key data.
e.CleanSignets()
// Serialize to base58.
base58data, err := e.ToBase58()
if err != nil {
return "", err
}
// Transform to text format.
if short {
return fmt.Sprintf(
"%s:%s",
ExportEnvelopeKeyword,
base58data,
), nil
}
return fmt.Sprintf(
"%s:%s:%s:%s",
ExportEnvelopeKeyword,
e.SuiteID,
e.Name,
base58data,
), nil
}
// KeyFromTextFormat loads a secret key from the text format.
func KeyFromTextFormat(textFormat string) (*Signet, error) {
// Check the identifier.
if !strings.HasPrefix(textFormat, ExportKeyPrefix) {
return nil, errors.New("not a secret")
}
// Parse the data section.
splitted := strings.Split(textFormat, ":")
if len(splitted) < 2 {
return nil, errors.New("invalid format")
}
return SignetFromBase58(splitted[len(splitted)-1])
}
// SenderFromTextFormat loads a sender (private key) from the text format.
func SenderFromTextFormat(textFormat string) (*Signet, error) {
// Check the identifier.
if !strings.HasPrefix(textFormat, ExportSenderPrefix) {
return nil, errors.New("not a sender")
}
// Parse the data section.
splitted := strings.Split(textFormat, ":")
if len(splitted) < 2 {
return nil, errors.New("invalid format")
}
return SignetFromBase58(splitted[len(splitted)-1])
}
// RecipientFromTextFormat loads a recipient (public key) from the text format.
func RecipientFromTextFormat(textFormat string) (*Signet, error) {
// Check the identifier.
if !strings.HasPrefix(textFormat, ExportRecipientPrefix) {
return nil, errors.New("not a recipient")
}
// Parse the data section.
splitted := strings.Split(textFormat, ":")
if len(splitted) < 2 {
return nil, errors.New("invalid format")
}
return SignetFromBase58(splitted[len(splitted)-1])
}
// EnvelopeFromTextFormat loads an envelope from the text format.
func EnvelopeFromTextFormat(textFormat string) (*Envelope, error) {
// Check the identifier.
if !strings.HasPrefix(textFormat, ExportEnvelopePrefix) {
return nil, errors.New("not an envelope")
}
// Parse the data section.
splitted := strings.Split(textFormat, ":")
if len(splitted) < 2 {
return nil, errors.New("invalid format")
}
return EnvelopeFromBase58(splitted[len(splitted)-1])
}
var replaceForTextFormatMatcher = regexp.MustCompile(`[^A-Za-z0-9]+`)
// toTextFormatString makes a string compatible with the text format.
func toTextFormatString(s string) string {
return strings.ToLower(
strings.Trim(
replaceForTextFormatMatcher.ReplaceAllString(s, "-"), "-",
),
)
}

View file

@ -3,9 +3,8 @@ package jess
import (
"errors"
"github.com/safing/portbase/formats/dsd"
"github.com/safing/portbase/container"
"github.com/safing/structures/container"
"github.com/safing/structures/dsd"
)
/*
@ -16,10 +15,8 @@ import (
- Data: byte block
*/
var (
// ErrIncompatibleFileFormatVersion is returned when an incompatible wire format is encountered.
ErrIncompatibleFileFormatVersion = errors.New("incompatible file format version")
)
// ErrIncompatibleFileFormatVersion is returned when an incompatible wire format is encountered.
var ErrIncompatibleFileFormatVersion = errors.New("incompatible file format version")
// ToFileFormat serializes the letter for storing it as a file.
func (letter *Letter) ToFileFormat() (*container.Container, error) {

View file

@ -3,7 +3,7 @@ package jess
import (
"errors"
"github.com/safing/portbase/container"
"github.com/safing/structures/container"
)
/*
@ -24,10 +24,8 @@ import (
- MAC: byte block
*/
var (
// ErrIncompatibleWireFormatVersion is returned when an incompatible wire format is encountered.
ErrIncompatibleWireFormatVersion = errors.New("incompatible wire format version")
)
// ErrIncompatibleWireFormatVersion is returned when an incompatible wire format is encountered.
var ErrIncompatibleWireFormatVersion = errors.New("incompatible wire format version")
// ToWire serializes to letter for sending it over a network connection.
func (letter *Letter) ToWire() (*container.Container, error) {
@ -87,7 +85,8 @@ func (letter *Letter) ToWire() (*container.Container, error) {
}
// LetterFromWireData is a relay to LetterFromWire to quickly fix import issues of godep.
// DEPRECATED
//
// Deprecated: Please use LetterFromWire with a fresh container directly.
func LetterFromWireData(data []byte) (*Letter, error) {
return LetterFromWire(container.New(data))
}

View file

@ -10,9 +10,8 @@ import (
"encoding/json"
"fmt"
"github.com/safing/portbase/container"
"github.com/safing/portbase/formats/dsd"
"github.com/safing/structures/container"
"github.com/safing/structures/dsd"
)
// Letter is the data format for encrypted data at rest or in transit.
@ -150,7 +149,7 @@ func (letter *Letter) ToJSON() ([]byte, error) {
return json.Marshal(letter)
}
// LetterFromJSON loads a json-serialized letter
// LetterFromJSON loads a json-serialized letter.
func LetterFromJSON(data []byte) (*Letter, error) {
letter := &Letter{}
@ -186,7 +185,7 @@ func LetterFromDSD(data []byte) (*Letter, error) {
const (
// Field IDs for signing
// These IDs MUST NOT CHANGE
// These IDs MUST NOT CHANGE.
fieldIDLetterVersion uint64 = 1 // signed, MAC'd (may not exist when wired)
fieldIDLetterSuiteID uint64 = 2 // signed, MAC'd (may not exist when wired)

View file

@ -9,6 +9,8 @@ import (
)
func TestSerialization(t *testing.T) {
t.Parallel()
subject := &Letter{
Version: 1,
SuiteID: SuiteComplete,
@ -36,6 +38,8 @@ func TestSerialization(t *testing.T) {
}
func testSerialize(t *testing.T, letter *Letter, wireFormat bool) { //nolint:unparam
t.Helper()
// File Format
fileData, err := letter.ToFileFormat()
@ -85,10 +89,9 @@ func (letter *Letter) CheckEqual(other *Letter) error {
letterValue := reflect.ValueOf(*letter)
otherValue := reflect.ValueOf(*other)
var ok bool
numElements := letterValue.NumField()
for i := 0; i < numElements; i++ {
ok := false
name := letterValue.Type().Field(i).Name
switch name {
case "Data": // TODO: this required special handling in the past, leave it here for now.

View file

@ -1,19 +1,25 @@
// Package lhash provides integrated labeled hashes.
//
//nolint:gci
package lhash
import (
"crypto"
"hash"
"io"
// register SHA2 in Go's internal registry
// Register SHA2 in Go's internal registry.
_ "crypto/sha256"
_ "crypto/sha512"
// register SHA3 in Go's internal registry
// Register SHA3 in Go's internal registry.
_ "golang.org/x/crypto/sha3"
// register BLAKE2 in Go's internal registry
// Register BLAKE2 in Go's internal registry.
_ "golang.org/x/crypto/blake2b"
_ "golang.org/x/crypto/blake2s"
"github.com/zeebo/blake3"
)
// Algorithm is an identifier for a hash function.
@ -37,6 +43,8 @@ const (
BLAKE2b_256 Algorithm = 25
BLAKE2b_384 Algorithm = 26
BLAKE2b_512 Algorithm = 27
BLAKE3 Algorithm = 32
)
func (a Algorithm) new() hash.Hash {
@ -66,7 +74,7 @@ func (a Algorithm) new() hash.Hash {
case SHA3_512:
return crypto.SHA3_512.New()
// BLAKE2
// BLAKE2
case BLAKE2s_256:
return crypto.BLAKE2s_256.New()
case BLAKE2b_256:
@ -76,7 +84,77 @@ func (a Algorithm) new() hash.Hash {
case BLAKE2b_512:
return crypto.BLAKE2b_512.New()
// BLAKE3
case BLAKE3:
return blake3.New()
default:
return nil
}
}
func (a Algorithm) String() string {
switch a {
// SHA2
case SHA2_224:
return "SHA2_224"
case SHA2_256:
return "SHA2_256"
case SHA2_384:
return "SHA2_384"
case SHA2_512:
return "SHA2_512"
case SHA2_512_224:
return "SHA2_512_224"
case SHA2_512_256:
return "SHA2_512_256"
// SHA3
case SHA3_224:
return "SHA3_224"
case SHA3_256:
return "SHA3_256"
case SHA3_384:
return "SHA3_384"
case SHA3_512:
return "SHA3_512"
// BLAKE2
case BLAKE2s_256:
return "BLAKE2s_256"
case BLAKE2b_256:
return "BLAKE2b_256"
case BLAKE2b_384:
return "BLAKE2b_384"
case BLAKE2b_512:
return "BLAKE2b_512"
// BLAKE3
case BLAKE3:
return "BLAKE3"
default:
return "unknown"
}
}
// RawHasher returns a new raw hasher of the algorithm.
func (a Algorithm) RawHasher() hash.Hash {
return a.new()
}
// Digest creates a new labeled hash and digests the given data.
func (a Algorithm) Digest(data []byte) *LabeledHash {
return Digest(a, data)
}
// DigestFile creates a new labeled hash and digests the given file.
func (a Algorithm) DigestFile(pathToFile string) (*LabeledHash, error) {
return DigestFile(a, pathToFile)
}
// DigestFromReader creates a new labeled hash and digests from the given reader.
func (a Algorithm) DigestFromReader(reader io.Reader) (*LabeledHash, error) {
return DigestFromReader(a, reader)
}

View file

@ -1,15 +1,18 @@
package lhash
import (
"bufio"
"crypto/subtle"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"io"
"os"
"github.com/mr-tron/base58"
"github.com/safing/portbase/container"
"github.com/safing/structures/container"
)
// LabeledHash represents a typed hash value.
@ -21,8 +24,8 @@ type LabeledHash struct {
// Digest creates a new labeled hash and digests the given data.
func Digest(alg Algorithm, data []byte) *LabeledHash {
hasher := alg.new()
_, _ = hasher.Write(data) // never returns an error
defer hasher.Reset() // internal state may leak data if kept in memory
_, _ = hasher.Write(data) // Never returns an error.
defer hasher.Reset() // Internal state may leak data if kept in memory.
return &LabeledHash{
alg: alg,
@ -30,18 +33,46 @@ func Digest(alg Algorithm, data []byte) *LabeledHash {
}
}
// DigestFile creates a new labeled hash and digests the given file.
func DigestFile(alg Algorithm, pathToFile string) (*LabeledHash, error) {
// Open file that should be hashed.
file, err := os.Open(pathToFile)
if err != nil {
return nil, fmt.Errorf("failed to open file: %w", err)
}
return DigestFromReader(alg, file)
}
// DigestFromReader creates a new labeled hash and digests from the given reader.
func DigestFromReader(alg Algorithm, reader io.Reader) (*LabeledHash, error) {
hasher := alg.new()
defer hasher.Reset() // Internal state may leak data if kept in memory.
// Pipe all data directly to the hashing algorithm.
_, err := bufio.NewReader(reader).WriteTo(hasher)
if err != nil {
return nil, fmt.Errorf("failed to read: %w", err)
}
return &LabeledHash{
alg: alg,
digest: hasher.Sum(nil),
}, nil
}
// Load loads a labeled hash from the given []byte slice.
func Load(labeledHash []byte) (*LabeledHash, error) {
c := container.New(labeledHash)
algID, err := c.GetNextN64()
if err != nil {
return nil, fmt.Errorf("failed to parse algorithm ID: %s", err)
return nil, fmt.Errorf("failed to parse algorithm ID: %w", err)
}
digest, err := c.GetNextBlock()
if err != nil {
return nil, fmt.Errorf("failed to parse digest: %s", err)
return nil, fmt.Errorf("failed to parse digest: %w", err)
}
if c.Length() > 0 {
@ -67,7 +98,7 @@ func Load(labeledHash []byte) (*LabeledHash, error) {
func FromHex(hexEncoded string) (*LabeledHash, error) {
raw, err := hex.DecodeString(hexEncoded)
if err != nil {
return nil, fmt.Errorf("failed to decode hex: %s", err)
return nil, fmt.Errorf("failed to decode hex: %w", err)
}
return Load(raw)
@ -78,7 +109,7 @@ func FromHex(hexEncoded string) (*LabeledHash, error) {
func FromBase64(base64Encoded string) (*LabeledHash, error) {
raw, err := base64.RawURLEncoding.DecodeString(base64Encoded)
if err != nil {
return nil, fmt.Errorf("failed to decode base64: %s", err)
return nil, fmt.Errorf("failed to decode base64: %w", err)
}
return Load(raw)
@ -89,12 +120,22 @@ func FromBase64(base64Encoded string) (*LabeledHash, error) {
func FromBase58(base58Encoded string) (*LabeledHash, error) {
raw, err := base58.Decode(base58Encoded)
if err != nil {
return nil, fmt.Errorf("failed to decode base58: %s", err)
return nil, fmt.Errorf("failed to decode base58: %w", err)
}
return Load(raw)
}
// Algorithm returns the algorithm of the labeled hash.
func (lh *LabeledHash) Algorithm() Algorithm {
return lh.alg
}
// Sum returns the raw calculated hash digest.
func (lh *LabeledHash) Sum() []byte {
return lh.digest
}
// Bytes return the []byte representation of the labeled hash.
func (lh *LabeledHash) Bytes() []byte {
c := container.New()
@ -127,16 +168,45 @@ func (lh *LabeledHash) Equal(other *LabeledHash) bool {
subtle.ConstantTimeCompare(lh.digest, other.digest) == 1
}
// MatchesString returns true if the digest of the given string matches the hash.
func (lh *LabeledHash) MatchesString(s string) bool {
return lh.MatchesData([]byte(s))
// EqualRaw returns true if the given raw hash digest is equal.
// Equality is checked by comparing both the digest value only.
// The caller must make sure the same algorithm is used.
func (lh *LabeledHash) EqualRaw(otherDigest []byte) bool {
return subtle.ConstantTimeCompare(lh.digest, otherDigest) == 1
}
// Matches returns true if the digest of the given data matches the hash.
func (lh *LabeledHash) Matches(data []byte) bool {
return lh.Equal(Digest(lh.alg, data))
}
// MatchesData returns true if the digest of the given data matches the hash.
// Deprecated: Use Matches instead.
func (lh *LabeledHash) MatchesData(data []byte) bool {
hasher := lh.alg.new()
_, _ = hasher.Write(data) // never returns an error
defer hasher.Reset() // internal state may leak data if kept in memory
return subtle.ConstantTimeCompare(lh.digest, hasher.Sum(nil)) == 1
return lh.Equal(Digest(lh.alg, data))
}
// MatchesString returns true if the digest of the given string matches the hash.
func (lh *LabeledHash) MatchesString(s string) bool {
return lh.Matches([]byte(s))
}
// MatchesFile returns true if the digest of the given file matches the hash.
func (lh *LabeledHash) MatchesFile(pathToFile string) (bool, error) {
fileHash, err := DigestFile(lh.alg, pathToFile)
if err != nil {
return false, err
}
return lh.Equal(fileHash), nil
}
// MatchesReader returns true if the digest of the given reader matches the hash.
func (lh *LabeledHash) MatchesReader(reader io.Reader) (bool, error) {
readerHash, err := DigestFromReader(lh.alg, reader)
if err != nil {
return false, err
}
return lh.Equal(readerHash), nil
}

View file

@ -15,6 +15,7 @@ var (
)
func testAlgorithm(t *testing.T, alg Algorithm, emptyHex, foxHex string) {
t.Helper()
// setup
emptyBytes, err := hex.DecodeString(emptyHex)
@ -31,27 +32,29 @@ func testAlgorithm(t *testing.T, alg Algorithm, emptyHex, foxHex string) {
// test empty
lh := Digest(alg, testEmpty)
if !bytes.Equal(lh.Bytes()[2:], emptyBytes) {
t.Errorf("alg %d: test empty: digest mismatch, expected %+v, got %+v", alg, emptyBytes, lh.Bytes()[2:])
t.Errorf("alg %s: test empty: digest mismatch, expected %+v, got %+v",
alg, hex.EncodeToString(emptyBytes), hex.EncodeToString(lh.Bytes()[2:]))
}
// test fox
lh = Digest(alg, testFoxData)
if !bytes.Equal(lh.Bytes()[2:], foxBytes) {
t.Errorf("alg %d: test fox: digest mismatch, expected %+v, got %+v", alg, foxBytes, lh.Bytes()[2:])
t.Errorf("alg %s: test fox: digest mismatch, expected %+v, got %+v",
alg, hex.EncodeToString(foxBytes), hex.EncodeToString(lh.Bytes()[2:]))
}
// test matching with serialized/loaded labeled hash
if !lh.MatchesData(testFoxData) {
t.Errorf("alg %d: failed to match reference", alg)
if !lh.Matches(testFoxData) {
t.Errorf("alg %s: failed to match reference", alg)
}
if !lh.MatchesString(testFox) {
t.Errorf("alg %d: failed to match reference", alg)
t.Errorf("alg %s: failed to match reference", alg)
}
if lh.MatchesData(noMatchData) {
t.Errorf("alg %d: failed to non-match garbage", alg)
if lh.Matches(noMatchData) {
t.Errorf("alg %s: failed to non-match garbage", alg)
}
if lh.MatchesString(noMatch) {
t.Errorf("alg %d: failed to non-match garbage", alg)
t.Errorf("alg %s: failed to non-match garbage", alg)
}
// Test representations
@ -60,7 +63,7 @@ func testAlgorithm(t *testing.T, alg Algorithm, emptyHex, foxHex string) {
lhs := Digest(alg, testFoxData)
loaded, err := FromHex(lhs.Hex())
if err != nil {
t.Errorf("alg %d: failed to load from hex string: %s", alg, err)
t.Errorf("alg %s: failed to load from hex string: %s", alg, err)
return
}
testFormat(t, alg, lhs, loaded)
@ -69,7 +72,7 @@ func testAlgorithm(t *testing.T, alg Algorithm, emptyHex, foxHex string) {
lhs = Digest(alg, testFoxData)
loaded, err = FromBase64(lhs.Base64())
if err != nil {
t.Errorf("alg %d: failed to load from base64 string: %s", alg, err)
t.Errorf("alg %s: failed to load from base64 string: %s", alg, err)
return
}
testFormat(t, alg, lhs, loaded)
@ -78,56 +81,101 @@ func testAlgorithm(t *testing.T, alg Algorithm, emptyHex, foxHex string) {
lhs = Digest(alg, testFoxData)
loaded, err = FromBase58(lhs.Base58())
if err != nil {
t.Errorf("alg %d: failed to load from base58 string: %s", alg, err)
t.Errorf("alg %s: failed to load from base58 string: %s", alg, err)
return
}
testFormat(t, alg, lhs, loaded)
}
func testFormat(t *testing.T, alg Algorithm, lhs, loaded *LabeledHash) {
t.Helper()
noMatchLH := Digest(alg, noMatchData)
// Test equality.
if !lhs.Equal(loaded) {
t.Errorf("alg %d: equality test failed", alg)
t.Errorf("alg %s: equality test failed", alg)
}
if lhs.Equal(noMatchLH) {
t.Errorf("alg %d: non-equality test failed", alg)
t.Errorf("alg %s: non-equality test failed", alg)
}
// Test matching.
if !loaded.MatchesData(testFoxData) {
t.Errorf("alg %d: failed to match reference", alg)
if !loaded.Matches(testFoxData) {
t.Errorf("alg %s: failed to match reference", alg)
}
if !loaded.MatchesString(testFox) {
t.Errorf("alg %d: failed to match reference", alg)
t.Errorf("alg %s: failed to match reference", alg)
}
if loaded.MatchesData(noMatchData) {
t.Errorf("alg %d: failed to non-match garbage", alg)
if loaded.Matches(noMatchData) {
t.Errorf("alg %s: failed to non-match garbage", alg)
}
if loaded.MatchesString(noMatch) {
t.Errorf("alg %d: failed to non-match garbage", alg)
t.Errorf("alg %s: failed to non-match garbage", alg)
}
}
func TestHash(t *testing.T) {
t.Parallel()
testAlgorithm(t, SHA2_224,
"d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f",
"619cba8e8e05826e9b8c519c0a5c68f4fb653e8a3d8aa04bb2c8cd4c",
)
testAlgorithm(t, SHA2_256,
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c",
)
testAlgorithm(t, SHA2_384,
"38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b",
"ed892481d8272ca6df370bf706e4d7bc1b5739fa2177aae6c50e946678718fc67a7af2819a021c2fc34e91bdb63409d7",
)
testAlgorithm(t, SHA2_512,
"cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e",
"91ea1245f20d46ae9a037a989f54f1f790f0a47607eeb8a14d12890cea77a1bbc6c7ed9cf205e67b7f2b8fd4c7dfd3a7a8617e45f3c463d481c7e586c39ac1ed",
)
testAlgorithm(t, SHA2_512_224,
"6ed0dd02806fa89e25de060c19d3ac86cabb87d6a0ddd05c333b84f4",
"6d6a9279495ec4061769752e7ff9c68b6b0b3c5a281b7917ce0572de",
)
testAlgorithm(t, SHA2_512_256,
"c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a",
"1546741840f8a492b959d9b8b2344b9b0eb51b004bba35c0aebaac86d45264c3",
)
testAlgorithm(t, SHA3_224,
"6b4e03423667dbb73b6e15454f0eb1abd4597f9a1b078e3f5b5a6bc7",
"2d0708903833afabdd232a20201176e8b58c5be8a6fe74265ac54db0",
)
testAlgorithm(t, SHA3_256,
"a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a",
"a80f839cd4f83f6c3dafc87feae470045e4eb0d366397d5c6ce34ba1739f734d",
)
testAlgorithm(t, SHA3_384,
"0c63a75b845e4f7d01107d852e4c2485c51a50aaaa94fc61995e71bbee983a2ac3713831264adb47fb6bd1e058d5f004",
"1a34d81695b622df178bc74df7124fe12fac0f64ba5250b78b99c1273d4b080168e10652894ecad5f1f4d5b965437fb9",
)
testAlgorithm(t, SHA3_512,
"a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26",
"18f4f4bd419603f95538837003d9d254c26c23765565162247483f65c50303597bc9ce4d289f21d1c2f1f458828e33dc442100331b35e7eb031b5d38ba6460f8",
)
testAlgorithm(t, BLAKE2s_256,
"69217a3079908094e11121d042354a7c1f55b6482ca1a51e1b250dfd1ed0eef9",
"95bca6e1b761dca1323505cc629949a0e03edf11633cc7935bd8b56f393afcf2",
)
testAlgorithm(t, BLAKE2b_256,
"0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8",
"69d7d3b0afba81826d27024c17f7f183659ed0812cf27b382eaef9fdc29b5712",
)
testAlgorithm(t, BLAKE2b_384,
"b32811423377f52d7862286ee1a72ee540524380fda1724a6f25d7978c6fd3244a6caf0498812673c5e05ef583825100",
"16d65de1a3caf1c26247234c39af636284c7e19ca448c0de788272081410778852c94d9cef6b939968d4f872c7f78337",
)
testAlgorithm(t, BLAKE2b_512,
"786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce",
"87af9dc4afe5651b7aa89124b905fd214bf17c79af58610db86a0fb1e0194622a4e9d8e395b352223a8183b0d421c0994b98286cbf8c68a495902e0fe6e2bda2",
)
testAlgorithm(t, BLAKE3,
"af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262",
"4c9bd68d7f0baa2e167cef98295eb1ec99a3ec8f0656b33dbae943b387f31d5d",
)
}

46
pack
View file

@ -8,14 +8,15 @@ COL_BOLD="\033[01;01m"
COL_RED="\033[31m"
destDirPart1="dist"
destDirPart2="jess"
function check {
function prep {
# output
output="cmd"
output="cmd/jess"
# get version
version=$(grep "info.Set" cmd/main.go | cut -d'"' -f4)
# build versioned file name with platform
filename="jess_${GOOS}_${GOARCH}_v${version//./-}"
# build versioned file name
filename="jess_v${version//./-}"
# platform
platform="${GOOS}_${GOARCH}"
if [[ $GOOS == "windows" ]]; then
@ -23,59 +24,56 @@ function check {
output="${output}.exe"
fi
# build destination path
destPath=${destDirPart1}/$filename
destPath=${destDirPart1}/${platform}/${destDirPart2}/$filename
}
function check {
prep
# check if file exists
if [[ -f $destPath ]]; then
echo "$platform $version already built"
echo "[jess] $platform $version already built"
else
echo -e "${COL_BOLD}$platform $version${COL_OFF}"
echo -e "[jess] ${COL_BOLD}$platform $version${COL_OFF}"
fi
}
function build {
# output
output="cmd/cmd"
# get version
version=$(grep "info.Set" cmd/main.go | cut -d'"' -f4)
# build versioned file name with platform
filename="jess_${GOOS}_${GOARCH}_v${version//./-}"
# platform
platform="${GOOS}_${GOARCH}"
if [[ $GOOS == "windows" ]]; then
filename="${filename}.exe"
output="${output}.exe"
fi
# build destination path
destPath=${destDirPart1}/$filename
prep
# check if file exists
if [[ -f $destPath ]]; then
echo "$platform already built in version $version, skipping..."
echo "[jess] $platform already built in version $version, skipping..."
return
fi
# build
./cmd/build
if [[ $? -ne 0 ]]; then
echo -e "\n${COL_BOLD}$platform: ${COL_RED}BUILD FAILED.${COL_OFF}"
echo -e "\n${COL_BOLD}[jess] $platform: ${COL_RED}BUILD FAILED.${COL_OFF}"
exit 1
fi
mkdir -p $(dirname $destPath)
cp $output $destPath
echo -e "\n${COL_BOLD}$platform: successfully built.${COL_OFF}"
echo -e "\n${COL_BOLD}[jess] $platform: successfully built.${COL_OFF}"
}
function check_all {
GOOS=linux GOARCH=amd64 check
GOOS=windows GOARCH=amd64 check
GOOS=darwin GOARCH=amd64 check
GOOS=linux GOARCH=arm64 check
GOOS=windows GOARCH=arm64 check
GOOS=darwin GOARCH=arm64 check
}
function build_all {
GOOS=linux GOARCH=amd64 build
GOOS=windows GOARCH=amd64 build
GOOS=darwin GOARCH=amd64 build
GOOS=linux GOARCH=arm64 build
GOOS=windows GOARCH=arm64 build
GOOS=darwin GOARCH=arm64 build
}
function build_os {

View file

@ -6,7 +6,7 @@ import (
)
var (
// ASCII printable characters (character codes 32-127)
// ASCII printable characters (character codes 32-127).
passwordCharSets = []string{
"abcdefghijklmnopqrstuvwxyz",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
@ -16,7 +16,7 @@ var (
}
// extended ASCII codes (character code 128-255)
// assume pool size of 32 (a quarter), as not all of them are common / easily accessible on every keyboard
// assume pool size of 32 (a quarter), as not all of them are common / easily accessible on every keyboard.
passwordExtraPoolSize = 32
createPasswordCallback func(signet *Signet, minSecurityLevel int) error
@ -38,7 +38,6 @@ func SetPasswordCallbacks(
// CalculatePasswordSecurityLevel calculates the security level of the given password and iterations of the pbkdf algorithm.
func CalculatePasswordSecurityLevel(password string, iterations int) int {
// TODO: this calculation is pretty conservative and errs on the safe side
// maybe soften this up a litte, but couldn't find any scientific foundation for that

View file

@ -21,6 +21,8 @@ func getTestPassword(signet *Signet) error {
}
func TestCalculatePasswordSecurityLevel(t *testing.T) {
t.Parallel()
// basic weak
testPWSL(t, "asdf", -1)
testPWSL(t, "asdfasdf", -1)
@ -82,6 +84,8 @@ func TestCalculatePasswordSecurityLevel(t *testing.T) {
}
func testPWSL(t *testing.T, password string, expectedSecurityLevel int) {
t.Helper()
securityLevel := CalculatePasswordSecurityLevel(password, 1<<20)
if securityLevel < expectedSecurityLevel {

View file

@ -5,7 +5,7 @@ import (
"strings"
)
// Security requirements of a letter
// Security requirements of a letter.
const (
Confidentiality uint8 = iota
Integrity

View file

@ -3,6 +3,8 @@ package jess
import "testing"
func checkNoSpec(t *testing.T, a *Requirements, expectedNoSpec string) {
t.Helper()
noSpec := a.SerializeToNoSpec()
if noSpec != expectedNoSpec {
t.Errorf(`unexpected no spec "%s", expected "%s"`, noSpec, expectedNoSpec)
@ -10,6 +12,7 @@ func checkNoSpec(t *testing.T, a *Requirements, expectedNoSpec string) {
}
func TestRequirements(t *testing.T) {
t.Parallel()
a := NewRequirements()
checkNoSpec(t, a, "")

View file

@ -17,7 +17,10 @@ const (
)
var (
wireReKeyAfterMsgs uint64 = 100000 // re-exchange keys every 100000 messages
// Re-exchange keys every x messages.
// At 10_000_000 msgs with 1500 bytes per msg, this would result in
// re-exchanging keys every 15 GB.
wireReKeyAfterMsgs uint64 = 10_000_000
requiredWireSessionRequirements = NewRequirements().Remove(SenderAuthentication)
)
@ -40,14 +43,14 @@ type WireSession struct { //nolint:maligned // TODO
newKeyMaterial [][]byte
}
// kxPair is key exchange pair
// kxPair is key exchange pair.
type kxPair struct {
tool tools.ToolLogic
signet *Signet
peer *Signet
}
// kePair is key encapsulation "pair"
// kePair is key encapsulation "pair".
type kePair struct {
tool tools.ToolLogic
signet *Signet

View file

@ -48,7 +48,7 @@ type managedHasher struct {
hash hash.Hash
}
// Sum returns the hash sum of the managed hasher
// Sum returns the hash sum of the managed hasher.
func (sh *managedHasher) Sum() ([]byte, error) {
if sh == nil || sh.hash == nil {
return nil, errors.New("managed hasher is broken")
@ -56,7 +56,7 @@ func (sh *managedHasher) Sum() ([]byte, error) {
return sh.hash.Sum(nil), nil
}
func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
func newSession(e *Envelope) (*Session, error) { //nolint:maintidx
if e.suite == nil {
return nil, errors.New("suite not loaded")
}
@ -86,18 +86,18 @@ func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
// prepare variables
var (
keySourceAvailable bool = false
keySourceAvailable bool
totalSignetsSeen int
requireSecurityLevel bool = false
requireDefaultKeySize bool = false
requireSecurityLevel bool
requireDefaultKeySize bool
)
// tool init loop: start
for i, toolID := range s.envelope.suite.Tools {
///////////////////////////////////////
// ====================================
// tool init loop: check for duplicates
///////////////////////////////////////
// ====================================
for j, dupeToolID := range s.envelope.suite.Tools {
if i != j && toolID == dupeToolID {
@ -105,9 +105,9 @@ func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
}
}
//////////////////////////////////////
// ===================================
// tool init loop: parse, prep and get
//////////////////////////////////////
// ===================================
var (
hashTool *hashtools.HashTool
@ -135,9 +135,9 @@ func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
s.toolsWithState = append(s.toolsWithState, logic)
}
////////////////////////////////////////////////////////////
// ===========================================================
// tool init loop: assign tools to queues and add requirements
////////////////////////////////////////////////////////////
// ===========================================================
switch tool.Info.Purpose {
case tools.PurposeKeyDerivation:
@ -164,6 +164,7 @@ func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
case tools.PurposeSigning:
s.signers = append(s.signers, logic)
s.toolRequirements.Add(Integrity)
s.toolRequirements.Add(SenderAuthentication)
case tools.PurposeIntegratedCipher:
@ -180,9 +181,9 @@ func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
s.toolRequirements.Add(Integrity)
}
///////////////////////////////////////////////
// ============================================
// tool init loop: process options, get hashers
///////////////////////////////////////////////
// ============================================
for _, option := range tool.Info.Options {
switch option {
@ -242,9 +243,9 @@ func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
}
}
//////////////////////////////////
// ===============================
// tool init loop: initialize tool
//////////////////////////////////
// ===============================
// init tool
logic.Init(
@ -257,18 +258,18 @@ func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
hashSumFn,
)
/////////////////////////////////////////////////
// ==============================================
// tool init loop: calc and check security levels
/////////////////////////////////////////////////
// ==============================================
err = s.calcAndCheckSecurityLevel(logic, nil)
if err != nil {
return nil, err
}
/////////////////////////////////////////////
// ==========================================
// tool init loop: calculate default key size
/////////////////////////////////////////////
// ==========================================
// find biggest key size for default
if tool.Info.KeySize > s.DefaultSymmetricKeySize {
@ -277,9 +278,9 @@ func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
} // tool init loop: end
//////////////////////////////////////////////////////////
// =======================================================
// calc and check signet security levels, default key size
//////////////////////////////////////////////////////////
// =======================================================
for _, tool := range s.all {
@ -342,9 +343,9 @@ func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
return nil, err
}
/////////////////////////////////////////////////////////
// ======================================================
// check security level and default key size requirements
/////////////////////////////////////////////////////////
// ======================================================
// apply manual security level
if minimumSecurityLevel > 0 && minimumSecurityLevel > s.SecurityLevel {
@ -364,11 +365,11 @@ func newSession(e *Envelope) (*Session, error) { //nolint:gocognit,gocyclo
return nil, fmt.Errorf("this toolset requires the default key size to be set manually")
}
///////////////
// ============
// final checks
///////////////
// ============
// check requirements requirements
// check requirements
if s.toolRequirements.Empty() {
return nil, errors.New("envelope excludes all security requirements, no meaningful operation possible")
}
@ -517,7 +518,7 @@ func (s *Session) checkSecurityLevel(levelToCheck int, subject func() string) er
switch {
case minimumSecurityLevel > 0:
// check against minimumSecurityLevel
// minimumSecurityLevel overrides other checks
// (overrides other checks)
if levelToCheck < minimumSecurityLevel {
return fmt.Errorf(
`%s with a security level of %d is weaker than the desired security level of %d`,

View file

@ -7,12 +7,14 @@ import (
"io"
"time"
"github.com/safing/jess/tools"
"github.com/mr-tron/base58"
uuid "github.com/satori/go.uuid"
"github.com/safing/jess/tools"
"github.com/safing/structures/dsd"
)
// Special signet types
// Special signet types.
const (
SignetSchemePassword = "pw"
SignetSchemeKey = "key"
@ -134,6 +136,14 @@ func (signet *Signet) SetLoadedKeys(pubKey crypto.PublicKey, privKey crypto.Priv
// AsRecipient returns a public version of the Signet.
func (signet *Signet) AsRecipient() (*Signet, error) {
// Check special signet schemes.
switch signet.Scheme {
case SignetSchemeKey:
return nil, errors.New("keys cannot be a recipient")
case SignetSchemePassword:
return nil, errors.New("passwords cannot be a recipient")
}
// load so we can split keys
err := signet.LoadKey()
if err != nil {
@ -176,7 +186,7 @@ func (signet *Signet) LoadKey() error {
return signet.tool.StaticLogic.LoadKey(signet)
}
// Tool returns the tool of the signet
// Tool returns the tool of the signet.
func (signet *Signet) Tool() (*tools.Tool, error) {
// load tool
err := signet.loadTool()
@ -249,3 +259,60 @@ func (signet *Signet) AssignUUID() error {
signet.ID = u.String()
return nil
}
// ToBytes serializes the Signet to a byte slice.
func (signet *Signet) ToBytes() ([]byte, error) {
// Make sure the key is stored in the serializable format.
if err := signet.StoreKey(); err != nil {
return nil, fmt.Errorf("failed to serialize the key: %w", err)
}
// Serialize Signet.
data, err := dsd.Dump(signet, dsd.CBOR)
if err != nil {
return nil, fmt.Errorf("failed to serialize the signet: %w", err)
}
return data, nil
}
// SignetFromBytes parses and loads a serialized signet.
func SignetFromBytes(data []byte) (*Signet, error) {
signet := &Signet{}
// Parse Signet from data.
if _, err := dsd.Load(data, signet); err != nil {
return nil, fmt.Errorf("failed to parse data format: %w", err)
}
// Load the key.
if err := signet.LoadKey(); err != nil {
return nil, fmt.Errorf("failed to parse key: %w", err)
}
return signet, nil
}
// ToBase58 serializes the signet and encodes it with base58.
func (signet *Signet) ToBase58() (string, error) {
// Serialize Signet.
data, err := signet.ToBytes()
if err != nil {
return "", err
}
// Encode and return.
return base58.Encode(data), nil
}
// SignetFromBase58 parses and loads a base58 encoded serialized signet.
func SignetFromBase58(base58Encoded string) (*Signet, error) {
// Decode string.
data, err := base58.Decode(base58Encoded)
if err != nil {
return nil, fmt.Errorf("failed to decode base58: %w", err)
}
// Parse and return.
return SignetFromBytes(data)
}

View file

@ -1,6 +1,6 @@
package jess
// Suite status options
// Suite status options.
const (
SuiteStatusDeprecated uint8 = 0
SuiteStatusPermitted uint8 = 1

View file

@ -1,75 +1,27 @@
package jess
// Currently Recommended Suites.
var (
// SuiteKey is a cipher suite for encryption with a key.
SuiteKey = SuiteKeyV1
// SuitePassword is a cipher suite for encryption with a password.
SuitePassword = SuitePasswordV1
// SuiteRcptOnly is a cipher suite for encrypting for someone, but without verifying the sender/source.
SuiteRcptOnly = SuiteRcptOnlyV1
// SuiteSign is a cipher suite for signing (no encryption).
SuiteSign = SuiteSignV1
// SuiteSignFile is a cipher suite for signing files (no encryption).
SuiteSignFile = SuiteSignFileV1
// SuiteComplete is a cipher suite for both encrypting for someone and signing.
SuiteComplete = SuiteCompleteV1
// SuiteWire is a cipher suite for network communication, including authentication of the server, but not the client.
SuiteWire = SuiteWireV1
)
// Suite Lists.
var (
// lists
suitesMap = make(map[string]*Suite)
suitesList []*Suite
// suite definitions
// SuiteKeyV1 is a cipher suite for encryption with a key.
SuiteKeyV1 = registerSuite(&Suite{
ID: "key_v1",
Tools: []string{"HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
Provides: NewRequirements(),
SecurityLevel: 128,
Status: SuiteStatusRecommended,
})
// SuitePasswordV1 is a cipher suite for encryption with a password.
SuitePasswordV1 = registerSuite(&Suite{
ID: "pw_v1",
Tools: []string{"SCRYPT-20", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
Provides: NewRequirements(),
SecurityLevel: 128,
Status: SuiteStatusRecommended,
})
// SuiteRcptOnlyV1 is a cipher suite for encrypting for someone, but without verifying the sender/source.
SuiteRcptOnlyV1 = registerSuite(&Suite{
ID: "rcpt_v1",
Tools: []string{"ECDH-X25519", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
Provides: NewRequirements().Remove(SenderAuthentication),
SecurityLevel: 128,
Status: SuiteStatusRecommended,
})
// SuiteSignV1 is a cipher suite for signing (no encryption).
SuiteSignV1 = registerSuite(&Suite{
ID: "sign_v1",
Tools: []string{"Ed25519(BLAKE2b-256)"},
Provides: newEmptyRequirements().Add(SenderAuthentication),
SecurityLevel: 128,
Status: SuiteStatusRecommended,
})
// SuiteCompleteV1 is a cipher suite for both encrypting for someone and signing.
SuiteCompleteV1 = registerSuite(&Suite{
ID: "v1",
Tools: []string{"ECDH-X25519", "Ed25519(BLAKE2b-256)", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
Provides: NewRequirements(),
SecurityLevel: 128,
Status: SuiteStatusRecommended,
})
// SuiteWireV1 is a cipher suite for network communication, including authentication of the server, but not the client.
SuiteWireV1 = registerSuite(&Suite{
ID: "w1",
Tools: []string{"ECDH-X25519", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
Provides: NewRequirements().Remove(SenderAuthentication),
SecurityLevel: 128,
Status: SuiteStatusRecommended,
})
// currently recommended suites
// SuiteKey is a a cipher suite for encryption with a key.
SuiteKey = SuiteKeyV1
// SuitePassword is a a cipher suite for encryption with a password.
SuitePassword = SuitePasswordV1
// SuiteRcptOnly is a a cipher suite for encrypting for someone, but without verifying the sender/source.
SuiteRcptOnly = SuiteRcptOnlyV1
// SuiteSign is a a cipher suite for signing (no encryption).
SuiteSign = SuiteSignV1
// SuiteComplete is a a cipher suite for both encrypting for someone and signing.
SuiteComplete = SuiteCompleteV1
// SuiteWire is a a cipher suite for network communication, including authentication of the server, but not the client.
SuiteWire = SuiteWireV1
)
func registerSuite(suite *Suite) (suiteID string) {

View file

@ -11,6 +11,8 @@ import (
)
func getSuite(t *testing.T, suiteID string) (suite *Suite) {
t.Helper()
suite, ok := GetSuite(suiteID)
if !ok {
t.Fatalf("suite %s does not exist", suiteID)
@ -20,6 +22,8 @@ func getSuite(t *testing.T, suiteID string) (suite *Suite) {
}
func TestSuites(t *testing.T) {
t.Parallel()
for _, suite := range Suites() {
err := suiteBullshitCheck(suite)
@ -91,10 +95,9 @@ func TestSuites(t *testing.T) {
}
}
}
func suiteBullshitCheck(suite *Suite) error { //nolint:gocognit,gocyclo
func suiteBullshitCheck(suite *Suite) error { //nolint:maintidx
// pre checks
if suite.Provides == nil {
return errors.New("provides no requirement attributes")
@ -121,9 +124,9 @@ func suiteBullshitCheck(suite *Suite) error { //nolint:gocognit,gocyclo
// tool check loop: start
for i, toolID := range suite.Tools {
////////////////////////////////////////
// =====================================
// tool check loop: check for duplicates
////////////////////////////////////////
// =====================================
for j, dupeToolID := range suite.Tools {
if i != j && toolID == dupeToolID {
@ -131,9 +134,9 @@ func suiteBullshitCheck(suite *Suite) error { //nolint:gocognit,gocyclo
}
}
///////////////////////////////////////
// ====================================
// tool check loop: parse, prep and get
///////////////////////////////////////
// ====================================
var (
hashTool *hashtools.HashTool
@ -161,9 +164,9 @@ func suiteBullshitCheck(suite *Suite) error { //nolint:gocognit,gocyclo
s.toolsWithState = append(s.toolsWithState, logic)
}
///////////////////////////////////////////////////////////////
// ============================================================
// tool check loop: assign tools to queues and add requirements
///////////////////////////////////////////////////////////////
// ============================================================
switch tool.Info.Purpose {
case tools.PurposeKeyDerivation:
@ -190,6 +193,7 @@ func suiteBullshitCheck(suite *Suite) error { //nolint:gocognit,gocyclo
case tools.PurposeSigning:
s.signers = append(s.signers, logic)
s.toolRequirements.Add(Integrity)
s.toolRequirements.Add(SenderAuthentication)
case tools.PurposeIntegratedCipher:
@ -206,9 +210,9 @@ func suiteBullshitCheck(suite *Suite) error { //nolint:gocognit,gocyclo
s.toolRequirements.Add(Integrity)
}
////////////////////////////////////////////////
// =============================================
// tool check loop: process options, get hashers
////////////////////////////////////////////////
// =============================================
for _, option := range tool.Info.Options {
switch option {
@ -260,9 +264,9 @@ func suiteBullshitCheck(suite *Suite) error { //nolint:gocognit,gocyclo
}
}
///////////////////////////////////
// ================================
// tool check loop: initialize tool
///////////////////////////////////
// ================================
// init tool
logic.Init(
@ -275,9 +279,9 @@ func suiteBullshitCheck(suite *Suite) error { //nolint:gocognit,gocyclo
hashSumFn,
)
//////////////////////////////////////////////////
// ===============================================
// tool check loop: calc and check security levels
//////////////////////////////////////////////////
// ===============================================
err = s.calcAndCheckSecurityLevel(logic, nil)
if err != nil {
@ -286,11 +290,11 @@ func suiteBullshitCheck(suite *Suite) error { //nolint:gocognit,gocyclo
} // tool check loop: end
///////////////
// ============
// final checks
///////////////
// ============
// check requirements requirements
// check requirements
if s.toolRequirements.Empty() {
return errors.New("suite does not provide any security attributes")
}
@ -320,9 +324,9 @@ func suiteBullshitCheck(suite *Suite) error { //nolint:gocognit,gocyclo
return errors.New("key derivation tool specified, but not needed")
}
/////////////////////////////////////////
// ======================================
// check if values match suite definition
/////////////////////////////////////////
// ======================================
// check if security level matches
if s.SecurityLevel != suite.SecurityLevel {
@ -338,9 +342,9 @@ func suiteBullshitCheck(suite *Suite) error { //nolint:gocognit,gocyclo
)
}
///////////////////////////////////////////////////////////
// ========================================================
// check if computeSuiteAttributes returns the same results
///////////////////////////////////////////////////////////
// ========================================================
computedSuite := computeSuiteAttributes(suite.Tools, assumeKey)
if computedSuite == nil {
@ -361,23 +365,23 @@ func suiteBullshitCheck(suite *Suite) error { //nolint:gocognit,gocyclo
}
func computeSuiteAttributes(toolIDs []string, assumeKey bool) *Suite {
new := &Suite{
newSuite := &Suite{
Provides: newEmptyRequirements(),
SecurityLevel: 0,
}
// if we have a key
if assumeKey {
new.Provides.Add(SenderAuthentication)
new.Provides.Add(RecipientAuthentication)
newSuite.Provides.Add(SenderAuthentication)
newSuite.Provides.Add(RecipientAuthentication)
}
// check all security levels and collect attributes
for _, toolID := range toolIDs {
///////////////////////////////////////
// ====================================
// tool check loop: parse, prep and get
///////////////////////////////////////
// ====================================
var hashTool *hashtools.HashTool
@ -398,38 +402,39 @@ func computeSuiteAttributes(toolIDs []string, assumeKey bool) *Suite {
// create logic instance and add to logic and state lists
logic := tool.Factory()
//////////////////////////////////////
// ===================================
// tool check loop: collect attributes
//////////////////////////////////////
// ===================================
switch tool.Info.Purpose {
case tools.PurposePassDerivation:
new.Provides.Add(SenderAuthentication)
new.Provides.Add(RecipientAuthentication)
newSuite.Provides.Add(SenderAuthentication)
newSuite.Provides.Add(RecipientAuthentication)
case tools.PurposeKeyExchange:
new.Provides.Add(RecipientAuthentication)
newSuite.Provides.Add(RecipientAuthentication)
case tools.PurposeKeyEncapsulation:
new.Provides.Add(RecipientAuthentication)
newSuite.Provides.Add(RecipientAuthentication)
case tools.PurposeSigning:
new.Provides.Add(SenderAuthentication)
newSuite.Provides.Add(Integrity)
newSuite.Provides.Add(SenderAuthentication)
case tools.PurposeIntegratedCipher:
new.Provides.Add(Confidentiality)
new.Provides.Add(Integrity)
newSuite.Provides.Add(Confidentiality)
newSuite.Provides.Add(Integrity)
case tools.PurposeCipher:
new.Provides.Add(Confidentiality)
newSuite.Provides.Add(Confidentiality)
case tools.PurposeMAC:
new.Provides.Add(Integrity)
newSuite.Provides.Add(Integrity)
}
////////////////////////////////////////////////
// =============================================
// tool check loop: process options, get hashers
////////////////////////////////////////////////
// =============================================
for _, option := range tool.Info.Options {
switch option {
@ -442,9 +447,9 @@ func computeSuiteAttributes(toolIDs []string, assumeKey bool) *Suite {
}
}
///////////////////////////////////
// ================================
// tool check loop: initialize tool
///////////////////////////////////
// ================================
// init tool
logic.Init(
@ -456,19 +461,19 @@ func computeSuiteAttributes(toolIDs []string, assumeKey bool) *Suite {
nil,
)
//////////////////////////////////////////
// =======================================
// tool check loop: compute security level
//////////////////////////////////////////
// =======================================
toolSecurityLevel, err := logic.SecurityLevel(nil)
if err != nil {
return nil
}
if new.SecurityLevel == 0 || toolSecurityLevel < new.SecurityLevel {
new.SecurityLevel = toolSecurityLevel
if newSuite.SecurityLevel == 0 || toolSecurityLevel < newSuite.SecurityLevel {
newSuite.SecurityLevel = toolSecurityLevel
}
}
return new
return newSuite
}

61
suites_v1.go Normal file
View file

@ -0,0 +1,61 @@
package jess //nolint:dupl
var (
// SuiteKeyV1 is a cipher suite for encryption with a key.
SuiteKeyV1 = registerSuite(&Suite{
ID: "key_v1",
Tools: []string{"HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
Provides: NewRequirements(),
SecurityLevel: 128,
Status: SuiteStatusRecommended,
})
// SuitePasswordV1 is a cipher suite for encryption with a password.
SuitePasswordV1 = registerSuite(&Suite{
ID: "pw_v1",
Tools: []string{"SCRYPT-20", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
Provides: NewRequirements(),
SecurityLevel: 128,
Status: SuiteStatusRecommended,
})
// SuiteRcptOnlyV1 is a cipher suite for encrypting for someone, but without verifying the sender/source.
SuiteRcptOnlyV1 = registerSuite(&Suite{
ID: "rcpt_v1",
Tools: []string{"ECDH-X25519", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
Provides: NewRequirements().Remove(SenderAuthentication),
SecurityLevel: 128,
Status: SuiteStatusRecommended,
})
// SuiteSignV1 is a cipher suite for signing (no encryption).
SuiteSignV1 = registerSuite(&Suite{
ID: "sign_v1",
Tools: []string{"Ed25519(BLAKE2b-256)"},
Provides: newEmptyRequirements().Add(Integrity).Add(SenderAuthentication),
SecurityLevel: 128,
Status: SuiteStatusRecommended,
})
// SuiteSignFileV1 is a cipher suite for signing files (no encryption).
// SHA2_256 is chosen for better compatibility with other tool sets and workflows.
SuiteSignFileV1 = registerSuite(&Suite{
ID: "signfile_v1",
Tools: []string{"Ed25519(SHA2-256)"},
Provides: newEmptyRequirements().Add(Integrity).Add(SenderAuthentication),
SecurityLevel: 128,
Status: SuiteStatusRecommended,
})
// SuiteCompleteV1 is a cipher suite for both encrypting for someone and signing.
SuiteCompleteV1 = registerSuite(&Suite{
ID: "v1",
Tools: []string{"ECDH-X25519", "Ed25519(BLAKE2b-256)", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
Provides: NewRequirements(),
SecurityLevel: 128,
Status: SuiteStatusRecommended,
})
// SuiteWireV1 is a cipher suite for network communication, including authentication of the server, but not the client.
SuiteWireV1 = registerSuite(&Suite{
ID: "w1",
Tools: []string{"ECDH-X25519", "HKDF(BLAKE2b-256)", "CHACHA20-POLY1305"},
Provides: NewRequirements().Remove(SenderAuthentication),
SecurityLevel: 128,
Status: SuiteStatusRecommended,
})
)

61
suites_v2.go Normal file
View file

@ -0,0 +1,61 @@
package jess //nolint:dupl
var (
// SuiteKeyV2 is a cipher suite for encryption with a key.
SuiteKeyV2 = registerSuite(&Suite{
ID: "key_v2",
Tools: []string{"BLAKE3-KDF", "CHACHA20-POLY1305"},
Provides: NewRequirements(),
SecurityLevel: 128,
Status: SuiteStatusPermitted,
})
// SuitePasswordV2 is a cipher suite for encryption with a password.
SuitePasswordV2 = registerSuite(&Suite{
ID: "pw_v2",
Tools: []string{"SCRYPT-20", "BLAKE3-KDF", "CHACHA20-POLY1305"},
Provides: NewRequirements(),
SecurityLevel: 128,
Status: SuiteStatusPermitted,
})
// SuiteRcptOnlyV2 is a cipher suite for encrypting for someone, but without verifying the sender/source.
SuiteRcptOnlyV2 = registerSuite(&Suite{
ID: "rcpt_v2",
Tools: []string{"ECDH-X25519", "BLAKE3-KDF", "CHACHA20-POLY1305"},
Provides: NewRequirements().Remove(SenderAuthentication),
SecurityLevel: 128,
Status: SuiteStatusPermitted,
})
// SuiteSignV2 is a cipher suite for signing (no encryption).
SuiteSignV2 = registerSuite(&Suite{
ID: "sign_v2",
Tools: []string{"Ed25519(BLAKE3)"},
Provides: newEmptyRequirements().Add(Integrity).Add(SenderAuthentication),
SecurityLevel: 128,
Status: SuiteStatusPermitted,
})
// SuiteSignFileV2 is a cipher suite for signing files (no encryption).
// SHA2_256 is chosen for better compatibility with other tool sets and workflows.
SuiteSignFileV2 = registerSuite(&Suite{
ID: "signfile_v2",
Tools: []string{"Ed25519(BLAKE3)"},
Provides: newEmptyRequirements().Add(Integrity).Add(SenderAuthentication),
SecurityLevel: 128,
Status: SuiteStatusPermitted,
})
// SuiteCompleteV2 is a cipher suite for both encrypting for someone and signing.
SuiteCompleteV2 = registerSuite(&Suite{
ID: "v2",
Tools: []string{"ECDH-X25519", "Ed25519(BLAKE3)", "BLAKE3-KDF", "CHACHA20-POLY1305"},
Provides: NewRequirements(),
SecurityLevel: 128,
Status: SuiteStatusPermitted,
})
// SuiteWireV2 is a cipher suite for network communication, including authentication of the server, but not the client.
SuiteWireV2 = registerSuite(&Suite{
ID: "w2",
Tools: []string{"ECDH-X25519", "BLAKE3-KDF", "CHACHA20-POLY1305"},
Provides: NewRequirements().Remove(SenderAuthentication),
SecurityLevel: 128,
Status: SuiteStatusPermitted,
})
)

View file

@ -5,7 +5,6 @@ import (
"sync"
"github.com/safing/jess"
"github.com/safing/jess/tools"
)

View file

@ -7,6 +7,7 @@ import (
)
func TestSupply(t *testing.T) {
t.Parallel()
total := 10
supply := NewSignetSupply(total)

87
test
View file

@ -4,22 +4,22 @@ warnings=0
errors=0
scripted=0
goUp="\\e[1A"
all=0
fullTestFlags="-short"
install=0
testonly=0
function help {
echo "usage: $0 [command] [options]"
echo ""
echo "commands:"
echo " <none> run baseline tests"
echo " all run all tests"
echo " install install deps for running baseline tests"
echo " install all install deps for running all tests"
echo " full run full tests (ie. not short)"
echo " install install deps for running tests"
echo ""
echo "options:"
echo " --scripted dont jump console lines (still use colors)"
echo " [package] run tests only on this package"
echo " --scripted don't jump console lines (still use colors)"
echo " --test-only run tests only, no linters"
echo " [package] run only on this package"
}
function run {
@ -62,22 +62,6 @@ function run {
rm -f $tmpfile
}
function checkformat {
if [[ $scripted -eq 0 ]]; then
echo "[......] gofmt $1"
fi
output=$(gofmt -l $GOPATH/src/$1/*.go)
if [[ $output == "" ]]; then
echo -e "${goUp}[\e[01;32m OK \e[00m] gofmt $*"
else
echo -e "${goUp}[\e[01;31m FAIL \e[00m] gofmt $*"
echo "The following files do not conform to gofmt:"
gofmt -l $GOPATH/src/$1/*.go # keeps format
errors=$((errors+1))
fi
}
# get and switch to script dir
baseDir="$( cd "$(dirname "$0")" && pwd )"
cd "$baseDir"
@ -94,12 +78,15 @@ while true; do
goUp=""
shift 1
;;
"--test-only")
testonly=1
shift 1
;;
"install")
install=1
shift 1
;;
"all")
all=1
"full")
fullTestFlags=""
shift 1
;;
@ -117,12 +104,9 @@ fi
# install
if [[ $install -eq 1 ]]; then
echo "installing dependencies..."
echo "$ go get -u golang.org/x/lint/golint"
go get -u golang.org/x/lint/golint
if [[ $all -eq 1 ]]; then
echo "$ go get -u github.com/golangci/golangci-lint/cmd/golangci-lint"
go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
fi
# TODO: update golangci-lint version regularly
echo "$ curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.44.0"
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.44.0
exit 0
fi
@ -131,24 +115,18 @@ if [[ $(which go) == "" ]]; then
echo "go command not found"
exit 1
fi
if [[ $(which gofmt) == "" ]]; then
echo "gofmt command not found"
exit 1
fi
if [[ $(which golint) == "" ]]; then
echo "golint command not found"
echo "install with: go get -u golang.org/x/lint/golint"
echo "or run: ./test install"
exit 1
fi
if [[ $all -eq 1 ]]; then
if [[ $testonly -eq 0 ]]; then
if [[ $(which gofmt) == "" ]]; then
echo "gofmt command not found"
exit 1
fi
if [[ $(which golangci-lint) == "" ]]; then
echo "golangci-lint command not found"
echo "install locally with: go get -u github.com/golangci/golangci-lint/cmd/golangci-lint"
echo "or run: ./test install all"
echo ""
echo "hint: install for CI with: curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin vX.Y.Z"
echo "install with: curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin vX.Y.Z"
echo "don't forget to specify the version you want"
echo "or run: ./test install"
echo ""
echo "alternatively, install the current dev version with: go get -u github.com/golangci/golangci-lint/cmd/golangci-lint"
exit 1
fi
fi
@ -156,15 +134,10 @@ fi
# target selection
if [[ "$1" == "" ]]; then
# get all packages
packages=$(go list ./...)
packages=$(go list -e ./...)
else
# single package testing
packages=$(go list)/$1
if [[ ! -d "$GOPATH/src/$packages" ]]; then
echo "go package $packages does not exist"
help
exit 1
fi
packages=$(go list -e)/$1
echo "note: only running tests for package $packages"
fi
@ -174,13 +147,13 @@ echo "running tests for ${platformInfo//$'\n'/ }:"
# run vet/test on packages
for package in $packages; do
packagename=${package#github.com/safing/jess} #TODO: could be queried with `go list .`
packagename=${packagename#/}
echo ""
echo $package
checkformat $package
run golint -set_exit_status -min_confidence 1.0 $package
run go vet $package
if [[ $all -eq 1 ]]; then
run golangci-lint run $GOPATH/src/$package
if [[ $testonly -eq 0 ]]; then
run go vet $package
run golangci-lint run $packagename
fi
run go test -cover $fullTestFlags $package
done

View file

@ -2,8 +2,7 @@ package jess
import (
"github.com/safing/jess/tools"
// import all tools
// Import all tools.
_ "github.com/safing/jess/tools/all"
)

View file

@ -1,7 +1,9 @@
// Package all imports all tool subpackages
package all
import (
// Import all tool subpackages
// Import all tool subpackages.
_ "github.com/safing/jess/tools/blake3"
_ "github.com/safing/jess/tools/ecdh"
_ "github.com/safing/jess/tools/gostdlib"
)

68
tools/blake3/kdf.go Normal file
View file

@ -0,0 +1,68 @@
package blake3
import (
"errors"
"fmt"
"io"
"github.com/zeebo/blake3"
"github.com/safing/jess/tools"
)
func init() {
tools.Register(&tools.Tool{
Info: &tools.ToolInfo{
Name: "BLAKE3-KDF",
Purpose: tools.PurposeKeyDerivation,
SecurityLevel: 128,
Comment: "cryptographic hash function based on Bao and BLAKE2",
Author: "Jean-Philippe Aumasson et al., 2020",
},
Factory: func() tools.ToolLogic { return &KDF{} },
})
}
// KDF implements the cryptographic interface for BLAKE3 key derivation.
type KDF struct {
tools.ToolLogicBase
reader io.Reader
}
// InitKeyDerivation implements the ToolLogic interface.
func (keyder *KDF) InitKeyDerivation(nonce []byte, material ...[]byte) error {
// Check params.
if len(material) < 1 || len(material[0]) == 0 || len(nonce) == 0 {
return errors.New("must supply at least one key and a nonce as key material")
}
// Setup KDF.
// Use nonce as kdf context.
h := blake3.NewDeriveKey(string(nonce))
// Then add all the key material.
for _, m := range material {
_, _ = h.Write(m)
}
// Get key reader.
keyder.reader = h.Digest()
return nil
}
// DeriveKey implements the ToolLogic interface.
func (keyder *KDF) DeriveKey(size int) ([]byte, error) {
key := make([]byte, size)
return key, keyder.DeriveKeyWriteTo(key)
}
// DeriveKeyWriteTo implements the ToolLogic interface.
func (keyder *KDF) DeriveKeyWriteTo(newKey []byte) error {
n, err := io.ReadFull(keyder.reader, newKey)
if err != nil {
return fmt.Errorf("failed to generate key: %w", err)
}
if n != len(newKey) {
return errors.New("failed to generate key: EOF")
}
return nil
}

View file

@ -3,21 +3,20 @@ package ecdh
import (
"crypto"
"crypto/elliptic"
"fmt"
"math/big"
"github.com/safing/jess/tools"
"github.com/safing/portbase/container"
"github.com/aead/ecdh"
"github.com/safing/jess/tools"
"github.com/safing/structures/container"
)
var (
nistCurveInfo = &tools.ToolInfo{
Purpose: tools.PurposeKeyExchange,
Comment: "FIPS 186",
Author: "NIST, 2009",
}
)
var nistCurveInfo = &tools.ToolInfo{
Purpose: tools.PurposeKeyExchange,
Comment: "FIPS 186",
Author: "NIST, 2009",
}
func init() {
tools.Register(&tools.Tool{
@ -117,13 +116,20 @@ func (ec *NistCurve) StoreKey(signet tools.SignetInt) error {
c.AppendNumber(1)
// store public key
curvePoint := pubKey.(ecdh.Point)
curvePoint, ok := pubKey.(ecdh.Point)
if !ok {
return fmt.Errorf("public key of invalid type %T", pubKey)
}
c.AppendAsBlock(curvePoint.X.Bytes())
c.AppendAsBlock(curvePoint.Y.Bytes())
// store private key
if !public {
c.Append(privKey.([]byte))
privKeyData, ok := privKey.([]byte)
if !ok {
return fmt.Errorf("private key of invalid type %T", privKey)
}
c.Append(privKeyData)
}
signet.SetStoredKey(c.CompileData(), public)

View file

@ -2,11 +2,12 @@ package ecdh
import (
"crypto"
"github.com/safing/jess/tools"
"github.com/safing/portbase/container"
"fmt"
"github.com/aead/ecdh"
"github.com/safing/jess/tools"
"github.com/safing/structures/container"
)
func init() {
@ -87,10 +88,16 @@ func (ec *X25519Curve) StoreKey(signet tools.SignetInt) error {
c.AppendNumber(1)
// store keys
pubKeyData := pubKey.([32]byte)
pubKeyData, ok := pubKey.([32]byte)
if !ok {
return fmt.Errorf("public key of invalid type %T", pubKey)
}
c.Append(pubKeyData[:])
if !public {
privKeyData := privKey.([32]byte)
privKeyData, ok := privKey.([32]byte)
if !ok {
return fmt.Errorf("private key of invalid type %T", privKey)
}
c.Append(privKeyData[:])
}

View file

@ -7,7 +7,6 @@ import (
"github.com/safing/jess/tools"
)
//nolint:dupl
func init() {
aesCtrInfo := &tools.ToolInfo{
Purpose: tools.PurposeCipher,

View file

@ -7,7 +7,6 @@ import (
"github.com/safing/jess/tools"
)
//nolint:dupl
func init() {
aesGcmInfo := &tools.ToolInfo{
Purpose: tools.PurposeIntegratedCipher,

View file

@ -3,9 +3,9 @@ package gostdlib
import (
"crypto/cipher"
"github.com/safing/jess/tools"
"golang.org/x/crypto/chacha20poly1305"
"github.com/safing/jess/tools"
)
func init() {

View file

@ -6,7 +6,7 @@ import (
"errors"
"github.com/safing/jess/tools"
"github.com/safing/portbase/container"
"github.com/safing/structures/container"
)
func init() {

View file

@ -5,10 +5,10 @@ import (
"fmt"
"io"
"github.com/safing/jess/tools"
"github.com/safing/portbase/container"
"golang.org/x/crypto/hkdf"
"github.com/safing/jess/tools"
"github.com/safing/structures/container"
)
func init() {
@ -56,12 +56,12 @@ func (keyder *HKDF) DeriveKey(size int) ([]byte, error) {
}
// DeriveKeyWriteTo implements the ToolLogic interface.
func (keyder *HKDF) DeriveKeyWriteTo(new []byte) error {
n, err := io.ReadFull(keyder.reader, new)
func (keyder *HKDF) DeriveKeyWriteTo(newKey []byte) error {
n, err := io.ReadFull(keyder.reader, newKey)
if err != nil {
return fmt.Errorf("failed to generate key: %s", err)
return fmt.Errorf("failed to generate key: %w", err)
}
if n != len(new) {
if n != len(newKey) {
return errors.New("failed to generate key: EOF")
}
return nil

View file

@ -4,9 +4,9 @@ import (
"crypto/sha256"
"hash"
"github.com/safing/jess/tools"
"golang.org/x/crypto/pbkdf2"
"github.com/safing/jess/tools"
)
func init() {

View file

@ -3,8 +3,9 @@ package gostdlib
import (
"errors"
"golang.org/x/crypto/poly1305" //nolint:staticcheck // TODO: replace with newer package
"github.com/safing/jess/tools"
"golang.org/x/crypto/poly1305"
)
func init() {

View file

@ -8,8 +8,7 @@ import (
"math/big"
"github.com/safing/jess/tools"
"github.com/safing/portbase/container"
"github.com/safing/structures/container"
)
type rsaBase struct {
@ -163,7 +162,7 @@ func (base *rsaBase) SecurityLevel(signet tools.SignetInt) (int, error) {
if pubkey == nil {
err := signet.LoadKey()
if err != nil {
return 0, fmt.Errorf("failed to load key to calculate security level: %s", err)
return 0, fmt.Errorf("failed to load key to calculate security level: %w", err)
}
pubkey = signet.PublicKey()
}

View file

@ -2,6 +2,7 @@ package gostdlib
import (
"crypto/rsa"
"errors"
"github.com/safing/jess/tools"
)
@ -38,11 +39,14 @@ func (pss *RsaPSS) Sign(data, associatedData []byte, signet tools.SignetInt) ([]
if err != nil {
return nil, err
}
if pss.HashTool().CryptoHashID == 0 {
return nil, errors.New("tool PSS is only compatible with Golang crypto.Hash hash functions")
}
return rsa.SignPSS(
pss.Helper().Random(),
rsaPrivKey,
pss.HashTool().Hash,
pss.HashTool().CryptoHashID,
hashsum,
nil, // *rsa.PSSOptions
)
@ -59,10 +63,13 @@ func (pss *RsaPSS) Verify(data, associatedData, signature []byte, signet tools.S
if err != nil {
return err
}
if pss.HashTool().CryptoHashID == 0 {
return errors.New("tool PSS is only compatible with Golang crypto.Hash hash functions")
}
return rsa.VerifyPSS(
rsaPubKey,
pss.HashTool().Hash,
pss.HashTool().CryptoHashID,
hashsum,
signature,
nil, // *rsa.PSSOptions

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