Author SHA1 Message Date
GitHub Actions b662811909 doc: regenerate groff and html man pages 2026-08-28 21:20:09 +00:00
Filippo Valsorda 5f603b5057 all: update dependencies and GitHub Actions
Closes #736
Fixes #731
2026-08-28 23:16:03 +02:00
Filippo Valsorda a68b8e921b README: add dependencies badge 2026-08-28 23:06:33 +02:00
Steve WeisandClaude 706dfc1e79 all: minor cleanups (#700)
Replace two hand-rolled slicesEqual helpers with slices.Equal from
the standard library, which is already imported in age.go.
(age.go:211-221, plugin/plugin.go:666-676)

Fix godoc link typo [Indentity] -> [Identity].
(plugin/plugin.go:3)

Fix comment typo "not MinPayload" -> "so MinPayload".
(internal/inspect/inspect.go:25)

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-20 22:12:32 +01:00
Steve WeisandClaude 4c86929a84 internal/format: return correct byte count from writeWrapped (#698)
Co-authored-by: Claude <noreply@anthropic.com>
2026-03-20 22:02:09 +01:00
Steve WeisandClaude db09cd66d1 internal/inspect: fix tag stanza type names in post-quantum classifier (#695)
Co-authored-by: Claude <noreply@anthropic.com>
2026-03-20 21:49:27 +01:00
Filippo Valsorda 4a3a4ef00a SIGSUM.md: extend release playbook 2026-02-02 17:31:01 +01:00
Filippo Valsorda 10561a774f .github/workflows: remove one-off darwin/amd64 backfill workflow
Updates #676
2026-01-21 18:56:39 +01:00
Filippo Valsorda c918a2c113 .github/workflows: add darwin/amd64 backfill workflow
Updates #676
2026-01-21 18:49:52 +01:00
Filippo Valsorda beb5dfa28c .github/workflows: add darwin/amd64 build target
Closes #676
2026-01-21 18:49:46 +01:00
Filippo Valsorda 0293aca1d7 SIGSUM.md: add release playbook
Updates #617
2025-12-31 16:19:37 +01:00
GitHub Actions e7601d8a67 doc: regenerate groff and html man pages 2025-12-31 11:05:09 +00:00
brettwhiteinc acfa73142b doc: fix post-quantum example in age-keygen manpage (#675) 2025-12-31 12:03:49 +01:00
Filippo Valsorda b8564adb6d .github/workflows: inject version into source release artifact
Updates #671
Updates golang/go#77020
2025-12-28 13:00:23 +01:00
Filippo Valsorda e4c611f778 cmd,extra: restore the Version link-time variable
We don't need it in our builds, but it's useful for downstream packagers.

Fixes #671
Updates NixOS/nixpkgs#474666
Updates golang/go#77020
2025-12-28 12:49:37 +01:00
28 changed files with 234 additions and 116 deletions

No files matched your search

+14 -8
View File
@@ -17,16 +17,17 @@ jobs:
- {GOOS: linux, GOARCH: arm, GOARM: 6}
- {GOOS: linux, GOARCH: arm64}
- {GOOS: darwin, GOARCH: arm64}
- {GOOS: darwin, GOARCH: amd64}
- {GOOS: windows, GOARCH: amd64}
- {GOOS: freebsd, GOARCH: amd64}
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache: false
@@ -61,7 +62,7 @@ jobs:
GOARCH: ${{ matrix.GOARCH }}
GOARM: ${{ matrix.GOARM }}
- name: Upload workflow artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: age-artifacts-${{ matrix.GOOS }}-${{ matrix.GOARCH }}
path: age-*
@@ -70,12 +71,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v7
with:
go-version-file: go.mod
cache: false
@@ -86,9 +87,14 @@ jobs:
mkdir "$DIR/age"
git archive --format=tar.gz HEAD | tar -xz -C "$DIR/age"
( cd "$DIR/age"; go mod vendor )
for cmd in "$DIR"/age/{cmd,extra}/*; do
echo "package main" >> "$cmd/version.go"
echo "" >> "$cmd/version.go"
echo "func init() { Version = \"$VERSION\" }" >> "$cmd/version.go"
done
tar -cvzf "age-$VERSION-source.tar.gz" -C "$DIR" age
- name: Upload workflow artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: age-artifacts-source
path: age-*-source.tar.gz
@@ -103,12 +109,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Download workflow artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
pattern: age-artifacts-*
merge-multiple: true
- name: Generate artifacts attestation
uses: actions/attest-build-provenance@v3
uses: actions/attest-build-provenance@v4
with:
subject-path: age-*
- name: Upload release artifacts
+5 -5
View File
@@ -13,10 +13,10 @@ jobs:
name: Ronn
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: geomys/sandboxed-step@v1.2.1
- uses: geomys/sandboxed-step@v1.2.2
with:
persist-workspace-changes: true
run: |
@@ -29,7 +29,7 @@ jobs:
awk '/Filippo Valsorda/ { $0 = "<p>Filippo Valsorda <a href=\"mailto:age@filippo.io\" data-bare-link=\"true\">age@filippo.io</a></p>" } { print }' "$f" > "$f.tmp"
mv "$f.tmp" "$f"
done
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
with:
name: man-pages
path: |
@@ -42,10 +42,10 @@ jobs:
contents: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v7
with:
persist-credentials: true
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v8
with:
name: man-pages
path: doc/
+11 -11
View File
@@ -21,10 +21,10 @@ jobs:
- windows-latest
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-go@v6
- uses: actions/setup-go@v7
with:
go-version: ${{ matrix.go.go-version }}
- run: |
@@ -38,13 +38,13 @@ jobs:
- { go-version: stable }
- { go-version: oldstable }
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-go@v6
- uses: actions/setup-go@v7
with:
go-version: ${{ matrix.go.go-version }}
- uses: geomys/sandboxed-step@v1.2.1
- uses: geomys/sandboxed-step@v1.2.2
with:
run: |
go get -u -t ./...
@@ -52,24 +52,24 @@ jobs:
staticcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-go@v6
- uses: actions/setup-go@v7
with:
go-version: stable
- uses: geomys/sandboxed-step@v1.2.1
- uses: geomys/sandboxed-step@v1.2.2
with:
run: go run honnef.co/go/tools/cmd/staticcheck@latest ./...
govulncheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-go@v6
- uses: actions/setup-go@v7
with:
go-version: stable
- uses: geomys/sandboxed-step@v1.2.1
- uses: geomys/sandboxed-step@v1.2.2
with:
run: go run golang.org/x/vuln/cmd/govulncheck@latest ./...
+2 -1
View File
@@ -7,6 +7,7 @@
</p>
[![Go Reference](https://pkg.go.dev/badge/filippo.io/age.svg)](https://pkg.go.dev/filippo.io/age)
[![Dependencies](https://badges.api.sourcespotter.com/deps?firstparty=filippo.io&package=filippo.io%2Fage)](https://sourcespotter.com/deps/?firstparty=filippo.io&package=filippo.io%2Fage)
[![man page](<https://img.shields.io/badge/age(1)-man%20page-lightgrey>)](https://filippo.io/age/age.1)
[![C2SP specification](https://img.shields.io/badge/%C2%A7%23-specification-blueviolet)](https://age-encryption.org/v1)
@@ -151,7 +152,7 @@ On Windows, Linux, macOS, and FreeBSD you can use the pre-built binaries.
```
https://dl.filippo.io/age/latest?for=linux/amd64
https://dl.filippo.io/age/v1.3.0?for=darwin/arm64
https://dl.filippo.io/age/v1.3.1?for=darwin/arm64
...
```
+23 -3
View File
@@ -11,13 +11,33 @@ ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM1WpnEswJLPzvXJDiswowy48U+G+G1kmgwUE2eaRHZG
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAz2WM5CyPLqiNjk7CLl4roDXwKhQ0QExXLebukZEZFS
EOF
curl -JLO "https://dl.filippo.io/age/v1.3.0?for=darwin/arm64"
curl -JLO "https://dl.filippo.io/age/v1.3.0?for=darwin/arm64&proof"
curl -JLO "https://dl.filippo.io/age/v1.3.1?for=darwin/arm64"
curl -JLO "https://dl.filippo.io/age/v1.3.1?for=darwin/arm64&proof"
go install sigsum.org/sigsum-go/cmd/sigsum-verify@v0.13.1
sigsum-verify -k age-sigsum-key.pub -P sigsum-generic-2025-1 \
age-v1.3.0-darwin-arm64.tar.gz.proof < age-v1.3.0-darwin-arm64.tar.gz
age-v1.3.1-darwin-arm64.tar.gz.proof < age-v1.3.1-darwin-arm64.tar.gz
```
You can learn more about what's happening above in the [Sigsum
docs](https://www.sigsum.org/getting-started/).
### Release playbook
Dear future me, to sign a new release and produce Sigsum proofs, run the following
```
VERSION=v1.3.1
go install sigsum.org/sigsum-go/cmd/sigsum-verify@latest
go install github.com/tillitis/tkey-ssh-agent/cmd/tkey-ssh-agent@main
tkey-ssh-agent --agent-socket tkey-ssh-agent.sock --uss
passage -c other/tkey-ssh-sigsum-age
SSH_AUTH_SOCK=tkey-ssh-agent.sock ssh-add -L > tkey-ssh-agent.pub
passage other/sigsum-ratelimit > sigsum-ratelimit
gh release download $VERSION --repo FiloSottile/age --dir artifacts/
SSH_AUTH_SOCK=tkey-ssh-agent.sock sigsum-submit -k tkey-ssh-agent.pub -P sigsum-generic-2025-1 -a sigsum-ratelimit -d filippo.io artifacts/*
gh release upload $VERSION --repo FiloSottile/age artifacts/*.proof
```
In the future, we will move to reproducing the artifacts locally, and signing
those instead of the ones built by GitHub Actions.
+1 -13
View File
@@ -130,7 +130,7 @@ func encryptHdr(fileKey []byte, recipients ...Recipient) (*format.Header, error)
sort.Strings(l)
if i == 0 {
labels = l
} else if !slicesEqual(labels, l) {
} else if !slices.Equal(labels, l) {
return nil, incompatibleLabelsError(labels, l)
}
for _, s := range stanzas {
@@ -208,18 +208,6 @@ func wrapWithLabels(r Recipient, fileKey []byte) (s []*Stanza, labels []string,
return
}
func slicesEqual(s1, s2 []string) bool {
if len(s1) != len(s2) {
return false
}
for i := range s1 {
if s1[i] != s2[i] {
return false
}
}
return true
}
func incompatibleLabelsError(l1, l2 []string) error {
hasPQ1 := slices.Contains(l1, "postquantum")
hasPQ2 := slices.Contains(l2, "postquantum")
+7 -4
View File
@@ -24,6 +24,10 @@ Options:
INPUT defaults to standard input. "-" may be used as INPUT to explicitly
read from standard input.`
// Version can be set at link time to override debug.BuildInfo.Main.Version when
// building manually without git history. It should look like "v1.2.3".
var Version string
func main() {
flag.Usage = func() { fmt.Fprintf(os.Stderr, "%s\n", usage) }
@@ -37,11 +41,10 @@ func main() {
flag.Parse()
if versionFlag {
if buildInfo, ok := debug.ReadBuildInfo(); ok {
fmt.Println(buildInfo.Main.Version)
return
if buildInfo, ok := debug.ReadBuildInfo(); ok && Version == "" {
Version = buildInfo.Main.Version
}
fmt.Println("(unknown)")
fmt.Println(Version)
return
}
+13 -8
View File
@@ -56,6 +56,10 @@ Examples:
$ age-keygen -y key.txt
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p`
// Version can be set at link time to override debug.BuildInfo.Main.Version when
// building manually without git history. It should look like "v1.2.3".
var Version string
func main() {
log.SetFlags(0)
flag.Usage = func() { fmt.Fprintf(os.Stderr, "%s\n", usage) }
@@ -69,6 +73,15 @@ func main() {
flag.StringVar(&outFlag, "o", "", "output to `FILE` (default stdout)")
flag.StringVar(&outFlag, "output", "", "output to `FILE` (default stdout)")
flag.Parse()
if versionFlag {
if buildInfo, ok := debug.ReadBuildInfo(); ok && Version == "" {
Version = buildInfo.Main.Version
}
fmt.Println(Version)
return
}
if len(flag.Args()) != 0 && !convertFlag {
errorf("too many arguments")
}
@@ -78,14 +91,6 @@ func main() {
if pqFlag && convertFlag {
errorf("-pq cannot be used with -y")
}
if versionFlag {
if buildInfo, ok := debug.ReadBuildInfo(); ok {
fmt.Println(buildInfo.Main.Version)
return
}
fmt.Println("(unknown)")
return
}
out := os.Stdout
if outFlag != "" {
@@ -7,6 +7,7 @@ import (
"io"
"log"
"os"
"runtime/debug"
"strconv"
"strings"
@@ -73,6 +74,10 @@ When decrypting, you can set AGE_PASSPHRASE_MAX_WORK_FACTOR to limit the
maximum scrypt work factor accepted (between 1 and 30, default 30). This can
be used to avoid very slow decryptions.`
// Version can be set at link time to override debug.BuildInfo.Main.Version when
// building manually without git history. It should look like "v1.2.3".
var Version string
func main() {
flag.Usage = func() { fmt.Fprintf(os.Stderr, "%s\n", usage) }
@@ -80,6 +85,19 @@ func main() {
if err != nil {
log.Fatal(err)
}
p.RegisterFlags(nil)
versionFlag := flag.Bool("version", false, "print the version")
flag.Parse()
if *versionFlag {
if buildInfo, ok := debug.ReadBuildInfo(); ok && Version == "" {
Version = buildInfo.Main.Version
}
fmt.Println(Version)
return
}
p.HandleIdentityAsRecipient(func(data []byte) (age.Recipient, error) {
if len(data) != 0 {
return nil, fmt.Errorf("batchpass identity does not take any payload")
+7 -6
View File
@@ -98,6 +98,10 @@ func (f *identityFlags) addPluginFlag(value string) error {
return nil
}
// Version can be set at link time to override debug.BuildInfo.Main.Version when
// building manually without git history. It should look like "v1.2.3".
var Version string
func main() {
flag.Usage = func() { fmt.Fprintf(os.Stderr, "%s\n", usage) }
@@ -136,13 +140,10 @@ func main() {
flag.Parse()
if versionFlag {
if buildInfo, ok := debug.ReadBuildInfo(); ok {
// TODO: use buildInfo.Settings to prepare a pseudoversion such as
// v0.0.0-20210817164053-32db794688a5+dirty on Go 1.18+.
fmt.Println(buildInfo.Main.Version)
return
if buildInfo, ok := debug.ReadBuildInfo(); ok && Version == "" {
Version = buildInfo.Main.Version
}
fmt.Println("(unknown)")
fmt.Println(Version)
return
}
+1 -1
View File
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.9.1
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
.TH "AGE\-INSPECT" "1" "December 2025" ""
.TH "AGE\-INSPECT" "1" "August 2026" ""
.SH "NAME"
\fBage\-inspect\fR \- inspect age(1) encrypted files
.SH "SYNOPSIS"
+1 -1
View File
@@ -195,7 +195,7 @@ Tip: for machine-readable output, use --json.
<ol class='man-decor man-foot man foot'>
<li class='tl'></li>
<li class='tc'>December 2025</li>
<li class='tc'>August 2026</li>
<li class='tr'>age-inspect(1)</li>
</ol>
+2 -2
View File
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.9.1
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
.TH "AGE\-KEYGEN" "1" "December 2025" ""
.TH "AGE\-KEYGEN" "1" "August 2026" ""
.SH "NAME"
\fBage\-keygen\fR \- generate age(1) key pairs
.SH "SYNOPSIS"
@@ -53,7 +53,7 @@ AGE\-SECRET\-KEY\-1N9JEPW6DWJ0ZQUDX63F5A03GX8QUW7PXDE39N8UYF82VZ9PC8UFS3M7XA9
Write a new post\-quantum identity to \fBkey\.txt\fR:
.IP "" 4
.nf
$ age\-keygen \-o key\.txt
$ age\-keygen \-pq \-o key\.txt
Public key: age1pq1cd[\|\.\|\.\|\. 1950 more characters \|\.\|\.\|\.]
.fi
.IP "" 0
+2 -2
View File
@@ -130,7 +130,7 @@ AGE-SECRET-KEY-1N9JEPW6DWJ0ZQUDX63F5A03GX8QUW7PXDE39N8UYF82VZ9PC8UFS3M7XA9
<p>Write a new post-quantum identity to <code>key.txt</code>:</p>
<pre><code>$ age-keygen -o key.txt
<pre><code>$ age-keygen -pq -o key.txt
Public key: age1pq1cd[... 1950 more characters ...]
</code></pre>
@@ -150,7 +150,7 @@ age1pq1cd[... 1950 more characters ...]
<ol class='man-decor man-foot man foot'>
<li class='tl'></li>
<li class='tc'>December 2025</li>
<li class='tc'>August 2026</li>
<li class='tr'>age-keygen(1)</li>
</ol>
+1 -1
View File
@@ -52,7 +52,7 @@ Generate a new traditional identity:
Write a new post-quantum identity to `key.txt`:
$ age-keygen -o key.txt
$ age-keygen -pq -o key.txt
Public key: age1pq1cd[... 1950 more characters ...]
Convert an identity to a recipient:
+1 -1
View File
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.9.1
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
.TH "AGE\-PLUGIN\-BATCHPASS" "1" "December 2025" ""
.TH "AGE\-PLUGIN\-BATCHPASS" "1" "August 2026" ""
.SH "NAME"
\fBage\-plugin\-batchpass\fR \- non\-interactive passphrase encryption plugin for age(1)
.SH "SYNOPSIS"
+1 -1
View File
@@ -174,7 +174,7 @@ persisted in the shell history or leaked to other users on multi-user systems.</
<ol class='man-decor man-foot man foot'>
<li class='tl'></li>
<li class='tc'>December 2025</li>
<li class='tc'>August 2026</li>
<li class='tr'>age-plugin-batchpass(1)</li>
</ol>
+1 -1
View File
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.9.1
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
.TH "AGE" "1" "December 2025" ""
.TH "AGE" "1" "August 2026" ""
.SH "NAME"
\fBage\fR \- simple, modern, and secure file encryption
.SH "SYNOPSIS"
+1 -1
View File
@@ -461,7 +461,7 @@ $ age -d -i age-yubikey-identity-388178f3.txt secrets.txt.age
<ol class='man-decor man-foot man foot'>
<li class='tl'></li>
<li class='tc'>December 2025</li>
<li class='tc'>August 2026</li>
<li class='tr'>age(1)</li>
</ol>
+8 -5
View File
@@ -30,12 +30,16 @@ implementation of age that supports plugins.
Recipients work out of the box, while identities need to be converted to plugin
identities with -identity. If OUTPUT already exists, it is not overwritten.`
// Version can be set at link time to override debug.BuildInfo.Main.Version when
// building manually without git history. It should look like "v1.2.3".
var Version string
func main() {
log.SetFlags(0)
p, err := plugin.New("pq")
if err != nil {
log.Fatal(err)
errorf("failed to create plugin: %v", err)
}
p.RegisterFlags(nil)
@@ -50,11 +54,10 @@ func main() {
flag.Parse()
if versionFlag {
if buildInfo, ok := debug.ReadBuildInfo(); ok {
fmt.Println(buildInfo.Main.Version)
return
if buildInfo, ok := debug.ReadBuildInfo(); ok && Version == "" {
Version = buildInfo.Main.Version
}
fmt.Println("(unknown)")
fmt.Println(Version)
return
}
+19
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"log"
"os"
"runtime/debug"
"filippo.io/age"
"filippo.io/age/plugin"
@@ -18,6 +19,10 @@ support to any version and implementation of age that supports plugins.
Usually, tagged recipients are the public side of private keys held in hardware,
where the identity side is handled by a different plugin.`
// Version can be set at link time to override debug.BuildInfo.Main.Version when
// building manually without git history. It should look like "v1.2.3".
var Version string
func main() {
flag.Usage = func() { fmt.Fprintf(os.Stderr, "%s\n", usage) }
@@ -25,8 +30,22 @@ func main() {
if err != nil {
log.Fatal(err)
}
p.RegisterFlags(nil)
versionFlag := flag.Bool("version", false, "print the version")
flag.Parse()
if *versionFlag {
if buildInfo, ok := debug.ReadBuildInfo(); ok && Version == "" {
Version = buildInfo.Main.Version
}
fmt.Println(Version)
return
}
p.HandleRecipient(func(b []byte) (age.Recipient, error) {
return tag.NewClassicRecipient(b)
})
os.Exit(p.Main())
}
+19
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"log"
"os"
"runtime/debug"
"filippo.io/age"
"filippo.io/age/plugin"
@@ -19,6 +20,10 @@ implementation of age that supports plugins.
Usually, tagged recipients are the public side of private keys held in hardware,
where the identity side is handled by a different plugin.`
// Version can be set at link time to override debug.BuildInfo.Main.Version when
// building manually without git history. It should look like "v1.2.3".
var Version string
func main() {
flag.Usage = func() { fmt.Fprintf(os.Stderr, "%s\n", usage) }
@@ -26,8 +31,22 @@ func main() {
if err != nil {
log.Fatal(err)
}
p.RegisterFlags(nil)
versionFlag := flag.Bool("version", false, "print the version")
flag.Parse()
if *versionFlag {
if buildInfo, ok := debug.ReadBuildInfo(); ok && Version == "" {
Version = buildInfo.Main.Version
}
fmt.Println(Version)
return
}
p.HandleRecipient(func(b []byte) (age.Recipient, error) {
return tag.NewHybridRecipient(b)
})
os.Exit(p.Main())
}
+9 -9
View File
@@ -1,22 +1,22 @@
module filippo.io/age
go 1.24.0
go 1.25.0
// Release build version.
toolchain go1.25.5
toolchain go1.27.0
require (
filippo.io/edwards25519 v1.1.0
filippo.io/edwards25519 v1.2.0
filippo.io/hpke v0.4.0
filippo.io/nistec v0.0.4
golang.org/x/crypto v0.45.0
golang.org/x/sys v0.38.0
golang.org/x/term v0.37.0
golang.org/x/crypto v0.55.0
golang.org/x/sys v0.47.0
golang.org/x/term v0.45.0
)
// Test dependencies.
require (
c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd
github.com/rogpeppe/go-internal v1.14.1
golang.org/x/tools v0.39.0 // indirect
c2sp.org/CCTV/age v0.0.0-20260605191406-1e3d2860d46e
github.com/rogpeppe/go-internal v1.16.0
golang.org/x/tools v0.49.0 // indirect
)
+14 -14
View File
@@ -1,18 +1,18 @@
c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd h1:ZLsPO6WdZ5zatV4UfVpr7oAwLGRZ+sebTUruuM4Ra3M=
c2sp.org/CCTV/age v0.0.0-20251208015420-e9274a7bdbfd/go.mod h1:SrHC2C7r5GkDk8R+NFVzYy/sdj0Ypg9htaPXQq5Cqeo=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
c2sp.org/CCTV/age v0.0.0-20260605191406-1e3d2860d46e h1:N9DpijOhcuCLIlgcX/6qhBQeMQIlBxsVQITjbpJaBso=
c2sp.org/CCTV/age v0.0.0-20260605191406-1e3d2860d46e/go.mod h1:SrHC2C7r5GkDk8R+NFVzYy/sdj0Ypg9htaPXQq5Cqeo=
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
filippo.io/hpke v0.4.0 h1:p575VVQ6ted4pL+it6M00V/f2qTZITO0zgmdKCkd5+A=
filippo.io/hpke v0.4.0/go.mod h1:EmAN849/P3qdeK+PCMkDpDm83vRHM5cDipBJ8xbQLVY=
filippo.io/nistec v0.0.4 h1:F14ZHT5htWlMnQVPndX9ro9arf56cBhQxq4LnDI491s=
filippo.io/nistec v0.0.4/go.mod h1:PK/lw8I1gQT4hUML4QGaqljwdDaFcMyFKSXN7kjrtKI=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g=
github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
+2 -1
View File
@@ -76,6 +76,7 @@ func (w *WrappedBase64Encoder) writeWrapped(p []byte) (int, error) {
if w.buf.Len() != 0 {
panic("age: internal error: non-empty WrappedBase64Encoder.buf")
}
total := len(p)
for len(p) > 0 {
toWrite := min(ColumnsPerLine-(w.written%ColumnsPerLine), len(p))
n, _ := w.buf.Write(p[:toWrite])
@@ -91,7 +92,7 @@ func (w *WrappedBase64Encoder) writeWrapped(p []byte) (int, error) {
// are not recoverable anyway.
return 0, err
}
return len(p), nil
return total, nil
}
// LastLineIsEmpty returns whether the last output line was empty, either
+3 -3
View File
@@ -21,7 +21,7 @@ type Metadata struct {
Header int64 `json:"header"`
Armor int64 `json:"armor"`
Overhead int64 `json:"overhead"`
// Currently, we don't do any padding, not MinPayload == MaxPayload and
// Currently, we don't do any padding, so MinPayload == MaxPayload and
// MinPadding == MaxPadding == 0, but that might change in the future.
MinPayload int64 `json:"min_payload"`
MaxPayload int64 `json:"max_payload"`
@@ -61,9 +61,9 @@ func Inspect(r io.Reader, fileSize int64) (*Metadata, error) {
for _, s := range hdr.Recipients {
data.StanzaTypes = append(data.StanzaTypes, s.Type)
switch s.Type {
case "X25519", "ssh-rsa", "ssh-ed25519", "age-encryption.org/p256tag", "piv-p256":
case "X25519", "ssh-rsa", "ssh-ed25519", "p256tag", "piv-p256":
data.Postquantum = "no"
case "mlkem768x25519", "scrypt", "age-encryption.org/mlkem768p256tag":
case "mlkem768x25519", "scrypt", "mlkem768p256tag":
if data.Postquantum != "no" {
data.Postquantum = "yes"
}
+45
View File
@@ -1,12 +1,57 @@
package inspect
import (
"bytes"
"fmt"
"testing"
"filippo.io/age/internal/format"
"filippo.io/age/internal/stream"
)
// buildFile serializes a header with a single stanza of the given type,
// followed by the minimal valid encrypted payload (a 16-byte stream nonce
// and a single empty ChaCha20-Poly1305 chunk).
func buildFile(t *testing.T, stanzaType string) []byte {
t.Helper()
hdr := &format.Header{
Recipients: []*format.Stanza{{Type: stanzaType}},
MAC: make([]byte, 32),
}
buf := &bytes.Buffer{}
if err := hdr.Marshal(buf); err != nil {
t.Fatalf("Header.Marshal: %v", err)
}
// Append nonce (16 bytes) + poly1305 tag for empty chunk (16 bytes).
buf.Write(make([]byte, 16+16))
return buf.Bytes()
}
func TestInspectTagStanzas(t *testing.T) {
tests := []struct {
stanzaType string
want string
}{
{stanzaType: "p256tag", want: "no"},
{stanzaType: "mlkem768p256tag", want: "yes"},
}
for _, tt := range tests {
t.Run(tt.stanzaType, func(t *testing.T) {
f := buildFile(t, tt.stanzaType)
md, err := Inspect(bytes.NewReader(f), int64(len(f)))
if err != nil {
t.Fatalf("Inspect: %v", err)
}
if got := md.Postquantum; got != tt.want {
t.Errorf("Postquantum = %q, want %q", got, tt.want)
}
if len(md.StanzaTypes) != 1 || md.StanzaTypes[0] != tt.stanzaType {
t.Errorf("StanzaTypes = %v, want [%q]", md.StanzaTypes, tt.stanzaType)
}
})
}
}
func TestStreamOverhead(t *testing.T) {
tests := []struct {
payloadSize int64
+3 -14
View File
@@ -1,6 +1,6 @@
// Package plugin implements the age plugin protocol.
//
// [Recipient] and [Indentity] are plugin clients, that execute plugin binaries to
// [Recipient] and [Identity] are plugin clients, that execute plugin binaries to
// perform encryption and decryption operations.
//
// [Plugin] is a framework for writing age plugins, that exposes an [age.Recipient]
@@ -14,6 +14,7 @@ import (
"fmt"
"io"
"os"
"slices"
"strconv"
"filippo.io/age"
@@ -358,7 +359,7 @@ func wrapWithLabels(r age.Recipient, fileKey []byte) ([]*age.Stanza, []string, e
}
func checkLabels(ll, labels []string) error {
if !slicesEqual(ll, labels) {
if !slices.Equal(ll, labels) {
return fmt.Errorf("labels %q do not match previous recipients %q", ll, labels)
}
return nil
@@ -662,15 +663,3 @@ func (p *Plugin) writeError(args []string, err error) error {
}
return nil
}
func slicesEqual(s1, s2 []string) bool {
if len(s1) != len(s2) {
return false
}
for i := range s1 {
if s1[i] != s2[i] {
return false
}
}
return true
}