mirror of
https://github.com/FiloSottile/age.git
synced 2026-01-14 15:32:48 +00:00
Compare commits
179 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbe6ce5eeb | ||
|
|
1e1badabf7 | ||
|
|
2293a9afef | ||
|
|
01fe9cd84a | ||
|
|
bd0511b415 | ||
|
|
febaaded87 | ||
|
|
0a40718a93 | ||
|
|
7ed486868a | ||
|
|
2a761fcb8c | ||
|
|
98e7afcbac | ||
|
|
5ef63b6153 | ||
|
|
bc21ece498 | ||
|
|
69c21b83fb | ||
|
|
35cf02b1d0 | ||
|
|
29b68c20fc | ||
|
|
101cc86763 | ||
|
|
6ad4560f4a | ||
|
|
93055632ad | ||
|
|
294b0aa1e3 | ||
|
|
f1f96c25e0 | ||
|
|
9fd564d543 | ||
|
|
c89f0b932e | ||
|
|
dd733c5c0f | ||
|
|
004b544d83 | ||
|
|
02181d83e9 | ||
|
|
6976c5fca5 | ||
|
|
980763a16e | ||
|
|
4740a92ef9 | ||
|
|
6c36e167c8 | ||
|
|
9f0a2d25ac | ||
|
|
b6537b1865 | ||
|
|
486b6dac96 | ||
|
|
877ca247e3 | ||
|
|
502b180b17 | ||
|
|
8e3f74c283 | ||
|
|
edf7388f77 | ||
|
|
5471e05672 | ||
|
|
c6dcfa1efc | ||
|
|
a1fabee4c8 | ||
|
|
7354aa0d08 | ||
|
|
bf8d2a3911 | ||
|
|
5d5c9c48d8 | ||
|
|
e05ce267a6 | ||
|
|
000e931101 | ||
|
|
ebf48f1bbc | ||
|
|
3f2209cab9 | ||
|
|
bbac0a501c | ||
|
|
02a01c6a6f | ||
|
|
8328d19d3e | ||
|
|
1f84a45175 | ||
|
|
084c974f53 | ||
|
|
36ae5671cf | ||
|
|
8a02f4801f | ||
|
|
e41463e117 | ||
|
|
f66877cfa5 | ||
|
|
891be91d42 | ||
|
|
a5d38ae6ce | ||
|
|
799c2bf8e8 | ||
|
|
e84d74239e | ||
|
|
95ba0188d1 | ||
|
|
0ab5c738fb | ||
|
|
de7c1fb565 | ||
|
|
92fb4d508c | ||
|
|
331b242a9c | ||
|
|
c50f1ae2e1 | ||
|
|
8023f06ce2 | ||
|
|
4f7bb44386 | ||
|
|
3f56ac13fb | ||
|
|
2e090545df | ||
|
|
2088adf268 | ||
|
|
eaa4e03cfe | ||
|
|
f8a121dd87 | ||
|
|
bb4493a7cd | ||
|
|
92713afd1e | ||
|
|
787044bdb6 | ||
|
|
a787511e01 | ||
|
|
e49b1f9afe | ||
|
|
78bedc2873 | ||
|
|
f4112110f1 | ||
|
|
acb1170bbc | ||
|
|
56f6acca37 | ||
|
|
5824a13b57 | ||
|
|
c0e80ef2c9 | ||
|
|
eeb9a079da | ||
|
|
fbe51d501c | ||
|
|
87a982b72e | ||
|
|
5a0da177e9 | ||
|
|
349ed5ed3f | ||
|
|
384d0393e0 | ||
|
|
7bad9c2ad8 | ||
|
|
01b56b117c | ||
|
|
f6a5b94705 | ||
|
|
cff70cffe2 | ||
|
|
73416d1ac5 | ||
|
|
30d8e65e03 | ||
|
|
765400f0c1 | ||
|
|
cb539f931c | ||
|
|
d6c77e0838 | ||
|
|
ac31f5c935 | ||
|
|
e8771b6d8a | ||
|
|
d8fa2fb0de | ||
|
|
3e1aa11e9d | ||
|
|
f7fcbef542 | ||
|
|
299ec50c32 | ||
|
|
2e20ca5fa7 | ||
|
|
d717942b93 | ||
|
|
3b4fb67296 | ||
|
|
f01e37b4d7 | ||
|
|
3411802309 | ||
|
|
4169274d04 | ||
|
|
e4ae4cf884 | ||
|
|
ab3707c085 | ||
|
|
7665b87dc2 | ||
|
|
08f52cc125 | ||
|
|
a21c212358 | ||
|
|
dfa2052cb7 | ||
|
|
0c41827056 | ||
|
|
3432b88db9 | ||
|
|
e2fc716c8b | ||
|
|
3bd9ab8e9b | ||
|
|
4e1d7631e5 | ||
|
|
1c95ceae09 | ||
|
|
be70dba53e | ||
|
|
50c2f22ba7 | ||
|
|
878682b574 | ||
|
|
3d7a7ff0b8 | ||
|
|
776e1780a9 | ||
|
|
427edf35cc | ||
|
|
8d88096476 | ||
|
|
5cad72c62e | ||
|
|
fd1b393f9c | ||
|
|
79211ba255 | ||
|
|
552aa0a07d | ||
|
|
47d8133c52 | ||
|
|
36b0a4f611 | ||
|
|
fda89073cd | ||
|
|
7756fbfe45 | ||
|
|
57f6b8acae | ||
|
|
e08055f4e5 | ||
|
|
7cb6b84758 | ||
|
|
4ea591b25f | ||
|
|
9d4b2ae7ac | ||
|
|
1ddf01df2c | ||
|
|
f4e28fe809 | ||
|
|
0703f86521 | ||
|
|
fb97277f8d | ||
|
|
fa5b575ceb | ||
|
|
cde103daae | ||
|
|
b403e96be8 | ||
|
|
329a7ece8f | ||
|
|
3cd503dce9 | ||
|
|
cd28d56599 | ||
|
|
a94f3c3dc9 | ||
|
|
6596145a2c | ||
|
|
7a262e1ffd | ||
|
|
0b895a9340 | ||
|
|
c9aca162ef | ||
|
|
c7c3012437 | ||
|
|
e58a8859b9 | ||
|
|
fb293ef526 | ||
|
|
3d5b49a348 | ||
|
|
cd4b2476bc | ||
|
|
759a88d3e8 | ||
|
|
85763d390a | ||
|
|
fff82986fa | ||
|
|
67ce088a41 | ||
|
|
3ad0bbed99 | ||
|
|
7a55783693 | ||
|
|
d271e916cf | ||
|
|
50254ff522 | ||
|
|
7a335c9d5d | ||
|
|
ff1b4ffb08 | ||
|
|
e63c22e327 | ||
|
|
a6a173e24f | ||
|
|
b4e0d7718f | ||
|
|
9e65644c3f | ||
|
|
290a2fd5ec | ||
|
|
bad2c0d2e0 | ||
|
|
dabc470bfe |
@@ -1,9 +0,0 @@
|
||||
env:
|
||||
CIRRUS_CLONE_DEPTH: 1
|
||||
|
||||
freebsd_12_task:
|
||||
freebsd_instance:
|
||||
image: freebsd-12-1-release-amd64
|
||||
install_script: pkg install -y go
|
||||
build_script: go build -v ./...
|
||||
test_script: go test -race ./...
|
||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1 +1,2 @@
|
||||
*.age binary
|
||||
testdata/testkit/* binary
|
||||
|
||||
34
.github/CONTRIBUTING.md
vendored
Normal file
34
.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
## Issues
|
||||
|
||||
I want to hear about any issues you encounter while using age.
|
||||
|
||||
Particularly appreciated are well researched, complete [issues](https://github.com/FiloSottile/age/issues/new/choose) with lots of context, **focusing on the intended outcome and/or use case**. Issues don't have to be just about bugs: if something was hard to figure out or unexpected, please file a **[UX report](https://github.com/FiloSottile/age/discussions/new?category=UX-reports)**! ✨
|
||||
|
||||
Not all issue reports might lead to a change, so please don't be offended if yours doesn't, but they are precious datapoints to understand how age could work better in aggregate.
|
||||
|
||||
## Pull requests
|
||||
|
||||
age is a little unusual in how it is maintained. I like to keep the code style consistent and complexity to a minimum, and going through many iterations of code review is a significant toil on both contributors and maintainers. age is also small enough that such a time investment is unlikely to pay off over ongoing contributions.
|
||||
|
||||
Therefore, **be prepared for your change to get reimplemented rather than merged**, and please don't be offended if that happens. PRs are still appreciated as a way to clarify the intended behavior, but are not at all required: prefer focusing on providing detailed context in an issue report instead.
|
||||
|
||||
To learn more, please see my [maintenance policy](https://github.com/FiloSottile/FiloSottile/blob/main/maintenance.md).
|
||||
|
||||
<!-- ## Feature requests
|
||||
|
||||
age is small, simple, and config-free by design. A lot of effort is put into resisting scope creep and enabling use cases by integrating and interoperating well with other projects rather than by adding features.
|
||||
|
||||
In particular, I'm unlikely to merge into the main repo anything I don't use myself, as I would not be the best person to maintain it. However, I'm always happy to discuss, learn about, and link to any age-related project! -->
|
||||
|
||||
## Other ways to contribute
|
||||
|
||||
age itself is not community maintained, but its ecosystem very much is, and that's where a lot of the strength of age is! Here are some ideas for ways to contribute to age and its ecosystem, besides contributing to this repository.
|
||||
|
||||
* **Write an article about how to use age for a certain community or use case.** The number one reason people don't use age is because they haven't heard about it and existing tutorials present more complex alternatives.
|
||||
* Integrate age into existing projects that might use it, for example replacing legacy alternatives.
|
||||
* Build and maintain an [age plugin](https://c2sp.org/age-plugin) for a KMS or platform.
|
||||
* Watch the [discussions](https://github.com/FiloSottile/age/discussions) and help other users.
|
||||
* Provide bindings in a language or framework that doesn't support age well.
|
||||
* Package age for an ecosystem that doesn't have packages yet.
|
||||
|
||||
If you build or write something related to age, [let me know](https://github.com/FiloSottile/age/discussions/new?category=general)! 💖
|
||||
4
.github/ISSUE_TEMPLATE/bug-report.md
vendored
4
.github/ISSUE_TEMPLATE/bug-report.md
vendored
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report about a bug in this implementation.
|
||||
name: Bug report 🐞
|
||||
about: Did you encounter a bug in this implementation?
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
10
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
10
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
contact_links:
|
||||
- name: UX report ✨
|
||||
url: https://github.com/FiloSottile/age/discussions/new?category=UX-reports
|
||||
about: Was age hard to use? It's not you, it's us. We want to hear about it.
|
||||
- name: Spec feedback 📃
|
||||
url: https://github.com/FiloSottile/age/discussions/new?category=Spec-feedback
|
||||
about: Have a comment about the age spec as it's implemented by this and other tools?
|
||||
- name: Questions, feature requests, and more 💬
|
||||
url: https://github.com/FiloSottile/age/discussions
|
||||
about: Do you need support? Did you make something with age? Do you have an idea? Tell us about it!
|
||||
15
.github/ISSUE_TEMPLATE/spec-feedback.md
vendored
15
.github/ISSUE_TEMPLATE/spec-feedback.md
vendored
@@ -1,15 +0,0 @@
|
||||
---
|
||||
name: Spec feedback
|
||||
about: Have a comment about the age spec as it's implemented by this and other tools?
|
||||
title: 'spec: '
|
||||
labels: 'spec'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- This is the issue tracker of a specific implementation of
|
||||
the age format, which is specified at https://age-encryption.org/v1
|
||||
|
||||
Please consider using the mailing list to discuss the specification:
|
||||
|
||||
https://age-encryption.org/ml -->
|
||||
21
.github/ISSUE_TEMPLATE/ux-report.md
vendored
21
.github/ISSUE_TEMPLATE/ux-report.md
vendored
@@ -1,21 +0,0 @@
|
||||
---
|
||||
name: UX report
|
||||
about: Was age hard to use? It's not you, it's us. We want to hear about it.
|
||||
title: 'UX: '
|
||||
labels: 'UX report'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- Did age not do what you expected?
|
||||
Was it hard to figure out how to do something?
|
||||
Could an error message be more helpful?
|
||||
It's not you, it's us. We want to hear about it. -->
|
||||
|
||||
## What were you trying to do
|
||||
|
||||
## What happened
|
||||
|
||||
```
|
||||
<insert terminal transcript here>
|
||||
```
|
||||
118
.github/workflows/build.yml
vendored
118
.github/workflows/build.yml
vendored
@@ -1,72 +1,86 @@
|
||||
name: Build and upload binaries
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
push:
|
||||
pull_request:
|
||||
name: Build binaries
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
binaries:
|
||||
name: Build and upload
|
||||
build:
|
||||
name: Build binaries
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- {GOOS: linux, GOARCH: amd64}
|
||||
- {GOOS: linux, GOARCH: arm, GOARM: 6}
|
||||
- {GOOS: linux, GOARCH: arm64}
|
||||
- {GOOS: darwin, GOARCH: amd64}
|
||||
- {GOOS: darwin, GOARCH: arm64}
|
||||
- {GOOS: windows, GOARCH: amd64}
|
||||
- {GOOS: freebsd, GOARCH: amd64}
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.x
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build binaries
|
||||
- name: Build binary
|
||||
run: |
|
||||
VERSION=$(git describe --tags)
|
||||
function build_age() {
|
||||
DIR="$(mktemp -d)"
|
||||
mkdir "$DIR/age"
|
||||
cp LICENSE "$DIR/age"
|
||||
echo -e "\n---\n" >> "$DIR/age/LICENSE"
|
||||
curl "https://golang.org/LICENSE?m=text" >> "$DIR/age/LICENSE"
|
||||
go build -o "$DIR/age" -ldflags "-X main.Version=$VERSION" ./cmd/...
|
||||
if [ "$GOOS" == "windows" ]; then
|
||||
( cd "$DIR"; zip age.zip -r age )
|
||||
mv "$DIR/age.zip" "age-$VERSION-$GOOS-$GOARCH.zip"
|
||||
else
|
||||
tar -cvzf "age-$VERSION-$GOOS-$GOARCH.tar.gz" -C "$DIR" age
|
||||
cp LICENSE "$RUNNER_TEMP/LICENSE"
|
||||
echo -e "\n---\n" >> "$RUNNER_TEMP/LICENSE"
|
||||
curl -L "https://go.dev/LICENSE?m=text" >> "$RUNNER_TEMP/LICENSE"
|
||||
VERSION="$(git describe --tags)"
|
||||
DIR="$(mktemp -d)"
|
||||
mkdir "$DIR/age"
|
||||
cp "$RUNNER_TEMP/LICENSE" "$DIR/age"
|
||||
go build -o "$DIR/age" -ldflags "-X main.Version=$VERSION" -trimpath ./cmd/...
|
||||
if [ "$GOOS" == "windows" ]; then
|
||||
sudo apt-get update && sudo apt-get install -y osslsigncode
|
||||
if [ -n "${{ secrets.SIGN_PASS }}" ]; then
|
||||
for exe in "$DIR"/age/*.exe; do
|
||||
/usr/bin/osslsigncode sign -t "http://timestamp.comodoca.com" \
|
||||
-certs .github/workflows/certs/uitacllc.crt \
|
||||
-key .github/workflows/certs/uitacllc.key \
|
||||
-pass "${{ secrets.SIGN_PASS }}" \
|
||||
-n age -in "$exe" -out "$exe.signed"
|
||||
mv "$exe.signed" "$exe"
|
||||
done
|
||||
fi
|
||||
}
|
||||
export CGO_ENABLED=0
|
||||
GOOS=linux GOARCH=amd64 build_age
|
||||
GOOS=linux GOARCH=arm GOARM=6 build_age
|
||||
GOOS=linux GOARCH=arm64 build_age
|
||||
GOOS=darwin GOARCH=amd64 build_age
|
||||
GOOS=windows GOARCH=amd64 build_age
|
||||
( cd "$DIR"; zip age.zip -r age )
|
||||
mv "$DIR/age.zip" "age-$VERSION-$GOOS-$GOARCH.zip"
|
||||
else
|
||||
tar -cvzf "age-$VERSION-$GOOS-$GOARCH.tar.gz" -C "$DIR" age
|
||||
fi
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
GOOS: ${{ matrix.GOOS }}
|
||||
GOARCH: ${{ matrix.GOARCH }}
|
||||
GOARM: ${{ matrix.GOARM }}
|
||||
- name: Upload workflow artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: age-binaries
|
||||
name: age-binaries-${{ matrix.GOOS }}-${{ matrix.GOARCH }}
|
||||
path: age-*
|
||||
- name: Upload release artifacts
|
||||
uses: actions/github-script@v3
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
upload:
|
||||
name: Upload release binaries
|
||||
if: github.event_name == 'release'
|
||||
needs: build
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download workflow artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const fs = require("fs").promises;
|
||||
const { repo: { owner, repo }, sha } = context;
|
||||
|
||||
const release = await github.repos.getReleaseByTag({
|
||||
owner, repo,
|
||||
tag: process.env.GITHUB_REF.replace("refs/tags/", ""),
|
||||
});
|
||||
console.log("Release:", { release });
|
||||
|
||||
for (let file of await fs.readdir(".")) {
|
||||
if (!file.startsWith("age-")) continue;
|
||||
console.log("Uploading", file);
|
||||
await github.repos.uploadReleaseAsset({
|
||||
owner, repo,
|
||||
release_id: release.data.id,
|
||||
name: file,
|
||||
data: await fs.readFile(file),
|
||||
});
|
||||
}
|
||||
pattern: age-binaries-*
|
||||
merge-multiple: true
|
||||
- name: Upload release artifacts
|
||||
run: gh release upload "$GITHUB_REF_NAME" age-*
|
||||
env:
|
||||
GH_REPO: ${{ github.repository }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
14
.github/workflows/certs/README
vendored
Normal file
14
.github/workflows/certs/README
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
In this folder there are
|
||||
|
||||
uitacllc.crt
|
||||
|
||||
PKCS#7 encoded certificate chain for a code signing certificate issued
|
||||
to Up in the Air Consulting LLC valid until Sep 26 23:59:59 2024 GMT.
|
||||
|
||||
https://crt.sh/?id=5339775059
|
||||
|
||||
uitacllc.key
|
||||
|
||||
PEM encrypted private key for the leaf certificate above.
|
||||
Its passphrase is long and randomly generated, so the awful legacy key
|
||||
derivation doesn't really matter, and it makes osslsigncode happy.
|
||||
BIN
.github/workflows/certs/uitacllc.crt
vendored
Normal file
BIN
.github/workflows/certs/uitacllc.crt
vendored
Normal file
Binary file not shown.
42
.github/workflows/certs/uitacllc.key
vendored
Normal file
42
.github/workflows/certs/uitacllc.key
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: AES-256-CBC,B93C1A166F3677D68FB9CB3E8A184729
|
||||
|
||||
UriYsaq3tLyvycDDB2YeQ+9L1P5VCPcfVkYR1ocleF8WxNDUPdz3RqbryAZZdXVO
|
||||
0bcvAHTXkdI4Oiw5mN0S8fGsNq9zn+pyResx3lXtgN3oCDCe2SQn28uEEKxPzud5
|
||||
0NRXYoBP+pLDjiuQ/6Lp7DovnAO/uxaPFvYRMiknNVOhwyHGWZyuUe01S9J9im7y
|
||||
vgc1wkyQzmABIhARynEXHp3KnM9aF8X1/ck839lQRBFrvRFNm5rqiON26spr1Hu5
|
||||
znrbVGROYk0XNdH5VHDk7V9k+v2WLL/b4nxlMymZpDzr9pXzX8olpLnQrarsMbHe
|
||||
ysfXNTtQi5Dq6KXURW8VA4DmxAzTRNUxe2aA4JnAEyFU5LDLetTN9F9M7BUkHbXH
|
||||
RpSbZqDjPwg7U98vuSwxjIkncHSiYYi3FmSoupLvV+eIP6qRSgONdzGlP5NTn4Lh
|
||||
N1lYMPHPldH6UjLHrldkYN16TQlrqNHZExN91XvsZVjpyAgErY18xwi3CTEco45D
|
||||
fRqsiWXtoas4LkafhSY0vfl5aFhY9YPUpS6uFdgWBvgcQeYb8meX5Nr4dNXVk5Wa
|
||||
yRlYlW/X0TWC0T9qaBOPN/z7OWO5aL4jYRcKQQ+aR8gFcHGGCpRAKD369OneXfOQ
|
||||
MD9UHoPG4WTBg/NU9OSskcywfuSOkwAGfBVNXrnEj6tYFjsjYK2nC2gm+opUCfm0
|
||||
a1FeDb5nQSOgOJKUCO6Aj+0NvDvVLUOsTk1lfzSugIkmUOdV+rXHnrZC+90q8KfN
|
||||
S2JlzwSZNg0e+VxZpnD7k7axHkbHrbebtrLvzKVnrh3s0OFAXN0isMw7yhhWtzUe
|
||||
mPoQTZusLDOAJe/QPuNlDUgr4uoVZtoXrPzoZZkw2VFLwYy2g/EYvlK9BdVVTnRm
|
||||
9Hq9IBDrZw+SV/7roaeVOXbzrQoxEoXcL7eo6iWvV5Q7Ll5C4ovelHKy3IAzcpYP
|
||||
6LKfxAO2sIKTALrHbtBNG+O4RTtxOva1hyg27V4v2k53CF/GhoBRPSpbbupwppXc
|
||||
lJJ9RtMTRfhCv/ObhdsJED+YUqFifTJfcnQ1iGN8dnBuGrjXxVCN0wgmv46Pdhn0
|
||||
tUfGlkFquOOWamaVaIvp6JCVUDa1ezMzleILoYvrxvOuP+dGVrwTwVCXpx4JuUgp
|
||||
d72/w+EnqlZnwsAzdrErJFXnHux981ZoojmG94km1B6gPPwMB8JRcD67lfhG/vne
|
||||
IpTuuzGaSInf24cGNig01hbBuKSg79yNY0llkECPBXbEhfkemEMhg1WHoNP2eG8j
|
||||
MHS5OCT5KiOfi77pSO3M2mGB1HWYE5R0lcMibukK9ZdyIYcTeMZ0RcGm6YSNv570
|
||||
ok/Ex4LUCW66AIWFefmbIOtJSIMHlNKWRPJwnJxVoE5qgH0f/2xL3k15vpI55lAS
|
||||
sabzegnYlElPbUlZGhgwjKknxgqMhFIW/ZS0h2FukFLwipr4qI47nHWz5dguNkYn
|
||||
48sSKg3YMhVx/sT+X2A/6zqsC+p4PT7Ti5ruWb7S9L9vRuBdIDNE9qAwuz0g8Bs3
|
||||
WhOx6OW2ZqDQEuRhN0lyGA0mwRC4HPFE9b8dnN8lNm+RsnMfNoFxzPnqtsxhEAwa
|
||||
2a4ijT97ka94lDy7WQ2bwLRz7trKV/T6MeETKE4s7+z2dMTr1f8IwA2uCovFmO9T
|
||||
aMQAePFEtDT3qwIPu0zH1ocSCkZ50f7RgVmp4FNn03uT/TnsASrr5CS9m8A9gjEn
|
||||
QiztQyqt27fTT61YkNdA6lwbpFiByugVbS+mWsNa9kvBkgQkcMQwgrELmU9sYdBT
|
||||
nRMa60i0nEINT/x3zFvT6R7Dl/O8/QhXLeYv20X2roghPw48IovLb8x7dT3YEQSn
|
||||
ARIXXVPxwOVvS8xcCa69/+1HjC6vNG9dNNnAsVHxB8mDTBqmmLzAMOVzDoNWEgDd
|
||||
zoRhQ3ORb1brPlKWg8um/svLiSV63ZYi2J8LPamoGmZ/7J8i5rjOpOeG493UICBR
|
||||
JymmYGUo6/C1Ze8swdMHApVU/spo0s8BCGkMjYUAaxXD7RufN2DuY30Vny/DMn4y
|
||||
XasuHS9RstD2Okv25PD06Y2H52HJ6MNdArmPZRe0k2ZbhATs5dXOfmaF5Z0f4IkE
|
||||
G+hsxE1wlCo900ewntx16sBCbI0v9aE+Napf2+ueqPQ06CdfiTG5yOmeXzgR/8zS
|
||||
KVmTHpmmFpYtj/N350BLAVb/Hwzmh+ieWnO7TUjvNAHUn2i5LZU65rN3GOlPyIlz
|
||||
DzB2T6KjOUPFKqSRrIin14HLyf5w0vDuJhe5Zpe0hhYKvoKhwCEVefbmkasWeso3
|
||||
xsXxOOoL39GA0QpYjR6ztqR8fS9jTeu5IY+zY5LO8yS7+StP3H8CcqRMuxb3ntym
|
||||
-----END RSA PRIVATE KEY-----
|
||||
32
.github/workflows/gotip.yml
vendored
32
.github/workflows/gotip.yml
vendored
@@ -1,32 +0,0 @@
|
||||
on: [push, pull_request]
|
||||
name: Go tip tests
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Install Go tip (UNIX)
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
git clone --filter=tree:0 https://go.googlesource.com/go $HOME/gotip
|
||||
cd $HOME/gotip/src && ./make.bash
|
||||
echo "$HOME/gotip/bin" >> $GITHUB_PATH
|
||||
echo "GOROOT=" >> $GITHUB_ENV # workaround actions/virtual-environments#2655
|
||||
- name: Install Go tip (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
git clone --filter=tree:0 https://go.googlesource.com/go $HOME/gotip
|
||||
cd $HOME/gotip/src && ./make.bat
|
||||
echo "$HOME/gotip/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
echo "GOROOT=" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: go version
|
||||
- name: Run tests
|
||||
run: go test -race ./...
|
||||
13
.github/workflows/interop.yml
vendored
13
.github/workflows/interop.yml
vendored
@@ -1,13 +1,16 @@
|
||||
name: Interoperability tests
|
||||
on: push
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
trigger:
|
||||
name: Trigger
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger interoperability tests in str4d/rage
|
||||
run: |
|
||||
curl -X POST https://api.github.com/repos/str4d/rage/dispatches \
|
||||
-H 'Accept: application/vnd.github.v3+json' \
|
||||
-H 'Authorization: token ${{ secrets.RAGE_INTEROP_ACCESS_TOKEN }}' \
|
||||
--data '{"event_type": "age-interop-request", "client_payload": { "sha": "'"$GITHUB_SHA"'" }}'
|
||||
run: >
|
||||
gh api repos/str4d/rage/dispatches
|
||||
--field event_type="age-interop-request"
|
||||
--field client_payload[sha]="$GITHUB_SHA"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RAGE_INTEROP_ACCESS_TOKEN }}
|
||||
|
||||
58
.github/workflows/ronn.yml
vendored
Normal file
58
.github/workflows/ronn.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: Generate man pages
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
paths:
|
||||
- '**.ronn'
|
||||
- '**/ronn.yml'
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
ronn:
|
||||
name: Ronn
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install ronn
|
||||
run: sudo apt-get update && sudo apt-get install -y ronn
|
||||
- name: Run ronn
|
||||
run: bash -O globstar -c 'ronn **/*.ronn'
|
||||
- name: Undo email mangling
|
||||
# rdiscount randomizes the output for no good reason, which causes
|
||||
# changes to always get committed. Sigh.
|
||||
# https://github.com/davidfstr/rdiscount/blob/6b1471ec3/ext/generate.c#L781-L795
|
||||
run: |-
|
||||
for f in doc/*.html; do
|
||||
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
|
||||
- name: Upload generated files
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: man-pages
|
||||
path: |
|
||||
doc/*.1
|
||||
doc/*.html
|
||||
commit:
|
||||
name: Commit changes
|
||||
needs: ronn
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Download generated files
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: man-pages
|
||||
path: doc/
|
||||
- name: Commit and push if changed
|
||||
run: |-
|
||||
git config user.name "GitHub Actions"
|
||||
git config user.email "actions@users.noreply.github.com"
|
||||
git add doc/
|
||||
git commit -m "doc: regenerate groff and html man pages" || exit 0
|
||||
git push
|
||||
41
.github/workflows/test.yml
vendored
41
.github/workflows/test.yml
vendored
@@ -1,22 +1,55 @@
|
||||
on: [push, pull_request]
|
||||
name: Go tests
|
||||
on: [push, pull_request]
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go: [1.15.x, 1.16.x]
|
||||
go: [1.19.x, 1.x]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Install Go ${{ matrix.go }}
|
||||
uses: actions/setup-go@v2
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Run tests
|
||||
run: go test -race ./...
|
||||
gotip:
|
||||
name: Test (Go tip)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Install bootstrap Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: stable
|
||||
- name: Install Go tip (UNIX)
|
||||
if: runner.os != 'Windows'
|
||||
run: |
|
||||
git clone --filter=tree:0 https://go.googlesource.com/go $HOME/gotip
|
||||
cd $HOME/gotip/src && ./make.bash
|
||||
echo "$HOME/gotip/bin" >> $GITHUB_PATH
|
||||
- name: Install Go tip (Windows)
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
git clone --filter=tree:0 https://go.googlesource.com/go $HOME/gotip
|
||||
cd $HOME/gotip/src && ./make.bat
|
||||
echo "$HOME/gotip/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- run: go version
|
||||
- name: Run tests
|
||||
run: go test -race ./...
|
||||
|
||||
6
AUTHORS
Normal file
6
AUTHORS
Normal file
@@ -0,0 +1,6 @@
|
||||
# This is the official list of age authors for copyright purposes.
|
||||
# To be included, send a change adding the individual or company
|
||||
# who owns a contribution's copyright.
|
||||
|
||||
Google LLC
|
||||
Filippo Valsorda
|
||||
@@ -1,20 +0,0 @@
|
||||
# Copyright 2019 Google LLC
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file or at
|
||||
# https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
class Age < Formula
|
||||
desc "Simple, modern, secure file encryption"
|
||||
homepage "https://filippo.io/age"
|
||||
url "https://github.com/FiloSottile/age/archive/v1.0.0-beta6.zip"
|
||||
sha256 "6ffa23aee0f03c3e00707915e4300591847a2b0c5157ca7a696eb39bfeb7359c"
|
||||
|
||||
depends_on "go" => :build
|
||||
|
||||
def install
|
||||
mkdir bin
|
||||
system "go", "build", "-trimpath", "-o", bin, "-ldflags", "-X main.Version=v#{version}", "filippo.io/age/cmd/..."
|
||||
prefix.install_metafiles
|
||||
end
|
||||
end
|
||||
4
LICENSE
4
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright 2019 Google LLC
|
||||
Copyright 2019 The age Authors
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
@@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google LLC nor the names of its
|
||||
* Neither the name of the age project nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
|
||||
222
README.md
222
README.md
@@ -1,8 +1,16 @@
|
||||
# age
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/FiloSottile/age/blob/main/logo/logo_white.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://github.com/FiloSottile/age/blob/main/logo/logo.svg">
|
||||
<img alt="The age logo, a wireframe of St. Peters dome in Rome, with the text: age, file encryption" width="600" src="https://github.com/FiloSottile/age/blob/main/logo/logo.svg">
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
[](https://pkg.go.dev/filippo.io/age)
|
||||
[](https://pkg.go.dev/filippo.io/age)
|
||||
[-man%20page-lightgrey>)](https://filippo.io/age/age.1)
|
||||
[](https://age-encryption.org/v1)
|
||||
|
||||
age is a simple, modern and secure file encryption tool, format, and library.
|
||||
age is a simple, modern and secure file encryption tool, format, and Go library.
|
||||
|
||||
It features small explicit keys, no config options, and UNIX-style composability.
|
||||
|
||||
@@ -13,28 +21,164 @@ $ tar cvz ~/data | age -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9
|
||||
$ age --decrypt -i key.txt data.tar.gz.age > data.tar.gz
|
||||
```
|
||||
|
||||
The format specification is at [age-encryption.org/v1](https://age-encryption.org/v1). To discuss the spec or other age related topics, please email [the mailing list](https://groups.google.com/d/forum/age-dev) at age-dev@googlegroups.com. age was designed by [@Benjojo12](https://twitter.com/Benjojo12) and [@FiloSottile](https://twitter.com/FiloSottile).
|
||||
📜 The format specification is at [age-encryption.org/v1](https://age-encryption.org/v1). age was designed by [@Benjojo12](https://twitter.com/Benjojo12) and [@FiloSottile](https://twitter.com/FiloSottile).
|
||||
|
||||
An alternative interoperable Rust implementation is available at [github.com/str4d/rage](https://github.com/str4d/rage).
|
||||
📬 Follow the maintenance of this project by subscribing to [Maintainer Dispatches](https://filippo.io/newsletter)!
|
||||
|
||||
🦀 An alternative interoperable Rust implementation is available at [github.com/str4d/rage](https://github.com/str4d/rage).
|
||||
|
||||
🔑 Hardware PIV tokens such as YubiKeys are supported through the [age-plugin-yubikey](https://github.com/str4d/age-plugin-yubikey) plugin.
|
||||
|
||||
✨ For more plugins, implementations, tools, and integrations, check out the [awesome age](https://github.com/FiloSottile/awesome-age) list.
|
||||
|
||||
💬 The author pronounces it `[aɡe̞]` [with a hard *g*](https://translate.google.com/?sl=it&text=aghe), like GIF, and is always spelled lowercase.
|
||||
|
||||
## Installation
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td>Homebrew (macOS or Linux)</td>
|
||||
<td>
|
||||
<code>brew install age</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>MacPorts</td>
|
||||
<td>
|
||||
<code>port install age</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Alpine Linux v3.15+</td>
|
||||
<td>
|
||||
<code>apk add age</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Arch Linux</td>
|
||||
<td>
|
||||
<code>pacman -S age</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Debian 12+ (Bookworm)</td>
|
||||
<td>
|
||||
<code>apt install age</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Debian 11 (Bullseye)</td>
|
||||
<td>
|
||||
<code>apt install age/bullseye-backports</code>
|
||||
(<a href="https://backports.debian.org/Instructions/#index2h2">enable backports</a> for age v1.0.0+)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Fedora 33+</td>
|
||||
<td>
|
||||
<code>dnf install age</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Gentoo Linux</td>
|
||||
<td>
|
||||
<code>emerge app-crypt/age</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>NixOS / Nix</td>
|
||||
<td>
|
||||
<code>nix-env -i age</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>openSUSE Tumbleweed</td>
|
||||
<td>
|
||||
<code>zypper install age</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ubuntu 22.04+</td>
|
||||
<td>
|
||||
<code>apt install age</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Void Linux</td>
|
||||
<td>
|
||||
<code>xbps-install age</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>FreeBSD</td>
|
||||
<td>
|
||||
<code>pkg install age</code> (security/age)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>OpenBSD 6.7+</td>
|
||||
<td>
|
||||
<code>pkg_add age</code> (security/age)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Chocolatey (Windows)</td>
|
||||
<td>
|
||||
<code>choco install age.portable</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Scoop (Windows)</td>
|
||||
<td>
|
||||
<code>scoop bucket add extras && scoop install age</code>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>pkgx</td>
|
||||
<td>
|
||||
<code>pkgx install age</code>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
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.1.1?for=darwin/arm64
|
||||
...
|
||||
```
|
||||
|
||||
If your system has [a supported version of Go](https://go.dev/dl/), you can build from source.
|
||||
|
||||
```
|
||||
go install filippo.io/age/cmd/...@latest
|
||||
```
|
||||
|
||||
Help from new packagers is very welcome.
|
||||
|
||||
## Usage
|
||||
|
||||
For the full documentation, read [the age(1) man page](https://filippo.io/age/age.1).
|
||||
|
||||
```
|
||||
Usage:
|
||||
age (-r RECIPIENT | -R PATH)... [--armor] [-o OUTPUT] [INPUT]
|
||||
age --passphrase [--armor] [-o OUTPUT] [INPUT]
|
||||
age [--encrypt] (-r RECIPIENT | -R PATH)... [--armor] [-o OUTPUT] [INPUT]
|
||||
age [--encrypt] --passphrase [--armor] [-o OUTPUT] [INPUT]
|
||||
age --decrypt [-i PATH]... [-o OUTPUT] [INPUT]
|
||||
|
||||
Options:
|
||||
-e, --encrypt Encrypt the input to the output. Default if omitted.
|
||||
-d, --decrypt Decrypt the input to the output.
|
||||
-o, --output OUTPUT Write the result to the file at path OUTPUT.
|
||||
-a, --armor Encrypt to a PEM encoded format.
|
||||
-p, --passphrase Encrypt with a passphrase.
|
||||
-r, --recipient RECIPIENT Encrypt to the specified RECIPIENT. Can be repeated.
|
||||
-R, --recipients-file PATH Encrypt to recipients listed at PATH. Can be repeated.
|
||||
-d, --decrypt Decrypt the input to the output.
|
||||
-i, --identity PATH Use the identity file at PATH. Can be repeated.
|
||||
|
||||
INPUT defaults to standard input, and OUTPUT defaults to standard output.
|
||||
If OUTPUT exists, it will be overwritten.
|
||||
|
||||
RECIPIENT can be an age public key generated by age-keygen ("age1...")
|
||||
or an SSH public key ("ssh-ed25519 AAAA...", "ssh-rsa AAAA...").
|
||||
@@ -45,8 +189,12 @@ read recipients from standard input.
|
||||
|
||||
Identity files contain one or more secret keys ("AGE-SECRET-KEY-1..."),
|
||||
one per line, or an SSH key. Empty lines and lines starting with "#" are
|
||||
ignored as comments. Multiple key files can be provided, and any unused ones
|
||||
ignored as comments. Passphrase encrypted age files can be used as
|
||||
identity files. Multiple key files can be provided, and any unused ones
|
||||
will be ignored. "-" may be used to read identities from standard input.
|
||||
|
||||
When --encrypt is specified explicitly, -i can also be used to encrypt to an
|
||||
identity file symmetrically, instead or in addition to normal recipients.
|
||||
```
|
||||
|
||||
### Multiple recipients
|
||||
@@ -85,6 +233,22 @@ $ age -d secrets.txt.age > secrets.txt
|
||||
Enter passphrase:
|
||||
```
|
||||
|
||||
### Passphrase-protected key files
|
||||
|
||||
If an identity file passed to `-i` is a passphrase encrypted age file, it will be automatically decrypted.
|
||||
|
||||
```
|
||||
$ age-keygen | age -p > key.age
|
||||
Public key: age1yhm4gctwfmrpz87tdslm550wrx6m79y9f2hdzt0lndjnehwj0ukqrjpyx5
|
||||
Enter passphrase (leave empty to autogenerate a secure one):
|
||||
Using the autogenerated passphrase "hip-roast-boring-snake-mention-east-wasp-honey-input-actress".
|
||||
$ age -r age1yhm4gctwfmrpz87tdslm550wrx6m79y9f2hdzt0lndjnehwj0ukqrjpyx5 secrets.txt > secrets.txt.age
|
||||
$ age -d -i key.age secrets.txt.age > secrets.txt
|
||||
Enter passphrase for identity file "key.age":
|
||||
```
|
||||
|
||||
Passphrase-protected identity files are not necessary for most use cases, where access to the encrypted identity file implies access to the whole system. However, they can be useful if the identity file is stored remotely.
|
||||
|
||||
### SSH keys
|
||||
|
||||
As a convenience feature, age also supports encrypting to `ssh-rsa` and `ssh-ed25519` SSH public keys, and decrypting with the respective private key file. (`ssh-agent` is not supported.)
|
||||
@@ -105,43 +269,3 @@ $ curl https://github.com/benjojo.keys | age -R - example.jpg > example.jpg.age
|
||||
```
|
||||
|
||||
Keep in mind that people might not protect SSH keys long-term, since they are revokable when used only for authentication, and that SSH keys held on YubiKeys can't be used to decrypt files.
|
||||
|
||||
## Installation
|
||||
|
||||
On macOS or Linux, you can use Homebrew:
|
||||
|
||||
```
|
||||
brew tap filippo.io/age https://filippo.io/age
|
||||
brew install age
|
||||
```
|
||||
|
||||
On Windows, Linux, and macOS, you can use [the pre-built binaries](https://github.com/FiloSottile/age/releases).
|
||||
|
||||
If your system has [Go 1.13+](https://golang.org/dl/), you can build from source:
|
||||
|
||||
```
|
||||
git clone https://filippo.io/age && cd age
|
||||
go build -o . filippo.io/age/cmd/...
|
||||
```
|
||||
|
||||
On Arch Linux, age is available from AUR as [`age`](https://aur.archlinux.org/packages/age/) or [`age-git`](https://aur.archlinux.org/packages/age-git/):
|
||||
|
||||
```bash
|
||||
git clone https://aur.archlinux.org/age.git
|
||||
cd age
|
||||
makepkg -si
|
||||
```
|
||||
|
||||
On OpenBSD -current and 6.7+, you can use the port:
|
||||
|
||||
```
|
||||
pkg_add age
|
||||
```
|
||||
|
||||
On all supported versions of FreeBSD, you can build the security/age port or use pkg:
|
||||
|
||||
```
|
||||
pkg install age
|
||||
```
|
||||
|
||||
Help from new packagers is very welcome.
|
||||
|
||||
87
age.go
87
age.go
@@ -1,8 +1,6 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Copyright 2019 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package age implements file encryption according to the age-encryption.org/v1
|
||||
// specification.
|
||||
@@ -12,13 +10,13 @@
|
||||
// ScryptRecipient and ScryptIdentity. For compatibility with existing SSH keys
|
||||
// use the filippo.io/age/agessh package.
|
||||
//
|
||||
// Age encrypted files are binary and not malleable. For encoding them as text,
|
||||
// age encrypted files are binary and not malleable. For encoding them as text,
|
||||
// use the filippo.io/age/armor package.
|
||||
//
|
||||
// Key management
|
||||
// # Key management
|
||||
//
|
||||
// Age does not have a global keyring. Instead, since age keys are small,
|
||||
// textual, and cheap, you are encoraged to generate dedicated keys for each
|
||||
// age does not have a global keyring. Instead, since age keys are small,
|
||||
// textual, and cheap, you are encouraged to generate dedicated keys for each
|
||||
// task and application.
|
||||
//
|
||||
// Recipient public keys can be passed around as command line flags and in
|
||||
@@ -35,6 +33,16 @@
|
||||
// encryption operations. If you need to tie into existing key management
|
||||
// infrastructure, you might want to consider implementing your own Recipient
|
||||
// and Identity.
|
||||
//
|
||||
// # Backwards compatibility
|
||||
//
|
||||
// Files encrypted with a stable version (not alpha, beta, or release candidate)
|
||||
// of age, or with any v1.0.0 beta or release candidate, will decrypt with any
|
||||
// later versions of the v1 API. This might change in v2, in which case v1 will
|
||||
// be maintained with security fixes for compatibility with older files.
|
||||
//
|
||||
// If decrypting an older file poses a security risk, doing so might require an
|
||||
// explicit opt-in in the API.
|
||||
package age
|
||||
|
||||
import (
|
||||
@@ -43,6 +51,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"filippo.io/age/internal/format"
|
||||
"filippo.io/age/internal/stream"
|
||||
@@ -76,6 +85,21 @@ type Recipient interface {
|
||||
Wrap(fileKey []byte) ([]*Stanza, error)
|
||||
}
|
||||
|
||||
// RecipientWithLabels can be optionally implemented by a Recipient, in which
|
||||
// case Encrypt will use WrapWithLabels instead of Wrap.
|
||||
//
|
||||
// Encrypt will succeed only if the labels returned by all the recipients
|
||||
// (assuming the empty set for those that don't implement RecipientWithLabels)
|
||||
// are the same.
|
||||
//
|
||||
// This can be used to ensure a recipient is only used with other recipients
|
||||
// with equivalent properties (for example by setting a "postquantum" label) or
|
||||
// to ensure a recipient is always used alone (by returning a random label, for
|
||||
// example to preserve its authentication properties).
|
||||
type RecipientWithLabels interface {
|
||||
WrapWithLabels(fileKey []byte) (s []*Stanza, labels []string, err error)
|
||||
}
|
||||
|
||||
// A Stanza is a section of the age header that encapsulates the file key as
|
||||
// encrypted to a specific recipient.
|
||||
//
|
||||
@@ -109,20 +133,22 @@ func Encrypt(dst io.Writer, recipients ...Recipient) (io.WriteCloser, error) {
|
||||
}
|
||||
|
||||
hdr := &format.Header{}
|
||||
var labels []string
|
||||
for i, r := range recipients {
|
||||
stanzas, err := r.Wrap(fileKey)
|
||||
stanzas, l, err := wrapWithLabels(r, fileKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to wrap key for recipient #%d: %v", i, err)
|
||||
}
|
||||
sort.Strings(l)
|
||||
if i == 0 {
|
||||
labels = l
|
||||
} else if !slicesEqual(labels, l) {
|
||||
return nil, fmt.Errorf("incompatible recipients")
|
||||
}
|
||||
for _, s := range stanzas {
|
||||
hdr.Recipients = append(hdr.Recipients, (*format.Stanza)(s))
|
||||
}
|
||||
}
|
||||
for _, s := range hdr.Recipients {
|
||||
if s.Type == "scrypt" && len(hdr.Recipients) != 1 {
|
||||
return nil, errors.New("an scrypt recipient must be the only one")
|
||||
}
|
||||
}
|
||||
if mac, err := headerMAC(fileKey, hdr); err != nil {
|
||||
return nil, fmt.Errorf("failed to compute header MAC: %v", err)
|
||||
} else {
|
||||
@@ -143,6 +169,26 @@ func Encrypt(dst io.Writer, recipients ...Recipient) (io.WriteCloser, error) {
|
||||
return stream.NewWriter(streamKey(fileKey, nonce), dst)
|
||||
}
|
||||
|
||||
func wrapWithLabels(r Recipient, fileKey []byte) (s []*Stanza, labels []string, err error) {
|
||||
if r, ok := r.(RecipientWithLabels); ok {
|
||||
return r.WrapWithLabels(fileKey)
|
||||
}
|
||||
s, err = r.Wrap(fileKey)
|
||||
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
|
||||
}
|
||||
|
||||
// NoIdentityMatchError is returned by Decrypt when none of the supplied
|
||||
// identities match the encrypted file.
|
||||
type NoIdentityMatchError struct {
|
||||
@@ -166,16 +212,7 @@ func Decrypt(src io.Reader, identities ...Identity) (io.Reader, error) {
|
||||
|
||||
hdr, payload, err := format.Parse(src)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read header: %v", err)
|
||||
}
|
||||
if len(hdr.Recipients) > 20 {
|
||||
return nil, errors.New("too many recipients")
|
||||
}
|
||||
|
||||
for _, r := range hdr.Recipients {
|
||||
if r.Type == "scrypt" && len(hdr.Recipients) != 1 {
|
||||
return nil, errors.New("an scrypt recipient must be the only one")
|
||||
}
|
||||
return nil, fmt.Errorf("failed to read header: %w", err)
|
||||
}
|
||||
|
||||
stanzas := make([]*Stanza, 0, len(hdr.Recipients))
|
||||
@@ -208,7 +245,7 @@ func Decrypt(src io.Reader, identities ...Identity) (io.Reader, error) {
|
||||
|
||||
nonce := make([]byte, streamNonceSize)
|
||||
if _, err := io.ReadFull(payload, nonce); err != nil {
|
||||
return nil, fmt.Errorf("failed to read nonce: %v", err)
|
||||
return nil, fmt.Errorf("failed to read nonce: %w", err)
|
||||
}
|
||||
|
||||
return stream.NewReader(streamKey(fileKey, nonce), payload)
|
||||
|
||||
77
age_test.go
77
age_test.go
@@ -1,8 +1,6 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Copyright 2019 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package age_test
|
||||
|
||||
@@ -10,7 +8,6 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -78,7 +75,7 @@ func ExampleDecrypt() {
|
||||
}
|
||||
|
||||
func ExampleParseIdentities() {
|
||||
keyFile, err := os.Open("testdata/keys.txt")
|
||||
keyFile, err := os.Open("testdata/example_keys.txt")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open private keys file: %v", err)
|
||||
}
|
||||
@@ -146,7 +143,7 @@ func TestEncryptDecryptX25519(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
outBytes, err := ioutil.ReadAll(out)
|
||||
outBytes, err := io.ReadAll(out)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -183,7 +180,7 @@ func TestEncryptDecryptScrypt(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
outBytes, err := ioutil.ReadAll(out)
|
||||
outBytes, err := io.ReadAll(out)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -223,3 +220,67 @@ AGE-SECRET-KEY--1D6K0SGAX3NU66R4GYFZY0UQWCLM3UUSF3CXLW4KXZM342WQSJ82QKU59Q`},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type testRecipient struct {
|
||||
labels []string
|
||||
}
|
||||
|
||||
func (testRecipient) Wrap(fileKey []byte) ([]*age.Stanza, error) {
|
||||
panic("expected WrapWithLabels instead")
|
||||
}
|
||||
|
||||
func (t testRecipient) WrapWithLabels(fileKey []byte) (s []*age.Stanza, labels []string, err error) {
|
||||
return []*age.Stanza{{Type: "test"}}, t.labels, nil
|
||||
}
|
||||
|
||||
func TestLabels(t *testing.T) {
|
||||
scrypt, err := age.NewScryptRecipient("xxx")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
i, err := age.GenerateX25519Identity()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
x25519 := i.Recipient()
|
||||
pqc := testRecipient{[]string{"postquantum"}}
|
||||
pqcAndFoo := testRecipient{[]string{"postquantum", "foo"}}
|
||||
fooAndPQC := testRecipient{[]string{"foo", "postquantum"}}
|
||||
|
||||
if _, err := age.Encrypt(io.Discard, scrypt, scrypt); err == nil {
|
||||
t.Error("expected two scrypt recipients to fail")
|
||||
}
|
||||
if _, err := age.Encrypt(io.Discard, scrypt, x25519); err == nil {
|
||||
t.Error("expected x25519 mixed with scrypt to fail")
|
||||
}
|
||||
if _, err := age.Encrypt(io.Discard, x25519, scrypt); err == nil {
|
||||
t.Error("expected x25519 mixed with scrypt to fail")
|
||||
}
|
||||
if _, err := age.Encrypt(io.Discard, pqc, x25519); err == nil {
|
||||
t.Error("expected x25519 mixed with pqc to fail")
|
||||
}
|
||||
if _, err := age.Encrypt(io.Discard, x25519, pqc); err == nil {
|
||||
t.Error("expected x25519 mixed with pqc to fail")
|
||||
}
|
||||
if _, err := age.Encrypt(io.Discard, pqc, pqc); err != nil {
|
||||
t.Errorf("expected two pqc to work, got %v", err)
|
||||
}
|
||||
if _, err := age.Encrypt(io.Discard, pqc); err != nil {
|
||||
t.Errorf("expected one pqc to work, got %v", err)
|
||||
}
|
||||
if _, err := age.Encrypt(io.Discard, pqcAndFoo, pqc); err == nil {
|
||||
t.Error("expected pqc+foo mixed with pqc to fail")
|
||||
}
|
||||
if _, err := age.Encrypt(io.Discard, pqc, pqcAndFoo); err == nil {
|
||||
t.Error("expected pqc+foo mixed with pqc to fail")
|
||||
}
|
||||
if _, err := age.Encrypt(io.Discard, pqc, pqc, pqcAndFoo); err == nil {
|
||||
t.Error("expected pqc+foo mixed with pqc to fail")
|
||||
}
|
||||
if _, err := age.Encrypt(io.Discard, pqcAndFoo, pqcAndFoo); err != nil {
|
||||
t.Errorf("expected two pqc+foo to work, got %v", err)
|
||||
}
|
||||
if _, err := age.Encrypt(io.Discard, pqcAndFoo, fooAndPQC); err != nil {
|
||||
t.Errorf("expected pqc+foo mixed with foo+pqc to work, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Copyright 2019 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package agessh provides age.Identity and age.Recipient implementations of
|
||||
// types "ssh-rsa" and "ssh-ed25519", which allow reusing existing SSH keys for
|
||||
@@ -24,10 +22,10 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"filippo.io/age"
|
||||
"filippo.io/age/internal/format"
|
||||
"filippo.io/edwards25519"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/curve25519"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
@@ -65,6 +63,9 @@ func NewRSARecipient(pk ssh.PublicKey) (*RSARecipient, error) {
|
||||
} else {
|
||||
return nil, errors.New("pk does not implement ssh.CryptoPublicKey")
|
||||
}
|
||||
if r.pubKey.Size() < 2048/8 {
|
||||
return nil, errors.New("RSA key size is too small")
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
@@ -102,7 +103,7 @@ func NewRSAIdentity(key *rsa.PrivateKey) (*RSAIdentity, error) {
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (i *RSAIdentity) Recipient() age.Recipient {
|
||||
func (i *RSAIdentity) Recipient() *RSARecipient {
|
||||
return &RSARecipient{
|
||||
sshKey: i.sshKey,
|
||||
pubKey: &i.k.PublicKey,
|
||||
@@ -186,37 +187,14 @@ func ParseRecipient(s string) (age.Recipient, error) {
|
||||
return r, nil
|
||||
}
|
||||
|
||||
var curve25519P, _ = new(big.Int).SetString("57896044618658097711785492504343953926634992332820282019728792003956564819949", 10)
|
||||
|
||||
func ed25519PublicKeyToCurve25519(pk ed25519.PublicKey) ([]byte, error) {
|
||||
// ed25519.PublicKey is a little endian representation of the y-coordinate,
|
||||
// with the most significant bit set based on the sign of the x-coordinate.
|
||||
bigEndianY := make([]byte, ed25519.PublicKeySize)
|
||||
for i, b := range pk {
|
||||
bigEndianY[ed25519.PublicKeySize-i-1] = b
|
||||
// See https://blog.filippo.io/using-ed25519-keys-for-encryption and
|
||||
// https://pkg.go.dev/filippo.io/edwards25519#Point.BytesMontgomery.
|
||||
p, err := new(edwards25519.Point).SetBytes(pk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bigEndianY[0] &= 0b0111_1111
|
||||
|
||||
// The Montgomery u-coordinate is derived through the bilinear map
|
||||
//
|
||||
// u = (1 + y) / (1 - y)
|
||||
//
|
||||
// See https://blog.filippo.io/using-ed25519-keys-for-encryption.
|
||||
y := new(big.Int).SetBytes(bigEndianY)
|
||||
denom := new(big.Int).Sub(big.NewInt(1), y)
|
||||
if denom = denom.ModInverse(denom, curve25519P); denom == nil {
|
||||
return nil, errors.New("invalid point")
|
||||
}
|
||||
u := y.Mul(y.Add(y, big.NewInt(1)), denom)
|
||||
u.Mod(u, curve25519P)
|
||||
|
||||
out := make([]byte, curve25519.PointSize)
|
||||
uBytes := u.Bytes()
|
||||
for i, b := range uBytes {
|
||||
out[len(uBytes)-i-1] = b
|
||||
}
|
||||
|
||||
return out, nil
|
||||
return p.BytesMontgomery(), nil
|
||||
}
|
||||
|
||||
const ed25519Label = "age-encryption.org/v1/ssh-ed25519"
|
||||
@@ -296,6 +274,9 @@ func ParseIdentity(pemBytes []byte) (age.Identity, error) {
|
||||
switch k := k.(type) {
|
||||
case *ed25519.PrivateKey:
|
||||
return NewEd25519Identity(*k)
|
||||
// ParseRawPrivateKey returns inconsistent types. See Issue 429.
|
||||
case ed25519.PrivateKey:
|
||||
return NewEd25519Identity(k)
|
||||
case *rsa.PrivateKey:
|
||||
return NewRSAIdentity(k)
|
||||
}
|
||||
@@ -310,7 +291,7 @@ func ed25519PrivateKeyToCurve25519(pk ed25519.PrivateKey) []byte {
|
||||
return out[:curve25519.ScalarSize]
|
||||
}
|
||||
|
||||
func (i *Ed25519Identity) Recipient() age.Recipient {
|
||||
func (i *Ed25519Identity) Recipient() *Ed25519Recipient {
|
||||
return &Ed25519Recipient{
|
||||
sshKey: i.sshKey,
|
||||
theirPublicKey: i.ourPublicKey,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Copyright 2019 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package agessh_test
|
||||
|
||||
@@ -19,7 +17,7 @@ import (
|
||||
)
|
||||
|
||||
func TestSSHRSARoundTrip(t *testing.T) {
|
||||
pk, err := rsa.GenerateKey(rand.Reader, 768)
|
||||
pk, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Copyright 2019 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package agessh
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
@@ -24,6 +23,7 @@ import (
|
||||
// pass the result to NewEd25519Identity or NewRSAIdentity.
|
||||
type EncryptedSSHIdentity struct {
|
||||
pubKey ssh.PublicKey
|
||||
recipient age.Recipient
|
||||
pemBytes []byte
|
||||
passphrase func() ([]byte, error)
|
||||
|
||||
@@ -41,22 +41,34 @@ type EncryptedSSHIdentity struct {
|
||||
// passphrase is a callback that will be invoked by Unwrap when the passphrase
|
||||
// is necessary.
|
||||
func NewEncryptedSSHIdentity(pubKey ssh.PublicKey, pemBytes []byte, passphrase func() ([]byte, error)) (*EncryptedSSHIdentity, error) {
|
||||
switch t := pubKey.Type(); t {
|
||||
case "ssh-ed25519", "ssh-rsa":
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported SSH key type: %v", t)
|
||||
}
|
||||
return &EncryptedSSHIdentity{
|
||||
i := &EncryptedSSHIdentity{
|
||||
pubKey: pubKey,
|
||||
pemBytes: pemBytes,
|
||||
passphrase: passphrase,
|
||||
}, nil
|
||||
}
|
||||
switch t := pubKey.Type(); t {
|
||||
case "ssh-ed25519":
|
||||
r, err := NewEd25519Recipient(pubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i.recipient = r
|
||||
case "ssh-rsa":
|
||||
r, err := NewRSARecipient(pubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
i.recipient = r
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported SSH key type: %v", t)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
var _ age.Identity = &EncryptedSSHIdentity{}
|
||||
|
||||
func (i *EncryptedSSHIdentity) Recipient() (age.Recipient, error) {
|
||||
return ParseRecipient(string(ssh.MarshalAuthorizedKey(i.pubKey)))
|
||||
func (i *EncryptedSSHIdentity) Recipient() age.Recipient {
|
||||
return i.recipient
|
||||
}
|
||||
|
||||
// Unwrap implements age.Identity. If the private key is still encrypted, and
|
||||
@@ -94,19 +106,20 @@ func (i *EncryptedSSHIdentity) Unwrap(stanzas []*age.Stanza) (fileKey []byte, er
|
||||
return nil, fmt.Errorf("failed to decrypt SSH key file: %v", err)
|
||||
}
|
||||
|
||||
var pubKey interface {
|
||||
Equal(x crypto.PublicKey) bool
|
||||
}
|
||||
switch k := k.(type) {
|
||||
case *ed25519.PrivateKey:
|
||||
i.decrypted, err = NewEd25519Identity(*k)
|
||||
// TODO: here and below, better check that the two public keys match,
|
||||
// rather than just the type.
|
||||
if i.pubKey.Type() != ssh.KeyAlgoED25519 {
|
||||
return nil, fmt.Errorf("mismatched private (%s) and public (%s) SSH key types", ssh.KeyAlgoED25519, i.pubKey.Type())
|
||||
}
|
||||
pubKey = k.Public().(ed25519.PublicKey)
|
||||
// ParseRawPrivateKey returns inconsistent types. See Issue 429.
|
||||
case ed25519.PrivateKey:
|
||||
i.decrypted, err = NewEd25519Identity(k)
|
||||
pubKey = k.Public().(ed25519.PublicKey)
|
||||
case *rsa.PrivateKey:
|
||||
i.decrypted, err = NewRSAIdentity(k)
|
||||
if i.pubKey.Type() != ssh.KeyAlgoRSA {
|
||||
return nil, fmt.Errorf("mismatched private (%s) and public (%s) SSH key types", ssh.KeyAlgoRSA, i.pubKey.Type())
|
||||
}
|
||||
pubKey = &k.PublicKey
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected SSH key type: %T", k)
|
||||
}
|
||||
@@ -114,5 +127,9 @@ func (i *EncryptedSSHIdentity) Unwrap(stanzas []*age.Stanza) (fileKey []byte, er
|
||||
return nil, fmt.Errorf("invalid SSH key: %v", err)
|
||||
}
|
||||
|
||||
if exp := i.pubKey.(ssh.CryptoPublicKey).CryptoPublicKey(); !pubKey.Equal(exp) {
|
||||
return nil, fmt.Errorf("mismatched private and public SSH key")
|
||||
}
|
||||
|
||||
return i.decrypted.Unwrap(stanzas)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Copyright 2019 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package armor provides a strict, streaming implementation of the ASCII
|
||||
// armoring format for age files.
|
||||
@@ -16,6 +14,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"filippo.io/age/internal/format"
|
||||
@@ -28,7 +27,7 @@ const (
|
||||
|
||||
type armoredWriter struct {
|
||||
started, closed bool
|
||||
encoder io.WriteCloser
|
||||
encoder *format.WrappedBase64Encoder
|
||||
dst io.Writer
|
||||
}
|
||||
|
||||
@@ -50,15 +49,20 @@ func (a *armoredWriter) Close() error {
|
||||
if err := a.encoder.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := io.WriteString(a.dst, "\n"+Footer+"\n")
|
||||
footer := Footer + "\n"
|
||||
if !a.encoder.LastLineIsEmpty() {
|
||||
footer = "\n" + footer
|
||||
}
|
||||
_, err := io.WriteString(a.dst, footer)
|
||||
return err
|
||||
}
|
||||
|
||||
func NewWriter(dst io.Writer) io.WriteCloser {
|
||||
// TODO: write a test with aligned and misaligned sizes, and 8 and 10 steps.
|
||||
return &armoredWriter{dst: dst,
|
||||
encoder: base64.NewEncoder(base64.StdEncoding.Strict(),
|
||||
format.NewlineWriter(dst))}
|
||||
return &armoredWriter{
|
||||
dst: dst,
|
||||
encoder: format.NewWrappedBase64Encoder(base64.StdEncoding, dst),
|
||||
}
|
||||
}
|
||||
|
||||
type armoredReader struct {
|
||||
@@ -85,22 +89,47 @@ func (r *armoredReader) Read(p []byte) (int, error) {
|
||||
|
||||
getLine := func() ([]byte, error) {
|
||||
line, err := r.r.ReadBytes('\n')
|
||||
if err != nil && len(line) == 0 {
|
||||
if err == io.EOF {
|
||||
err = errors.New("invalid armor: unexpected EOF")
|
||||
}
|
||||
if err == io.EOF && len(line) == 0 {
|
||||
return nil, io.ErrUnexpectedEOF
|
||||
} else if err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
return bytes.TrimSpace(line), nil
|
||||
line = bytes.TrimSuffix(line, []byte("\n"))
|
||||
line = bytes.TrimSuffix(line, []byte("\r"))
|
||||
return line, nil
|
||||
}
|
||||
|
||||
if !r.started {
|
||||
const maxWhitespace = 1024
|
||||
drainTrailing := func() error {
|
||||
buf, err := io.ReadAll(io.LimitReader(r.r, maxWhitespace))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(bytes.TrimSpace(buf)) != 0 {
|
||||
return errors.New("trailing data after armored file")
|
||||
}
|
||||
if len(buf) == maxWhitespace {
|
||||
return errors.New("too much trailing whitespace")
|
||||
}
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
var removedWhitespace int
|
||||
for !r.started {
|
||||
line, err := getLine()
|
||||
if err != nil {
|
||||
return 0, r.setErr(err)
|
||||
}
|
||||
// Ignore leading whitespace.
|
||||
if len(bytes.TrimSpace(line)) == 0 {
|
||||
removedWhitespace += len(line) + 1
|
||||
if removedWhitespace > maxWhitespace {
|
||||
return 0, r.setErr(errors.New("too much leading whitespace"))
|
||||
}
|
||||
continue
|
||||
}
|
||||
if string(line) != Header {
|
||||
return 0, r.setErr(errors.New("invalid armor first line: " + string(line)))
|
||||
return 0, r.setErr(fmt.Errorf("invalid first line: %q", line))
|
||||
}
|
||||
r.started = true
|
||||
}
|
||||
@@ -109,15 +138,15 @@ func (r *armoredReader) Read(p []byte) (int, error) {
|
||||
return 0, r.setErr(err)
|
||||
}
|
||||
if string(line) == Footer {
|
||||
return 0, r.setErr(io.EOF)
|
||||
return 0, r.setErr(drainTrailing())
|
||||
}
|
||||
if len(line) > format.ColumnsPerLine {
|
||||
return 0, r.setErr(errors.New("invalid armor: column limit exceeded"))
|
||||
return 0, r.setErr(errors.New("column limit exceeded"))
|
||||
}
|
||||
r.unread = r.buf[:]
|
||||
n, err := base64.StdEncoding.Strict().Decode(r.unread, line)
|
||||
if err != nil {
|
||||
return 0, r.setErr(errors.New("invalid armor: " + err.Error()))
|
||||
return 0, r.setErr(err)
|
||||
}
|
||||
r.unread = r.unread[:n]
|
||||
|
||||
@@ -127,9 +156,9 @@ func (r *armoredReader) Read(p []byte) (int, error) {
|
||||
return 0, r.setErr(err)
|
||||
}
|
||||
if string(line) != Footer {
|
||||
return 0, r.setErr(errors.New("invalid armor closing line: " + string(line)))
|
||||
return 0, r.setErr(fmt.Errorf("invalid closing line: %q", line))
|
||||
}
|
||||
r.err = io.EOF
|
||||
r.setErr(drainTrailing())
|
||||
}
|
||||
|
||||
nn := copy(p, r.unread)
|
||||
@@ -137,7 +166,22 @@ func (r *armoredReader) Read(p []byte) (int, error) {
|
||||
return nn, nil
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return "invalid armor: " + e.err.Error()
|
||||
}
|
||||
|
||||
func (e *Error) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
func (r *armoredReader) setErr(err error) error {
|
||||
if err != io.EOF {
|
||||
err = &Error{err}
|
||||
}
|
||||
r.err = err
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Copyright 2019 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package armor_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"filippo.io/age"
|
||||
"filippo.io/age/armor"
|
||||
"filippo.io/age/internal/format"
|
||||
)
|
||||
|
||||
func ExampleNewWriter() {
|
||||
@@ -87,9 +91,15 @@ kB/RRusYjn+KVJ+KTioxj0THtzZPXcjFKuQ1
|
||||
}
|
||||
|
||||
func TestArmor(t *testing.T) {
|
||||
t.Run("PartialLine", func(t *testing.T) { testArmor(t, 611) })
|
||||
t.Run("FullLine", func(t *testing.T) { testArmor(t, 10*format.BytesPerLine) })
|
||||
}
|
||||
|
||||
func testArmor(t *testing.T, size int) {
|
||||
buf := &bytes.Buffer{}
|
||||
w := armor.NewWriter(buf)
|
||||
plain := make([]byte, 611)
|
||||
plain := make([]byte, size)
|
||||
rand.Read(plain)
|
||||
if _, err := w.Write(plain); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -101,12 +111,21 @@ func TestArmor(t *testing.T) {
|
||||
if block == nil {
|
||||
t.Fatal("PEM decoding failed")
|
||||
}
|
||||
if len(block.Headers) != 0 {
|
||||
t.Error("unexpected headers")
|
||||
}
|
||||
if block.Type != "AGE ENCRYPTED FILE" {
|
||||
t.Errorf("unexpected type %q", block.Type)
|
||||
}
|
||||
if !bytes.Equal(block.Bytes, plain) {
|
||||
t.Error("PEM decoded value doesn't match")
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), pem.EncodeToMemory(block)) {
|
||||
t.Error("PEM re-encoded value doesn't match")
|
||||
}
|
||||
|
||||
r := armor.NewReader(buf)
|
||||
out, err := ioutil.ReadAll(r)
|
||||
out, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -114,3 +133,50 @@ func TestArmor(t *testing.T) {
|
||||
t.Error("decoded value doesn't match")
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzMalleability(f *testing.F) {
|
||||
tests, err := filepath.Glob("../testdata/testkit/*")
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
for _, test := range tests {
|
||||
contents, err := os.ReadFile(test)
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
header, contents, ok := bytes.Cut(contents, []byte("\n\n"))
|
||||
if !ok {
|
||||
f.Fatal("testkit file without header")
|
||||
}
|
||||
if bytes.Contains(header, []byte("armored: yes")) {
|
||||
f.Add(contents)
|
||||
}
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
r := armor.NewReader(bytes.NewReader(data))
|
||||
content, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
if _, ok := err.(*armor.Error); !ok {
|
||||
t.Errorf("error type is %T: %v", err, err)
|
||||
}
|
||||
t.Skip()
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
w := armor.NewWriter(buf)
|
||||
if _, err := w.Write(content); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(normalize(buf.Bytes()), normalize(data)) {
|
||||
t.Error("re-encoded output different from input")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func normalize(f []byte) []byte {
|
||||
f = bytes.TrimSpace(f)
|
||||
f = bytes.Replace(f, []byte("\r\n"), []byte("\n"), -1)
|
||||
return f
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Copyright 2019 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
@@ -16,7 +14,7 @@ import (
|
||||
"time"
|
||||
|
||||
"filippo.io/age"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
const usage = `Usage:
|
||||
@@ -27,7 +25,7 @@ Options:
|
||||
-o, --output OUTPUT Write the result to the file at path OUTPUT.
|
||||
-y Convert an identity file to a recipients file.
|
||||
|
||||
age-keygen generates a new standard X25519 key pair, and outputs it to
|
||||
age-keygen generates a new native X25519 key pair, and outputs it to
|
||||
standard output or to the OUTPUT file.
|
||||
|
||||
If an OUTPUT file is specified, the public key is printed to standard error.
|
||||
@@ -70,10 +68,10 @@ func main() {
|
||||
flag.StringVar(&outFlag, "output", "", "output to `FILE` (default stdout)")
|
||||
flag.Parse()
|
||||
if len(flag.Args()) != 0 && !convertFlag {
|
||||
log.Fatalf("age-keygen takes no arguments")
|
||||
errorf("too many arguments")
|
||||
}
|
||||
if len(flag.Args()) > 1 && convertFlag {
|
||||
log.Fatalf("Too many arguments")
|
||||
errorf("too many arguments")
|
||||
}
|
||||
if versionFlag {
|
||||
if Version != "" {
|
||||
@@ -92,23 +90,21 @@ func main() {
|
||||
if outFlag != "" {
|
||||
f, err := os.OpenFile(outFlag, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open output file %q: %v", outFlag, err)
|
||||
errorf("failed to open output file %q: %v", outFlag, err)
|
||||
}
|
||||
defer f.Close()
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
errorf("failed to close output file %q: %v", outFlag, err)
|
||||
}
|
||||
}()
|
||||
out = f
|
||||
}
|
||||
|
||||
if fi, err := out.Stat(); err == nil {
|
||||
if fi.Mode().IsRegular() && fi.Mode().Perm()&0004 != 0 {
|
||||
fmt.Fprintf(os.Stderr, "Warning: writing secret key to a world-readable file.\n")
|
||||
}
|
||||
}
|
||||
|
||||
in := os.Stdin
|
||||
if inFile := flag.Arg(0); inFile != "" && inFile != "-" {
|
||||
f, err := os.Open(inFile)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open input file %q: %v", inFile, err)
|
||||
errorf("failed to open input file %q: %v", inFile, err)
|
||||
}
|
||||
defer f.Close()
|
||||
in = f
|
||||
@@ -117,6 +113,9 @@ func main() {
|
||||
if convertFlag {
|
||||
convert(in, out)
|
||||
} else {
|
||||
if fi, err := out.Stat(); err == nil && fi.Mode().IsRegular() && fi.Mode().Perm()&0004 != 0 {
|
||||
warning("writing secret key to a world-readable file")
|
||||
}
|
||||
generate(out)
|
||||
}
|
||||
}
|
||||
@@ -124,10 +123,10 @@ func main() {
|
||||
func generate(out *os.File) {
|
||||
k, err := age.GenerateX25519Identity()
|
||||
if err != nil {
|
||||
log.Fatalf("Internal error: %v", err)
|
||||
errorf("internal error: %v", err)
|
||||
}
|
||||
|
||||
if !terminal.IsTerminal(int(out.Fd())) {
|
||||
if !term.IsTerminal(int(out.Fd())) {
|
||||
fmt.Fprintf(os.Stderr, "Public key: %s\n", k.Recipient())
|
||||
}
|
||||
|
||||
@@ -139,16 +138,25 @@ func generate(out *os.File) {
|
||||
func convert(in io.Reader, out io.Writer) {
|
||||
ids, err := age.ParseIdentities(in)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse input: %v", err)
|
||||
errorf("failed to parse input: %v", err)
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
log.Fatalf("No identities found in the input")
|
||||
errorf("no identities found in the input")
|
||||
}
|
||||
for _, id := range ids {
|
||||
id, ok := id.(*age.X25519Identity)
|
||||
if !ok {
|
||||
log.Fatalf("Internal error: unexpected identity type: %T", id)
|
||||
errorf("internal error: unexpected identity type: %T", id)
|
||||
}
|
||||
fmt.Fprintf(out, "%s\n", id.Recipient())
|
||||
}
|
||||
}
|
||||
|
||||
func errorf(format string, v ...interface{}) {
|
||||
log.Printf("age-keygen: error: "+format, v...)
|
||||
log.Fatalf("age-keygen: report unexpected or unhelpful errors at https://filippo.io/age/report")
|
||||
}
|
||||
|
||||
func warning(msg string) {
|
||||
log.Printf("age-keygen: warning: " + msg)
|
||||
}
|
||||
|
||||
372
cmd/age/age.go
372
cmd/age/age.go
@@ -1,8 +1,6 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Copyright 2019 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
@@ -12,26 +10,19 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
_log "log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"filippo.io/age"
|
||||
"filippo.io/age/agessh"
|
||||
"filippo.io/age/armor"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"filippo.io/age/plugin"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
type multiFlag []string
|
||||
|
||||
func (f *multiFlag) String() string { return fmt.Sprint(*f) }
|
||||
|
||||
func (f *multiFlag) Set(value string) error {
|
||||
*f = append(*f, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
const usage = `Usage:
|
||||
age [--encrypt] (-r RECIPIENT | -R PATH)... [--armor] [-o OUTPUT] [INPUT]
|
||||
age [--encrypt] --passphrase [--armor] [-o OUTPUT] [INPUT]
|
||||
@@ -59,7 +50,8 @@ read recipients from standard input.
|
||||
|
||||
Identity files contain one or more secret keys ("AGE-SECRET-KEY-1..."),
|
||||
one per line, or an SSH key. Empty lines and lines starting with "#" are
|
||||
ignored as comments. Multiple key files can be provided, and any unused ones
|
||||
ignored as comments. Passphrase encrypted age files can be used as
|
||||
identity files. Multiple key files can be provided, and any unused ones
|
||||
will be ignored. "-" may be used to read identities from standard input.
|
||||
|
||||
When --encrypt is specified explicitly, -i can also be used to encrypt to an
|
||||
@@ -76,21 +68,52 @@ Example:
|
||||
// golang.org/issue/29814 and golang.org/issue/29228.
|
||||
var Version string
|
||||
|
||||
// stdinInUse is used to ensure only one of input, recipients, or identities
|
||||
// file is read from stdin. It's a singleton like os.Stdin.
|
||||
var stdinInUse bool
|
||||
|
||||
type multiFlag []string
|
||||
|
||||
func (f *multiFlag) String() string { return fmt.Sprint(*f) }
|
||||
|
||||
func (f *multiFlag) Set(value string) error {
|
||||
*f = append(*f, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
type identityFlag struct {
|
||||
Type, Value string
|
||||
}
|
||||
|
||||
// identityFlags tracks -i and -j flags, preserving their relative order, so
|
||||
// that "age -d -j agent -i encrypted-fallback-keys.age" behaves as expected.
|
||||
type identityFlags []identityFlag
|
||||
|
||||
func (f *identityFlags) addIdentityFlag(value string) error {
|
||||
*f = append(*f, identityFlag{Type: "i", Value: value})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *identityFlags) addPluginFlag(value string) error {
|
||||
*f = append(*f, identityFlag{Type: "j", Value: value})
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
_log.SetFlags(0)
|
||||
flag.Usage = func() { fmt.Fprintf(os.Stderr, "%s\n", usage) }
|
||||
|
||||
if len(os.Args) == 1 {
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
var (
|
||||
outFlag string
|
||||
decryptFlag, encryptFlag bool
|
||||
passFlag, versionFlag, armorFlag bool
|
||||
recipientFlags, identityFlags multiFlag
|
||||
recipientFlags multiFlag
|
||||
recipientsFileFlags multiFlag
|
||||
identityFlags identityFlags
|
||||
)
|
||||
|
||||
flag.BoolVar(&versionFlag, "version", false, "print the version")
|
||||
@@ -108,8 +131,9 @@ func main() {
|
||||
flag.Var(&recipientFlags, "recipient", "recipient (can be repeated)")
|
||||
flag.Var(&recipientsFileFlags, "R", "recipients file (can be repeated)")
|
||||
flag.Var(&recipientsFileFlags, "recipients-file", "recipients file (can be repeated)")
|
||||
flag.Var(&identityFlags, "i", "identity (can be repeated)")
|
||||
flag.Var(&identityFlags, "identity", "identity (can be repeated)")
|
||||
flag.Func("i", "identity (can be repeated)", identityFlags.addIdentityFlag)
|
||||
flag.Func("identity", "identity (can be repeated)", identityFlags.addIdentityFlag)
|
||||
flag.Func("j", "data-less plugin (can be repeated)", identityFlags.addPluginFlag)
|
||||
flag.Parse()
|
||||
|
||||
if versionFlag {
|
||||
@@ -118,6 +142,8 @@ func main() {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -126,78 +152,138 @@ func main() {
|
||||
}
|
||||
|
||||
if flag.NArg() > 1 {
|
||||
logFatalf("Error: too many arguments: %q.\n"+
|
||||
"Note that the input file must be specified after all flags.", flag.Args())
|
||||
var hints []string
|
||||
quotedArgs := strings.Trim(fmt.Sprintf("%q", flag.Args()), "[]")
|
||||
|
||||
// If the second argument looks like a flag, suggest moving the first
|
||||
// argument to the back (as long as the arguments don't need quoting).
|
||||
if strings.HasPrefix(flag.Arg(1), "-") {
|
||||
hints = append(hints, "the input file must be specified after all flags")
|
||||
|
||||
safe := true
|
||||
unsafeShell := regexp.MustCompile(`[^\w@%+=:,./-]`)
|
||||
for _, arg := range os.Args {
|
||||
if unsafeShell.MatchString(arg) {
|
||||
safe = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if safe {
|
||||
i := len(os.Args) - flag.NArg()
|
||||
newArgs := append([]string{}, os.Args[:i]...)
|
||||
newArgs = append(newArgs, os.Args[i+1:]...)
|
||||
newArgs = append(newArgs, os.Args[i])
|
||||
hints = append(hints, "did you mean:")
|
||||
hints = append(hints, " "+strings.Join(newArgs, " "))
|
||||
}
|
||||
} else {
|
||||
hints = append(hints, "only a single input file may be specified at a time")
|
||||
}
|
||||
|
||||
errorWithHint("too many INPUT arguments: "+quotedArgs, hints...)
|
||||
}
|
||||
|
||||
switch {
|
||||
case decryptFlag:
|
||||
if encryptFlag {
|
||||
logFatalf("Error: -e/--encrypt can't be used with -d/--decrypt.")
|
||||
errorf("-e/--encrypt can't be used with -d/--decrypt")
|
||||
}
|
||||
if armorFlag {
|
||||
logFatalf("Error: -a/--armor can't be used with -d/--decrypt.\n" +
|
||||
"Note that armored files are detected automatically.")
|
||||
errorWithHint("-a/--armor can't be used with -d/--decrypt",
|
||||
"note that armored files are detected automatically")
|
||||
}
|
||||
if passFlag {
|
||||
logFatalf("Error: -p/--passphrase can't be used with -d/--decrypt.\n" +
|
||||
"Note that password protected files are detected automatically.")
|
||||
errorWithHint("-p/--passphrase can't be used with -d/--decrypt",
|
||||
"note that password protected files are detected automatically")
|
||||
}
|
||||
if len(recipientFlags) > 0 {
|
||||
logFatalf("Error: -r/--recipient can't be used with -d/--decrypt.\n" +
|
||||
"Did you mean to use -i/--identity to specify a private key?")
|
||||
errorWithHint("-r/--recipient can't be used with -d/--decrypt",
|
||||
"did you mean to use -i/--identity to specify a private key?")
|
||||
}
|
||||
if len(recipientsFileFlags) > 0 {
|
||||
logFatalf("Error: -R/--recipients-file can't be used with -d/--decrypt.\n" +
|
||||
"Did you mean to use -i/--identity to specify a private key?")
|
||||
errorWithHint("-R/--recipients-file can't be used with -d/--decrypt",
|
||||
"did you mean to use -i/--identity to specify a private key?")
|
||||
}
|
||||
default: // encrypt
|
||||
if len(identityFlags) > 0 && !encryptFlag {
|
||||
logFatalf("Error: -i/--identity can't be used in encryption mode unless symmetric encryption is explicitly selected with -e/--encrypt.\n" +
|
||||
"Did you forget to specify -d/--decrypt?")
|
||||
errorWithHint("-i/--identity and -j can't be used in encryption mode unless symmetric encryption is explicitly selected with -e/--encrypt",
|
||||
"did you forget to specify -d/--decrypt?")
|
||||
}
|
||||
if len(recipientFlags)+len(recipientsFileFlags)+len(identityFlags) == 0 && !passFlag {
|
||||
logFatalf("Error: missing recipients.\n" +
|
||||
"Did you forget to specify -r/--recipient, -R/--recipients-file or -p/--passphrase?")
|
||||
errorWithHint("missing recipients",
|
||||
"did you forget to specify -r/--recipient, -R/--recipients-file or -p/--passphrase?")
|
||||
}
|
||||
if len(recipientFlags) > 0 && passFlag {
|
||||
logFatalf("Error: -p/--passphrase can't be combined with -r/--recipient.")
|
||||
errorf("-p/--passphrase can't be combined with -r/--recipient")
|
||||
}
|
||||
if len(recipientsFileFlags) > 0 && passFlag {
|
||||
logFatalf("Error: -p/--passphrase can't be combined with -R/--recipients-file.")
|
||||
errorf("-p/--passphrase can't be combined with -R/--recipients-file")
|
||||
}
|
||||
if len(identityFlags) > 0 && passFlag {
|
||||
logFatalf("Error: -p/--passphrase can't be combined with -i/--identity.")
|
||||
errorf("-p/--passphrase can't be combined with -i/--identity and -j")
|
||||
}
|
||||
}
|
||||
|
||||
var inUseFiles []string
|
||||
for _, i := range identityFlags {
|
||||
if i.Type != "i" {
|
||||
continue
|
||||
}
|
||||
inUseFiles = append(inUseFiles, absPath(i.Value))
|
||||
}
|
||||
for _, f := range recipientsFileFlags {
|
||||
inUseFiles = append(inUseFiles, absPath(f))
|
||||
}
|
||||
|
||||
var in io.Reader = os.Stdin
|
||||
var out io.Writer = os.Stdout
|
||||
if name := flag.Arg(0); name != "" && name != "-" {
|
||||
inUseFiles = append(inUseFiles, absPath(name))
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
logFatalf("Error: failed to open input file %q: %v", name, err)
|
||||
errorf("failed to open input file %q: %v", name, err)
|
||||
}
|
||||
defer f.Close()
|
||||
in = f
|
||||
} else {
|
||||
stdinInUse = true
|
||||
if decryptFlag && term.IsTerminal(int(os.Stdin.Fd())) {
|
||||
// If the input comes from a TTY, assume it's armored, and buffer up
|
||||
// to the END line (or EOF/EOT) so that a password prompt or the
|
||||
// output don't get in the way of typing the input. See Issue 364.
|
||||
buf, err := bufferTerminalInput(in)
|
||||
if err != nil {
|
||||
errorf("failed to buffer terminal input: %v", err)
|
||||
}
|
||||
in = buf
|
||||
}
|
||||
}
|
||||
if name := outFlag; name != "" && name != "-" {
|
||||
for _, f := range inUseFiles {
|
||||
if f == absPath(name) {
|
||||
errorf("input and output file are the same: %q", name)
|
||||
}
|
||||
}
|
||||
f := newLazyOpener(name)
|
||||
defer f.Close()
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
errorf("failed to close output file %q: %v", name, err)
|
||||
}
|
||||
}()
|
||||
out = f
|
||||
} else if terminal.IsTerminal(int(os.Stdout.Fd())) {
|
||||
} else if term.IsTerminal(int(os.Stdout.Fd())) {
|
||||
if name != "-" {
|
||||
if decryptFlag {
|
||||
// TODO: buffer the output and check it's printable.
|
||||
} else if !armorFlag {
|
||||
// If the output wouldn't be armored, refuse to send binary to
|
||||
// the terminal unless explicitly requested with "-o -".
|
||||
logFatalf("Error: refusing to output binary to the terminal.\n" +
|
||||
`Did you mean to use -a/--armor? Force with "-o -".`)
|
||||
errorWithHint("refusing to output binary to the terminal",
|
||||
"did you mean to use -a/--armor?",
|
||||
`force anyway with "-o -"`)
|
||||
}
|
||||
}
|
||||
if in == os.Stdin && terminal.IsTerminal(int(os.Stdin.Fd())) {
|
||||
if in == os.Stdin && term.IsTerminal(int(os.Stdin.Fd())) {
|
||||
// If the input comes from a TTY and output will go to a TTY,
|
||||
// buffer it up so it doesn't get in the way of typing the input.
|
||||
buf := &bytes.Buffer{}
|
||||
@@ -207,22 +293,19 @@ func main() {
|
||||
}
|
||||
|
||||
switch {
|
||||
case decryptFlag && len(identityFlags) == 0:
|
||||
decryptPass(in, out)
|
||||
case decryptFlag:
|
||||
decrypt(identityFlags, in, out)
|
||||
decryptNotPass(identityFlags, in, out)
|
||||
case passFlag:
|
||||
pass, err := passphrasePromptForEncryption()
|
||||
if err != nil {
|
||||
logFatalf("Error: %v", err)
|
||||
}
|
||||
encryptPass(pass, in, out, armorFlag)
|
||||
encryptPass(in, out, armorFlag)
|
||||
default:
|
||||
encryptKeys(recipientFlags, recipientsFileFlags, identityFlags, in, out, armorFlag)
|
||||
encryptNotPass(recipientFlags, recipientsFileFlags, identityFlags, in, out, armorFlag)
|
||||
}
|
||||
}
|
||||
|
||||
func passphrasePromptForEncryption() (string, error) {
|
||||
fmt.Fprintf(os.Stderr, "Enter passphrase (leave empty to autogenerate a secure one): ")
|
||||
pass, err := readPassphrase()
|
||||
pass, err := readSecret("Enter passphrase (leave empty to autogenerate a secure one):")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read passphrase: %v", err)
|
||||
}
|
||||
@@ -233,10 +316,12 @@ func passphrasePromptForEncryption() (string, error) {
|
||||
words = append(words, randomWord())
|
||||
}
|
||||
p = strings.Join(words, "-")
|
||||
fmt.Fprintf(os.Stderr, "Using the autogenerated passphrase %q.\n", p)
|
||||
err := printfToTerminal("using autogenerated passphrase %q", p)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not print passphrase: %v", err)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Confirm passphrase: ")
|
||||
confirm, err := readPassphrase()
|
||||
confirm, err := readSecret("Confirm passphrase:")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read passphrase: %v", err)
|
||||
}
|
||||
@@ -247,84 +332,147 @@ func passphrasePromptForEncryption() (string, error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func encryptKeys(keys, files, identities []string, in io.Reader, out io.Writer, armor bool) {
|
||||
func encryptNotPass(recs, files []string, identities identityFlags, in io.Reader, out io.Writer, armor bool) {
|
||||
var recipients []age.Recipient
|
||||
for _, arg := range keys {
|
||||
for _, arg := range recs {
|
||||
r, err := parseRecipient(arg)
|
||||
if err, ok := err.(gitHubRecipientError); ok {
|
||||
errorWithHint(err.Error(), "instead, use recipient files like",
|
||||
" curl -O https://github.com/"+err.username+".keys",
|
||||
" age -R "+err.username+".keys")
|
||||
}
|
||||
if err != nil {
|
||||
logFatalf("Error: %v", err)
|
||||
errorf("%v", err)
|
||||
}
|
||||
recipients = append(recipients, r)
|
||||
}
|
||||
for _, name := range files {
|
||||
recs, err := parseRecipientsFile(name)
|
||||
if err != nil {
|
||||
logFatalf("Error: failed to parse recipient file %q: %v", name, err)
|
||||
errorf("failed to parse recipient file %q: %v", name, err)
|
||||
}
|
||||
recipients = append(recipients, recs...)
|
||||
}
|
||||
for _, name := range identities {
|
||||
ids, err := parseIdentitiesFile(name)
|
||||
if err != nil {
|
||||
logFatalf("Error reading %q: %v", name, err)
|
||||
}
|
||||
for _, id := range ids {
|
||||
r, err := identityToRecipient(id)
|
||||
for _, f := range identities {
|
||||
switch f.Type {
|
||||
case "i":
|
||||
ids, err := parseIdentitiesFile(f.Value)
|
||||
if err != nil {
|
||||
logFatalf("Internal error processing %q: %v", name, err)
|
||||
errorf("reading %q: %v", f.Value, err)
|
||||
}
|
||||
recipients = append(recipients, r)
|
||||
r, err := identitiesToRecipients(ids)
|
||||
if err != nil {
|
||||
errorf("internal error processing %q: %v", f.Value, err)
|
||||
}
|
||||
recipients = append(recipients, r...)
|
||||
case "j":
|
||||
id, err := plugin.NewIdentityWithoutData(f.Value, pluginTerminalUI)
|
||||
if err != nil {
|
||||
errorf("initializing %q: %v", f.Value, err)
|
||||
}
|
||||
recipients = append(recipients, id.Recipient())
|
||||
}
|
||||
}
|
||||
encrypt(recipients, in, out, armor)
|
||||
}
|
||||
|
||||
func encryptPass(pass string, in io.Reader, out io.Writer, armor bool) {
|
||||
func encryptPass(in io.Reader, out io.Writer, armor bool) {
|
||||
pass, err := passphrasePromptForEncryption()
|
||||
if err != nil {
|
||||
errorf("%v", err)
|
||||
}
|
||||
|
||||
r, err := age.NewScryptRecipient(pass)
|
||||
if err != nil {
|
||||
logFatalf("Error: %v", err)
|
||||
errorf("%v", err)
|
||||
}
|
||||
testOnlyConfigureScryptIdentity(r)
|
||||
encrypt([]age.Recipient{r}, in, out, armor)
|
||||
}
|
||||
|
||||
var testOnlyConfigureScryptIdentity = func(*age.ScryptRecipient) {}
|
||||
|
||||
func encrypt(recipients []age.Recipient, in io.Reader, out io.Writer, withArmor bool) {
|
||||
if withArmor {
|
||||
a := armor.NewWriter(out)
|
||||
defer func() {
|
||||
if err := a.Close(); err != nil {
|
||||
logFatalf("Error: %v", err)
|
||||
errorf("%v", err)
|
||||
}
|
||||
}()
|
||||
out = a
|
||||
}
|
||||
w, err := age.Encrypt(out, recipients...)
|
||||
if err != nil {
|
||||
logFatalf("Error: %v", err)
|
||||
errorf("%v", err)
|
||||
}
|
||||
if _, err := io.Copy(w, in); err != nil {
|
||||
logFatalf("Error: %v", err)
|
||||
errorf("%v", err)
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
logFatalf("Error: %v", err)
|
||||
errorf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func decrypt(keys []string, in io.Reader, out io.Writer) {
|
||||
// crlfMangledIntro and utf16MangledIntro are the intro lines of the age format
|
||||
// after mangling by various versions of PowerShell redirection, truncated to
|
||||
// the length of the correct intro line. See issue 290.
|
||||
const crlfMangledIntro = "age-encryption.org/v1" + "\r"
|
||||
const utf16MangledIntro = "\xff\xfe" + "a\x00g\x00e\x00-\x00e\x00n\x00c\x00r\x00y\x00p\x00"
|
||||
|
||||
type rejectScryptIdentity struct{}
|
||||
|
||||
func (rejectScryptIdentity) Unwrap(stanzas []*age.Stanza) ([]byte, error) {
|
||||
if len(stanzas) != 1 || stanzas[0].Type != "scrypt" {
|
||||
return nil, age.ErrIncorrectIdentity
|
||||
}
|
||||
errorWithHint("file is passphrase-encrypted but identities were specified with -i/--identity or -j",
|
||||
"remove all -i/--identity/-j flags to decrypt passphrase-encrypted files")
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func decryptNotPass(flags identityFlags, in io.Reader, out io.Writer) {
|
||||
identities := []age.Identity{rejectScryptIdentity{}}
|
||||
|
||||
for _, f := range flags {
|
||||
switch f.Type {
|
||||
case "i":
|
||||
ids, err := parseIdentitiesFile(f.Value)
|
||||
if err != nil {
|
||||
errorf("reading %q: %v", f.Value, err)
|
||||
}
|
||||
identities = append(identities, ids...)
|
||||
case "j":
|
||||
id, err := plugin.NewIdentityWithoutData(f.Value, pluginTerminalUI)
|
||||
if err != nil {
|
||||
errorf("initializing %q: %v", f.Value, err)
|
||||
}
|
||||
identities = append(identities, id)
|
||||
}
|
||||
}
|
||||
|
||||
decrypt(identities, in, out)
|
||||
}
|
||||
|
||||
func decryptPass(in io.Reader, out io.Writer) {
|
||||
identities := []age.Identity{
|
||||
// If there is an scrypt recipient (it will have to be the only one and)
|
||||
// this identity will be invoked.
|
||||
&LazyScryptIdentity{passphrasePrompt},
|
||||
&LazyScryptIdentity{passphrasePromptForDecryption},
|
||||
}
|
||||
|
||||
for _, name := range keys {
|
||||
ids, err := parseIdentitiesFile(name)
|
||||
if err != nil {
|
||||
logFatalf("Error reading %q: %v", name, err)
|
||||
}
|
||||
identities = append(identities, ids...)
|
||||
}
|
||||
decrypt(identities, in, out)
|
||||
}
|
||||
|
||||
func decrypt(identities []age.Identity, in io.Reader, out io.Writer) {
|
||||
rr := bufio.NewReader(in)
|
||||
if intro, _ := rr.Peek(len(crlfMangledIntro)); string(intro) == crlfMangledIntro ||
|
||||
string(intro) == utf16MangledIntro {
|
||||
errorWithHint("invalid header intro",
|
||||
"it looks like this file was corrupted by PowerShell redirection",
|
||||
"consider using -o or -a to encrypt files in PowerShell")
|
||||
}
|
||||
|
||||
if start, _ := rr.Peek(len(armor.Header)); string(start) == armor.Header {
|
||||
in = armor.NewReader(rr)
|
||||
} else {
|
||||
@@ -333,34 +481,47 @@ func decrypt(keys []string, in io.Reader, out io.Writer) {
|
||||
|
||||
r, err := age.Decrypt(in, identities...)
|
||||
if err != nil {
|
||||
logFatalf("Error: %v", err)
|
||||
errorf("%v", err)
|
||||
}
|
||||
out.Write(nil) // trigger the lazyOpener even if r is empty
|
||||
if _, err := io.Copy(out, r); err != nil {
|
||||
logFatalf("Error: %v", err)
|
||||
errorf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func passphrasePrompt() (string, error) {
|
||||
fmt.Fprintf(os.Stderr, "Enter passphrase: ")
|
||||
pass, err := readPassphrase()
|
||||
func passphrasePromptForDecryption() (string, error) {
|
||||
pass, err := readSecret("Enter passphrase:")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read passphrase: %v", err)
|
||||
}
|
||||
return string(pass), nil
|
||||
}
|
||||
|
||||
func identityToRecipient(id age.Identity) (age.Recipient, error) {
|
||||
switch id := id.(type) {
|
||||
case *age.X25519Identity:
|
||||
return id.Recipient(), nil
|
||||
case *agessh.RSAIdentity:
|
||||
return id.Recipient(), nil
|
||||
case *agessh.Ed25519Identity:
|
||||
return id.Recipient(), nil
|
||||
case *agessh.EncryptedSSHIdentity:
|
||||
return id.Recipient()
|
||||
func identitiesToRecipients(ids []age.Identity) ([]age.Recipient, error) {
|
||||
var recipients []age.Recipient
|
||||
for _, id := range ids {
|
||||
switch id := id.(type) {
|
||||
case *age.X25519Identity:
|
||||
recipients = append(recipients, id.Recipient())
|
||||
case *plugin.Identity:
|
||||
recipients = append(recipients, id.Recipient())
|
||||
case *agessh.RSAIdentity:
|
||||
recipients = append(recipients, id.Recipient())
|
||||
case *agessh.Ed25519Identity:
|
||||
recipients = append(recipients, id.Recipient())
|
||||
case *agessh.EncryptedSSHIdentity:
|
||||
recipients = append(recipients, id.Recipient())
|
||||
case *EncryptedIdentity:
|
||||
r, err := id.Recipients()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
recipients = append(recipients, r...)
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected identity type: %T", id)
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected identity type: %T", id)
|
||||
return recipients, nil
|
||||
}
|
||||
|
||||
type lazyOpener struct {
|
||||
@@ -390,8 +551,9 @@ func (l *lazyOpener) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func logFatalf(format string, v ...interface{}) {
|
||||
_log.Printf(format, v...)
|
||||
_log.Fatalf("[ Did age not do what you expected? Could an error be more useful?" +
|
||||
" Tell us: https://filippo.io/age/report ]")
|
||||
func absPath(name string) string {
|
||||
if abs, err := filepath.Abs(name); err == nil {
|
||||
return abs
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
@@ -1,88 +1,83 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Copyright 2019 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"bufio"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"filippo.io/age"
|
||||
"github.com/rogpeppe/go-internal/testscript"
|
||||
)
|
||||
|
||||
func TestVectors(t *testing.T) {
|
||||
defaultIDs, err := parseIdentitiesFile("testdata/default_key.txt")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
password, err := ioutil.ReadFile("testdata/default_password.txt")
|
||||
if err == nil {
|
||||
p := strings.TrimSpace(string(password))
|
||||
i, err := age.NewScryptIdentity(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defaultIDs = append(defaultIDs, i)
|
||||
}
|
||||
|
||||
files, _ := filepath.Glob("testdata/*.age")
|
||||
for _, f := range files {
|
||||
_, name := filepath.Split(f)
|
||||
name = strings.TrimSuffix(name, ".age")
|
||||
expectFailure := strings.HasPrefix(name, "fail_")
|
||||
expectNoMatch := strings.HasPrefix(name, "nomatch_")
|
||||
t.Run(name, func(t *testing.T) {
|
||||
identities := defaultIDs
|
||||
ids, err := parseIdentitiesFile("testdata/" + name + "_key.txt")
|
||||
if err == nil {
|
||||
identities = ids
|
||||
func TestMain(m *testing.M) {
|
||||
os.Exit(testscript.RunMain(m, map[string]func() int{
|
||||
"age": func() (exitCode int) {
|
||||
testOnlyPanicInsteadOfExit = true
|
||||
defer func() {
|
||||
if testOnlyDidExit {
|
||||
exitCode = recover().(int)
|
||||
}
|
||||
}()
|
||||
testOnlyConfigureScryptIdentity = func(r *age.ScryptRecipient) {
|
||||
r.SetWorkFactor(10)
|
||||
}
|
||||
password, err := ioutil.ReadFile("testdata/" + name + "_password.txt")
|
||||
if err == nil {
|
||||
p := strings.TrimSpace(string(password))
|
||||
i, err := age.NewScryptIdentity(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
identities = []age.Identity{i}
|
||||
testOnlyFixedRandomWord = "four"
|
||||
main()
|
||||
return 0
|
||||
},
|
||||
"age-plugin-test": func() (exitCode int) {
|
||||
// TODO: use plugin server package once it's available.
|
||||
switch os.Args[1] {
|
||||
case "--age-plugin=recipient-v1":
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanner.Scan() // add-recipient
|
||||
scanner.Scan() // body
|
||||
scanner.Scan() // grease
|
||||
scanner.Scan() // body
|
||||
scanner.Scan() // wrap-file-key
|
||||
scanner.Scan() // body
|
||||
fileKey := scanner.Text()
|
||||
scanner.Scan() // extension-labels
|
||||
scanner.Scan() // body
|
||||
scanner.Scan() // done
|
||||
scanner.Scan() // body
|
||||
os.Stdout.WriteString("-> recipient-stanza 0 test\n")
|
||||
os.Stdout.WriteString(fileKey + "\n")
|
||||
scanner.Scan() // ok
|
||||
scanner.Scan() // body
|
||||
os.Stdout.WriteString("-> done\n\n")
|
||||
return 0
|
||||
case "--age-plugin=identity-v1":
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanner.Scan() // add-identity
|
||||
scanner.Scan() // body
|
||||
scanner.Scan() // grease
|
||||
scanner.Scan() // body
|
||||
scanner.Scan() // recipient-stanza
|
||||
scanner.Scan() // body
|
||||
fileKey := scanner.Text()
|
||||
scanner.Scan() // done
|
||||
scanner.Scan() // body
|
||||
os.Stdout.WriteString("-> file-key 0\n")
|
||||
os.Stdout.WriteString(fileKey + "\n")
|
||||
scanner.Scan() // ok
|
||||
scanner.Scan() // body
|
||||
os.Stdout.WriteString("-> done\n\n")
|
||||
return 0
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
|
||||
in, err := os.Open("testdata/" + name + ".age")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r, err := age.Decrypt(in, identities...)
|
||||
if expectFailure {
|
||||
if err == nil {
|
||||
t.Fatal("expected Decrypt failure")
|
||||
}
|
||||
if e := (&age.NoIdentityMatchError{}); errors.As(err, &e) {
|
||||
t.Errorf("got ErrIncorrectIdentity, expected more specific error")
|
||||
}
|
||||
} else if expectNoMatch {
|
||||
if err == nil {
|
||||
t.Fatal("expected Decrypt failure")
|
||||
}
|
||||
if e := (&age.NoIdentityMatchError{}); !errors.As(err, &e) {
|
||||
t.Errorf("expected ErrIncorrectIdentity, got %v", err)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("%s", out)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
func TestScript(t *testing.T) {
|
||||
testscript.Run(t, testscript.Params{
|
||||
Dir: "testdata",
|
||||
// TODO: enable AGEDEBUG=plugin without breaking stderr checks.
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Copyright 2019 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"filippo.io/age"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
// LazyScryptIdentity is an age.Identity that requests a passphrase only if it
|
||||
// encounters an scrypt stanza. After obtaining a passphrase, it delegates to
|
||||
// ScryptIdentity.
|
||||
type LazyScryptIdentity struct {
|
||||
Passphrase func() (string, error)
|
||||
}
|
||||
@@ -22,6 +22,11 @@ type LazyScryptIdentity struct {
|
||||
var _ age.Identity = &LazyScryptIdentity{}
|
||||
|
||||
func (i *LazyScryptIdentity) Unwrap(stanzas []*age.Stanza) (fileKey []byte, err error) {
|
||||
for _, s := range stanzas {
|
||||
if s.Type == "scrypt" && len(stanzas) != 1 {
|
||||
return nil, errors.New("an scrypt recipient must be the only one")
|
||||
}
|
||||
}
|
||||
if len(stanzas) != 1 || stanzas[0].Type != "scrypt" {
|
||||
return nil, age.ErrIncorrectIdentity
|
||||
}
|
||||
@@ -45,23 +50,55 @@ func (i *LazyScryptIdentity) Unwrap(stanzas []*age.Stanza) (fileKey []byte, err
|
||||
return fileKey, err
|
||||
}
|
||||
|
||||
// stdinInUse is set in main. It's a singleton like os.Stdin.
|
||||
var stdinInUse bool
|
||||
type EncryptedIdentity struct {
|
||||
Contents []byte
|
||||
Passphrase func() (string, error)
|
||||
NoMatchWarning func()
|
||||
|
||||
func readPassphrase() ([]byte, error) {
|
||||
fd := int(os.Stdin.Fd())
|
||||
if !terminal.IsTerminal(fd) || stdinInUse {
|
||||
tty, err := os.Open("/dev/tty")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("standard input is not available or not a terminal, and opening /dev/tty failed: %v", err)
|
||||
}
|
||||
defer tty.Close()
|
||||
fd = int(tty.Fd())
|
||||
}
|
||||
defer fmt.Fprintf(os.Stderr, "\n")
|
||||
p, err := terminal.ReadPassword(fd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return p, nil
|
||||
identities []age.Identity
|
||||
}
|
||||
|
||||
var _ age.Identity = &EncryptedIdentity{}
|
||||
|
||||
func (i *EncryptedIdentity) Recipients() ([]age.Recipient, error) {
|
||||
if i.identities == nil {
|
||||
if err := i.decrypt(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return identitiesToRecipients(i.identities)
|
||||
}
|
||||
|
||||
func (i *EncryptedIdentity) Unwrap(stanzas []*age.Stanza) (fileKey []byte, err error) {
|
||||
if i.identities == nil {
|
||||
if err := i.decrypt(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
for _, id := range i.identities {
|
||||
fileKey, err = id.Unwrap(stanzas)
|
||||
if errors.Is(err, age.ErrIncorrectIdentity) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fileKey, nil
|
||||
}
|
||||
i.NoMatchWarning()
|
||||
return nil, age.ErrIncorrectIdentity
|
||||
}
|
||||
|
||||
func (i *EncryptedIdentity) decrypt() error {
|
||||
d, err := age.Decrypt(bytes.NewReader(i.Contents), &LazyScryptIdentity{i.Passphrase})
|
||||
if e := new(age.NoIdentityMatchError); errors.As(err, &e) {
|
||||
return fmt.Errorf("identity file is encrypted with age but not with a passphrase")
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decrypt identity file: %v", err)
|
||||
}
|
||||
i.identities, err = parseIdentities(d)
|
||||
return err
|
||||
}
|
||||
|
||||
122
cmd/age/parse.go
122
cmd/age/parse.go
@@ -1,8 +1,6 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Copyright 2019 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
@@ -11,27 +9,36 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"filippo.io/age"
|
||||
"filippo.io/age/agessh"
|
||||
"filippo.io/age/armor"
|
||||
"filippo.io/age/plugin"
|
||||
"golang.org/x/crypto/cryptobyte"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type gitHubRecipientError struct {
|
||||
username string
|
||||
}
|
||||
|
||||
func (gitHubRecipientError) Error() string {
|
||||
return `"github:" recipients were removed from the design`
|
||||
}
|
||||
|
||||
func parseRecipient(arg string) (age.Recipient, error) {
|
||||
switch {
|
||||
case strings.HasPrefix(arg, "age1") && strings.Count(arg, "1") > 1:
|
||||
return plugin.NewRecipient(arg, pluginTerminalUI)
|
||||
case strings.HasPrefix(arg, "age1"):
|
||||
return age.ParseX25519Recipient(arg)
|
||||
case strings.HasPrefix(arg, "ssh-"):
|
||||
return agessh.ParseRecipient(arg)
|
||||
case strings.HasPrefix(arg, "github:"):
|
||||
name := strings.TrimPrefix(arg, "github:")
|
||||
return nil, fmt.Errorf(`"github:" recipients were removed from the design.`+"\n"+
|
||||
"Instead, use recipient files like\n\n curl -O https://github.com/%s.keys\n age -R %s.keys\n\n", name, name)
|
||||
return nil, gitHubRecipientError{name}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unknown recipient type: %q", arg)
|
||||
@@ -72,7 +79,7 @@ func parseRecipientsFile(name string) ([]age.Recipient, error) {
|
||||
if err != nil {
|
||||
if t, ok := sshKeyType(line); ok {
|
||||
// Skip unsupported but valid SSH public keys with a warning.
|
||||
log.Printf("Warning: recipients file %q: ignoring unsupported SSH key of type %q at line %d", name, t, n)
|
||||
warningf("recipients file %q: ignoring unsupported SSH key of type %q at line %d", name, t, n)
|
||||
continue
|
||||
}
|
||||
// Hide the error since it might unintentionally leak the contents
|
||||
@@ -114,8 +121,8 @@ func sshKeyType(s string) (string, bool) {
|
||||
}
|
||||
|
||||
// parseIdentitiesFile parses a file that contains age or SSH keys. It returns
|
||||
// one of *age.X25519Identity, *agessh.RSAIdentity, *agessh.Ed25519Identity, or
|
||||
// *agessh.EncryptedSSHIdentity.
|
||||
// one or more of *age.X25519Identity, *agessh.RSAIdentity, *agessh.Ed25519Identity,
|
||||
// *agessh.EncryptedSSHIdentity, or *EncryptedIdentity.
|
||||
func parseIdentitiesFile(name string) ([]age.Identity, error) {
|
||||
var f *os.File
|
||||
if name == "-" {
|
||||
@@ -134,10 +141,42 @@ func parseIdentitiesFile(name string) ([]age.Identity, error) {
|
||||
}
|
||||
|
||||
b := bufio.NewReader(f)
|
||||
const pemHeader = "-----BEGIN"
|
||||
if peeked, _ := b.Peek(len(pemHeader)); string(peeked) == pemHeader {
|
||||
p, _ := b.Peek(14) // length of "age-encryption" and "-----BEGIN AGE"
|
||||
peeked := string(p)
|
||||
|
||||
switch {
|
||||
// An age encrypted file, plain or armored.
|
||||
case peeked == "age-encryption" || peeked == "-----BEGIN AGE":
|
||||
var r io.Reader = b
|
||||
if peeked == "-----BEGIN AGE" {
|
||||
r = armor.NewReader(r)
|
||||
}
|
||||
const privateKeySizeLimit = 1 << 24 // 16 MiB
|
||||
contents, err := io.ReadAll(io.LimitReader(r, privateKeySizeLimit))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read %q: %v", name, err)
|
||||
}
|
||||
if len(contents) == privateKeySizeLimit {
|
||||
return nil, fmt.Errorf("failed to read %q: file too long", name)
|
||||
}
|
||||
return []age.Identity{&EncryptedIdentity{
|
||||
Contents: contents,
|
||||
Passphrase: func() (string, error) {
|
||||
pass, err := readSecret(fmt.Sprintf("Enter passphrase for identity file %q:", name))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not read passphrase: %v", err)
|
||||
}
|
||||
return string(pass), nil
|
||||
},
|
||||
NoMatchWarning: func() {
|
||||
warningf("encrypted identity file %q didn't match file's recipients", name)
|
||||
},
|
||||
}}, nil
|
||||
|
||||
// Another PEM file, possibly an SSH private key.
|
||||
case strings.HasPrefix(peeked, "-----BEGIN"):
|
||||
const privateKeySizeLimit = 1 << 14 // 16 KiB
|
||||
contents, err := ioutil.ReadAll(io.LimitReader(b, privateKeySizeLimit))
|
||||
contents, err := io.ReadAll(io.LimitReader(b, privateKeySizeLimit))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read %q: %v", name, err)
|
||||
}
|
||||
@@ -145,11 +184,53 @@ func parseIdentitiesFile(name string) ([]age.Identity, error) {
|
||||
return nil, fmt.Errorf("failed to read %q: file too long", name)
|
||||
}
|
||||
return parseSSHIdentity(name, contents)
|
||||
}
|
||||
|
||||
ids, err := age.ParseIdentities(b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read %q: %v", name, err)
|
||||
// An unencrypted age identity file.
|
||||
default:
|
||||
ids, err := parseIdentities(b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read %q: %v", name, err)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
}
|
||||
|
||||
func parseIdentity(s string) (age.Identity, error) {
|
||||
switch {
|
||||
case strings.HasPrefix(s, "AGE-PLUGIN-"):
|
||||
return plugin.NewIdentity(s, pluginTerminalUI)
|
||||
case strings.HasPrefix(s, "AGE-SECRET-KEY-1"):
|
||||
return age.ParseX25519Identity(s)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown identity type")
|
||||
}
|
||||
}
|
||||
|
||||
// parseIdentities is like age.ParseIdentities, but supports plugin identities.
|
||||
func parseIdentities(f io.Reader) ([]age.Identity, error) {
|
||||
const privateKeySizeLimit = 1 << 24 // 16 MiB
|
||||
var ids []age.Identity
|
||||
scanner := bufio.NewScanner(io.LimitReader(f, privateKeySizeLimit))
|
||||
var n int
|
||||
for scanner.Scan() {
|
||||
n++
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "#") || line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
i, err := parseIdentity(line)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error at line %d: %v", n, err)
|
||||
}
|
||||
ids = append(ids, i)
|
||||
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, fmt.Errorf("failed to read secret keys file: %v", err)
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return nil, fmt.Errorf("no secret keys found")
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
@@ -165,8 +246,7 @@ func parseSSHIdentity(name string, pemBytes []byte) ([]age.Identity, error) {
|
||||
}
|
||||
}
|
||||
passphrasePrompt := func() ([]byte, error) {
|
||||
fmt.Fprintf(os.Stderr, "Enter passphrase for %q: ", name)
|
||||
pass, err := readPassphrase()
|
||||
pass, err := readSecret(fmt.Sprintf("Enter passphrase for %q:", name))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read passphrase for %q: %v", name, err)
|
||||
}
|
||||
@@ -198,7 +278,7 @@ Use a file for which the corresponding ".pub" file exists, or convert the privat
|
||||
Ensure %q exists, or convert the private key %q to a modern format with "ssh-keygen -p -m RFC4716"`, name, err, name+".pub", name)
|
||||
}
|
||||
defer f.Close()
|
||||
contents, err := ioutil.ReadAll(f)
|
||||
contents, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read %q: %v", name+".pub", err)
|
||||
}
|
||||
|
||||
6
cmd/age/testdata/default_key.txt
vendored
6
cmd/age/testdata/default_key.txt
vendored
@@ -1,6 +0,0 @@
|
||||
# created: 2021-02-02T13:09:43+01:00
|
||||
# public key: age1xmwwc06ly3ee5rytxm9mflaz2u56jjj36s0mypdrwsvlul66mv4q47ryef
|
||||
AGE-SECRET-KEY-1EGTZVFFV20835NWYV6270LXYVK2VKNX2MMDKWYKLMGR48UAWX40Q2P2LM0
|
||||
|
||||
# TODO: regenerate empty_recipient_body.age
|
||||
AGE-SECRET-KEY-1TRYTV7PQS5XPUYSTAQZCD7DQCWC7Q77YJD7UVFJRMW4J82Q6930QS70MRX
|
||||
1
cmd/age/testdata/default_password.txt
vendored
1
cmd/age/testdata/default_password.txt
vendored
@@ -1 +0,0 @@
|
||||
now-major-idea-author-clerk-bronze-all-soul-uncover-glad
|
||||
5
cmd/age/testdata/ed25519.age
vendored
5
cmd/age/testdata/ed25519.age
vendored
@@ -1,5 +0,0 @@
|
||||
age-encryption.org/v1
|
||||
-> ssh-ed25519 cp09gQ Kf5JDNFFDUaOvups2MDfP47PlrpJthnmz0WNMfGj+C8
|
||||
hQ76lMAsG2pjR8GHTU+XU0giePyzE3prVmAw5MbMxSk
|
||||
--- CjO8qf3vd83otqHSflgWP5gQoe2Roo9tf/zgWEy9t0U
|
||||
èÙ®Ò%JðÇnj<bÖ: %UgZ4#™±àNÂ韟
|
||||
32
cmd/age/testdata/ed25519.txt
vendored
Normal file
32
cmd/age/testdata/ed25519.txt
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# encrypt and decrypt a file with -R
|
||||
age -R key.pem.pub -o test.age input
|
||||
age -d -i key.pem test.age
|
||||
cmp stdout input
|
||||
! stderr .
|
||||
|
||||
# encrypt and decrypt a file with -i
|
||||
age -e -i key.pem -o test.age input
|
||||
age -d -i key.pem test.age
|
||||
cmp stdout input
|
||||
! stderr .
|
||||
|
||||
# encrypt and decrypt a file with the wrong key
|
||||
age -R otherkey.pem.pub -o test.age input
|
||||
! age -d -i key.pem test.age
|
||||
stderr 'no identity matched any of the recipients'
|
||||
! stdout .
|
||||
|
||||
-- input --
|
||||
test
|
||||
-- key.pem --
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACB/aTuac9tiWRGrKEtixFlryYlGCPTOpdbmXN9RRmDF2gAAAKDgV/GC4Ffx
|
||||
ggAAAAtzc2gtZWQyNTUxOQAAACB/aTuac9tiWRGrKEtixFlryYlGCPTOpdbmXN9RRmDF2g
|
||||
AAAECvFoQXQzXgJLQ+Gz4PfEcfyZwC2gUjOiWTD//mTPyD8H9pO5pz22JZEasoS2LEWWvJ
|
||||
iUYI9M6l1uZc31FGYMXaAAAAG2ZpbGlwcG9AQmlzdHJvbWF0aC1NMS5sb2NhbAEC
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
-- key.pem.pub --
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH9pO5pz22JZEasoS2LEWWvJiUYI9M6l1uZc31FGYMXa
|
||||
-- otherkey.pem.pub --
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJFlMdZUMrWjJ3hh60MLALXSqUdAjBo/qEMJzvpekpoM
|
||||
7
cmd/age/testdata/ed25519_key.txt
vendored
7
cmd/age/testdata/ed25519_key.txt
vendored
@@ -1,7 +0,0 @@
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACB/aTuac9tiWRGrKEtixFlryYlGCPTOpdbmXN9RRmDF2gAAAKDgV/GC4Ffx
|
||||
ggAAAAtzc2gtZWQyNTUxOQAAACB/aTuac9tiWRGrKEtixFlryYlGCPTOpdbmXN9RRmDF2g
|
||||
AAAECvFoQXQzXgJLQ+Gz4PfEcfyZwC2gUjOiWTD//mTPyD8H9pO5pz22JZEasoS2LEWWvJ
|
||||
iUYI9M6l1uZc31FGYMXaAAAAG2ZpbGlwcG9AQmlzdHJvbWF0aC1NMS5sb2NhbAEC
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
1
cmd/age/testdata/ed25519_key.txt.pub
vendored
1
cmd/age/testdata/ed25519_key.txt.pub
vendored
@@ -1 +0,0 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH9pO5pz22JZEasoS2LEWWvJiUYI9M6l1uZc31FGYMXa
|
||||
7
cmd/age/testdata/empty_recipient_body.age
vendored
7
cmd/age/testdata/empty_recipient_body.age
vendored
@@ -1,7 +0,0 @@
|
||||
age-encryption.org/v1
|
||||
-> X25519 alRneDshIh43nwyD5+fhuTD5TReSn88f2us4hzZPyzU
|
||||
pGduNK5MUhnuzMxW0qbZnC2k7mRzz69bbJpKQrRc7uc
|
||||
-> A7)h-grease !,_
|
||||
|
||||
--- 5bA0uXjBxI6wuI5SseCRgD5/G8LkSVISRe/hnrQMb9s
|
||||
¦Ã 1‘·¬Žä6_RÙäÚ…›…U<ï1ús®ÿ?`Ö+–’$§HÖW‚v?w8ZW
|
||||
126
cmd/age/testdata/encrypted_keys.txt
vendored
Normal file
126
cmd/age/testdata/encrypted_keys.txt
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
# TODO: age-encrypted private keys, multiple identities, -i ordering, -e -i,
|
||||
# age file password prompt during encryption
|
||||
|
||||
[!linux] [!darwin] skip # no pty support
|
||||
[darwin] [go1.20] skip # https://go.dev/issue/61779
|
||||
|
||||
# use an encrypted OpenSSH private key without .pub file
|
||||
age -R key_ed25519.pub -o ed25519.age input
|
||||
rm key_ed25519.pub
|
||||
ttyin terminal
|
||||
age -d -i key_ed25519 ed25519.age
|
||||
cmp stdout input
|
||||
! stderr .
|
||||
|
||||
# -e -i with an encrypted OpenSSH private key
|
||||
age -e -i key_ed25519 -o ed25519.age input
|
||||
ttyin terminal
|
||||
age -d -i key_ed25519 ed25519.age
|
||||
cmp stdout input
|
||||
|
||||
# a file encrypted to the wrong key does not ask for the password
|
||||
age -R key_ed25519_other.pub -o ed25519_other.age input
|
||||
! age -d -i key_ed25519 ed25519_other.age
|
||||
stderr 'no identity matched any of the recipients'
|
||||
|
||||
# use an encrypted legacy PEM private key with a .pub file
|
||||
age -R key_rsa_legacy.pub -o rsa_legacy.age input
|
||||
ttyin terminal
|
||||
age -d -i key_rsa_legacy rsa_legacy.age
|
||||
cmp stdout input
|
||||
! stderr .
|
||||
age -R key_rsa_other.pub -o rsa_other.age input
|
||||
! age -d -i key_rsa_legacy rsa_other.age
|
||||
stderr 'no identity matched any of the recipients'
|
||||
|
||||
# -e -i with an encrypted legacy PEM private key
|
||||
age -e -i key_rsa_legacy -o rsa_legacy.age input
|
||||
ttyin terminal
|
||||
age -d -i key_rsa_legacy rsa_legacy.age
|
||||
cmp stdout input
|
||||
|
||||
# legacy PEM private key without a .pub file causes an error
|
||||
rm key_rsa_legacy.pub
|
||||
! age -d -i key_rsa_legacy rsa_legacy.age
|
||||
stderr 'key_rsa_legacy.pub'
|
||||
|
||||
# mismatched .pub file causes an error
|
||||
cp key_rsa_legacy key_rsa_other
|
||||
ttyin terminal
|
||||
! age -d -i key_rsa_other rsa_other.age
|
||||
stderr 'mismatched private and public SSH key'
|
||||
|
||||
# buffer armored ciphertext before prompting if stdin is the terminal
|
||||
ttyin terminal
|
||||
age -e -i key_ed25519 -a -o test.age input
|
||||
exec cat test.age terminal # concatenated ciphertext + password
|
||||
ttyin -stdin stdout
|
||||
age -d -i key_ed25519
|
||||
ttyout 'Enter passphrase'
|
||||
! stderr .
|
||||
cmp stdout input
|
||||
|
||||
-- input --
|
||||
test
|
||||
-- terminal --
|
||||
password
|
||||
-- key_ed25519 --
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCuvb97i7
|
||||
U6Dz4+4SaF3kK1AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIKaVctg4/hmFbfof
|
||||
Tv+yrC2IweO/Dd2AVDijFpaMO9fmAAAAoMO7yEnisRmzFdiExNt3XTYuLdP9m3jgOCroiF
|
||||
TtBhh1lAB2qggzWExMRP3Ak8+AloXEcWiACwBYnqwxhQMh0RDCDKC/H/4SXO+ds4HFWil+
|
||||
4bGF9wYZFU7IEjIK91CPGJ6YoWPn9dSdEjjbuCJtOMwHsysGyw5n/qSFPmSAPmA4YL2OzM
|
||||
WFOJ5gB5o1LKZkDTcdt7kPziIoVd5QkqpnYsE=
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
-- key_ed25519.pub --
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKaVctg4/hmFbfofTv+yrC2IweO/Dd2AVDijFpaMO9fm
|
||||
-- key_ed25519_other.pub --
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINbTd+xfSBYKR/1Hp7FsoxwQAdIOk1Khye6ALBj7e1CV
|
||||
-- key_rsa_legacy --
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: AES-128-CBC,8045E7CF19D7794F4ADF5AC63179D985
|
||||
|
||||
OESHhWCho337W1Ajg+iMbsZx/FPtHM3YPHu/d1U51ERIUh0wVof2SK0ooENokr6g
|
||||
O3fcv9Xga+Na4Ez+gsFRsIZOdqrJq+QBH0CAKi+Mz4KsU7teAobUBJgRB31Wt7eI
|
||||
39KGZeaBJLMQ0FzQkDx5MCOg98iu9rt+Pg1bH8X88wV4vOv+tG4nmqgdpDmouo1Q
|
||||
uW1TJxrdPhkINjaPZZ7gvjS8wuG9+qwQY76I0hGun9secf4VZDysqUnUp8UHYovR
|
||||
dbvKCbglQy18mGL4kREJ/hH/9/maefS+pTMb2UX0onp9j7l3yNSvL4A4xW85ii6x
|
||||
liVMnZvLvbfPtI7jjZtC8CjshRkZke4fSZF2nZP7zK2qVcqDFCtemaks+0i2ksel
|
||||
D8clUKhBmq23VNAt+iy1stwHBporuaE6kEVJail5WPpgdfQjifpaMbTsZgOK+vGL
|
||||
GKi8vSJWfMU3lTf/N++ks2FWxdq0TgQirsKsQ5mWobfxc1XehvvdJj8hUtArrP32
|
||||
d4ge5DXPpmtkCzrc1+wt8Py/ANl9jV6c+4fCbpQ2snyzdFEhFtXHCEpMguN9MhKI
|
||||
gaZIfAxvYcQr8Gwew/IB02Phda9tvDiedHvyHGJmSy/87fR6ECh47VDFL/UYu4jG
|
||||
0hRtAZMMddGNfoosnO6OKBd09cgvXKCsUrbpAI7dF5TP5ItDkVb08hW446jBdgS9
|
||||
7QqB0rPmlAjsJi7fsrDw7Nq9pOdqqCEwUMc9Lztnv66aX1d/Y2vQm9mrsDbyZKqn
|
||||
g621rg7E4UHf7EGiDblfS234+TsNvwZ6sEbivU+3zqglPiOF71m6D0cKgaUZPOua
|
||||
GNdyQz5e73hYa+NJ76IZ+IqkoJAFXBkd1nWcN6DUBYiKvqd4qO9xD+JvNtiFlQ9d
|
||||
pyO9t4FTGvySh8CKyEUEdtj+2ftCIuZaUD2L5YJU1tlQV0EH42InOmkmphbHkW5v
|
||||
lNAoZAny1Z0P6O0gn7xtVrgd7paVQfDCJtkvsm5zR6Yei5FUgY/9NPaRotzuZVAY
|
||||
EfQC7JPdSdb5yusnXh7B9jGkgxhMIb6EPFFjIZ4iaV1RVgINSisGMSFzlqOz702b
|
||||
Cawsr9nD438cjzMNYEmrihZZBjHon6hHrLmM9Aj2xgprsoNLP1jJQ6WpZDlrYsj0
|
||||
XS0tSJmh0pM4Ey6j1VWNoaOxVseYLW7J9wGVfH/HJAc2k6Wg46P2e8lMT6Sj4YsT
|
||||
EguDhUjXrgePC53ohcSF+I6x35Q1D6ttMnc3ODzmIcCisxAvWdAqi1yRlnBotRwg
|
||||
S2vq3HU0yJFG8pJqw4vU9A9DlaMMT+ejEH+9xVwAWM+7n2lJcgthtWuShZCE6BB+
|
||||
jVobSlTMArzQj4klTSbew1m9Waa6kKDezsAY66mryVNofCCeYDOBRecCm5JyMnWf
|
||||
WBVnNx+kZ/YyvYeBcSh34u8rkjqGpzfM/oPE7GwIoZvbAirjLohL7u8oq2bfAYG0
|
||||
/xIPwPJw1O3o5PHeu84bVIRqcKzGeaVL+5aUiZP9uNGUpqJWA5q2Sa5BOXV46yqO
|
||||
DIS8q7uPCSbt5mPXPDGJ1CupCdA1stUf2kb0cDJ+LpUbPND9SebBlxSuR1D/YGqv
|
||||
wlzfN5Usv/h/XNl98bYtpY8/skKPecyx3wG3VtwWH/5XVhvHz4TENjlKv/L2pbUC
|
||||
Dv83WcL1N/i+jerYxDRmGe3NQOvyW4JaNzzjgb74T7rE1/3lf6qkmUHjxfo4VZAF
|
||||
L/q2782OUs5Qt4/pYAIISzLdBw6XtTjZHirqa6YNrFvGucB3NG49AC0b1Z0acfrS
|
||||
iimC2TvZpwunlLbyz2SQQL2c1zQ3U/Yfh2F1Zt8o6kK3RgKSSx57rK6nV7hXMGGp
|
||||
C4HV3nLetZg8HexicqeRANLXuUDbCSpN8K4nW5G2g/yKPfsQHBV/RWEDfhndykja
|
||||
+SmoY5IB+2zEbCC3MWiP9ZdIcCYOsq8wDZESMMW40DlVICjrf6UOqQ+ogci20qLS
|
||||
CmpgmOPAaBZJG/sBU79eHUSjPCK6yDpSyc30oVn8FnoBTmOpt7R++Ub8RJxReXBt
|
||||
+6o0NXYCJNaeVnk1bE4iavkJrXJCZvu44VBLS0WUs9W8TD4Iq8kNHsfQsfOuBXnQ
|
||||
ncgoIe9HppnMGNoSzjYBNL/rprlbaOE55TkPqiQsiskRcaoeY53aTxoIykHmoj8G
|
||||
wJo/00IR+NYir7tr03Vriw+uywPPGucVJGWTUGsNbHlS5j941IltflIf6FitElNr
|
||||
JxVuJLgYiP3JhmWpdqA/uidYJMbIjunpn/8rVLrAil04SCSfUmaCdl7dkQ9x+3Mf
|
||||
Erm699vIBQwvv0i+mcwKEvqSrhhNQ2F7vrb7NL8I2wUEPgQbv1PxSV6X0aYcxYVI
|
||||
-----END RSA PRIVATE KEY-----
|
||||
-- key_rsa_legacy.pub --
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCky7Clp8I3LVoqZWtat+QR6KmM0evFilmFhwenINIBbb8eS3ftDSkQy2YRrlAvO3h4EZffOIxANGL/yKVlRCIzvjsphi+tTHscZsQhwMnLEmxEayTq20hZKcwNA8TQdh2TW/w0KZmNZcxlTn4IK8W16komHcoH/qrRiXq8z3ROcfnv3Q4Hll9MUCwBkfy2DdBpWUMidQ1dAK4i3vXdseF74hJ0jFbPtS5mlpOsJZa0sdH1dnEl5M8wZS3PxyzM6JMkgzG7INp4sO/xGIisjl/QuSh2Fu93/EogdGXxIZChniUfzBx1DaHlerPPNSMP+uLbaOIAQrIPozhfdUdsCFDMoB7/PA6g1WVYZWAqjBZZW/GMOzPhih57NIFBSyMTzMi1KS6OBvYJvPf4IcvOa3May9ylLG/wZVhrHlQPbSsbRrraVtJ1P4gGQJ5U4d2AD2q+XtMb5f2i/holMXTVQl7Fa7RYi1TblDuW5OZCvmIawePBXAYbPg0OVFs3vAVEuAM=
|
||||
-- key_rsa_other.pub --
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDQiCWw2W++gX4wcwpDo6QIouwQ9PPwCVe7QPICzxztG27mzeKRM4xT2LURGSaQqg7OYIUTGrLqNsaLZW+FHHQlRAVv1LEbdEFa5JermBMJ5j/HxamE/7oV60gMRlgKW+4IZhVMPgRZaaXU0YPb9oACdMNM8kPkc5JaOJ8iO6B1RViybjLD+tsEEPXLp3Mrj+sJqs+IvNlJKXdeefOjNrGmLHKIFdHiWlZ+aAW+QLfMQiNXoTbGybFUSpNEbmK/1ITiRAly94NoUK9LoriueXR+WJIm9wP4SfHw+hMBz1cywdF2wwKmWWegizV/USEmhyNXUzHZzjbkgE84DrIq+NA7SUmw6C8ClMjdnRnnoIyga99yMIrYMny1KW/bk1NK4u6Tv17E+FFOS3vf2Gcj01/jOmAUIQwL8MjAHhnsZ4XAA5NHa2NRGWm+hw7fx5uX42Gyz8HidFda5Lij1pASBcx4U3qwb62X+IVN50jGIP6kRNmGtMLY1JgaoGDDkw9r6mU=
|
||||
@@ -1,7 +0,0 @@
|
||||
age-encryption.org/v1
|
||||
-> scrypt z8U9dYMQuK1fFdvtpQYLEQ 10
|
||||
5SVjw1bbFCZLdI1FR7RqfTd3yWo4KS1ikOjvz60Bpqhrv0W6o6/2oszxZEm1gEUC
|
||||
|
||||
--- YXxSwONGPBbV7woMuEFTYuA03qTYUF1k0Y8j/NDEu1o
|
||||
’Ì
|
||||
Å!í‡ i<7F>Þ.f¦æš{ŸdËIE]© ñn”$È!b®2
|
||||
@@ -1,6 +0,0 @@
|
||||
age-encryption.org/v1
|
||||
-> X25519 UkSgrxSETNpdkHY8EwiiRivqks2QJLUzsNsVjUTDcmw
|
||||
8yB9TqsBo4Ypchw07AtemV5TW4sGwyPDPMIfRg8Ve8rbDXt4tCwnnKcMq2K6aoqx
|
||||
|
||||
--- vUhLU0U9Dc8YhbKy4SxKuq0iSqqjBWGnHfZG+9+O4v4
|
||||
タケ<EFBFBD><EFBFBD>イhエW諾餞I暠fッニ<EFBDAF>索Q;ミRh皷w
|
||||
BIN
cmd/age/testdata/fail_scrypt_and_x25519.age
vendored
BIN
cmd/age/testdata/fail_scrypt_and_x25519.age
vendored
Binary file not shown.
@@ -1,5 +0,0 @@
|
||||
age-encryption.org/v1
|
||||
-> scrypt 1Q6WlGmsRulbN7bmUw8A1Q 23
|
||||
GP2lnzFuk1dgEkcMPmK6KkmuOm5gIWJzLeuwGcRsvAY
|
||||
--- vXvOsVbDbMc0x5Js1FS6k1ViOJ3H2ZdSUZo9bfvbzmU
|
||||
=ñÅÙœ£¨v>¹vhKMû'ÕßúÙ½NeçS†Äñ\(_ó
|
||||
5
cmd/age/testdata/nomatch_scrypt.age
vendored
5
cmd/age/testdata/nomatch_scrypt.age
vendored
@@ -1,5 +0,0 @@
|
||||
age-encryption.org/v1
|
||||
-> scrypt X6oOTRAjCR1xid0PlnNMFA 10
|
||||
hszKAHhyFVpUgt9niYpdYXVhhN+r+oiCLPZukDdQZBQ
|
||||
--- 7BRJPVjbIC1JntvHrA13PQrnsa3lkwhnNF/Pbo4BPs4
|
||||
4|)ÆS|Û‹Ò¿D<C2BF>÷§}’Í2è%<25>e<EFBFBD>=óœ6˜»ÇZ
|
||||
5
cmd/age/testdata/nomatch_x25519.age
vendored
5
cmd/age/testdata/nomatch_x25519.age
vendored
@@ -1,5 +0,0 @@
|
||||
age-encryption.org/v1
|
||||
-> X25519 Rp86RQ3LgUJpQy4X2RMUhURlBP28tCaLQ2ssysJfRhg
|
||||
83YXad/lj3/wFM4n7vlGIiBSgfhG8lfiP5U7ajjK3HM
|
||||
--- O2+UpzetsP2+7BPyGQ4C6VMTY6zwp5TiNpVcFy4qdyM
|
||||
璇漡<0E>(擔Q:|c蔑鞮蓤<>=f<>6b坿!
|
||||
78
cmd/age/testdata/output_file.txt
vendored
Normal file
78
cmd/age/testdata/output_file.txt
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
# https://github.com/FiloSottile/age/issues/57
|
||||
age -r age1xmwwc06ly3ee5rytxm9mflaz2u56jjj36s0mypdrwsvlul66mv4q47ryef -o test.age input
|
||||
! age -o test.out -d -i wrong.txt test.age
|
||||
! exists test.out
|
||||
! age -o test.out -d test.age
|
||||
! exists test.out
|
||||
! age -o test.out -d -i notexist test.age
|
||||
! exists test.out
|
||||
! age -o test.out -d -i wrong.txt notexist
|
||||
! exists test.out
|
||||
! age -o test.out -r BAD
|
||||
! exists test.out
|
||||
! age -o test.out -r age1xmwwc06ly3ee5rytxm9mflaz2u56jjj36s0mypdrwsvlul66mv4q47ryef notexist
|
||||
! exists test.out
|
||||
! age -o test.out -p notexist
|
||||
! exists test.out
|
||||
|
||||
# https://github.com/FiloSottile/age/issues/555
|
||||
age -r age1xmwwc06ly3ee5rytxm9mflaz2u56jjj36s0mypdrwsvlul66mv4q47ryef -o empty.age empty
|
||||
exists empty.age
|
||||
age -d -i key.txt empty.age
|
||||
! stdout .
|
||||
! stderr .
|
||||
age -d -i key.txt -o new empty.age
|
||||
! stderr .
|
||||
cmp new empty
|
||||
|
||||
# https://github.com/FiloSottile/age/issues/491
|
||||
cp input inputcopy
|
||||
! age -r age1xmwwc06ly3ee5rytxm9mflaz2u56jjj36s0mypdrwsvlul66mv4q47ryef -o inputcopy inputcopy
|
||||
stderr 'input and output file are the same'
|
||||
cmp inputcopy input
|
||||
! age -r age1xmwwc06ly3ee5rytxm9mflaz2u56jjj36s0mypdrwsvlul66mv4q47ryef -o ./inputcopy inputcopy
|
||||
stderr 'input and output file are the same'
|
||||
cmp inputcopy input
|
||||
mkdir foo
|
||||
! age -r age1xmwwc06ly3ee5rytxm9mflaz2u56jjj36s0mypdrwsvlul66mv4q47ryef -o inputcopy foo/../inputcopy
|
||||
stderr 'input and output file are the same'
|
||||
cmp inputcopy input
|
||||
cp key.txt keycopy
|
||||
age -e -i keycopy -o test.age input
|
||||
! age -d -i keycopy -o keycopy test.age
|
||||
stderr 'input and output file are the same'
|
||||
cmp key.txt keycopy
|
||||
|
||||
[!linux] [!darwin] skip # no pty support
|
||||
[darwin] [go1.20] skip # https://go.dev/issue/61779
|
||||
|
||||
ttyin terminal
|
||||
! age -p -o inputcopy inputcopy
|
||||
stderr 'input and output file are the same'
|
||||
cmp inputcopy input
|
||||
|
||||
# https://github.com/FiloSottile/age/issues/159
|
||||
ttyin terminal
|
||||
age -p -a -o test.age input
|
||||
ttyin terminalwrong
|
||||
! age -o test.out -d test.age
|
||||
ttyout 'Enter passphrase'
|
||||
stderr 'incorrect passphrase'
|
||||
! exists test.out
|
||||
|
||||
-- terminal --
|
||||
password
|
||||
password
|
||||
-- terminalwrong --
|
||||
wrong
|
||||
-- input --
|
||||
age
|
||||
-- empty --
|
||||
-- key.txt --
|
||||
# created: 2021-02-02T13:09:43+01:00
|
||||
# public key: age1xmwwc06ly3ee5rytxm9mflaz2u56jjj36s0mypdrwsvlul66mv4q47ryef
|
||||
AGE-SECRET-KEY-1EGTZVFFV20835NWYV6270LXYVK2VKNX2MMDKWYKLMGR48UAWX40Q2P2LM0
|
||||
-- wrong.txt --
|
||||
# created: 2024-06-16T12:14:00+02:00
|
||||
# public key: age10k7vsqmeg3sp8elfyq5ts55feg4huarpcaf9dmljn9umydg3gymsvx4dp9
|
||||
AGE-SECRET-KEY-1NPX08S4LELW9K68FKU0U05XXEKG6X7GT004TPNYLF86H3M00D3FQ3VQQNN
|
||||
13
cmd/age/testdata/pkcs8.txt
vendored
Normal file
13
cmd/age/testdata/pkcs8.txt
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# https://github.com/FiloSottile/age/discussions/428
|
||||
# encrypt and decrypt a file with an Ed25519 key encoded with PKCS#8
|
||||
age -e -i key.pem -o test.age input
|
||||
age -d -i key.pem test.age
|
||||
cmp stdout input
|
||||
! stderr .
|
||||
|
||||
-- input --
|
||||
test
|
||||
-- key.pem --
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VwBCIEIJT4Wpo+YG11yybKL/bYXQW7ekz4PAsmV/4tfmY1vU7x
|
||||
-----END PRIVATE KEY-----
|
||||
20
cmd/age/testdata/plugin.txt
vendored
Normal file
20
cmd/age/testdata/plugin.txt
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# encrypt and decrypt a file with a test plugin
|
||||
age -r age1test10qdmzv9q -o test.age input
|
||||
age -d -i key.txt test.age
|
||||
cmp stdout input
|
||||
! stderr .
|
||||
|
||||
# very long identity and recipient
|
||||
age -R long-recipient.txt -o test.age input
|
||||
age -d -i long-key.txt test.age
|
||||
cmp stdout input
|
||||
! stderr .
|
||||
|
||||
-- input --
|
||||
test
|
||||
-- key.txt --
|
||||
AGE-PLUGIN-TEST-10Q32NLXM
|
||||
-- long-recipient.txt --
|
||||
age1test10pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7qj6rl8p
|
||||
-- long-key.txt --
|
||||
AGE-PLUGIN-TEST-10PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7RC0PU8S7Q5U8SUD
|
||||
13
cmd/age/testdata/rsa.age
vendored
13
cmd/age/testdata/rsa.age
vendored
@@ -1,13 +0,0 @@
|
||||
age-encryption.org/v1
|
||||
-> ssh-rsa jw/33g
|
||||
xv12FD3f2d7snIcuXBznTOWAlgCovW1Dqttk9uljWKy3GtRZ3t8jEWkQEOYkOk0M
|
||||
EHA6sHWfdPLdlS3DjQYjcaLFvwh3+XVKYNzP9MhLg8P8xvxVkn4aiCKd8ivEisp0
|
||||
bKGi4g/TJz/JKUg1SGbqDg966to0P5AWrkwAD7OMykQToqo56flrKXgFPleSWVWu
|
||||
umiwbxFYs7ltbRYvjzdpIj9l30lXkzrADP3RrrvTu/qT0IN3PMi3bOqm0kKz0vkd
|
||||
p4NpxKmfqQXavU+YZiyQL637V3cbKIAEJ1qmpkd2Tr2oUhfD5IgAoT1nC5tCIzRb
|
||||
DkPwM4k2FJgVX0KKvW3i0+k5tve4XWg82vq2OCj8+sl3A8cLX3g5zhh53DovUBVm
|
||||
qDU2HWf++3q9kUy1al0sFb2es4ih+tK74nPjBJZtX0n+4lMngz557+XuYnzZ2OkW
|
||||
QEq3b7Trdidw7Ak9S14tdXhj8oy7J1jdHsQ8/wehAc1v8MuBb1O7LxVIFxzBEBCA
|
||||
|
||||
--- QdCY4BN4vwp5jb+AFsyoHkvKW+EneZsZjPURH2tCF18
|
||||
«)KVMARsS‘M’ÚëûØ‚Kí>›ÕÕ«`¤~T0n·
|
||||
@@ -1,3 +1,23 @@
|
||||
# encrypt and decrypt a file with -R
|
||||
age -R key.pem.pub -o test.age input
|
||||
age -d -i key.pem test.age
|
||||
cmp stdout input
|
||||
! stderr .
|
||||
|
||||
# encrypt and decrypt a file with -i
|
||||
age -e -i key.pem -o test.age input
|
||||
age -d -i key.pem test.age
|
||||
cmp stdout input
|
||||
! stderr .
|
||||
|
||||
# encrypt and decrypt a file with the wrong key
|
||||
age -R otherkey.pem.pub -o test.age input
|
||||
! age -d -i key.pem test.age
|
||||
stderr 'no identity matched any of the recipients'
|
||||
|
||||
-- input --
|
||||
test
|
||||
-- key.pem --
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
|
||||
NhAAAAAwEAAQAAAYEA1C04rdClHoW4oG4bEGmaNqFy4DLoPJ0358w4XH+XBM3TiWcheouW
|
||||
@@ -36,3 +56,7 @@ EX0mNDXOlKOP2YAIMrDt5CkPdEh6qQG21LCZXTWmwheZ9iN2vOl/fKqUW9lqd/kTe6WsON
|
||||
hIpZhs2+oz54Riq1ZwzO9NkcYrvZoDKbDopL1r2ibw0mkgCJrxpWi0Yt2Iooh4GXXqP5C9
|
||||
T8hrZCbrVJkjKd5QAAABtmaWxpcHBvQEJpc3Ryb21hdGgtTTEubG9jYWwBAgMEBQY=
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
-- key.pem.pub --
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDULTit0KUehbigbhsQaZo2oXLgMug8nTfnzDhcf5cEzdOJZyF6i5aRQbqbXIOYeTS3Shpp/iE6d60qi93JBBfveAZqr76tK7lVK8fstvAAGgbo9L9Ru5nhWX0HTlZUOodUT2E0rgAeoFzfvcZTaajzPmikrESmuaJLcdJ3crbCIyovkTxc85KbSn+Ky5grntdVR7GXve0HuIgwuSXGNyO0/hEQyhCEco4w53LZDXl3Dlxgb52u0QCsFFiQJmZ3RWnbCz2mNjM9Vo5KRt2pJ4u74lVsfXCvAqG4GsDd5WXCNRNaQ7gUdw1eSnvvcy3hxeEqLNN9tHaFCjEuyPfXfTVd3t0SlXreyoNbV75rYvZ2F6aLxyMMys+5bpnX0tu2LSq4Bvj5xIsBokjgEd3UXyj1eErsQdUJ0gghAMpXOjVbISwPAGMvUsH2unGbjvfaxnK57S7KAXOYznjhBf5ayIleAc+4LVD/KeMtGbpkyXXozC9jwH9cZBbGU4BtElAbzh8=
|
||||
-- otherkey.pem.pub --
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDF0OPu95EY25O5KmYFLIkiZZFKUlfvaRgmfIT6OcZvPRXBzo0MS/lcrYvAc0RsUVbZ1B3Y9oWmKt/IMXTztCXiza70rO1NI7ciayv5svY/wGMoveutddhA64IjrQKs4m+6Qmjs/dYTnfsk1BzmXrdRKUSqH6c4Id7pRLC1ySLu+4og3nTTpBRBpg+uSkc4Ua6ce6A6RX14PPJ+TAXMfZyKNyaubQhgzLB/CfdXxZqWdAnyooiE7fb6CEB5uppnA5BpPdcWAkSixbwxRHbRC+OSCqMOV6+z+NlO/qSOKJcXfCQnJP/qjJTJde0dYhXG4RILOzIkGVieGJJONDXvj61mMj568IhJz0AEf/UMhvEL79iJ6yZW82Go/zcYkDDfd3KRE3pW+6p9Onu3XqOiQABS+9rEVRBnqYsPajiHBIanBeXpWKGbjznakvxhdRifhOWwAsQDfLmGzh+JnV1vOUjyxKtLNv9zi/oeuYCaIyF7F6en8LMbYSz8YONMZygGxMU=
|
||||
1
cmd/age/testdata/rsa_key.txt.pub
vendored
1
cmd/age/testdata/rsa_key.txt.pub
vendored
@@ -1 +0,0 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDULTit0KUehbigbhsQaZo2oXLgMug8nTfnzDhcf5cEzdOJZyF6i5aRQbqbXIOYeTS3Shpp/iE6d60qi93JBBfveAZqr76tK7lVK8fstvAAGgbo9L9Ru5nhWX0HTlZUOodUT2E0rgAeoFzfvcZTaajzPmikrESmuaJLcdJ3crbCIyovkTxc85KbSn+Ky5grntdVR7GXve0HuIgwuSXGNyO0/hEQyhCEco4w53LZDXl3Dlxgb52u0QCsFFiQJmZ3RWnbCz2mNjM9Vo5KRt2pJ4u74lVsfXCvAqG4GsDd5WXCNRNaQ7gUdw1eSnvvcy3hxeEqLNN9tHaFCjEuyPfXfTVd3t0SlXreyoNbV75rYvZ2F6aLxyMMys+5bpnX0tu2LSq4Bvj5xIsBokjgEd3UXyj1eErsQdUJ0gghAMpXOjVbISwPAGMvUsH2unGbjvfaxnK57S7KAXOYznjhBf5ayIleAc+4LVD/KeMtGbpkyXXozC9jwH9cZBbGU4BtElAbzh8=
|
||||
60
cmd/age/testdata/scrypt.txt
vendored
Normal file
60
cmd/age/testdata/scrypt.txt
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
[!linux] [!darwin] skip # no pty support
|
||||
[darwin] [go1.20] skip # https://go.dev/issue/61779
|
||||
|
||||
# encrypt with a provided passphrase
|
||||
stdin input
|
||||
ttyin terminal
|
||||
age -p -o test.age
|
||||
ttyout 'Enter passphrase'
|
||||
! stderr .
|
||||
! stdout .
|
||||
|
||||
# decrypt with a provided passphrase
|
||||
ttyin terminal
|
||||
age -d test.age
|
||||
ttyout 'Enter passphrase'
|
||||
! stderr .
|
||||
cmp stdout input
|
||||
|
||||
# decrypt with the wrong passphrase
|
||||
ttyin wrong
|
||||
! age -d test.age
|
||||
stderr 'incorrect passphrase'
|
||||
|
||||
# encrypt with a generated passphrase
|
||||
stdin input
|
||||
ttyin empty
|
||||
age -p -o test.age
|
||||
! stderr .
|
||||
! stdout .
|
||||
ttyin autogenerated
|
||||
age -d test.age
|
||||
cmp stdout input
|
||||
|
||||
# fail when -i is present
|
||||
ttyin terminal
|
||||
! age -d -i key.txt test.age
|
||||
stderr 'file is passphrase-encrypted but identities were specified'
|
||||
|
||||
# fail when passphrases don't match
|
||||
ttyin wrong
|
||||
! age -p -o fail.age
|
||||
stderr 'passphrases didn''t match'
|
||||
! exists fail.age
|
||||
|
||||
-- terminal --
|
||||
password
|
||||
password
|
||||
-- wrong --
|
||||
PASSWORD
|
||||
password
|
||||
-- input --
|
||||
test
|
||||
-- key.txt --
|
||||
# created: 2021-02-02T13:09:43+01:00
|
||||
# public key: age1xmwwc06ly3ee5rytxm9mflaz2u56jjj36s0mypdrwsvlul66mv4q47ryef
|
||||
AGE-SECRET-KEY-1EGTZVFFV20835NWYV6270LXYVK2VKNX2MMDKWYKLMGR48UAWX40Q2P2LM0
|
||||
-- autogenerated --
|
||||
four-four-four-four-four-four-four-four-four-four
|
||||
-- empty --
|
||||
|
||||
5
cmd/age/testdata/scrypt_work_factor_10.age
vendored
5
cmd/age/testdata/scrypt_work_factor_10.age
vendored
@@ -1,5 +0,0 @@
|
||||
age-encryption.org/v1
|
||||
-> scrypt qEa/WztCd2KJ4mKwNf1Yrw 10
|
||||
TQZ4GpAaH4aR4oSDWZTgeRT4wRby4jwmtB02dElWmVQ
|
||||
--- kOiEP6uoMyK9GKIsV77o4oaPuEr2Q0vdcu+1RKC3lLU
|
||||
hŸo²P¤V
w§\5~4ôënE½oÕd>rOš°mÒÛ¨
|
||||
57
cmd/age/testdata/terminal.txt
vendored
Normal file
57
cmd/age/testdata/terminal.txt
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
[!linux] [!darwin] skip # no pty support
|
||||
[darwin] [go1.20] skip # https://go.dev/issue/61779
|
||||
|
||||
# controlling terminal is used instead of stdin/stderr
|
||||
ttyin terminal
|
||||
age -p -o test.age input
|
||||
! stderr .
|
||||
|
||||
# autogenerated passphrase is printed to terminal
|
||||
ttyin empty
|
||||
age -p -o test.age input
|
||||
ttyout 'autogenerated passphrase'
|
||||
! stderr .
|
||||
|
||||
# with no controlling terminal, stdin terminal is used
|
||||
## TODO: enable once https://golang.org/issue/53601 is fixed
|
||||
## and Noctty is added to testscript.
|
||||
# noctty
|
||||
# ttyin -stdin terminal
|
||||
# age -p -o test.age input
|
||||
# ! stderr .
|
||||
|
||||
# no terminal causes an error
|
||||
## TODO: enable once https://golang.org/issue/53601 is fixed
|
||||
## and Noctty is added to testscript.
|
||||
# noctty
|
||||
# ! age -p -o test.age input
|
||||
# stderr 'standard input is not a terminal'
|
||||
|
||||
# prompt for password before plaintext if stdin is the terminal
|
||||
exec cat terminal input # concatenated password + input
|
||||
ttyin -stdin stdout
|
||||
age -p -a -o test.age
|
||||
ttyout 'Enter passphrase'
|
||||
! stderr .
|
||||
# check the file was encrypted correctly
|
||||
ttyin terminal
|
||||
age -d test.age
|
||||
cmp stdout input
|
||||
|
||||
# buffer armored ciphertext before prompting if stdin is the terminal
|
||||
ttyin terminal
|
||||
age -p -a -o test.age input
|
||||
exec cat test.age terminal # concatenated ciphertext + password
|
||||
ttyin -stdin stdout
|
||||
age -d
|
||||
ttyout 'Enter passphrase'
|
||||
! stderr .
|
||||
cmp stdout input
|
||||
|
||||
-- input --
|
||||
test
|
||||
-- terminal --
|
||||
password
|
||||
password
|
||||
-- empty --
|
||||
|
||||
20
cmd/age/testdata/usage.txt
vendored
Normal file
20
cmd/age/testdata/usage.txt
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
# -help
|
||||
age -p -help
|
||||
! stdout .
|
||||
stderr 'Usage:'
|
||||
|
||||
# -h
|
||||
age -p -h
|
||||
! stdout .
|
||||
stderr 'Usage:'
|
||||
|
||||
# unknown flag
|
||||
! age -p -this-flag-does-not-exist
|
||||
! stdout .
|
||||
stderr 'flag provided but not defined'
|
||||
stderr 'Usage:'
|
||||
|
||||
# no arguments
|
||||
! age
|
||||
! stdout .
|
||||
stderr 'Usage:'
|
||||
23
cmd/age/testdata/x25519.txt
vendored
Normal file
23
cmd/age/testdata/x25519.txt
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# encrypt and decrypt a file with -r
|
||||
age -r age1xmwwc06ly3ee5rytxm9mflaz2u56jjj36s0mypdrwsvlul66mv4q47ryef -o test.age input
|
||||
age -d -i key.txt test.age
|
||||
cmp stdout input
|
||||
! stderr .
|
||||
|
||||
# encrypt and decrypt a file with -i
|
||||
age -e -i key.txt -o test.age input
|
||||
age -d -i key.txt test.age
|
||||
cmp stdout input
|
||||
! stderr .
|
||||
|
||||
# encrypt and decrypt a file with the wrong key
|
||||
age -r age12phkzssndd5axajas2h74vtge62c86xjhd6u9anyanqhzvdg6sps0xthgl -o test.age input
|
||||
! age -d -i key.txt test.age
|
||||
stderr 'no identity matched any of the recipients'
|
||||
|
||||
-- input --
|
||||
test
|
||||
-- key.txt --
|
||||
# created: 2021-02-02T13:09:43+01:00
|
||||
# public key: age1xmwwc06ly3ee5rytxm9mflaz2u56jjj36s0mypdrwsvlul66mv4q47ryef
|
||||
AGE-SECRET-KEY-1EGTZVFFV20835NWYV6270LXYVK2VKNX2MMDKWYKLMGR48UAWX40Q2P2LM0
|
||||
226
cmd/age/tui.go
Normal file
226
cmd/age/tui.go
Normal file
@@ -0,0 +1,226 @@
|
||||
// Copyright 2021 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
// This file implements the terminal UI of cmd/age. The rules are:
|
||||
//
|
||||
// - Anything that requires user interaction goes to the terminal,
|
||||
// and is erased afterwards if possible. This UI would be possible
|
||||
// to replace with a pinentry with no output or UX changes.
|
||||
//
|
||||
// - Everything else goes to standard error with an "age:" prefix.
|
||||
// No capitalized initials and no periods at the end.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"filippo.io/age/armor"
|
||||
"filippo.io/age/plugin"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// l is a logger with no prefixes.
|
||||
var l = log.New(os.Stderr, "", 0)
|
||||
|
||||
func printf(format string, v ...interface{}) {
|
||||
l.Printf("age: "+format, v...)
|
||||
}
|
||||
|
||||
func errorf(format string, v ...interface{}) {
|
||||
l.Printf("age: error: "+format, v...)
|
||||
l.Printf("age: report unexpected or unhelpful errors at https://filippo.io/age/report")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
func warningf(format string, v ...interface{}) {
|
||||
l.Printf("age: warning: "+format, v...)
|
||||
}
|
||||
|
||||
func errorWithHint(error string, hints ...string) {
|
||||
l.Printf("age: error: %s", error)
|
||||
for _, hint := range hints {
|
||||
l.Printf("age: hint: %s", hint)
|
||||
}
|
||||
l.Printf("age: report unexpected or unhelpful errors at https://filippo.io/age/report")
|
||||
exit(1)
|
||||
}
|
||||
|
||||
// If testOnlyPanicInsteadOfExit is true, exit will set testOnlyDidExit and
|
||||
// panic instead of calling os.Exit. This way, the wrapper in TestMain can
|
||||
// recover the panic and return the exit code only if it was originated in exit.
|
||||
var testOnlyPanicInsteadOfExit bool
|
||||
var testOnlyDidExit bool
|
||||
|
||||
func exit(code int) {
|
||||
if testOnlyPanicInsteadOfExit {
|
||||
testOnlyDidExit = true
|
||||
panic(code)
|
||||
}
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
// clearLine clears the current line on the terminal, or opens a new line if
|
||||
// terminal escape codes don't work.
|
||||
func clearLine(out io.Writer) {
|
||||
const (
|
||||
CUI = "\033[" // Control Sequence Introducer
|
||||
CPL = CUI + "F" // Cursor Previous Line
|
||||
EL = CUI + "K" // Erase in Line
|
||||
)
|
||||
|
||||
// First, open a new line, which is guaranteed to work everywhere. Then, try
|
||||
// to erase the line above with escape codes.
|
||||
//
|
||||
// (We use CRLF instead of LF to work around an apparent bug in WSL2's
|
||||
// handling of CONOUT$. Only when running a Windows binary from WSL2, the
|
||||
// cursor would not go back to the start of the line with a simple LF.
|
||||
// Honestly, it's impressive CONIN$ and CONOUT$ work at all inside WSL2.)
|
||||
fmt.Fprintf(out, "\r\n"+CPL+EL)
|
||||
}
|
||||
|
||||
// withTerminal runs f with the terminal input and output files, if available.
|
||||
// withTerminal does not open a non-terminal stdin, so the caller does not need
|
||||
// to check stdinInUse.
|
||||
func withTerminal(f func(in, out *os.File) error) error {
|
||||
if runtime.GOOS == "windows" {
|
||||
in, err := os.OpenFile("CONIN$", os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
out, err := os.OpenFile("CONOUT$", os.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
return f(in, out)
|
||||
} else if tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0); err == nil {
|
||||
defer tty.Close()
|
||||
return f(tty, tty)
|
||||
} else if term.IsTerminal(int(os.Stdin.Fd())) {
|
||||
return f(os.Stdin, os.Stdin)
|
||||
} else {
|
||||
return fmt.Errorf("standard input is not a terminal, and /dev/tty is not available: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func printfToTerminal(format string, v ...interface{}) error {
|
||||
return withTerminal(func(_, out *os.File) error {
|
||||
_, err := fmt.Fprintf(out, "age: "+format+"\n", v...)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// readSecret reads a value from the terminal with no echo. The prompt is ephemeral.
|
||||
func readSecret(prompt string) (s []byte, err error) {
|
||||
err = withTerminal(func(in, out *os.File) error {
|
||||
fmt.Fprintf(out, "%s ", prompt)
|
||||
defer clearLine(out)
|
||||
s, err = term.ReadPassword(int(in.Fd()))
|
||||
return err
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// readCharacter reads a single character from the terminal with no echo. The
|
||||
// prompt is ephemeral.
|
||||
func readCharacter(prompt string) (c byte, err error) {
|
||||
err = withTerminal(func(in, out *os.File) error {
|
||||
fmt.Fprintf(out, "%s ", prompt)
|
||||
defer clearLine(out)
|
||||
|
||||
oldState, err := term.MakeRaw(int(in.Fd()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer term.Restore(int(in.Fd()), oldState)
|
||||
|
||||
b := make([]byte, 1)
|
||||
if _, err := in.Read(b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c = b[0]
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var pluginTerminalUI = &plugin.ClientUI{
|
||||
DisplayMessage: func(name, message string) error {
|
||||
printf("%s plugin: %s", name, message)
|
||||
return nil
|
||||
},
|
||||
RequestValue: func(name, message string, _ bool) (s string, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
warningf("could not read value for age-plugin-%s: %v", name, err)
|
||||
}
|
||||
}()
|
||||
secret, err := readSecret(message)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(secret), nil
|
||||
},
|
||||
Confirm: func(name, message, yes, no string) (choseYes bool, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
warningf("could not read value for age-plugin-%s: %v", name, err)
|
||||
}
|
||||
}()
|
||||
if no == "" {
|
||||
message += fmt.Sprintf(" (press enter for %q)", yes)
|
||||
_, err := readSecret(message)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
message += fmt.Sprintf(" (press [1] for %q or [2] for %q)", yes, no)
|
||||
for {
|
||||
selection, err := readCharacter(message)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
switch selection {
|
||||
case '1':
|
||||
return true, nil
|
||||
case '2':
|
||||
return false, nil
|
||||
case '\x03': // CTRL-C
|
||||
return false, errors.New("user cancelled prompt")
|
||||
default:
|
||||
warningf("reading value for age-plugin-%s: invalid selection %q", name, selection)
|
||||
}
|
||||
}
|
||||
},
|
||||
WaitTimer: func(name string) {
|
||||
printf("waiting on %s plugin...", name)
|
||||
},
|
||||
}
|
||||
|
||||
func bufferTerminalInput(in io.Reader) (io.Reader, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
if _, err := buf.ReadFrom(ReaderFunc(func(p []byte) (n int, err error) {
|
||||
if bytes.Contains(buf.Bytes(), []byte(armor.Footer+"\n")) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return in.Read(p)
|
||||
})); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
type ReaderFunc func(p []byte) (n int, err error)
|
||||
|
||||
func (f ReaderFunc) Read(p []byte) (n int, err error) { return f(p) }
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Copyright 2019 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
@@ -12,7 +10,12 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var testOnlyFixedRandomWord string
|
||||
|
||||
func randomWord() string {
|
||||
if testOnlyFixedRandomWord != "" {
|
||||
return testOnlyFixedRandomWord
|
||||
}
|
||||
buf := make([]byte, 2)
|
||||
if _, err := rand.Read(buf); err != nil {
|
||||
panic(err)
|
||||
|
||||
56
doc/age-keygen.1
Normal file
56
doc/age-keygen.1
Normal file
@@ -0,0 +1,56 @@
|
||||
.\" generated with Ronn-NG/v0.9.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
|
||||
.TH "AGE\-KEYGEN" "1" "June 2024" ""
|
||||
.SH "NAME"
|
||||
\fBage\-keygen\fR \- generate age(1) key pairs
|
||||
.SH "SYNOPSIS"
|
||||
\fBage\-keygen\fR [\fB\-o\fR \fIOUTPUT\fR]
|
||||
.br
|
||||
\fBage\-keygen\fR \fB\-y\fR [\fB\-o\fR \fIOUTPUT\fR] [\fIINPUT\fR]
|
||||
.br
|
||||
.SH "DESCRIPTION"
|
||||
\fBage\-keygen\fR generates a new native age(1) key pair, and outputs the identity to standard output or to the \fIOUTPUT\fR file\. The output includes the public key and the current time as comments\.
|
||||
.P
|
||||
If the output is not going to a terminal, \fBage\-keygen\fR prints the public key to standard error\.
|
||||
.SH "OPTIONS"
|
||||
.TP
|
||||
\fB\-o\fR, \fB\-\-output\fR=\fIOUTPUT\fR
|
||||
Write the identity to \fIOUTPUT\fR instead of standard output\.
|
||||
.IP
|
||||
If \fIOUTPUT\fR already exists, it is not overwritten\.
|
||||
.TP
|
||||
\fB\-y\fR
|
||||
Read an identity file from \fIINPUT\fR or from standard input and output the corresponding recipient(s), one per line, with no comments\.
|
||||
.TP
|
||||
\fB\-\-version\fR
|
||||
Print the version and exit\.
|
||||
.SH "EXAMPLES"
|
||||
Generate a new identity:
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ age\-keygen
|
||||
# created: 2021\-01\-02T15:30:45+01:00
|
||||
# public key: age1lvyvwawkr0mcnnnncaghunadrqkmuf9e6507x9y920xxpp866cnql7dp2z
|
||||
AGE\-SECRET\-KEY\-1N9JEPW6DWJ0ZQUDX63F5A03GX8QUW7PXDE39N8UYF82VZ9PC8UFS3M7XA9
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Write a new identity to \fBkey\.txt\fR:
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ age\-keygen \-o key\.txt
|
||||
Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Convert an identity to a recipient:
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ age\-keygen \-y key\.txt
|
||||
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
|
||||
.fi
|
||||
.IP "" 0
|
||||
.SH "SEE ALSO"
|
||||
age(1)
|
||||
.SH "AUTHORS"
|
||||
Filippo Valsorda \fIage@filippo\.io\fR
|
||||
146
doc/age-keygen.1.html
Normal file
146
doc/age-keygen.1.html
Normal file
@@ -0,0 +1,146 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv='content-type' content='text/html;charset=utf8'>
|
||||
<meta name='generator' content='Ronn-NG/v0.9.1 (http://github.com/apjanke/ronn-ng/tree/0.9.1)'>
|
||||
<title>age-keygen(1) - generate age(1) key pairs</title>
|
||||
<style type='text/css' media='all'>
|
||||
/* style: man */
|
||||
body#manpage {margin:0}
|
||||
.mp {max-width:100ex;padding:0 9ex 1ex 4ex}
|
||||
.mp p,.mp pre,.mp ul,.mp ol,.mp dl {margin:0 0 20px 0}
|
||||
.mp h2 {margin:10px 0 0 0}
|
||||
.mp > p,.mp > pre,.mp > ul,.mp > ol,.mp > dl {margin-left:8ex}
|
||||
.mp h3 {margin:0 0 0 4ex}
|
||||
.mp dt {margin:0;clear:left}
|
||||
.mp dt.flush {float:left;width:8ex}
|
||||
.mp dd {margin:0 0 0 9ex}
|
||||
.mp h1,.mp h2,.mp h3,.mp h4 {clear:left}
|
||||
.mp pre {margin-bottom:20px}
|
||||
.mp pre+h2,.mp pre+h3 {margin-top:22px}
|
||||
.mp h2+pre,.mp h3+pre {margin-top:5px}
|
||||
.mp img {display:block;margin:auto}
|
||||
.mp h1.man-title {display:none}
|
||||
.mp,.mp code,.mp pre,.mp tt,.mp kbd,.mp samp,.mp h3,.mp h4 {font-family:monospace;font-size:14px;line-height:1.42857142857143}
|
||||
.mp h2 {font-size:16px;line-height:1.25}
|
||||
.mp h1 {font-size:20px;line-height:2}
|
||||
.mp {text-align:justify;background:#fff}
|
||||
.mp,.mp code,.mp pre,.mp pre code,.mp tt,.mp kbd,.mp samp {color:#131211}
|
||||
.mp h1,.mp h2,.mp h3,.mp h4 {color:#030201}
|
||||
.mp u {text-decoration:underline}
|
||||
.mp code,.mp strong,.mp b {font-weight:bold;color:#131211}
|
||||
.mp em,.mp var {font-style:italic;color:#232221;text-decoration:none}
|
||||
.mp a,.mp a:link,.mp a:hover,.mp a code,.mp a pre,.mp a tt,.mp a kbd,.mp a samp {color:#0000ff}
|
||||
.mp b.man-ref {font-weight:normal;color:#434241}
|
||||
.mp pre {padding:0 4ex}
|
||||
.mp pre code {font-weight:normal;color:#434241}
|
||||
.mp h2+pre,h3+pre {padding-left:0}
|
||||
ol.man-decor,ol.man-decor li {margin:3px 0 10px 0;padding:0;float:left;width:33%;list-style-type:none;text-transform:uppercase;color:#999;letter-spacing:1px}
|
||||
ol.man-decor {width:100%}
|
||||
ol.man-decor li.tl {text-align:left}
|
||||
ol.man-decor li.tc {text-align:center;letter-spacing:4px}
|
||||
ol.man-decor li.tr {text-align:right;float:right}
|
||||
</style>
|
||||
</head>
|
||||
<!--
|
||||
The following styles are deprecated and will be removed at some point:
|
||||
div#man, div#man ol.man, div#man ol.head, div#man ol.man.
|
||||
|
||||
The .man-page, .man-decor, .man-head, .man-foot, .man-title, and
|
||||
.man-navigation should be used instead.
|
||||
-->
|
||||
<body id='manpage'>
|
||||
<div class='mp' id='man'>
|
||||
|
||||
<div class='man-navigation' style='display:none'>
|
||||
<a href="#NAME">NAME</a>
|
||||
<a href="#SYNOPSIS">SYNOPSIS</a>
|
||||
<a href="#DESCRIPTION">DESCRIPTION</a>
|
||||
<a href="#OPTIONS">OPTIONS</a>
|
||||
<a href="#EXAMPLES">EXAMPLES</a>
|
||||
<a href="#SEE-ALSO">SEE ALSO</a>
|
||||
<a href="#AUTHORS">AUTHORS</a>
|
||||
</div>
|
||||
|
||||
<ol class='man-decor man-head man head'>
|
||||
<li class='tl'>age-keygen(1)</li>
|
||||
<li class='tc'></li>
|
||||
<li class='tr'>age-keygen(1)</li>
|
||||
</ol>
|
||||
|
||||
|
||||
|
||||
<h2 id="NAME">NAME</h2>
|
||||
<p class="man-name">
|
||||
<code>age-keygen</code> - <span class="man-whatis">generate <a class="man-ref" href="age.1.html">age<span class="s">(1)</span></a> key pairs</span>
|
||||
</p>
|
||||
<h2 id="SYNOPSIS">SYNOPSIS</h2>
|
||||
|
||||
<p><code>age-keygen</code> [<code>-o</code> <var>OUTPUT</var>]<br>
|
||||
<code>age-keygen</code> <code>-y</code> [<code>-o</code> <var>OUTPUT</var>] [<var>INPUT</var>]<br></p>
|
||||
|
||||
<h2 id="DESCRIPTION">DESCRIPTION</h2>
|
||||
|
||||
<p><code>age-keygen</code> generates a new native <a class="man-ref" href="age.1.html">age<span class="s">(1)</span></a> key pair, and outputs the identity to
|
||||
standard output or to the <var>OUTPUT</var> file. The output includes the public key and
|
||||
the current time as comments.</p>
|
||||
|
||||
<p>If the output is not going to a terminal, <code>age-keygen</code> prints the public key to
|
||||
standard error.</p>
|
||||
|
||||
<h2 id="OPTIONS">OPTIONS</h2>
|
||||
|
||||
<dl>
|
||||
<dt>
|
||||
<code>-o</code>, <code>--output</code>=<var>OUTPUT</var>
|
||||
</dt>
|
||||
<dd> Write the identity to <var>OUTPUT</var> instead of standard output.
|
||||
|
||||
<p>If <var>OUTPUT</var> already exists, it is not overwritten.</p>
|
||||
</dd>
|
||||
<dt><code>-y</code></dt>
|
||||
<dd> Read an identity file from <var>INPUT</var> or from standard input and output the
|
||||
corresponding recipient(s), one per line, with no comments.</dd>
|
||||
<dt><code>--version</code></dt>
|
||||
<dd> Print the version and exit.</dd>
|
||||
</dl>
|
||||
|
||||
<h2 id="EXAMPLES">EXAMPLES</h2>
|
||||
|
||||
<p>Generate a new identity:</p>
|
||||
|
||||
<pre><code>$ age-keygen
|
||||
# created: 2021-01-02T15:30:45+01:00
|
||||
# public key: age1lvyvwawkr0mcnnnncaghunadrqkmuf9e6507x9y920xxpp866cnql7dp2z
|
||||
AGE-SECRET-KEY-1N9JEPW6DWJ0ZQUDX63F5A03GX8QUW7PXDE39N8UYF82VZ9PC8UFS3M7XA9
|
||||
</code></pre>
|
||||
|
||||
<p>Write a new identity to <code>key.txt</code>:</p>
|
||||
|
||||
<pre><code>$ age-keygen -o key.txt
|
||||
Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
|
||||
</code></pre>
|
||||
|
||||
<p>Convert an identity to a recipient:</p>
|
||||
|
||||
<pre><code>$ age-keygen -y key.txt
|
||||
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
|
||||
</code></pre>
|
||||
|
||||
<h2 id="SEE-ALSO">SEE ALSO</h2>
|
||||
|
||||
<p><a class="man-ref" href="age.1.html">age<span class="s">(1)</span></a></p>
|
||||
|
||||
<h2 id="AUTHORS">AUTHORS</h2>
|
||||
|
||||
<p>Filippo Valsorda <a href="mailto:age@filippo.io" data-bare-link="true">age@filippo.io</a></p>
|
||||
|
||||
<ol class='man-decor man-foot man foot'>
|
||||
<li class='tl'></li>
|
||||
<li class='tc'>June 2024</li>
|
||||
<li class='tr'>age-keygen(1)</li>
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
57
doc/age-keygen.1.ronn
Normal file
57
doc/age-keygen.1.ronn
Normal file
@@ -0,0 +1,57 @@
|
||||
age-keygen(1) -- generate age(1) key pairs
|
||||
====================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`age-keygen` [`-o` <OUTPUT>]<br>
|
||||
`age-keygen` `-y` [`-o` <OUTPUT>] [<INPUT>]<br>
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`age-keygen` generates a new native age(1) key pair, and outputs the identity to
|
||||
standard output or to the <OUTPUT> file. The output includes the public key and
|
||||
the current time as comments.
|
||||
|
||||
If the output is not going to a terminal, `age-keygen` prints the public key to
|
||||
standard error.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-o`, `--output`=<OUTPUT>:
|
||||
Write the identity to <OUTPUT> instead of standard output.
|
||||
|
||||
If <OUTPUT> already exists, it is not overwritten.
|
||||
|
||||
* `-y`:
|
||||
Read an identity file from <INPUT> or from standard input and output the
|
||||
corresponding recipient(s), one per line, with no comments.
|
||||
|
||||
* `--version`:
|
||||
Print the version and exit.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
Generate a new identity:
|
||||
|
||||
$ age-keygen
|
||||
# created: 2021-01-02T15:30:45+01:00
|
||||
# public key: age1lvyvwawkr0mcnnnncaghunadrqkmuf9e6507x9y920xxpp866cnql7dp2z
|
||||
AGE-SECRET-KEY-1N9JEPW6DWJ0ZQUDX63F5A03GX8QUW7PXDE39N8UYF82VZ9PC8UFS3M7XA9
|
||||
|
||||
Write a new identity to `key.txt`:
|
||||
|
||||
$ age-keygen -o key.txt
|
||||
Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
|
||||
|
||||
Convert an identity to a recipient:
|
||||
|
||||
$ age-keygen -y key.txt
|
||||
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
age(1)
|
||||
|
||||
## AUTHORS
|
||||
|
||||
Filippo Valsorda <age@filippo.io>
|
||||
248
doc/age.1
Normal file
248
doc/age.1
Normal file
@@ -0,0 +1,248 @@
|
||||
.\" generated with Ronn-NG/v0.9.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
|
||||
.TH "AGE" "1" "June 2024" ""
|
||||
.SH "NAME"
|
||||
\fBage\fR \- simple, modern, and secure file encryption
|
||||
.SH "SYNOPSIS"
|
||||
\fBage\fR [\fB\-\-encrypt\fR] (\fB\-r\fR \fIRECIPIENT\fR | \fB\-R\fR \fIPATH\fR)\|\.\|\.\|\. [\fB\-\-armor\fR] [\fB\-o\fR \fIOUTPUT\fR] [\fIINPUT\fR]
|
||||
.br
|
||||
\fBage\fR [\fB\-\-encrypt\fR] \fB\-\-passphrase\fR [\fB\-\-armor\fR] [\fB\-o\fR \fIOUTPUT\fR] [\fIINPUT\fR]
|
||||
.br
|
||||
\fBage\fR \fB\-\-decrypt\fR [\fB\-i\fR \fIPATH\fR | \fB\-j\fR \fIPLUGIN\fR]\|\.\|\.\|\. [\fB\-o\fR \fIOUTPUT\fR] [\fIINPUT\fR]
|
||||
.br
|
||||
.SH "DESCRIPTION"
|
||||
\fBage\fR encrypts or decrypts \fIINPUT\fR to \fIOUTPUT\fR\. The \fIINPUT\fR argument is optional and defaults to standard input\. Only a single \fIINPUT\fR file may be specified\. If \fB\-o\fR is not specified, \fIOUTPUT\fR defaults to standard output\.
|
||||
.P
|
||||
If \fB\-p\fR/\fB\-\-passphrase\fR is specified, the file is encrypted with a passphrase requested interactively\. Otherwise, it's encrypted to one or more \fIRECIPIENTS\fR specified with \fB\-r\fR/\fB\-\-recipient\fR or \fB\-R\fR/\fB\-\-recipients\-file\fR\. Every recipient can decrypt the file\.
|
||||
.P
|
||||
In \fB\-d\fR/\fB\-\-decrypt\fR mode, passphrase\-encrypted files are detected automatically and the passphrase is requested interactively\. Otherwise, one or more \fIIDENTITIES\fR specified with \fB\-i\fR/\fB\-\-identity\fR are used to decrypt the file\.
|
||||
.P
|
||||
\fBage\fR encrypted files are binary and not malleable, with around 200 bytes of overhead per recipient, plus 16 bytes every 64KiB of plaintext\.
|
||||
.SH "OPTIONS"
|
||||
.TP
|
||||
\fB\-o\fR, \fB\-\-output\fR=\fIOUTPUT\fR
|
||||
Write encrypted or decrypted file to \fIOUTPUT\fR instead of standard output\. If \fIOUTPUT\fR already exists it will be overwritten\.
|
||||
.IP
|
||||
If encrypting without \fB\-\-armor\fR, \fBage\fR will refuse to output binary to a TTY\. This can be forced by specifying \fB\-\fR as \fIOUTPUT\fR\.
|
||||
.TP
|
||||
\fB\-\-version\fR
|
||||
Print the version and exit\.
|
||||
.SS "Encryption options"
|
||||
.TP
|
||||
\fB\-e\fR, \fB\-\-encrypt\fR
|
||||
Encrypt \fIINPUT\fR to \fIOUTPUT\fR\. This is the default\.
|
||||
.TP
|
||||
\fB\-r\fR, \fB\-\-recipient\fR=\fIRECIPIENT\fR
|
||||
Encrypt to the explicitly specified \fIRECIPIENT\fR\. See the \fIRECIPIENTS AND IDENTITIES\fR section for possible recipient formats\.
|
||||
.IP
|
||||
This option can be repeated and combined with other recipient flags, and the file can be decrypted by all provided recipients independently\.
|
||||
.TP
|
||||
\fB\-R\fR, \fB\-\-recipients\-file\fR=\fIPATH\fR
|
||||
Encrypt to the \fIRECIPIENTS\fR listed in the file at \fIPATH\fR, one per line\. Empty lines and lines starting with \fB#\fR are ignored as comments\.
|
||||
.IP
|
||||
If \fIPATH\fR is \fB\-\fR, the recipients are read from standard input\. In this case, the \fIINPUT\fR argument must be specified\.
|
||||
.IP
|
||||
This option can be repeated and combined with other recipient flags, and the file can be decrypted by all provided recipients independently\.
|
||||
.TP
|
||||
\fB\-p\fR, \fB\-\-passphrase\fR
|
||||
Encrypt with a passphrase, requested interactively from the terminal\. \fBage\fR will offer to auto\-generate a secure passphrase\.
|
||||
.IP
|
||||
This option can't be used with other recipient flags\.
|
||||
.TP
|
||||
\fB\-a\fR, \fB\-\-armor\fR
|
||||
Encrypt to an ASCII\-only "armored" encoding\.
|
||||
.IP
|
||||
\fBage\fR armor is a strict version of PEM with type \fBAGE ENCRYPTED FILE\fR, canonical "strict" Base64, no headers, and no support for leading and trailing extra data\.
|
||||
.IP
|
||||
Decryption transparently detects and decodes ASCII armoring\.
|
||||
.TP
|
||||
\fB\-i\fR, \fB\-\-identity\fR=\fIPATH\fR
|
||||
Encrypt to the \fIRECIPIENTS\fR corresponding to the \fIIDENTITIES\fR listed in the file at \fIPATH\fR\. This is equivalent to converting the file at \fIPATH\fR to a recipients file with \fBage\-keygen \-y\fR and then passing that to \fB\-R\fR/\fB\-\-recipients\-file\fR\.
|
||||
.IP
|
||||
For the format of \fIPATH\fR, see the definition of \fB\-i\fR/\fB\-\-identity\fR in the \fIDecryption options\fR section\.
|
||||
.IP
|
||||
\fB\-e\fR/\fB\-\-encrypt\fR must be explicitly specified when using \fB\-i\fR/\fB\-\-identity\fR in encryption mode to avoid confusion\.
|
||||
.TP
|
||||
\fB\-j\fR \fIPLUGIN\fR
|
||||
Encrypt using the data\-less \fIplugin\fR \fIPLUGIN\fR\.
|
||||
.IP
|
||||
This is equivalent to using \fB\-i\fR/\fB\-\-identity\fR with a file that contains a single plugin \fBIDENTITY\fR that encodes no plugin\-specific data\.
|
||||
.IP
|
||||
\fB\-e\fR/\fB\-\-encrypt\fR must be explicitly specified when using \fB\-j\fR in encryption mode to avoid confusion\.
|
||||
.SS "Decryption options"
|
||||
.TP
|
||||
\fB\-d\fR, \fB\-\-decrypt\fR
|
||||
Decrypt \fIINPUT\fR to \fIOUTPUT\fR\.
|
||||
.IP
|
||||
If \fIINPUT\fR is passphrase encrypted, it will be automatically detected and the passphrase will be requested interactively\. Otherwise, the \fIIDENTITIES\fR specified with \fB\-i\fR/\fB\-\-identity\fR are used\.
|
||||
.IP
|
||||
ASCII armoring is transparently detected and decoded\.
|
||||
.TP
|
||||
\fB\-i\fR, \fB\-\-identity\fR=\fIPATH\fR
|
||||
Decrypt using the \fIIDENTITIES\fR at \fIPATH\fR\.
|
||||
.IP
|
||||
\fIPATH\fR may be one of the following:
|
||||
.IP
|
||||
a\. A file listing \fIIDENTITIES\fR one per line\. Empty lines and lines starting with "\fB#\fR" are ignored as comments\.
|
||||
.IP
|
||||
b\. A passphrase encrypted age file, containing \fIIDENTITIES\fR one per line like above\. The passphrase is requested interactively\. Note that passphrase\-protected identity files are not necessary for most use cases, where access to the encrypted identity file implies access to the whole system\.
|
||||
.IP
|
||||
c\. An SSH private key file, in PKCS#1, PKCS#8, or OpenSSH format\. If the private key is password\-protected, the password is requested interactively only if the SSH identity matches the file\. See the \fISSH keys\fR section for more information, including supported key types\.
|
||||
.IP
|
||||
d\. "\fB\-\fR", causing one of the options above to be read from standard input\. In this case, the \fIINPUT\fR argument must be specified\.
|
||||
.IP
|
||||
This option can be repeated\. Identities are tried in the order in which are provided, and the first one matching one of the file's recipients is used\. Unused identities are ignored, but it is an error if the \fIINPUT\fR file is passphrase\-encrypted and \fB\-i\fR/\fB\-\-identity\fR is specified\.
|
||||
.TP
|
||||
\fB\-j\fR \fIPLUGIN\fR
|
||||
Decrypt using the data\-less \fIplugin\fR \fIPLUGIN\fR\.
|
||||
.IP
|
||||
This is equivalent to using \fB\-i\fR/\fB\-\-identity\fR with a file that contains a single plugin \fBIDENTITY\fR that encodes no plugin\-specific data\.
|
||||
.SH "RECIPIENTS AND IDENTITIES"
|
||||
\fBRECIPIENTS\fR are public values, like a public key, that a file can be encrypted to\. \fBIDENTITIES\fR are private values, like a private key, that allow decrypting a file encrypted to the corresponding \fBRECIPIENT\fR\.
|
||||
.SS "Native X25519 keys"
|
||||
Native \fBage\fR key pairs are generated with age\-keygen(1), and provide small encodings and strong encryption based on X25519\. They are the recommended recipient type for most applications\.
|
||||
.P
|
||||
A \fBRECIPIENT\fR encoding begins with \fBage1\fR and looks like the following:
|
||||
.IP "" 4
|
||||
.nf
|
||||
age1gde3ncmahlqd9gg50tanl99r960llztrhfapnmx853s4tjum03uqfssgdh
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
An \fBIDENTITY\fR encoding begins with \fBAGE\-SECRET\-KEY\-1\fR and looks like the following:
|
||||
.IP "" 4
|
||||
.nf
|
||||
AGE\-SECRET\-KEY\-1KTYK6RVLN5TAPE7VF6FQQSKZ9HWWCDSKUGXXNUQDWZ7XXT5YK5LSF3UTKQ
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
An encrypted file can't be linked to the native recipient it's encrypted to without access to the corresponding identity\.
|
||||
.SS "SSH keys"
|
||||
As a convenience feature, \fBage\fR also supports encrypting to RSA or Ed25519 ssh(1) keys\. RSA keys must be at least 2048 bits\. This feature employs more complex cryptography, and should only be used when a native key is not available for the recipient\. Note that SSH keys might not be protected long\-term by the recipient, since they are revokable when used only for authentication\.
|
||||
.P
|
||||
A \fBRECIPIENT\fR encoding is an SSH public key in \fBauthorized_keys\fR format (see the \fBAUTHORIZED_KEYS FILE FORMAT\fR section of sshd(8)), starting with \fBssh\-rsa\fR or \fBssh\-ed25519\fR, like the following:
|
||||
.IP "" 4
|
||||
.nf
|
||||
ssh\-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDULTit0KUehbi[\|\.\|\.\|\.]GU4BtElAbzh8=
|
||||
ssh\-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH9pO5pz22JZEas[\|\.\|\.\|\.]l1uZc31FGYMXa
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
The comment at the end of the line, if present, is ignored\.
|
||||
.P
|
||||
In recipient files passed to \fB\-R\fR/\fB\-\-recipients\-file\fR, unsupported but valid SSH public keys are ignored with a warning, to facilitate using \fBauthorized_keys\fR or GitHub \fB\.keys\fR files\. (See \fIEXAMPLES\fR\.)
|
||||
.P
|
||||
An \fBIDENTITY\fR is an SSH private key \fIfile\fR passed individually to \fB\-i\fR/\fB\-\-identity\fR\. Note that keys held on hardware tokens such as YubiKeys or accessed via ssh\-agent(1) are not supported\.
|
||||
.P
|
||||
An encrypted file \fIcan\fR be linked to the SSH public key it was encrypted to\. This is so that \fBage\fR can identify the correct SSH private key before requesting its password, if any\.
|
||||
.SS "Plugins"
|
||||
\fBage\fR can be extended through plugins\. A plugin is only loaded if a corresponding \fBRECIPIENT\fR or \fBIDENTITY\fR is specified\. (Simply decrypting a file encrypted with a plugin will not cause it to load, for security reasons among others\.)
|
||||
.P
|
||||
A \fBRECIPIENT\fR for a plugin named \fBexample\fR starts with \fBage1example1\fR, while an \fBIDENTITY\fR starts with \fBAGE\-PLUGIN\-EXAMPLE\-1\fR\. They both encode arbitrary plugin\-specific data, and are generated by the plugin\.
|
||||
.P
|
||||
When either is specified, \fBage\fR searches for \fBage\-plugin\-example\fR in the PATH and executes it to perform the file header encryption or decryption\. The plugin may request input from the user through \fBage\fR to complete the operation\.
|
||||
.P
|
||||
Plugins can be freely mixed with other plugins or natively supported keys\.
|
||||
.P
|
||||
A plugin is not bound to only encrypt or decrypt files meant for or generated by the plugin\. For example, a plugin can be used to decrypt files encrypted to a native X25519 \fBRECIPIENT\fR or even with a passphrase\. Similarly, a plugin can encrypt a file such that it can be decrypted without the use of any plugin\.
|
||||
.P
|
||||
Plugins for which the \fBIDENTITY\fR/\fBRECIPIENT\fR distinction doesn't make sense (such as a symmetric encryption plugin) may generate only an \fBIDENTITY\fR and instruct the user to perform encryption with the \fB\-e\fR/\fB\-\-encrypt\fR and \fB\-i\fR/\fB\-\-identity\fR flags\. Plugins for which the concept of separate identities doesn't make sense (such as a password\-encryption plugin) may instruct the user to use the \fB\-j\fR flag\.
|
||||
.SH "EXIT STATUS"
|
||||
\fBage\fR will exit 0 if and only if encryption or decryption are successful for the full length of the input\.
|
||||
.P
|
||||
If an error occurs during decryption, partial output might still be generated, but only if it was possible to securely authenticate it\. No unauthenticated output is ever released\.
|
||||
.SH "BACKWARDS COMPATIBILITY"
|
||||
Files encrypted with a stable version (not alpha, beta, or release candidate) of \fBage\fR, or with any v1\.0\.0 beta or release candidate, will decrypt with any later version of the tool\.
|
||||
.P
|
||||
If decrypting older files poses a security risk, doing so might cause an error by default\. In this case, a flag will be provided to force the operation\.
|
||||
.SH "EXAMPLES"
|
||||
Generate a new identity, encrypt data, and decrypt:
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ age\-keygen \-o key\.txt
|
||||
Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
|
||||
|
||||
$ tar cvz ~/data | age \-r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p > data\.tar\.gz\.age
|
||||
|
||||
$ age \-d \-o data\.tar\.gz \-i key\.txt data\.tar\.gz\.age
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Encrypt \fBexample\.jpg\fR to multiple recipients and output to \fBexample\.jpg\.age\fR:
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ age \-o example\.jpg\.age \-r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p \e
|
||||
\-r age1lggyhqrw2nlhcxprm67z43rta597azn8gknawjehu9d9dl0jq3yqqvfafg example\.jpg
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Encrypt to a list of recipients:
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ cat > recipients\.txt
|
||||
# Alice
|
||||
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
|
||||
# Bob
|
||||
age1lggyhqrw2nlhcxprm67z43rta597azn8gknawjehu9d9dl0jq3yqqvfafg
|
||||
|
||||
$ age \-R recipients\.txt example\.jpg > example\.jpg\.age
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Encrypt and decrypt a file using a passphrase:
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ age \-p secrets\.txt > secrets\.txt\.age
|
||||
Enter passphrase (leave empty to autogenerate a secure one):
|
||||
Using the autogenerated passphrase "release\-response\-step\-brand\-wrap\-ankle\-pair\-unusual\-sword\-train"\.
|
||||
|
||||
$ age \-d secrets\.txt\.age > secrets\.txt
|
||||
Enter passphrase:
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Encrypt and decrypt with a passphrase\-protected identity file:
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ age\-keygen | age \-p > key\.age
|
||||
Public key: age1yhm4gctwfmrpz87tdslm550wrx6m79y9f2hdzt0lndjnehwj0ukqrjpyx5
|
||||
Enter passphrase (leave empty to autogenerate a secure one):
|
||||
Using the autogenerated passphrase "hip\-roast\-boring\-snake\-mention\-east\-wasp\-honey\-input\-actress"\.
|
||||
|
||||
$ age \-r age1yhm4gctwfmrpz87tdslm550wrx6m79y9f2hdzt0lndjnehwj0ukqrjpyx5 secrets\.txt > secrets\.txt\.age
|
||||
|
||||
$ age \-d \-i key\.age secrets\.txt\.age > secrets\.txt
|
||||
Enter passphrase for identity file "key\.age":
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Encrypt and decrypt with an SSH public key:
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ age \-R ~/\.ssh/id_ed25519\.pub example\.jpg > example\.jpg\.age
|
||||
|
||||
$ age \-d \-i ~/\.ssh/id_ed25519 example\.jpg\.age > example\.jpg
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Encrypt and decrypt with age\-plugin\-yubikey:
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ age\-plugin\-yubikey # run interactive setup, generate identity file and obtain recipient
|
||||
|
||||
$ age \-r age1yubikey1qwt50d05nh5vutpdzmlg5wn80xq5negm4uj9ghv0snvdd3yysf5yw3rhl3t secrets\.txt > secrets\.txt\.age
|
||||
|
||||
$ age \-d \-i age\-yubikey\-identity\-388178f3\.txt secrets\.txt\.age
|
||||
.fi
|
||||
.IP "" 0
|
||||
.P
|
||||
Encrypt to the SSH keys of a GitHub user:
|
||||
.IP "" 4
|
||||
.nf
|
||||
$ curl https://github\.com/benjojo\.keys | age \-R \- example\.jpg > example\.jpg\.age
|
||||
.fi
|
||||
.IP "" 0
|
||||
.SH "SEE ALSO"
|
||||
age\-keygen(1)
|
||||
.SH "AUTHORS"
|
||||
Filippo Valsorda \fIage@filippo\.io\fR
|
||||
441
doc/age.1.html
Normal file
441
doc/age.1.html
Normal file
@@ -0,0 +1,441 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv='content-type' content='text/html;charset=utf8'>
|
||||
<meta name='generator' content='Ronn-NG/v0.9.1 (http://github.com/apjanke/ronn-ng/tree/0.9.1)'>
|
||||
<title>age(1) - simple, modern, and secure file encryption</title>
|
||||
<style type='text/css' media='all'>
|
||||
/* style: man */
|
||||
body#manpage {margin:0}
|
||||
.mp {max-width:100ex;padding:0 9ex 1ex 4ex}
|
||||
.mp p,.mp pre,.mp ul,.mp ol,.mp dl {margin:0 0 20px 0}
|
||||
.mp h2 {margin:10px 0 0 0}
|
||||
.mp > p,.mp > pre,.mp > ul,.mp > ol,.mp > dl {margin-left:8ex}
|
||||
.mp h3 {margin:0 0 0 4ex}
|
||||
.mp dt {margin:0;clear:left}
|
||||
.mp dt.flush {float:left;width:8ex}
|
||||
.mp dd {margin:0 0 0 9ex}
|
||||
.mp h1,.mp h2,.mp h3,.mp h4 {clear:left}
|
||||
.mp pre {margin-bottom:20px}
|
||||
.mp pre+h2,.mp pre+h3 {margin-top:22px}
|
||||
.mp h2+pre,.mp h3+pre {margin-top:5px}
|
||||
.mp img {display:block;margin:auto}
|
||||
.mp h1.man-title {display:none}
|
||||
.mp,.mp code,.mp pre,.mp tt,.mp kbd,.mp samp,.mp h3,.mp h4 {font-family:monospace;font-size:14px;line-height:1.42857142857143}
|
||||
.mp h2 {font-size:16px;line-height:1.25}
|
||||
.mp h1 {font-size:20px;line-height:2}
|
||||
.mp {text-align:justify;background:#fff}
|
||||
.mp,.mp code,.mp pre,.mp pre code,.mp tt,.mp kbd,.mp samp {color:#131211}
|
||||
.mp h1,.mp h2,.mp h3,.mp h4 {color:#030201}
|
||||
.mp u {text-decoration:underline}
|
||||
.mp code,.mp strong,.mp b {font-weight:bold;color:#131211}
|
||||
.mp em,.mp var {font-style:italic;color:#232221;text-decoration:none}
|
||||
.mp a,.mp a:link,.mp a:hover,.mp a code,.mp a pre,.mp a tt,.mp a kbd,.mp a samp {color:#0000ff}
|
||||
.mp b.man-ref {font-weight:normal;color:#434241}
|
||||
.mp pre {padding:0 4ex}
|
||||
.mp pre code {font-weight:normal;color:#434241}
|
||||
.mp h2+pre,h3+pre {padding-left:0}
|
||||
ol.man-decor,ol.man-decor li {margin:3px 0 10px 0;padding:0;float:left;width:33%;list-style-type:none;text-transform:uppercase;color:#999;letter-spacing:1px}
|
||||
ol.man-decor {width:100%}
|
||||
ol.man-decor li.tl {text-align:left}
|
||||
ol.man-decor li.tc {text-align:center;letter-spacing:4px}
|
||||
ol.man-decor li.tr {text-align:right;float:right}
|
||||
</style>
|
||||
</head>
|
||||
<!--
|
||||
The following styles are deprecated and will be removed at some point:
|
||||
div#man, div#man ol.man, div#man ol.head, div#man ol.man.
|
||||
|
||||
The .man-page, .man-decor, .man-head, .man-foot, .man-title, and
|
||||
.man-navigation should be used instead.
|
||||
-->
|
||||
<body id='manpage'>
|
||||
<div class='mp' id='man'>
|
||||
|
||||
<div class='man-navigation' style='display:none'>
|
||||
<a href="#NAME">NAME</a>
|
||||
<a href="#SYNOPSIS">SYNOPSIS</a>
|
||||
<a href="#DESCRIPTION">DESCRIPTION</a>
|
||||
<a href="#OPTIONS">OPTIONS</a>
|
||||
<a href="#RECIPIENTS-AND-IDENTITIES">RECIPIENTS AND IDENTITIES</a>
|
||||
<a href="#EXIT-STATUS">EXIT STATUS</a>
|
||||
<a href="#BACKWARDS-COMPATIBILITY">BACKWARDS COMPATIBILITY</a>
|
||||
<a href="#EXAMPLES">EXAMPLES</a>
|
||||
<a href="#SEE-ALSO">SEE ALSO</a>
|
||||
<a href="#AUTHORS">AUTHORS</a>
|
||||
</div>
|
||||
|
||||
<ol class='man-decor man-head man head'>
|
||||
<li class='tl'>age(1)</li>
|
||||
<li class='tc'></li>
|
||||
<li class='tr'>age(1)</li>
|
||||
</ol>
|
||||
|
||||
|
||||
|
||||
<h2 id="NAME">NAME</h2>
|
||||
<p class="man-name">
|
||||
<code>age</code> - <span class="man-whatis">simple, modern, and secure file encryption</span>
|
||||
</p>
|
||||
<h2 id="SYNOPSIS">SYNOPSIS</h2>
|
||||
|
||||
<p><code>age</code> [<code>--encrypt</code>] (<code>-r</code> <var>RECIPIENT</var> | <code>-R</code> <var>PATH</var>)... [<code>--armor</code>] [<code>-o</code> <var>OUTPUT</var>] [<var>INPUT</var>]<br>
|
||||
<code>age</code> [<code>--encrypt</code>] <code>--passphrase</code> [<code>--armor</code>] [<code>-o</code> <var>OUTPUT</var>] [<var>INPUT</var>]<br>
|
||||
<code>age</code> <code>--decrypt</code> [<code>-i</code> <var>PATH</var> | <code>-j</code> <var>PLUGIN</var>]... [<code>-o</code> <var>OUTPUT</var>] [<var>INPUT</var>]<br></p>
|
||||
|
||||
<h2 id="DESCRIPTION">DESCRIPTION</h2>
|
||||
|
||||
<p><code>age</code> encrypts or decrypts <var>INPUT</var> to <var>OUTPUT</var>. The <var>INPUT</var> argument is
|
||||
optional and defaults to standard input. Only a single <var>INPUT</var> file may be
|
||||
specified. If <code>-o</code> is not specified, <var>OUTPUT</var> defaults to standard output.</p>
|
||||
|
||||
<p>If <code>-p</code>/<code>--passphrase</code> is specified, the file is encrypted with a passphrase
|
||||
requested interactively. Otherwise, it's encrypted to one or more
|
||||
<a href="#RECIPIENTS-AND-IDENTITIES" title="RECIPIENTS AND IDENTITIES" data-bare-link="true">RECIPIENTS</a> specified with <code>-r</code>/<code>--recipient</code> or
|
||||
<code>-R</code>/<code>--recipients-file</code>. Every recipient can decrypt the file.</p>
|
||||
|
||||
<p>In <code>-d</code>/<code>--decrypt</code> mode, passphrase-encrypted files are detected automatically
|
||||
and the passphrase is requested interactively. Otherwise, one or more
|
||||
<a href="#RECIPIENTS-AND-IDENTITIES" title="RECIPIENTS AND IDENTITIES" data-bare-link="true">IDENTITIES</a> specified with <code>-i</code>/<code>--identity</code> are
|
||||
used to decrypt the file.</p>
|
||||
|
||||
<p><code>age</code> encrypted files are binary and not malleable, with around 200 bytes of
|
||||
overhead per recipient, plus 16 bytes every 64KiB of plaintext.</p>
|
||||
|
||||
<h2 id="OPTIONS">OPTIONS</h2>
|
||||
|
||||
<dl>
|
||||
<dt>
|
||||
<code>-o</code>, <code>--output</code>=<var>OUTPUT</var>
|
||||
</dt>
|
||||
<dd> Write encrypted or decrypted file to <var>OUTPUT</var> instead of standard output.
|
||||
If <var>OUTPUT</var> already exists it will be overwritten.
|
||||
|
||||
<p>If encrypting without <code>--armor</code>, <code>age</code> will refuse to output binary to a
|
||||
TTY. This can be forced by specifying <code>-</code> as <var>OUTPUT</var>.</p>
|
||||
</dd>
|
||||
<dt><code>--version</code></dt>
|
||||
<dd> Print the version and exit.</dd>
|
||||
</dl>
|
||||
|
||||
<h3 id="Encryption-options">Encryption options</h3>
|
||||
|
||||
<dl>
|
||||
<dt>
|
||||
<code>-e</code>, <code>--encrypt</code>
|
||||
</dt>
|
||||
<dd> Encrypt <var>INPUT</var> to <var>OUTPUT</var>. This is the default.</dd>
|
||||
<dt>
|
||||
<code>-r</code>, <code>--recipient</code>=<var>RECIPIENT</var>
|
||||
</dt>
|
||||
<dd> Encrypt to the explicitly specified <var>RECIPIENT</var>. See the
|
||||
<a href="#RECIPIENTS-AND-IDENTITIES" title="RECIPIENTS AND IDENTITIES" data-bare-link="true">RECIPIENTS AND IDENTITIES</a> section for possible recipient formats.
|
||||
|
||||
<p>This option can be repeated and combined with other recipient flags,
|
||||
and the file can be decrypted by all provided recipients independently.</p>
|
||||
</dd>
|
||||
<dt>
|
||||
<code>-R</code>, <code>--recipients-file</code>=<var>PATH</var>
|
||||
</dt>
|
||||
<dd> Encrypt to the <a href="#RECIPIENTS-AND-IDENTITIES" title="RECIPIENTS AND IDENTITIES" data-bare-link="true">RECIPIENTS</a> listed in the
|
||||
file at <var>PATH</var>, one per line. Empty lines and lines starting with <code>#</code>
|
||||
are ignored as comments.
|
||||
|
||||
<p>If <var>PATH</var> is <code>-</code>, the recipients are read from standard input. In
|
||||
this case, the <var>INPUT</var> argument must be specified.</p>
|
||||
|
||||
<p>This option can be repeated and combined with other recipient flags,
|
||||
and the file can be decrypted by all provided recipients independently.</p>
|
||||
</dd>
|
||||
<dt>
|
||||
<code>-p</code>, <code>--passphrase</code>
|
||||
</dt>
|
||||
<dd> Encrypt with a passphrase, requested interactively from the terminal.
|
||||
<code>age</code> will offer to auto-generate a secure passphrase.
|
||||
|
||||
<p>This option can't be used with other recipient flags.</p>
|
||||
</dd>
|
||||
<dt>
|
||||
<code>-a</code>, <code>--armor</code>
|
||||
</dt>
|
||||
<dd> Encrypt to an ASCII-only "armored" encoding.
|
||||
|
||||
<p><code>age</code> armor is a strict version of PEM with type <code>AGE ENCRYPTED FILE</code>,
|
||||
canonical "strict" Base64, no headers, and no support for leading and
|
||||
trailing extra data.</p>
|
||||
|
||||
<p>Decryption transparently detects and decodes ASCII armoring.</p>
|
||||
</dd>
|
||||
<dt>
|
||||
<code>-i</code>, <code>--identity</code>=<var>PATH</var>
|
||||
</dt>
|
||||
<dd> Encrypt to the <a href="#RECIPIENTS-AND-IDENTITIES" title="RECIPIENTS AND IDENTITIES" data-bare-link="true">RECIPIENTS</a> corresponding to the
|
||||
<a href="#RECIPIENTS-AND-IDENTITIES" title="RECIPIENTS AND IDENTITIES" data-bare-link="true">IDENTITIES</a> listed in the file at <var>PATH</var>. This
|
||||
is equivalent to converting the file at <var>PATH</var> to a recipients file with
|
||||
<code>age-keygen -y</code> and then passing that to <code>-R</code>/<code>--recipients-file</code>.
|
||||
|
||||
<p>For the format of <var>PATH</var>, see the definition of <code>-i</code>/<code>--identity</code> in the
|
||||
<a href="#Decryption-options" title="Decryption options" data-bare-link="true">Decryption options</a> section.</p>
|
||||
|
||||
<p><code>-e</code>/<code>--encrypt</code> must be explicitly specified when using <code>-i</code>/<code>--identity</code>
|
||||
in encryption mode to avoid confusion.</p>
|
||||
</dd>
|
||||
<dt>
|
||||
<code>-j</code> <var>PLUGIN</var>
|
||||
</dt>
|
||||
<dd> Encrypt using the data-less <a href="#Plugins" title="Plugins" data-bare-link="true">plugin</a> <var>PLUGIN</var>.
|
||||
|
||||
<p>This is equivalent to using <code>-i</code>/<code>--identity</code> with a file that contains a
|
||||
single plugin <code>IDENTITY</code> that encodes no plugin-specific data.</p>
|
||||
|
||||
<p><code>-e</code>/<code>--encrypt</code> must be explicitly specified when using <code>-j</code> in encryption
|
||||
mode to avoid confusion.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<h3 id="Decryption-options">Decryption options</h3>
|
||||
|
||||
<dl>
|
||||
<dt>
|
||||
<code>-d</code>, <code>--decrypt</code>
|
||||
</dt>
|
||||
<dd> Decrypt <var>INPUT</var> to <var>OUTPUT</var>.
|
||||
|
||||
<p>If <var>INPUT</var> is passphrase encrypted, it will be automatically detected
|
||||
and the passphrase will be requested interactively. Otherwise, the
|
||||
<a href="#RECIPIENTS-AND-IDENTITIES" title="RECIPIENTS AND IDENTITIES" data-bare-link="true">IDENTITIES</a> specified with <code>-i</code>/<code>--identity</code>
|
||||
are used.</p>
|
||||
|
||||
<p>ASCII armoring is transparently detected and decoded.</p>
|
||||
</dd>
|
||||
<dt>
|
||||
<code>-i</code>, <code>--identity</code>=<var>PATH</var>
|
||||
</dt>
|
||||
<dd> Decrypt using the <a href="#RECIPIENTS-AND-IDENTITIES" title="RECIPIENTS AND IDENTITIES" data-bare-link="true">IDENTITIES</a> at <var>PATH</var>.
|
||||
|
||||
<p><var>PATH</var> may be one of the following:</p>
|
||||
|
||||
<p>a. A file listing <a href="#RECIPIENTS-AND-IDENTITIES" title="RECIPIENTS AND IDENTITIES" data-bare-link="true">IDENTITIES</a> one per line.
|
||||
Empty lines and lines starting with "<code>#</code>" are ignored as comments.</p>
|
||||
|
||||
<p>b. A passphrase encrypted age file, containing
|
||||
<a href="#RECIPIENTS-AND-IDENTITIES" title="RECIPIENTS AND IDENTITIES" data-bare-link="true">IDENTITIES</a> one per line like above.
|
||||
The passphrase is requested interactively. Note that passphrase-protected
|
||||
identity files are not necessary for most use cases, where access to the
|
||||
encrypted identity file implies access to the whole system.</p>
|
||||
|
||||
<p>c. An SSH private key file, in PKCS#1, PKCS#8, or OpenSSH format.
|
||||
If the private key is password-protected, the password is requested
|
||||
interactively only if the SSH identity matches the file. See the
|
||||
<a href="#SSH-keys" title="SSH keys" data-bare-link="true">SSH keys</a> section for more information, including supported key types.</p>
|
||||
|
||||
<p>d. "<code>-</code>", causing one of the options above to be read from standard input.
|
||||
In this case, the <var>INPUT</var> argument must be specified.</p>
|
||||
|
||||
<p>This option can be repeated. Identities are tried in the order in which are
|
||||
provided, and the first one matching one of the file's recipients is used.
|
||||
Unused identities are ignored, but it is an error if the <var>INPUT</var> file is
|
||||
passphrase-encrypted and <code>-i</code>/<code>--identity</code> is specified.</p>
|
||||
</dd>
|
||||
<dt>
|
||||
<code>-j</code> <var>PLUGIN</var>
|
||||
</dt>
|
||||
<dd> Decrypt using the data-less <a href="#Plugins" title="Plugins" data-bare-link="true">plugin</a> <var>PLUGIN</var>.
|
||||
|
||||
<p>This is equivalent to using <code>-i</code>/<code>--identity</code> with a file that contains a
|
||||
single plugin <code>IDENTITY</code> that encodes no plugin-specific data.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<h2 id="RECIPIENTS-AND-IDENTITIES">RECIPIENTS AND IDENTITIES</h2>
|
||||
|
||||
<p><code>RECIPIENTS</code> are public values, like a public key, that a file can be encrypted
|
||||
to. <code>IDENTITIES</code> are private values, like a private key, that allow decrypting
|
||||
a file encrypted to the corresponding <code>RECIPIENT</code>.</p>
|
||||
|
||||
<h3 id="Native-X25519-keys">Native X25519 keys</h3>
|
||||
|
||||
<p>Native <code>age</code> key pairs are generated with <a class="man-ref" href="age-keygen.1.html">age-keygen<span class="s">(1)</span></a>, and provide small
|
||||
encodings and strong encryption based on X25519. They are the recommended
|
||||
recipient type for most applications.</p>
|
||||
|
||||
<p>A <code>RECIPIENT</code> encoding begins with <code>age1</code> and looks like the following:</p>
|
||||
|
||||
<pre><code>age1gde3ncmahlqd9gg50tanl99r960llztrhfapnmx853s4tjum03uqfssgdh
|
||||
</code></pre>
|
||||
|
||||
<p>An <code>IDENTITY</code> encoding begins with <code>AGE-SECRET-KEY-1</code> and looks like the
|
||||
following:</p>
|
||||
|
||||
<pre><code>AGE-SECRET-KEY-1KTYK6RVLN5TAPE7VF6FQQSKZ9HWWCDSKUGXXNUQDWZ7XXT5YK5LSF3UTKQ
|
||||
</code></pre>
|
||||
|
||||
<p>An encrypted file can't be linked to the native recipient it's encrypted to
|
||||
without access to the corresponding identity.</p>
|
||||
|
||||
<h3 id="SSH-keys">SSH keys</h3>
|
||||
|
||||
<p>As a convenience feature, <code>age</code> also supports encrypting to RSA or Ed25519
|
||||
<span class="man-ref">ssh<span class="s">(1)</span></span> keys. RSA keys must be at least 2048 bits. This feature employs more
|
||||
complex cryptography, and should only be used when a native key is not available
|
||||
for the recipient. Note that SSH keys might not be protected long-term by the
|
||||
recipient, since they are revokable when used only for authentication.</p>
|
||||
|
||||
<p>A <code>RECIPIENT</code> encoding is an SSH public key in <code>authorized_keys</code> format
|
||||
(see the <code>AUTHORIZED_KEYS FILE FORMAT</code> section of <span class="man-ref">sshd<span class="s">(8)</span></span>), starting with
|
||||
<code>ssh-rsa</code> or <code>ssh-ed25519</code>, like the following:</p>
|
||||
|
||||
<pre><code>ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDULTit0KUehbi[...]GU4BtElAbzh8=
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH9pO5pz22JZEas[...]l1uZc31FGYMXa
|
||||
</code></pre>
|
||||
|
||||
<p>The comment at the end of the line, if present, is ignored.</p>
|
||||
|
||||
<p>In recipient files passed to <code>-R</code>/<code>--recipients-file</code>, unsupported but valid
|
||||
SSH public keys are ignored with a warning, to facilitate using
|
||||
<code>authorized_keys</code> or GitHub <code>.keys</code> files. (See <a href="#EXAMPLES" title="EXAMPLES" data-bare-link="true">EXAMPLES</a>.)</p>
|
||||
|
||||
<p>An <code>IDENTITY</code> is an SSH private key <em>file</em> passed individually to
|
||||
<code>-i</code>/<code>--identity</code>. Note that keys held on hardware tokens such as YubiKeys
|
||||
or accessed via <span class="man-ref">ssh-agent<span class="s">(1)</span></span> are not supported.</p>
|
||||
|
||||
<p>An encrypted file <em>can</em> be linked to the SSH public key it was encrypted to.
|
||||
This is so that <code>age</code> can identify the correct SSH private key before
|
||||
requesting its password, if any.</p>
|
||||
|
||||
<h3 id="Plugins">Plugins</h3>
|
||||
|
||||
<p><code>age</code> can be extended through plugins. A plugin is only loaded if a corresponding
|
||||
<code>RECIPIENT</code> or <code>IDENTITY</code> is specified. (Simply decrypting a file encrypted with
|
||||
a plugin will not cause it to load, for security reasons among others.)</p>
|
||||
|
||||
<p>A <code>RECIPIENT</code> for a plugin named <code>example</code> starts with <code>age1example1</code>, while an
|
||||
<code>IDENTITY</code> starts with <code>AGE-PLUGIN-EXAMPLE-1</code>. They both encode arbitrary
|
||||
plugin-specific data, and are generated by the plugin.</p>
|
||||
|
||||
<p>When either is specified, <code>age</code> searches for <code>age-plugin-example</code> in the PATH
|
||||
and executes it to perform the file header encryption or decryption. The plugin
|
||||
may request input from the user through <code>age</code> to complete the operation.</p>
|
||||
|
||||
<p>Plugins can be freely mixed with other plugins or natively supported keys.</p>
|
||||
|
||||
<p>A plugin is not bound to only encrypt or decrypt files meant for or generated by
|
||||
the plugin. For example, a plugin can be used to decrypt files encrypted to a
|
||||
native X25519 <code>RECIPIENT</code> or even with a passphrase. Similarly, a plugin can
|
||||
encrypt a file such that it can be decrypted without the use of any plugin.</p>
|
||||
|
||||
<p>Plugins for which the <code>IDENTITY</code>/<code>RECIPIENT</code> distinction doesn't make sense
|
||||
(such as a symmetric encryption plugin) may generate only an <code>IDENTITY</code> and
|
||||
instruct the user to perform encryption with the <code>-e</code>/<code>--encrypt</code> and
|
||||
<code>-i</code>/<code>--identity</code> flags. Plugins for which the concept of separate identities
|
||||
doesn't make sense (such as a password-encryption plugin) may instruct the user
|
||||
to use the <code>-j</code> flag.</p>
|
||||
|
||||
<h2 id="EXIT-STATUS">EXIT STATUS</h2>
|
||||
|
||||
<p><code>age</code> will exit 0 if and only if encryption or decryption are successful for the
|
||||
full length of the input.</p>
|
||||
|
||||
<p>If an error occurs during decryption, partial output might still be generated,
|
||||
but only if it was possible to securely authenticate it. No unauthenticated
|
||||
output is ever released.</p>
|
||||
|
||||
<h2 id="BACKWARDS-COMPATIBILITY">BACKWARDS COMPATIBILITY</h2>
|
||||
|
||||
<p>Files encrypted with a stable version (not alpha, beta, or release candidate) of
|
||||
<code>age</code>, or with any v1.0.0 beta or release candidate, will decrypt with any later
|
||||
version of the tool.</p>
|
||||
|
||||
<p>If decrypting older files poses a security risk, doing so might cause an error
|
||||
by default. In this case, a flag will be provided to force the operation.</p>
|
||||
|
||||
<h2 id="EXAMPLES">EXAMPLES</h2>
|
||||
|
||||
<p>Generate a new identity, encrypt data, and decrypt:</p>
|
||||
|
||||
<pre><code>$ age-keygen -o key.txt
|
||||
Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
|
||||
|
||||
$ tar cvz ~/data | age -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p > data.tar.gz.age
|
||||
|
||||
$ age -d -o data.tar.gz -i key.txt data.tar.gz.age
|
||||
</code></pre>
|
||||
|
||||
<p>Encrypt <code>example.jpg</code> to multiple recipients and output to <code>example.jpg.age</code>:</p>
|
||||
|
||||
<pre><code>$ age -o example.jpg.age -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p \
|
||||
-r age1lggyhqrw2nlhcxprm67z43rta597azn8gknawjehu9d9dl0jq3yqqvfafg example.jpg
|
||||
</code></pre>
|
||||
|
||||
<p>Encrypt to a list of recipients:</p>
|
||||
|
||||
<pre><code>$ cat > recipients.txt
|
||||
# Alice
|
||||
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
|
||||
# Bob
|
||||
age1lggyhqrw2nlhcxprm67z43rta597azn8gknawjehu9d9dl0jq3yqqvfafg
|
||||
|
||||
$ age -R recipients.txt example.jpg > example.jpg.age
|
||||
</code></pre>
|
||||
|
||||
<p>Encrypt and decrypt a file using a passphrase:</p>
|
||||
|
||||
<pre><code>$ age -p secrets.txt > secrets.txt.age
|
||||
Enter passphrase (leave empty to autogenerate a secure one):
|
||||
Using the autogenerated passphrase "release-response-step-brand-wrap-ankle-pair-unusual-sword-train".
|
||||
|
||||
$ age -d secrets.txt.age > secrets.txt
|
||||
Enter passphrase:
|
||||
</code></pre>
|
||||
|
||||
<p>Encrypt and decrypt with a passphrase-protected identity file:</p>
|
||||
|
||||
<pre><code>$ age-keygen | age -p > key.age
|
||||
Public key: age1yhm4gctwfmrpz87tdslm550wrx6m79y9f2hdzt0lndjnehwj0ukqrjpyx5
|
||||
Enter passphrase (leave empty to autogenerate a secure one):
|
||||
Using the autogenerated passphrase "hip-roast-boring-snake-mention-east-wasp-honey-input-actress".
|
||||
|
||||
$ age -r age1yhm4gctwfmrpz87tdslm550wrx6m79y9f2hdzt0lndjnehwj0ukqrjpyx5 secrets.txt > secrets.txt.age
|
||||
|
||||
$ age -d -i key.age secrets.txt.age > secrets.txt
|
||||
Enter passphrase for identity file "key.age":
|
||||
</code></pre>
|
||||
|
||||
<p>Encrypt and decrypt with an SSH public key:</p>
|
||||
|
||||
<pre><code>$ age -R ~/.ssh/id_ed25519.pub example.jpg > example.jpg.age
|
||||
|
||||
$ age -d -i ~/.ssh/id_ed25519 example.jpg.age > example.jpg
|
||||
</code></pre>
|
||||
|
||||
<p>Encrypt and decrypt with age-plugin-yubikey:</p>
|
||||
|
||||
<pre><code>$ age-plugin-yubikey # run interactive setup, generate identity file and obtain recipient
|
||||
|
||||
$ age -r age1yubikey1qwt50d05nh5vutpdzmlg5wn80xq5negm4uj9ghv0snvdd3yysf5yw3rhl3t secrets.txt > secrets.txt.age
|
||||
|
||||
$ age -d -i age-yubikey-identity-388178f3.txt secrets.txt.age
|
||||
</code></pre>
|
||||
|
||||
<p>Encrypt to the SSH keys of a GitHub user:</p>
|
||||
|
||||
<pre><code>$ curl https://github.com/benjojo.keys | age -R - example.jpg > example.jpg.age
|
||||
</code></pre>
|
||||
|
||||
<h2 id="SEE-ALSO">SEE ALSO</h2>
|
||||
|
||||
<p><a class="man-ref" href="age-keygen.1.html">age-keygen<span class="s">(1)</span></a></p>
|
||||
|
||||
<h2 id="AUTHORS">AUTHORS</h2>
|
||||
|
||||
<p>Filippo Valsorda <a href="mailto:age@filippo.io" data-bare-link="true">age@filippo.io</a></p>
|
||||
|
||||
<ol class='man-decor man-foot man foot'>
|
||||
<li class='tl'></li>
|
||||
<li class='tc'>June 2024</li>
|
||||
<li class='tr'>age(1)</li>
|
||||
</ol>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
315
doc/age.1.ronn
Normal file
315
doc/age.1.ronn
Normal file
@@ -0,0 +1,315 @@
|
||||
age(1) -- simple, modern, and secure file encryption
|
||||
====================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`age` [`--encrypt`] (`-r` <RECIPIENT> | `-R` <PATH>)... [`--armor`] [`-o` <OUTPUT>] [<INPUT>]<br>
|
||||
`age` [`--encrypt`] `--passphrase` [`--armor`] [`-o` <OUTPUT>] [<INPUT>]<br>
|
||||
`age` `--decrypt` [`-i` <PATH> | `-j` <PLUGIN>]... [`-o` <OUTPUT>] [<INPUT>]<br>
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`age` encrypts or decrypts <INPUT> to <OUTPUT>. The <INPUT> argument is
|
||||
optional and defaults to standard input. Only a single <INPUT> file may be
|
||||
specified. If `-o` is not specified, <OUTPUT> defaults to standard output.
|
||||
|
||||
If `-p`/`--passphrase` is specified, the file is encrypted with a passphrase
|
||||
requested interactively. Otherwise, it's encrypted to one or more
|
||||
[RECIPIENTS][RECIPIENTS AND IDENTITIES] specified with `-r`/`--recipient` or
|
||||
`-R`/`--recipients-file`. Every recipient can decrypt the file.
|
||||
|
||||
In `-d`/`--decrypt` mode, passphrase-encrypted files are detected automatically
|
||||
and the passphrase is requested interactively. Otherwise, one or more
|
||||
[IDENTITIES][RECIPIENTS AND IDENTITIES] specified with `-i`/`--identity` are
|
||||
used to decrypt the file.
|
||||
|
||||
`age` encrypted files are binary and not malleable, with around 200 bytes of
|
||||
overhead per recipient, plus 16 bytes every 64KiB of plaintext.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-o`, `--output`=<OUTPUT>:
|
||||
Write encrypted or decrypted file to <OUTPUT> instead of standard output.
|
||||
If <OUTPUT> already exists it will be overwritten.
|
||||
|
||||
If encrypting without `--armor`, `age` will refuse to output binary to a
|
||||
TTY. This can be forced by specifying `-` as <OUTPUT>.
|
||||
|
||||
* `--version`:
|
||||
Print the version and exit.
|
||||
|
||||
### Encryption options
|
||||
|
||||
* `-e`, `--encrypt`:
|
||||
Encrypt <INPUT> to <OUTPUT>. This is the default.
|
||||
|
||||
* `-r`, `--recipient`=<RECIPIENT>:
|
||||
Encrypt to the explicitly specified <RECIPIENT>. See the
|
||||
[RECIPIENTS AND IDENTITIES][] section for possible recipient formats.
|
||||
|
||||
This option can be repeated and combined with other recipient flags,
|
||||
and the file can be decrypted by all provided recipients independently.
|
||||
|
||||
* `-R`, `--recipients-file`=<PATH>:
|
||||
Encrypt to the [RECIPIENTS][RECIPIENTS AND IDENTITIES] listed in the
|
||||
file at <PATH>, one per line. Empty lines and lines starting with `#`
|
||||
are ignored as comments.
|
||||
|
||||
If <PATH> is `-`, the recipients are read from standard input. In
|
||||
this case, the <INPUT> argument must be specified.
|
||||
|
||||
This option can be repeated and combined with other recipient flags,
|
||||
and the file can be decrypted by all provided recipients independently.
|
||||
|
||||
* `-p`, `--passphrase`:
|
||||
Encrypt with a passphrase, requested interactively from the terminal.
|
||||
`age` will offer to auto-generate a secure passphrase.
|
||||
|
||||
This option can't be used with other recipient flags.
|
||||
|
||||
* `-a`, `--armor`:
|
||||
Encrypt to an ASCII-only "armored" encoding.
|
||||
|
||||
`age` armor is a strict version of PEM with type `AGE ENCRYPTED FILE`,
|
||||
canonical "strict" Base64, no headers, and no support for leading and
|
||||
trailing extra data.
|
||||
|
||||
Decryption transparently detects and decodes ASCII armoring.
|
||||
|
||||
* `-i`, `--identity`=<PATH>:
|
||||
Encrypt to the [RECIPIENTS][RECIPIENTS AND IDENTITIES] corresponding to the
|
||||
[IDENTITIES][RECIPIENTS AND IDENTITIES] listed in the file at <PATH>. This
|
||||
is equivalent to converting the file at <PATH> to a recipients file with
|
||||
`age-keygen -y` and then passing that to `-R`/`--recipients-file`.
|
||||
|
||||
For the format of <PATH>, see the definition of `-i`/`--identity` in the
|
||||
[Decryption options][] section.
|
||||
|
||||
`-e`/`--encrypt` must be explicitly specified when using `-i`/`--identity`
|
||||
in encryption mode to avoid confusion.
|
||||
|
||||
* `-j` <PLUGIN>:
|
||||
Encrypt using the data-less [plugin][Plugins] <PLUGIN>.
|
||||
|
||||
This is equivalent to using `-i`/`--identity` with a file that contains a
|
||||
single plugin `IDENTITY` that encodes no plugin-specific data.
|
||||
|
||||
`-e`/`--encrypt` must be explicitly specified when using `-j` in encryption
|
||||
mode to avoid confusion.
|
||||
|
||||
### Decryption options
|
||||
|
||||
* `-d`, `--decrypt`:
|
||||
Decrypt <INPUT> to <OUTPUT>.
|
||||
|
||||
If <INPUT> is passphrase encrypted, it will be automatically detected
|
||||
and the passphrase will be requested interactively. Otherwise, the
|
||||
[IDENTITIES][RECIPIENTS AND IDENTITIES] specified with `-i`/`--identity`
|
||||
are used.
|
||||
|
||||
ASCII armoring is transparently detected and decoded.
|
||||
|
||||
* `-i`, `--identity`=<PATH>:
|
||||
Decrypt using the [IDENTITIES][RECIPIENTS AND IDENTITIES] at <PATH>.
|
||||
|
||||
<PATH> may be one of the following:
|
||||
|
||||
a\. A file listing [IDENTITIES][RECIPIENTS AND IDENTITIES] one per line.
|
||||
Empty lines and lines starting with "`#`" are ignored as comments.
|
||||
|
||||
b\. A passphrase encrypted age file, containing
|
||||
[IDENTITIES][RECIPIENTS AND IDENTITIES] one per line like above.
|
||||
The passphrase is requested interactively. Note that passphrase-protected
|
||||
identity files are not necessary for most use cases, where access to the
|
||||
encrypted identity file implies access to the whole system.
|
||||
|
||||
c\. An SSH private key file, in PKCS#1, PKCS#8, or OpenSSH format.
|
||||
If the private key is password-protected, the password is requested
|
||||
interactively only if the SSH identity matches the file. See the
|
||||
[SSH keys][] section for more information, including supported key types.
|
||||
|
||||
d\. "`-`", causing one of the options above to be read from standard input.
|
||||
In this case, the <INPUT> argument must be specified.
|
||||
|
||||
This option can be repeated. Identities are tried in the order in which are
|
||||
provided, and the first one matching one of the file's recipients is used.
|
||||
Unused identities are ignored, but it is an error if the <INPUT> file is
|
||||
passphrase-encrypted and `-i`/`--identity` is specified.
|
||||
|
||||
* `-j` <PLUGIN>:
|
||||
Decrypt using the data-less [plugin][Plugins] <PLUGIN>.
|
||||
|
||||
This is equivalent to using `-i`/`--identity` with a file that contains a
|
||||
single plugin `IDENTITY` that encodes no plugin-specific data.
|
||||
|
||||
## RECIPIENTS AND IDENTITIES
|
||||
|
||||
`RECIPIENTS` are public values, like a public key, that a file can be encrypted
|
||||
to. `IDENTITIES` are private values, like a private key, that allow decrypting
|
||||
a file encrypted to the corresponding `RECIPIENT`.
|
||||
|
||||
### Native X25519 keys
|
||||
|
||||
Native `age` key pairs are generated with age-keygen(1), and provide small
|
||||
encodings and strong encryption based on X25519. They are the recommended
|
||||
recipient type for most applications.
|
||||
|
||||
A `RECIPIENT` encoding begins with `age1` and looks like the following:
|
||||
|
||||
age1gde3ncmahlqd9gg50tanl99r960llztrhfapnmx853s4tjum03uqfssgdh
|
||||
|
||||
An `IDENTITY` encoding begins with `AGE-SECRET-KEY-1` and looks like the
|
||||
following:
|
||||
|
||||
AGE-SECRET-KEY-1KTYK6RVLN5TAPE7VF6FQQSKZ9HWWCDSKUGXXNUQDWZ7XXT5YK5LSF3UTKQ
|
||||
|
||||
An encrypted file can't be linked to the native recipient it's encrypted to
|
||||
without access to the corresponding identity.
|
||||
|
||||
### SSH keys
|
||||
|
||||
As a convenience feature, `age` also supports encrypting to RSA or Ed25519
|
||||
ssh(1) keys. RSA keys must be at least 2048 bits. This feature employs more
|
||||
complex cryptography, and should only be used when a native key is not available
|
||||
for the recipient. Note that SSH keys might not be protected long-term by the
|
||||
recipient, since they are revokable when used only for authentication.
|
||||
|
||||
A `RECIPIENT` encoding is an SSH public key in `authorized_keys` format
|
||||
(see the `AUTHORIZED_KEYS FILE FORMAT` section of sshd(8)), starting with
|
||||
`ssh-rsa` or `ssh-ed25519`, like the following:
|
||||
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDULTit0KUehbi[...]GU4BtElAbzh8=
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH9pO5pz22JZEas[...]l1uZc31FGYMXa
|
||||
|
||||
The comment at the end of the line, if present, is ignored.
|
||||
|
||||
In recipient files passed to `-R`/`--recipients-file`, unsupported but valid
|
||||
SSH public keys are ignored with a warning, to facilitate using
|
||||
`authorized_keys` or GitHub `.keys` files. (See [EXAMPLES][].)
|
||||
|
||||
An `IDENTITY` is an SSH private key _file_ passed individually to
|
||||
`-i`/`--identity`. Note that keys held on hardware tokens such as YubiKeys
|
||||
or accessed via ssh-agent(1) are not supported.
|
||||
|
||||
An encrypted file _can_ be linked to the SSH public key it was encrypted to.
|
||||
This is so that `age` can identify the correct SSH private key before
|
||||
requesting its password, if any.
|
||||
|
||||
### Plugins
|
||||
|
||||
`age` can be extended through plugins. A plugin is only loaded if a corresponding
|
||||
`RECIPIENT` or `IDENTITY` is specified. (Simply decrypting a file encrypted with
|
||||
a plugin will not cause it to load, for security reasons among others.)
|
||||
|
||||
A `RECIPIENT` for a plugin named `example` starts with `age1example1`, while an
|
||||
`IDENTITY` starts with `AGE-PLUGIN-EXAMPLE-1`. They both encode arbitrary
|
||||
plugin-specific data, and are generated by the plugin.
|
||||
|
||||
When either is specified, `age` searches for `age-plugin-example` in the PATH
|
||||
and executes it to perform the file header encryption or decryption. The plugin
|
||||
may request input from the user through `age` to complete the operation.
|
||||
|
||||
Plugins can be freely mixed with other plugins or natively supported keys.
|
||||
|
||||
A plugin is not bound to only encrypt or decrypt files meant for or generated by
|
||||
the plugin. For example, a plugin can be used to decrypt files encrypted to a
|
||||
native X25519 `RECIPIENT` or even with a passphrase. Similarly, a plugin can
|
||||
encrypt a file such that it can be decrypted without the use of any plugin.
|
||||
|
||||
Plugins for which the `IDENTITY`/`RECIPIENT` distinction doesn't make sense
|
||||
(such as a symmetric encryption plugin) may generate only an `IDENTITY` and
|
||||
instruct the user to perform encryption with the `-e`/`--encrypt` and
|
||||
`-i`/`--identity` flags. Plugins for which the concept of separate identities
|
||||
doesn't make sense (such as a password-encryption plugin) may instruct the user
|
||||
to use the `-j` flag.
|
||||
|
||||
## EXIT STATUS
|
||||
|
||||
`age` will exit 0 if and only if encryption or decryption are successful for the
|
||||
full length of the input.
|
||||
|
||||
If an error occurs during decryption, partial output might still be generated,
|
||||
but only if it was possible to securely authenticate it. No unauthenticated
|
||||
output is ever released.
|
||||
|
||||
## BACKWARDS COMPATIBILITY
|
||||
|
||||
Files encrypted with a stable version (not alpha, beta, or release candidate) of
|
||||
`age`, or with any v1.0.0 beta or release candidate, will decrypt with any later
|
||||
version of the tool.
|
||||
|
||||
If decrypting older files poses a security risk, doing so might cause an error
|
||||
by default. In this case, a flag will be provided to force the operation.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
Generate a new identity, encrypt data, and decrypt:
|
||||
|
||||
$ age-keygen -o key.txt
|
||||
Public key: age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
|
||||
|
||||
$ tar cvz ~/data | age -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p > data.tar.gz.age
|
||||
|
||||
$ age -d -o data.tar.gz -i key.txt data.tar.gz.age
|
||||
|
||||
Encrypt `example.jpg` to multiple recipients and output to `example.jpg.age`:
|
||||
|
||||
$ age -o example.jpg.age -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p \
|
||||
-r age1lggyhqrw2nlhcxprm67z43rta597azn8gknawjehu9d9dl0jq3yqqvfafg example.jpg
|
||||
|
||||
Encrypt to a list of recipients:
|
||||
|
||||
$ cat > recipients.txt
|
||||
# Alice
|
||||
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
|
||||
# Bob
|
||||
age1lggyhqrw2nlhcxprm67z43rta597azn8gknawjehu9d9dl0jq3yqqvfafg
|
||||
|
||||
$ age -R recipients.txt example.jpg > example.jpg.age
|
||||
|
||||
Encrypt and decrypt a file using a passphrase:
|
||||
|
||||
$ age -p secrets.txt > secrets.txt.age
|
||||
Enter passphrase (leave empty to autogenerate a secure one):
|
||||
Using the autogenerated passphrase "release-response-step-brand-wrap-ankle-pair-unusual-sword-train".
|
||||
|
||||
$ age -d secrets.txt.age > secrets.txt
|
||||
Enter passphrase:
|
||||
|
||||
Encrypt and decrypt with a passphrase-protected identity file:
|
||||
|
||||
$ age-keygen | age -p > key.age
|
||||
Public key: age1yhm4gctwfmrpz87tdslm550wrx6m79y9f2hdzt0lndjnehwj0ukqrjpyx5
|
||||
Enter passphrase (leave empty to autogenerate a secure one):
|
||||
Using the autogenerated passphrase "hip-roast-boring-snake-mention-east-wasp-honey-input-actress".
|
||||
|
||||
$ age -r age1yhm4gctwfmrpz87tdslm550wrx6m79y9f2hdzt0lndjnehwj0ukqrjpyx5 secrets.txt > secrets.txt.age
|
||||
|
||||
$ age -d -i key.age secrets.txt.age > secrets.txt
|
||||
Enter passphrase for identity file "key.age":
|
||||
|
||||
Encrypt and decrypt with an SSH public key:
|
||||
|
||||
$ age -R ~/.ssh/id_ed25519.pub example.jpg > example.jpg.age
|
||||
|
||||
$ age -d -i ~/.ssh/id_ed25519 example.jpg.age > example.jpg
|
||||
|
||||
Encrypt and decrypt with age-plugin-yubikey:
|
||||
|
||||
$ age-plugin-yubikey # run interactive setup, generate identity file and obtain recipient
|
||||
|
||||
$ age -r age1yubikey1qwt50d05nh5vutpdzmlg5wn80xq5negm4uj9ghv0snvdd3yysf5yw3rhl3t secrets.txt > secrets.txt.age
|
||||
|
||||
$ age -d -i age-yubikey-identity-388178f3.txt secrets.txt.age
|
||||
|
||||
Encrypt to the SSH keys of a GitHub user:
|
||||
|
||||
$ curl https://github.com/benjojo.keys | age -R - example.jpg > example.jpg.age
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
age-keygen(1)
|
||||
|
||||
## AUTHORS
|
||||
|
||||
Filippo Valsorda <age@filippo.io>
|
||||
16
go.mod
16
go.mod
@@ -1,5 +1,17 @@
|
||||
module filippo.io/age
|
||||
|
||||
go 1.13
|
||||
go 1.19
|
||||
|
||||
require golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0
|
||||
golang.org/x/crypto v0.24.0
|
||||
golang.org/x/sys v0.21.0
|
||||
golang.org/x/term v0.21.0
|
||||
)
|
||||
|
||||
// Test dependencies.
|
||||
require (
|
||||
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805
|
||||
github.com/rogpeppe/go-internal v1.12.0
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
)
|
||||
|
||||
24
go.sum
24
go.sum
@@ -1,10 +1,14 @@
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY=
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805 h1:u2qwJeEvnypw+OCPUHmoZE3IqwfuN5kgDfo5MLzpNM0=
|
||||
c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805/go.mod h1:FomMrUJ2Lxt5jCLmZkG3FHa72zUprnhd3v/Z18Snm4w=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
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/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.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright (c) 2017 Takatoshi Nakagawa
|
||||
// Copyright (c) 2019 Google LLC
|
||||
// Copyright (c) 2019 The age Authors
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -111,9 +111,6 @@ func Encode(hrp string, data []byte) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(hrp)+len(values)+7 > 90 {
|
||||
return "", fmt.Errorf("too long: hrp length=%d, data length=%d", len(hrp), len(values))
|
||||
}
|
||||
if len(hrp) < 1 {
|
||||
return "", fmt.Errorf("invalid HRP: %q", hrp)
|
||||
}
|
||||
@@ -144,9 +141,6 @@ func Encode(hrp string, data []byte) (string, error) {
|
||||
|
||||
// Decode decodes a Bech32 string. If the string is uppercase, the HRP will be uppercase.
|
||||
func Decode(s string) (hrp string, data []byte, err error) {
|
||||
if len(s) > 90 {
|
||||
return "", nil, fmt.Errorf("too long: len=%d", len(s))
|
||||
}
|
||||
if strings.ToLower(s) != s && strings.ToUpper(s) != s {
|
||||
return "", nil, fmt.Errorf("mixed case")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Copyright (c) 2016-2017 The Lightning Network Developers
|
||||
// Copyright (c) 2019 Google LLC
|
||||
// Copyright (c) 2019 The age Authors
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
@@ -28,7 +28,7 @@ func TestBech32(t *testing.T) {
|
||||
str string
|
||||
valid bool
|
||||
}{
|
||||
{"A12UEL5L", true},
|
||||
{"A12UEL5L", true}, // empty
|
||||
{"a12uel5l", true},
|
||||
{"an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", true},
|
||||
{"abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", true},
|
||||
@@ -44,11 +44,12 @@ func TestBech32(t *testing.T) {
|
||||
{"1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", false}, // empty hrp
|
||||
// invalid character (DEL) in hrp
|
||||
{"spl" + string(rune(127)) + "t1checkupstagehandshakeupstreamerranterredcaperred2y9e3w", false},
|
||||
// too long
|
||||
{"11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", false},
|
||||
|
||||
// long vectors that we do accept despite the spec, see Issue 453
|
||||
{"long10pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7rc0pu8s7qfcsvr0", true},
|
||||
{"an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx", true},
|
||||
|
||||
// BIP 173 invalid vectors.
|
||||
{"an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx", false},
|
||||
{"pzry9x0s0muk", false},
|
||||
{"1pzry9x0s0muk", false},
|
||||
{"x1b4n0q5v", false},
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Copyright 2019 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package format implements the age file format.
|
||||
package format
|
||||
@@ -43,25 +41,40 @@ func DecodeString(s string) ([]byte, error) {
|
||||
var EncodeToString = b64.EncodeToString
|
||||
|
||||
const ColumnsPerLine = 64
|
||||
|
||||
const BytesPerLine = ColumnsPerLine / 4 * 3
|
||||
|
||||
// NewlineWriter returns a Writer that writes to dst, inserting an LF character
|
||||
// every ColumnsPerLine bytes. It does not insert a newline neither at the
|
||||
// beginning nor at the end of the stream, but it ensures the last line is
|
||||
// shorter than ColumnsPerLine, which means it might be empty.
|
||||
func NewlineWriter(dst io.Writer) io.Writer {
|
||||
return &newlineWriter{dst: dst}
|
||||
// NewWrappedBase64Encoder returns a WrappedBase64Encoder that writes to dst.
|
||||
func NewWrappedBase64Encoder(enc *base64.Encoding, dst io.Writer) *WrappedBase64Encoder {
|
||||
w := &WrappedBase64Encoder{dst: dst}
|
||||
w.enc = base64.NewEncoder(enc, WriterFunc(w.writeWrapped))
|
||||
return w
|
||||
}
|
||||
|
||||
type newlineWriter struct {
|
||||
type WriterFunc func(p []byte) (int, error)
|
||||
|
||||
func (f WriterFunc) Write(p []byte) (int, error) { return f(p) }
|
||||
|
||||
// WrappedBase64Encoder is a standard base64 encoder that inserts an LF
|
||||
// character every ColumnsPerLine bytes. It does not insert a newline neither at
|
||||
// the beginning nor at the end of the stream, but it ensures the last line is
|
||||
// shorter than ColumnsPerLine, which means it might be empty.
|
||||
type WrappedBase64Encoder struct {
|
||||
enc io.WriteCloser
|
||||
dst io.Writer
|
||||
written int
|
||||
buf bytes.Buffer
|
||||
}
|
||||
|
||||
func (w *newlineWriter) Write(p []byte) (int, error) {
|
||||
func (w *WrappedBase64Encoder) Write(p []byte) (int, error) { return w.enc.Write(p) }
|
||||
|
||||
func (w *WrappedBase64Encoder) Close() error {
|
||||
return w.enc.Close()
|
||||
}
|
||||
|
||||
func (w *WrappedBase64Encoder) writeWrapped(p []byte) (int, error) {
|
||||
if w.buf.Len() != 0 {
|
||||
panic("age: internal error: non-empty newlineWriter.buf")
|
||||
panic("age: internal error: non-empty WrappedBase64Encoder.buf")
|
||||
}
|
||||
for len(p) > 0 {
|
||||
toWrite := ColumnsPerLine - (w.written % ColumnsPerLine)
|
||||
@@ -84,13 +97,21 @@ func (w *newlineWriter) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// LastLineIsEmpty returns whether the last output line was empty, either
|
||||
// because no input was written, or because a multiple of BytesPerLine was.
|
||||
//
|
||||
// Calling LastLineIsEmpty before Close is meaningless.
|
||||
func (w *WrappedBase64Encoder) LastLineIsEmpty() bool {
|
||||
return w.written%ColumnsPerLine == 0
|
||||
}
|
||||
|
||||
const intro = "age-encryption.org/v1\n"
|
||||
|
||||
var recipientPrefix = []byte("->")
|
||||
var stanzaPrefix = []byte("->")
|
||||
var footerPrefix = []byte("---")
|
||||
|
||||
func (r *Stanza) Marshal(w io.Writer) error {
|
||||
if _, err := w.Write(recipientPrefix); err != nil {
|
||||
if _, err := w.Write(stanzaPrefix); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, a := range append([]string{r.Type}, r.Args...) {
|
||||
@@ -101,7 +122,7 @@ func (r *Stanza) Marshal(w io.Writer) error {
|
||||
if _, err := io.WriteString(w, "\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
ww := base64.NewEncoder(b64, NewlineWriter(w))
|
||||
ww := NewWrappedBase64Encoder(b64, w)
|
||||
if _, err := ww.Write(r.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -134,14 +155,81 @@ func (h *Header) Marshal(w io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
type ParseError string
|
||||
type StanzaReader struct {
|
||||
r *bufio.Reader
|
||||
err error
|
||||
}
|
||||
|
||||
func (e ParseError) Error() string {
|
||||
return "parsing age header: " + string(e)
|
||||
func NewStanzaReader(r *bufio.Reader) *StanzaReader {
|
||||
return &StanzaReader{r: r}
|
||||
}
|
||||
|
||||
func (r *StanzaReader) ReadStanza() (s *Stanza, err error) {
|
||||
// Read errors are unrecoverable.
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
defer func() { r.err = err }()
|
||||
|
||||
s = &Stanza{}
|
||||
|
||||
line, err := r.r.ReadBytes('\n')
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read line: %w", err)
|
||||
}
|
||||
if !bytes.HasPrefix(line, stanzaPrefix) {
|
||||
return nil, fmt.Errorf("malformed stanza opening line: %q", line)
|
||||
}
|
||||
prefix, args := splitArgs(line)
|
||||
if prefix != string(stanzaPrefix) || len(args) < 1 {
|
||||
return nil, fmt.Errorf("malformed stanza: %q", line)
|
||||
}
|
||||
for _, a := range args {
|
||||
if !isValidString(a) {
|
||||
return nil, fmt.Errorf("malformed stanza: %q", line)
|
||||
}
|
||||
}
|
||||
s.Type = args[0]
|
||||
s.Args = args[1:]
|
||||
|
||||
for {
|
||||
line, err := r.r.ReadBytes('\n')
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read line: %w", err)
|
||||
}
|
||||
|
||||
b, err := DecodeString(strings.TrimSuffix(string(line), "\n"))
|
||||
if err != nil {
|
||||
if bytes.HasPrefix(line, footerPrefix) || bytes.HasPrefix(line, stanzaPrefix) {
|
||||
return nil, fmt.Errorf("malformed body line %q: stanza ended without a short line\nNote: this might be a file encrypted with an old beta version of age or rage. Use age v1.0.0-beta6 or rage to decrypt it.", line)
|
||||
}
|
||||
return nil, errorf("malformed body line %q: %v", line, err)
|
||||
}
|
||||
if len(b) > BytesPerLine {
|
||||
return nil, errorf("malformed body line %q: too long", line)
|
||||
}
|
||||
s.Body = append(s.Body, b...)
|
||||
if len(b) < BytesPerLine {
|
||||
// A stanza body always ends with a short line.
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type ParseError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *ParseError) Error() string {
|
||||
return "parsing age header: " + e.err.Error()
|
||||
}
|
||||
|
||||
func (e *ParseError) Unwrap() error {
|
||||
return e.err
|
||||
}
|
||||
|
||||
func errorf(format string, a ...interface{}) error {
|
||||
return ParseError(fmt.Sprintf(format, a...))
|
||||
return &ParseError{fmt.Errorf(format, a...)}
|
||||
}
|
||||
|
||||
// Parse returns the header and a Reader that begins at the start of the
|
||||
@@ -152,68 +240,41 @@ func Parse(input io.Reader) (*Header, io.Reader, error) {
|
||||
|
||||
line, err := rr.ReadString('\n')
|
||||
if err != nil {
|
||||
return nil, nil, errorf("failed to read intro: %v", err)
|
||||
return nil, nil, errorf("failed to read intro: %w", err)
|
||||
}
|
||||
if line != intro {
|
||||
return nil, nil, errorf("unexpected intro: %q", line)
|
||||
}
|
||||
|
||||
var r *Stanza
|
||||
sr := NewStanzaReader(rr)
|
||||
for {
|
||||
line, err := rr.ReadBytes('\n')
|
||||
peek, err := rr.Peek(len(footerPrefix))
|
||||
if err != nil {
|
||||
return nil, nil, errorf("failed to read header: %v", err)
|
||||
return nil, nil, errorf("failed to read header: %w", err)
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(line, footerPrefix) {
|
||||
if r != nil {
|
||||
return nil, nil, errorf("malformed body line %q: reached footer without previous stanza being closed\nNote: this might be a file encrypted with an old beta version of rage. Use rage to decrypt it.", line)
|
||||
if bytes.Equal(peek, footerPrefix) {
|
||||
line, err := rr.ReadBytes('\n')
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to read header: %w", err)
|
||||
}
|
||||
|
||||
prefix, args := splitArgs(line)
|
||||
if prefix != string(footerPrefix) || len(args) != 1 {
|
||||
return nil, nil, errorf("malformed closing line: %q", line)
|
||||
}
|
||||
h.MAC, err = DecodeString(args[0])
|
||||
if err != nil {
|
||||
if err != nil || len(h.MAC) != 32 {
|
||||
return nil, nil, errorf("malformed closing line %q: %v", line, err)
|
||||
}
|
||||
break
|
||||
|
||||
} else if bytes.HasPrefix(line, recipientPrefix) {
|
||||
if r != nil {
|
||||
return nil, nil, errorf("malformed body line %q: new stanza started without previous stanza being closed\nNote: this might be a file encrypted with an old beta version of rage. Use rage to decrypt it.", line)
|
||||
}
|
||||
r = &Stanza{}
|
||||
prefix, args := splitArgs(line)
|
||||
if prefix != string(recipientPrefix) || len(args) < 1 {
|
||||
return nil, nil, errorf("malformed recipient: %q", line)
|
||||
}
|
||||
for _, a := range args {
|
||||
if !isValidString(a) {
|
||||
return nil, nil, errorf("malformed recipient: %q", line)
|
||||
}
|
||||
}
|
||||
r.Type = args[0]
|
||||
r.Args = args[1:]
|
||||
h.Recipients = append(h.Recipients, r)
|
||||
|
||||
} else if r != nil {
|
||||
b, err := DecodeString(strings.TrimSuffix(string(line), "\n"))
|
||||
if err != nil {
|
||||
return nil, nil, errorf("malformed body line %q: %v", line, err)
|
||||
}
|
||||
if len(b) > BytesPerLine {
|
||||
return nil, nil, errorf("malformed body line %q: too long", line)
|
||||
}
|
||||
r.Body = append(r.Body, b...)
|
||||
if len(b) < BytesPerLine {
|
||||
// Only the last line of a body can be short.
|
||||
r = nil
|
||||
}
|
||||
|
||||
} else {
|
||||
return nil, nil, errorf("unexpected line: %q", line)
|
||||
}
|
||||
|
||||
s, err := sr.ReadStanza()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to parse header: %w", err)
|
||||
}
|
||||
h.Recipients = append(h.Recipients, s)
|
||||
}
|
||||
|
||||
// If input is a bufio.Reader, rr might be equal to input because
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Copyright 2021 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package format_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"filippo.io/age/internal/format"
|
||||
@@ -39,3 +43,43 @@ func TestStanzaMarshal(t *testing.T) {
|
||||
t.Errorf("wrong 64 columns stanza encoding: expected %q, got %q", exp, buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzMalleability(f *testing.F) {
|
||||
tests, err := filepath.Glob("../../testdata/testkit/*")
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
for _, test := range tests {
|
||||
contents, err := os.ReadFile(test)
|
||||
if err != nil {
|
||||
f.Fatal(err)
|
||||
}
|
||||
_, contents, ok := bytes.Cut(contents, []byte("\n\n"))
|
||||
if !ok {
|
||||
f.Fatal("testkit file without header")
|
||||
}
|
||||
f.Add(contents)
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, data []byte) {
|
||||
h, payload, err := format.Parse(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
if h != nil {
|
||||
t.Error("h != nil on error")
|
||||
}
|
||||
if payload != nil {
|
||||
t.Error("payload != nil on error")
|
||||
}
|
||||
t.Skip()
|
||||
}
|
||||
w := &bytes.Buffer{}
|
||||
if err := h.Marshal(w); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := io.Copy(w, payload); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(w.Bytes(), data) {
|
||||
t.Error("Marshal output different from input")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Copyright 2019 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package stream implements a variant of the STREAM chunked encryption scheme.
|
||||
package stream
|
||||
@@ -10,6 +8,7 @@ package stream
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
@@ -68,7 +67,17 @@ func (r *Reader) Read(p []byte) (int, error) {
|
||||
r.unread = r.unread[n:]
|
||||
|
||||
if last {
|
||||
r.err = io.EOF
|
||||
// Ensure there is an EOF after the last chunk as expected. In other
|
||||
// words, check for trailing data after a full-length final chunk.
|
||||
// Hopefully, the underlying reader supports returning EOF even if it
|
||||
// had previously returned an EOF to ReadFull.
|
||||
if _, err := r.src.Read(make([]byte, 1)); err == nil {
|
||||
r.err = errors.New("trailing data after end of encrypted file")
|
||||
} else if err != io.EOF {
|
||||
r.err = fmt.Errorf("non-EOF error reading after end of encrypted file: %w", err)
|
||||
} else {
|
||||
r.err = io.EOF
|
||||
}
|
||||
}
|
||||
|
||||
return n, nil
|
||||
@@ -89,7 +98,11 @@ func (r *Reader) readChunk() (last bool, err error) {
|
||||
// A message can't end without a marked chunk. This message is truncated.
|
||||
return false, io.ErrUnexpectedEOF
|
||||
case err == io.ErrUnexpectedEOF:
|
||||
// The last chunk can be short.
|
||||
// The last chunk can be short, but not empty unless it's the first and
|
||||
// only chunk.
|
||||
if !nonceIsZero(&r.nonce) && n == r.a.Overhead() {
|
||||
return false, errors.New("last chunk is empty, try age v1.0.0, and please consider reporting this")
|
||||
}
|
||||
in = in[:n]
|
||||
last = true
|
||||
setLastChunkFlag(&r.nonce)
|
||||
@@ -130,6 +143,10 @@ func setLastChunkFlag(nonce *[chacha20poly1305.NonceSize]byte) {
|
||||
nonce[len(nonce)-1] = lastChunkFlag
|
||||
}
|
||||
|
||||
func nonceIsZero(nonce *[chacha20poly1305.NonceSize]byte) bool {
|
||||
return *nonce == [chacha20poly1305.NonceSize]byte{}
|
||||
}
|
||||
|
||||
type Writer struct {
|
||||
a cipher.AEAD
|
||||
dst io.Writer
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Copyright 2019 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package stream_test
|
||||
|
||||
@@ -81,10 +79,6 @@ func testRoundTrip(t *testing.T, stepSize, length int) {
|
||||
n = 0
|
||||
readBuf := make([]byte, stepSize)
|
||||
for n < length {
|
||||
b := length - n
|
||||
if b > stepSize {
|
||||
b = stepSize
|
||||
}
|
||||
nn, err := r.Read(readBuf)
|
||||
if err != nil {
|
||||
t.Fatalf("Read error at index %d: %v", n, err)
|
||||
|
||||
12
logo/README.md
Normal file
12
logo/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
The logos available in this folder are Copyright 2021 Filippo Valsorda.
|
||||
|
||||
Permission is granted to use the logos as long as they are unaltered, are not
|
||||
combined with other text or graphic, and are not used to imply your project is
|
||||
endorsed by or affiliated with the age project.
|
||||
|
||||
This permission can be revoked or rescinded for any reason and at any time,
|
||||
selectively or otherwise.
|
||||
|
||||
If you require different terms, please email age-logo@filippo.io.
|
||||
|
||||
The logos were designed by [Studiovagante](https://www.studiovagante.it).
|
||||
BIN
logo/logo.png
Normal file
BIN
logo/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
579
logo/logo.svg
Normal file
579
logo/logo.svg
Normal file
@@ -0,0 +1,579 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Livello_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 298.9 141.89" style="enable-background:new 0 0 298.9 141.89;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M160.3,88.5c-0.86,0-1.29-0.43-1.29-1.29v-5.94c-3.01,4.22-7.83,8.09-14.81,8.09c-11.71,0-19.8-10.07-19.8-22.38
|
||||
c0-12.31,8.09-22.38,19.8-22.38c6.89,0,11.62,3.61,14.81,8.18v-6.03c0-0.86,0.43-1.29,1.29-1.29h7.14c0.86,0,1.29,0.43,1.29,1.29
|
||||
v40.46c0,0.86-0.43,1.29-1.29,1.29H160.3z M146.7,80.07c7.14,0,12.48-5.85,12.48-13.08c0-7.23-5.34-13.08-12.48-13.08
|
||||
c-7.23,0-12.57,5.85-12.57,13.08C134.13,74.21,139.47,80.07,146.7,80.07z"/>
|
||||
<path d="M223.74,86.95c0,13.51-9.64,23.5-23.33,23.5c-7.14,0-13.6-2.93-17.39-6.8c-0.52-0.52-0.52-1.12-0.09-1.72l3.79-4.99
|
||||
c0.26-0.34,0.6-0.52,0.86-0.52c0.26,0,0.52,0.17,0.77,0.34c2.84,2.67,6.97,4.39,11.79,4.39c9.12,0,13.86-6.2,13.86-13.34v-6.97
|
||||
c-3.01,4.22-7.75,8.09-14.89,8.09c-11.62,0-19.71-9.73-19.71-22.03s8.09-22.29,19.71-22.29c6.89,0,11.71,3.61,14.89,8.18v-6.03
|
||||
c0-0.86,0.43-1.29,1.29-1.29h7.14c0.86,0,1.29,0.43,1.29,1.29V86.95z M201.7,79.64c7.23,0,12.57-5.42,12.57-12.74
|
||||
c0-7.32-5.34-13-12.57-13c-7.23,0-12.57,5.85-12.57,13C189.13,74.21,194.47,79.64,201.7,79.64z"/>
|
||||
<path d="M267.64,78.35c0.26-0.17,0.6-0.26,0.86-0.26c0.43,0,0.77,0.26,1.03,0.6l3.18,4.56c0.43,0.77,0.34,1.29-0.34,1.72
|
||||
c-4.22,2.67-9.38,4.39-15.92,4.39c-12.22,0-22.04-10.07-22.04-22.38c0-11.71,8.18-22.38,20.92-22.38
|
||||
c13.43,0,21.17,11.02,21.17,24.19c0,0.95-0.43,1.55-1.46,1.55h-31.42c1.12,5.85,6.02,10.76,13.08,10.76
|
||||
C261.18,81.1,264.28,80.15,267.64,78.35z M267.21,63.2c-1.38-6.54-5.25-10.93-11.88-10.93c-6.2,0-10.5,4.3-11.62,10.93H267.21z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M142.92,126.01c-0.12,0-0.18-0.06-0.18-0.16v-7.36c0-0.11,0.06-0.16,0.18-0.16h5.26c0.12,0,0.18,0.06,0.18,0.16v0.9
|
||||
c0,0.11-0.06,0.16-0.18,0.16h-4.05v2.18h3.51c0.12,0,0.18,0.05,0.18,0.16v0.87c0,0.09-0.05,0.16-0.18,0.16h-3.51v2.92
|
||||
c0,0.11-0.06,0.16-0.18,0.16H142.92z"/>
|
||||
<path d="M152.11,126.01c-0.12,0-0.18-0.06-0.18-0.16v-7.36c0-0.11,0.06-0.16,0.18-0.16h1.02c0.12,0,0.18,0.06,0.18,0.16v7.36
|
||||
c0,0.11-0.06,0.16-0.18,0.16H152.11z"/>
|
||||
<path d="M157.89,126.01c-0.12,0-0.18-0.06-0.18-0.16v-7.36c0-0.11,0.06-0.16,0.18-0.16h1.02c0.12,0,0.18,0.06,0.18,0.16v6.31
|
||||
h3.26c0.12,0,0.18,0.06,0.18,0.16v0.89c0,0.11-0.06,0.16-0.18,0.16H157.89z"/>
|
||||
<path d="M171.38,118.32c0.13,0,0.18,0.08,0.18,0.16v0.9c0,0.11-0.06,0.16-0.18,0.16h-3.64v2.02h3.21c0.12,0,0.18,0.06,0.18,0.16
|
||||
v0.87c0,0.11-0.06,0.16-0.18,0.16h-3.21v2.01h3.65c0.12,0,0.18,0.06,0.18,0.16v0.9c0,0.11-0.06,0.16-0.18,0.16h-4.86
|
||||
c-0.12,0-0.18-0.06-0.18-0.16v-7.36c0-0.11,0.06-0.16,0.18-0.16H171.38z"/>
|
||||
<path d="M187.4,118.32c0.13,0,0.18,0.08,0.18,0.16v0.9c0,0.11-0.06,0.16-0.18,0.16h-3.64v2.02h3.21c0.12,0,0.18,0.06,0.18,0.16
|
||||
v0.87c0,0.11-0.06,0.16-0.18,0.16h-3.21v2.01h3.65c0.12,0,0.18,0.06,0.18,0.16v0.9c0,0.11-0.06,0.16-0.18,0.16h-4.86
|
||||
c-0.12,0-0.18-0.06-0.18-0.16v-7.36c0-0.11,0.06-0.16,0.18-0.16H187.4z"/>
|
||||
<path d="M197.47,126.01c-0.16,0-0.22-0.04-0.31-0.16l-4.17-5.33v5.33c0,0.11-0.06,0.16-0.18,0.16h-1.03
|
||||
c-0.12,0-0.18-0.06-0.18-0.16v-7.36c0-0.11,0.06-0.16,0.18-0.16h1.04c0.16,0,0.23,0.07,0.32,0.16l4.16,5.33v-5.33
|
||||
c0-0.11,0.06-0.16,0.18-0.16h1.04c0.12,0,0.18,0.06,0.18,0.16v7.36c0,0.11-0.06,0.16-0.18,0.16H197.47z"/>
|
||||
<path d="M208.78,119.78c-0.04,0.05-0.1,0.09-0.18,0.09c-0.04,0-0.07-0.01-0.12-0.03c-0.48-0.26-0.94-0.39-1.56-0.39
|
||||
c-1.71,0-2.98,1.22-2.98,2.74c0,1.52,1.27,2.73,2.98,2.73c0.53,0,1.12-0.12,1.6-0.42c0.05-0.03,0.09-0.04,0.12-0.04
|
||||
c0.06,0,0.11,0.03,0.16,0.09l0.54,0.75c0.07,0.1,0.05,0.19-0.06,0.25c-0.7,0.39-1.48,0.59-2.36,0.59c-2.53,0-4.37-1.71-4.37-3.95
|
||||
c0-2.22,1.84-3.97,4.37-3.97c0.92,0,1.71,0.24,2.33,0.59c0.11,0.06,0.13,0.13,0.07,0.23L208.78,119.78z"/>
|
||||
<path d="M219.34,125.83c0.09,0.12,0.06,0.18-0.1,0.18h-1.15c-0.13,0-0.24-0.06-0.33-0.16l-1.88-2.47h-1.07v2.47
|
||||
c0,0.11-0.06,0.16-0.18,0.16h-1.03c-0.12,0-0.18-0.06-0.18-0.16v-7.36c0-0.11,0.06-0.16,0.18-0.16h2.84c1.67,0,2.8,1.12,2.8,2.53
|
||||
c0,1.44-1.22,2.15-1.9,2.33L219.34,125.83z M214.81,119.47v2.75h1.32c1.11,0,1.71-0.61,1.71-1.37c0-0.76-0.6-1.37-1.71-1.37
|
||||
H214.81z"/>
|
||||
<path d="M225.65,121.16l1.92-2.75c0.05-0.07,0.12-0.1,0.21-0.1h1.2c0.1,0,0.12,0.09,0.07,0.16l-2.68,3.78v3.58
|
||||
c0,0.11-0.06,0.16-0.18,0.16h-1.04c-0.12,0-0.18-0.06-0.18-0.16v-3.58l-2.7-3.78c-0.05-0.08-0.02-0.16,0.07-0.16h1.21
|
||||
c0.1,0,0.17,0.03,0.21,0.1L225.65,121.16z"/>
|
||||
<path d="M232.88,126.01c-0.12,0-0.18-0.06-0.18-0.16v-7.36c0-0.11,0.06-0.16,0.18-0.16h2.81c1.7,0,2.78,1.12,2.78,2.53
|
||||
c0,1.36-1.16,2.56-2.78,2.56h-1.6v2.44c0,0.11-0.06,0.16-0.18,0.16H232.88z M234.09,122.26h1.29c1.02,0,1.68-0.55,1.68-1.42
|
||||
c0-0.88-0.66-1.37-1.68-1.37h-1.29V122.26z"/>
|
||||
<path d="M244.19,126.01c-0.12,0-0.18-0.06-0.18-0.16v-6.3h-2.36c-0.12,0-0.18-0.06-0.18-0.16v-0.9c0-0.11,0.06-0.16,0.18-0.16
|
||||
h6.1c0.12,0,0.18,0.06,0.18,0.16v0.9c0,0.11-0.06,0.16-0.18,0.16h-2.36v6.3c0,0.11-0.06,0.16-0.18,0.16H244.19z"/>
|
||||
<path d="M251.84,126.01c-0.12,0-0.18-0.06-0.18-0.16v-7.36c0-0.11,0.06-0.16,0.18-0.16h1.02c0.12,0,0.18,0.06,0.18,0.16v7.36
|
||||
c0,0.11-0.06,0.16-0.18,0.16H251.84z"/>
|
||||
<path d="M265.64,122.17c0,2.2-1.9,3.95-4.38,3.95c-2.48,0-4.37-1.71-4.37-3.95c0-2.22,1.89-3.97,4.37-3.97
|
||||
C263.74,118.21,265.64,119.95,265.64,122.17z M264.24,122.17c0-1.52-1.27-2.74-2.98-2.74c-1.71,0-2.98,1.22-2.98,2.74
|
||||
c0,1.52,1.27,2.73,2.98,2.73C262.95,124.9,264.24,123.69,264.24,122.17z"/>
|
||||
<path d="M275.35,126.01c-0.16,0-0.22-0.04-0.31-0.16l-4.17-5.33v5.33c0,0.11-0.06,0.16-0.18,0.16h-1.03
|
||||
c-0.12,0-0.18-0.06-0.18-0.16v-7.36c0-0.11,0.06-0.16,0.18-0.16h1.04c0.16,0,0.23,0.07,0.32,0.16l4.16,5.33v-5.33
|
||||
c0-0.11,0.06-0.16,0.18-0.16h1.04c0.12,0,0.18,0.06,0.18,0.16v7.36c0,0.11-0.06,0.16-0.18,0.16H275.35z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M109.23,72.27c-0.38-0.68-0.75-1.38-1.15-2.06c-0.34-0.57-0.67-1.34-1.04-1.9c-0.45-0.69-0.71-1.22-1.19-1.9
|
||||
c-0.52-0.74-0.9-1.28-1.47-1.98c-0.63-0.79-1.47-1.74-2.14-2.5c-0.52-0.59-1.3-1.42-1.83-2.01c-0.16-0.18-2.04-2.14-3.51-3.45
|
||||
c-2.03-1.76-4.38-3.5-5.64-4.39c-0.94-0.72-1.83-1.25-2.92-2.02c-0.08-0.04-0.19-0.09-0.21-0.16c-0.08-0.33-0.33-0.33-0.59-0.32
|
||||
c-0.63,0.03-1.26,0.07-1.89,0.06c-0.71-0.01-1.42,0.04-2.13,0.08c-0.29,0.01-0.54-0.03-0.74-0.23c-0.13,0.07-0.24,0.18-0.35,0.19
|
||||
c-0.52,0.03-1.05,0.03-1.58,0.04c-0.2,0.01-0.35-0.07-0.4-0.25c-0.06-0.18-0.17-0.29-0.34-0.38c1.27-0.08,2.52-0.14,3.77-0.18
|
||||
c0.18-0.01,3.41,0.01,3.57-0.2c0.12-0.16,0.07-0.28-0.13-0.36c-0.08-0.03-0.18-0.06-0.23-0.12c-0.14-0.15-0.27-0.12-0.43-0.03
|
||||
c-0.14,0.07-0.29,0.14-0.44,0.15c-0.62,0.03-1.24,0.04-1.85,0.06c-0.55,0.02-3.59,0.11-4.58,0.08c-0.01-0.13-0.07-0.27-0.03-0.35
|
||||
c0.13-0.22,0.03-0.41-0.03-0.6c-0.1-0.34-0.19-0.68-0.16-1.04c0.02-0.3-0.1-0.45-0.45-0.57c-0.04,0.21-0.05,0.41-0.12,0.61
|
||||
c-0.05,0.14-0.02,0.24,0.03,0.37c0.21,0.52,0.4,1.04,0.61,1.63c-0.91,0-1.76,0-2.66,0c0.06-0.71-0.05-1.42,0.01-2.13
|
||||
c-0.21-0.32-0.35-0.33-0.6-0.49c0.36-0.21,0.72-0.12,1.09,0.04c0.11-0.24,0.32-0.21,0.52-0.21c0.72-0.01,1.44,0,2.16-0.03
|
||||
c0.77-0.03,1.54-0.08,2.32,0.02c0.44,0.06,0.87,0.12,1.28,0.18c0.14,0.79,0.61,1.09,1.09,1.59c0.26,0.27,0.69,0.61,1.05,0.73
|
||||
c0.02-0.02,0.03-0.04,0.05-0.07c-0.53-0.72-1.06-1.45-1.59-2.17c0-0.04,0-0.08,0.01-0.12c0.43-0.04,0.4-0.32,0.15-0.83
|
||||
c-0.07-0.16-0.1-0.33-0.2-0.43c-0.2-0.21-0.13-2.15-0.14-2.45c0-0.26,0.03-0.53,0.04-0.79c0.02-0.9-0.01-1.8,0.06-2.69
|
||||
c0.03-0.36-0.04-0.7-0.06-1.05c0-0.07-0.09-0.16-0.16-0.19c-0.09-0.04-0.21-0.03-0.31-0.03c-0.59,0.03-1.17,0.06-1.76,0.09
|
||||
c-0.03,0-0.43,0-0.46,0c-0.56-0.07-0.84-0.08-1.4-0.1c-0.92-0.04-1.51,0.05-2.43,0.04c-0.79,0-1.71-0.01-2.5-0.03
|
||||
c-0.45-0.01-1.04-0.03-1.48-0.05c-0.28-0.01-0.6-0.01-0.97-0.03c0.49-0.39,1.06-0.67,1.56-0.6c0.42,0.06,0.82,0.1,1.25,0.04
|
||||
c0.99-0.14,1.99-0.15,2.99-0.09c0.83,0.05,1.65,0.15,2.47,0.31c0.36,0.07,1.06,0.07,1.34,0c0-0.03-0.62-0.03-0.88-0.09
|
||||
c-0.25-0.05-0.64-0.09-0.89-0.14c-0.25-0.05-0.51-0.08-0.83-0.14c-0.11-0.17,0.29-0.14,0.31-0.14c0.8,0.01,1.43,0.07,2.23,0.1
|
||||
c0.85,0.03,1.7,0.07,2.56,0.1c0.22,0.01,0.36-0.11,0.5-0.34c0.03-0.3-0.65-0.21-0.95-0.31c-0.01-0.1-0.03-0.19-0.04-0.29
|
||||
c-0.18-0.12-0.35-0.1-0.51-0.08c-0.27,0.04-0.42-0.1-0.57-0.27c-0.16-0.17-0.33-0.34-0.48-0.51c-0.11-0.12-0.24-0.15-0.41-0.07
|
||||
c-0.01,0.25-0.01,0.64,0.13,0.94c-0.46-0.01-1.31,0.01-1.54,0.01c-0.76,0.01-1.06-0.01-1.86,0c-0.64,0.06-1.4,0.21-1.86,0.01
|
||||
c-0.09-0.17-0.16-0.64-0.32-0.66c-0.33,0.12-0.33,0.12-0.38,0.5C76.47,35.92,76.5,36,76.25,36c-0.24,0.04-0.42,0.03-0.61-0.07
|
||||
c0.06-0.21,0.11-0.35,0.13-0.49c0.04-0.21-0.07-0.33-0.28-0.33c-0.21-0.01-0.28,0.14-0.37,0.3c-0.29,0.52-0.43,0.58-1.01,0.44
|
||||
c0.01-0.04,0-0.08,0.02-0.11c0.12-0.15,0.13-0.32-0.04-0.41c-0.12-0.06-0.35-0.09-0.44-0.02c-0.21,0.15-0.37,0.38-0.57,0.6
|
||||
c-0.23,0-0.5,0-0.78,0c0-0.06-0.01-0.1,0-0.14c0.04-0.14,0.04-0.29-0.11-0.34c-0.15-0.05-0.29-0.01-0.38,0.18
|
||||
c-0.09,0.22-0.3,0.33-0.54,0.33c-0.24,0-0.49,0-0.8,0c0.24-0.27,0.43-0.51,0.64-0.74c0.17-0.19,0.18-0.2-0.01-0.45
|
||||
c-0.17,0.02-0.33,0.05-0.51,0.08c-0.11,0.65-0.61,0.91-1.13,1.1c-0.36,0.13-0.76,0.17-1.15,0.25c-0.13,0.02-0.23,0.06-0.25,0.2
|
||||
c-0.01,0.13,0.06,0.19,0.17,0.22c0.2,0.06,0.28,0.19,0.3,0.41c0.04,0.42,0.49,0.58,0.63,0.94c0.16-0.01,0.15,0.1,0.16,0.21
|
||||
c0.03,0.33,0.08,0.65,0.11,0.98c0.06,0.56,0.18,1.13,0.14,1.69c-0.08,1.06-0.01,2.11,0.01,3.16c0,0.26,0.03,0.52,0.04,0.76
|
||||
c0.47,0.34,0.73,0.19,0.79-0.5c0-0.01,0-0.03,0-0.04c-0.07-1.09-0.07-2.18-0.21-3.26c-0.12-0.91-0.07-1.84,0.12-2.76
|
||||
c0.02-0.09,0.05-0.17,0.07-0.27c0.2-0.01,0.38-0.02,0.61-0.03c0,0.18,0,0.33,0,0.49c-0.01,0.87-0.02,1.74-0.02,2.61
|
||||
c0.01,0.94,0.03,1.87,0.05,2.81c0,0.17,0,0.34,0.03,0.51c0.01,0.08,0.07,0.15,0.16,0.32c0.06-0.16,0.1-0.24,0.13-0.33
|
||||
c0.09,0.1,0.12,0.17,0.16,0.18c0.12,0.04,0.25,0.1,0.36,0.07c0.06-0.02,0.11-0.19,0.12-0.29c0-0.25-0.01-0.5-0.04-0.75
|
||||
c-0.06-0.5-0.16-0.99-0.19-1.49c-0.04-0.93-0.04-1.87-0.05-2.8c0-0.18,0.04-0.36,0.06-0.54c0.05-0.03,0.09-0.06,0.12-0.07
|
||||
c0.14-0.02,0.27-0.04,0.27-0.22c0-0.15-0.1-0.18-0.23-0.21c-0.11-0.03-0.2-0.14-0.3-0.21c0.01-0.03,0.02-0.05,0.03-0.08
|
||||
c0.29,0,0.59,0,0.9,0c0.04,0.17,0.12,0.36,0.13,0.55c0.06,0.89,0.12,1.79,0.16,2.69c0.05,1.11,0.07,2.21,0.11,3.32
|
||||
c0.01,0.23,0.03,0.46,0.05,0.72c-1.57,0-3.1,0-4.62,0c-0.07,0.33-0.04,0.38,0.22,0.52c0.4,0.2,0.46,0.47,0.17,0.82
|
||||
c-0.17,0.2-0.34,0.4-0.52,0.59c-0.12,0.13-0.24,0.25-0.37,0.35c-0.2,0.15-0.42,0.27-0.63,0.41c-0.39,0.27-0.79,0.51-1.29,0.55
|
||||
c-0.27,0.02-0.54,0.09-0.79,0.14c-0.12,0.37-0.12,0.37,0.09,0.61c-0.09,0.08,9.04-0.12,13.65-0.05c-0.04,0.11,0.02,0.53-0.02,0.67
|
||||
c-0.4,0-0.8,0.01-1.19,0c-0.16-0.01-0.32-0.06-0.54-0.11c-0.17,0.22-0.48,0.17-0.79,0.17c-0.46,0.01-0.92,0.04-1.38,0.05
|
||||
c-0.14,0-0.28-0.02-0.43-0.04c0-0.11,0-0.2,0-0.29c-0.36-0.13-0.43,0.25-0.59,0.33c-0.33-0.04-7.18,0.04-7.44,0.03
|
||||
c-0.34-0.01-0.68-0.06-1.02-0.08c-0.35-0.02-0.69,0.14-1.04-0.04c-0.13-0.07-0.25,0.02-0.34,0.14c-0.08,0.12-0.05,0.23,0.05,0.32
|
||||
c0.04,0.04,0.12,0.05,0.23,0.1c-0.41,0.24-0.7,0.4-0.98,0.59c-1.21,0.82-2.53,1.47-3.66,2.4c-0.34,0.28-0.73,0.5-1.07,0.78
|
||||
c-0.32,0.27-2.14,1.65-2.7,2.09c-0.6,0.47-5.88,5.57-6.81,6.58c-0.39,0.43-0.75,0.88-1.16,1.28c-0.38,0.37-1.25,1.52-1.37,1.67
|
||||
c-0.88,1.06-4.49,5.8-5.9,8.45c-1.36,2.37-3.84,8-4.06,8.63c-0.26,0.76-0.52,1.51-0.67,2.3c-0.1,0.49-0.92,3.62-1.05,4.36
|
||||
c-0.12,0.69-0.44,3.2-0.47,3.45c-0.1,0.9-0.19,1.81-0.28,2.75c-0.2,0.03-0.41,0.07-0.63,0.1c-0.16,0.02-0.25,0.12-0.26,0.26
|
||||
c-0.01,0.08,0.07,0.18,0.12,0.26c0.04,0.06,0.12,0.1,0.16,0.16c0.16,0.21,0.38,0.26,0.63,0.25c0.78-0.03,1.55-0.09,2.33-0.1
|
||||
c0.75-0.01,1.5,0,2.25,0.04c0.71,0.03,11.13,0.14,15.64,0.14c0.72,0,3.91-0.06,4.78-0.06c0.3,0,2.91-0.1,3.91-0.16
|
||||
c1-0.06,11.01-0.24,11.59-0.23c0.05,0,4.25,0.13,5.42,0.14c0.21,0,2.03,0.06,2.72,0.02c1.26-0.08,15.17,0.11,16.68,0.09
|
||||
c0.63-0.01,6.52-0.07,7.29-0.1c0.55-0.02,1.11-0.02,1.66-0.04c0.09,0,0.19-0.04,0.3-0.07c-0.07-0.25-0.22-0.33-0.41-0.38
|
||||
c-0.14-0.04-0.27-0.09-0.41-0.14c-0.15-0.04-0.29-0.11-0.44-0.12c-0.25-0.02-3.64,0.06-4.56,0.11c-0.43,0.02-3.52-0.02-3.93-0.01
|
||||
c-0.08,0-0.16-0.01-0.24-0.02c-0.25-0.02-0.49-0.05-0.74-0.07c-0.24-0.02-0.48-0.04-0.71-0.03c-0.83,0.02-5.75,0.01-6.65,0.02
|
||||
c-0.63,0.01-5.53-0.01-5.83-0.01c-0.81-0.02-18.52,0.09-19.63,0.08c-0.33,0-4.97,0.16-5.02,0.16c-0.28,0.01-5.25-0.03-5.59-0.03
|
||||
c-0.87,0.01-3.11,0-3.36,0.01c-0.51,0.01-2.73,0.01-3.32,0.02c-0.33,0-0.36,0-0.42-0.32c-0.17-0.9-0.18-1.81-0.15-2.72
|
||||
c0.03-0.71,0.18-1.42,0.2-2.12c0.03-1.3,0.29-2.56,0.56-3.83c0.02-0.08,0.04-0.22,0-0.25c-0.11-0.08-0.06-0.16-0.04-0.24
|
||||
c0.24-0.97,0.49-1.94,0.73-2.92c0.24-0.96,0.46-1.92,0.71-2.88c0.2-0.74,0.41-1.47,0.65-2.2c0.22-0.68,0.44-1.36,0.73-2
|
||||
c0.4-0.9,0.87-1.78,1.32-2.66c0.51-1,1.03-1.99,1.53-2.99c0.33-0.66,0.63-1.34,1.07-1.93c0.04-0.05,0.09-0.15,0.07-0.17
|
||||
c-0.2-0.18-0.02-0.3,0.06-0.43c0.25-0.4,0.51-0.8,0.77-1.2c0.49-0.75,0.97-1.5,1.47-2.25c0.73-1.08,3.38-4.74,4.06-5.43
|
||||
c0.59-0.59,2.32-2.61,2.62-2.92c0.42-0.43,1.09-1.41,0.97-1.48c0.33-0.34,0.62-0.67,0.95-0.98c0.47-0.44,0.96-0.86,1.43-1.29
|
||||
c0.27-0.25,1.37-1.21,1.53-1.57c0.01-0.03,0.08-0.05,0.13-0.06c0.35-0.08,0.66-0.23,0.83-0.56c0.17,0.01,0.33,0.01,0.54,0.02
|
||||
c-0.08,0.09-0.12,0.14-0.16,0.19c-0.25,0.3-0.51,0.6-0.76,0.9c-0.34,0.42-0.67,0.84-1.01,1.27c-0.43,0.54-0.91,1.04-1.21,1.68
|
||||
c-0.19,0.4-0.49,0.75-0.73,1.13c-0.7,1.12-1.41,2.22-2.04,3.39c-0.59,1.1-4.34,7.71-4.36,7.76c-0.18,0.37-0.35,0.74-0.52,1.1
|
||||
c-0.13,0.29-0.26,0.58-0.4,0.86c-0.32,0.63-0.66,1.26-0.97,1.9c-0.21,0.44-0.41,0.88-0.58,1.34c-0.43,1.11-0.84,2.22-1.25,3.33
|
||||
c-0.27,0.73-0.51,1.47-0.75,2.2c-0.21,0.65-0.42,1.31-0.6,1.96c-0.17,0.63-0.32,1.28-0.47,1.92c-0.02,0.1-0.05,0.21-0.07,0.31
|
||||
c-0.02,0.1-0.04,0.2-0.05,0.3c0.02,0,0.03,0.01,0.05,0.01c0.06-0.14,0.13-0.28,0.19-0.42c-0.04,0.35-0.12,0.67-0.21,1
|
||||
c-0.05,0.19-0.12,0.37-0.17,0.52c-0.22-0.14-0.42-0.3-0.65-0.41c-0.26-0.13-0.48-0.27-0.61-0.54c-0.05-0.1-0.14-0.19-0.24-0.26
|
||||
c-0.15-0.11-0.3-0.19-0.38-0.37c-0.03-0.07-0.18-0.12-0.26-0.11c-0.14,0.02-0.28,0.07-0.39,0.15c-0.53,0.34-1.05,0.7-1.59,1.02
|
||||
c-0.19,0.11-0.3,0.24-0.33,0.45c-0.02,0.22,0.15,0.29,0.31,0.34c-0.09,0.22-0.37,0.33-0.26,0.6c0.47,0.02,0.93,0.05,1.21-0.45
|
||||
c-0.13-0.01-0.24-0.02-0.35-0.02c0.16-0.67,0.56-1.06,1.23-1.18c0.25,0.18,0.51,0.37,0.8,0.58c-0.06,0.57,0.94,0.86,1.43,0.48
|
||||
c0,0.08,0.01,0.14,0,0.21c-0.1,0.52-0.2,1.03-0.29,1.55c-0.15,0.83-0.3,1.66-0.44,2.49c-0.06,0.36-0.1,0.73-0.13,1.1
|
||||
c-0.04,0.58-0.03,1.16-0.08,1.73c-0.05,0.56-0.31,4-0.28,4.67c0,0,0.03,0.4,0.03,0.19c0.08-0.17,0.09-0.34,0.14-0.5
|
||||
c0.22-0.76,0.31-1.55,0.39-2.33c0.09-0.84,0.19-1.68,0.31-2.51c0.17-1.09,0.37-2.18,0.55-3.27c0.15-0.9,0.3-1.79,0.46-2.69
|
||||
c0.15-0.83,0.29-1.66,0.47-2.48c0.12-0.54,0.32-1.06,0.47-1.59c0.16-0.6,0.3-1.2,0.46-1.8c0.24-0.88,0.49-1.75,0.9-2.57
|
||||
c0.15-0.3,0.27-0.6,0.4-0.91c0.02-0.06,0.06-0.17,0.04-0.18c-0.29-0.16-0.06-0.37,0-0.5c0.29-0.72,0.62-1.43,0.93-2.14
|
||||
c0.06-0.13,0.12-0.26,0.18-0.39c0.06-0.12,1.18-2.1,1.63-3.04c0.57-1.19,1.11-2.39,1.72-3.56c0.36-0.7,0.7-1.42,1.23-2.02
|
||||
c0.03-0.04,0.04-0.09,0.06-0.14c0.07-0.17,0.12-0.35,0.21-0.5c0.38-0.68,0.79-1.34,1.17-2.02c0.56-1.02,1.24-1.96,1.92-2.9
|
||||
c0.36-0.5,0.7-1.02,1.04-1.54c0.25-0.38,0.46-0.8,0.76-1.15c0.5-0.6,1.05-1.17,1.59-1.76c-0.09-0.22-0.09-0.24,0.11-0.43
|
||||
c0.4-0.38,1.05-1.28,0.96-1.32c0.23-0.21,0.42-0.31,0.61-0.42c0.38-0.21,0.38-0.21,0.62-0.15c-0.12,0.14-0.23,0.27-0.34,0.39
|
||||
c-0.44,0.51-0.88,1.01-1.3,1.54c-0.28,0.35-0.48,0.76-0.76,1.11c-0.8,1.03-1.48,2.14-2.16,3.25c-0.37,0.59-0.78,1.16-1.12,1.77
|
||||
c-0.53,0.95-1.05,1.92-1.53,2.9c-0.32,0.66-0.63,1.33-0.84,2.03c-0.19,0.63-0.39,1.26-0.66,1.86c-0.2,0.44-0.42,0.88-0.59,1.33
|
||||
c-0.32,0.82-0.6,1.66-0.93,2.48c-0.44,1.09-1.87,5.3-2.1,6.34c-0.19,0.86-0.35,1.73-0.58,2.58c-0.25,0.94-0.55,1.87-0.74,2.83
|
||||
c-0.09,0.45-0.26,0.89-0.32,1.34c-0.13,0.99-0.17,2-0.33,2.98c-0.18,1.09-0.14,2.18-0.21,3.27c-0.06,0.83,0.03,2.61,0.06,2.74
|
||||
c0.3-1.16,0.13-2.28,0.36-3.36c0.02,0.14,0.05,0.28,0.07,0.42c0.11-0.3,0.15-0.59,0.18-0.88c0.21-2.3,0.63-4.57,1.12-6.82
|
||||
c0.34-1.56,0.71-3.11,1.09-4.66c0.34-1.39,0.69-2.78,1.25-4.1c0.02-0.06,0.01-0.13,0.02-0.19c0.01-0.12,0.01-0.24,0.04-0.35
|
||||
c0.31-1.2,0.77-2.35,1.24-3.5c0.45-1.08,0.93-2.15,1.41-3.23c0.48-1.07,0.97-2.14,1.46-3.2c0.28-0.6,0.57-1.19,0.86-1.78
|
||||
c0.24-0.48,0.47-0.98,0.73-1.44c0.35-0.61,0.75-1.2,1.08-1.81c0.55-1.01,1.23-1.93,1.91-2.86c0.11-0.15,1.38-1.64,2-2.29
|
||||
c0.22-0.23,0.48-0.42,0.54-0.79c0.09,0.01,0.19,0.02,0.3,0.03c-0.08,0.14-0.16,0.25-0.21,0.36c-0.37,0.82-0.73,1.64-1.09,2.45
|
||||
c-0.34,0.76-0.56,1.56-0.84,2.34c-0.27,0.79-0.45,1.59-0.68,2.39c-0.13,0.48-0.27,0.96-0.41,1.44c-0.3,1.05-0.61,2.1-0.91,3.15
|
||||
c-0.19,0.68-0.36,1.37-0.56,2.06c-0.19,0.67-0.4,1.33-0.61,2c-0.15,0.48-0.32,0.96-0.47,1.4c-0.35-0.21-0.71-0.38-1.03-0.6
|
||||
c-0.46-0.32-0.97-0.32-1.48-0.26c-0.28,0.03-0.57,0.13-0.72,0.42c-0.27,0.51-0.53,1.03-0.78,1.55c-0.13,0.27-0.07,0.41,0.21,0.63
|
||||
c0.34-0.19,0.65-0.39,0.69-0.84c0.01-0.11,0.12-0.24,0.21-0.32c0.2-0.17,0.42-0.32,0.63-0.48c0.41-0.31,0.71-0.25,0.96,0.2
|
||||
c0.09,0.16,0.18,0.32,0.42,0.32c0-0.19,0-0.37,0-0.57c0.27,0.11,0.48,0.36,0.8,0.19c-0.12,0.43-0.22,0.8-0.32,1.18
|
||||
c-0.15,0.58-1.63,9.63-1.69,10.38c-0.05,0.6-0.12,1.21-0.2,1.81c-0.07,0.57-0.12,1.15-0.23,1.72c-0.15,0.76-0.16,1.52-0.16,2.28
|
||||
c0,0.34,0.12,0.69,0.07,1.01c-0.1,0.57-0.09,1.14-0.07,1.72c0.01,0.25,0.01,0.5-0.02,0.75c-0.04,0.31-0.11,0.62-0.16,0.93
|
||||
c-0.01,0.06,0.03,0.13,0.05,0.25c0.08-0.52,0.17-0.97,0.23-1.43c0.08-0.6,0.14-1.21,0.21-1.81c0.08-0.63,0.17-1.25,0.24-1.88
|
||||
c0.07-0.72,0.12-1.44,0.18-2.17c0.01-0.09,0.05-0.17,0.07-0.26c0.02,0.01,0.04,0.69,0.03,1.02c0.07-0.12,0.12-0.24,0.13-0.37
|
||||
c0.08-0.77,0.15-1.55,0.21-2.33c0.11-1.31,0.27-2.62,0.5-3.91c0-0.03,0.01-0.05,0.01-0.08c0.27-1.02,0.55-2.03,0.82-3.05
|
||||
c0.06-0.22,0.14-0.45,0.1-0.76c-0.19,0.3-0.17,0.64-0.42,0.83c0.09-0.41,0.27-0.8,0.3-1.21c0.06-0.71,0.47-1.33,0.41-2.06
|
||||
c0-0.01,0.01-0.03,0.01-0.04c0.08-0.26,0.09-0.52,0.13-0.78c0.1-0.6,0.24-1.19,0.39-1.78c0.42-1.63,0.84-3.26,1.27-4.89
|
||||
c0.21-0.8,0.36-1.62,0.77-2.36c0.03-0.05,0.04-0.14,0.01-0.17c-0.12-0.11-0.06-0.21-0.03-0.32c0.06-0.23,0.1-0.46,0.17-0.69
|
||||
c0.26-0.83,0.52-1.66,0.79-2.48c0.06-0.19,0.17-0.35,0.24-0.54c0.1-0.27,0.16-0.55,0.27-0.82c0.3-0.73,0.58-1.47,0.92-2.19
|
||||
c0.32-0.68,0.47-1.43,0.97-2.02c0.02-0.03,0.02-0.08,0.04-0.11c0.19-0.31,0.37-0.63,0.57-0.94c0.04-0.06,0.12-0.11,0.19-0.12
|
||||
c0.21-0.01,0.43-0.01,0.66-0.01c-0.09,0.3-0.17,0.57-0.25,0.83c-0.15,0.49-0.31,0.97-0.44,1.47c-0.25,0.94-0.46,1.89-0.71,2.83
|
||||
c-0.23,0.84-1.89,7.97-1.98,8.69c-0.07,0.56-1.18,5.38-1.35,9.4c-0.03,0.97-0.13,1.94-0.21,2.92c-0.02,0.3-0.04,0.6-0.07,0.9
|
||||
c-0.05,0.48-0.14,0.96-0.16,1.45c-0.04,0.99-0.05,1.98-0.08,2.97c-0.02,0.79-0.06,1.58-0.08,2.37c-0.01,0.49,0,0.97,0,1.46
|
||||
c0.1,0.07,0.22-0.88,0.29-1.4c0.01-0.08,0.02-0.17,0.06-0.22c0.11-0.12,0.08-0.22,0.06-0.36c-0.05-0.31-0.02-0.62,0.28-0.82
|
||||
c-0.16-0.33-0.11-0.65-0.06-0.98c0.06-0.46,0.11-0.91,0.14-1.37c0.06-0.98,0.1-1.97,0.16-2.95c0.01-0.23,0.04-0.47,0.19-0.77
|
||||
c0,0.72,0,1.35,0,1.98c0.05-0.17,0.04-0.33,0.05-0.5c0.03-0.46,0.06-0.92,0.09-1.38c0.07-1.13,0.12-2.26,0.23-3.39
|
||||
c0.09-0.9,0.24-1.8,0.38-2.7c0.03-0.2,1.07-5.9,1.25-6.86c0.2-1.04,0.35-2.1,0.56-3.14c0.3-1.46,0.63-2.91,0.95-4.36
|
||||
c0.23-1.06,0.49-2.1,0.94-3.09c0.05-0.1,0.03-0.23,0.05-0.35c-0.02,0-0.05-0.01-0.07-0.01c-0.09,0.16-0.18,0.32-0.26,0.48
|
||||
c0.1-0.85,0.67-2.45,1.06-3.12c0.2,0.01,0.41,0.02,0.6,0.03c0.06,0.21,0.11,0.37,0.16,0.53c0.27,0.81,0.55,1.62,0.82,2.43
|
||||
c0.33,0.97,0.66,1.94,0.92,2.93c0.14,0.56,0.28,1.12,0.4,1.69c0.11,0.53,0.18,1.06,0.27,1.59c0.07,0.4,0.16,0.79,0.23,1.19
|
||||
c0.12,0.65,0.25,1.29,0.33,1.94c0.03,0.27,0.18,0.5,0.21,0.76c0.08,0.61,0.19,1.22,0.24,1.84c0.09,1.07,0.14,2.15,0.23,3.22
|
||||
c0.06,0.76,0.14,1.52,0.23,2.28c0.09,0.76,0.27,1.49,0.45,2.23c0.17,0.69,0.34,1.39,0.28,2.1c-0.08,0.87,0.01,2.53,0.11,3.48
|
||||
c0.06-0.3,0.11-1.32,0.16-1.53c0.09-0.03-0.05-2.85-0.11-4.23c0.03,0,0.05-0.01,0.08-0.01c0.04,0.23,0.08,0.46,0.12,0.69
|
||||
c0.01-0.42,0.01-0.84-0.04-1.25c-0.1-0.88-0.23-1.75-0.35-2.62c-0.01-0.09-0.04-0.18-0.06-0.27c-0.05-0.19-0.11-0.38-0.15-0.57
|
||||
c-0.16-0.92-0.3-1.85-0.45-2.77c-0.08-0.48-0.17-0.96-0.24-1.44c-0.11-0.72-0.2-1.43-0.3-2.15c-0.07-0.51-0.15-1.01-0.24-1.52
|
||||
c-0.09-0.51-0.05-1.02-0.12-1.53c-0.07-0.48-0.11-0.96-0.23-1.43c-0.25-1-0.54-1.99-0.76-2.99c-0.28-1.25-0.52-2.5-0.93-3.72
|
||||
c-0.09-0.26-0.09-0.56-0.13-0.84c0.2-0.03,0.34-0.04,0.48-0.08c0.22-0.06,0.35,0,0.44,0.21c0.11,0.26,0.26,0.51,0.39,0.76
|
||||
c0.08,0.15,0.2,0.28,0.26,0.43c0.14,0.38,0.43,0.7,0.44,1.13c0,0.05,0.04,0.1,0.07,0.14c0.41,0.65,1.79,3.87,1.82,3.96
|
||||
c0.36,0.85,0.54,1.76,0.8,2.64c0.04,0.13,0.06,0.25,0.09,0.38c0.05,0.21,0.08,0.42,0.15,0.61c0.27,0.78,0.54,1.57,0.55,2.41
|
||||
c0,0.07,0.01,0.13,0.03,0.2c0.29,1.12,0.65,2.22,0.8,3.38c0.07,0.52,0.19,1.03,0.27,1.55c0.14,0.86,0.26,1.72,0.39,2.58
|
||||
c0.02,0.13,0.1,1.12,0.23,1.1c0.06-0.49-0.21-1.9-0.11-2.39c0.25,0.93,0.25,1.91,0.53,2.83c-0.04-0.69-0.1-1.37-0.2-2.05
|
||||
c-0.26-1.71-0.53-3.41-0.81-5.11c-0.16-0.96-0.34-1.92-0.51-2.88c-0.08-0.42-1.13-4.27-1.44-5.08c-0.45-1.17-0.88-2.35-1.35-3.51
|
||||
c-0.3-0.74-0.68-1.46-1-2.19c-0.15-0.35-0.39-0.69-0.38-1.12c0.23-0.05,0.42-0.06,0.59,0.12c0.37,0.39,2.71,3.45,3.14,3.98
|
||||
c0.81,1,1.58,2.04,2.14,3.21c0.29,0.61,0.64,1.18,0.93,1.79c0.38,0.77,0.75,1.54,1.09,2.33c0.52,1.18,1.01,2.37,1.52,3.56
|
||||
c0.2,0.46,0.39,0.92,0.59,1.38c0.07,0.15,0.12,0.31,0.19,0.46c0.12,0.28,0.11,0.62,0.4,0.82c0.02-0.04,0.04-0.09,0.03-0.13
|
||||
c-0.28-0.71-0.55-1.42-0.84-2.12c-0.49-1.22-0.99-2.43-1.49-3.65c-0.28-0.68-0.59-1.35-0.88-2.03c-0.28-0.66-0.53-1.33-0.82-1.99
|
||||
c-0.16-0.36-0.37-0.7-0.55-1.04c-0.12-0.23-0.25-0.47-0.37-0.7c0.02-0.01,0.04-0.03,0.07-0.04c0.13,0.19,0.27,0.37,0.39,0.57
|
||||
c0.2,0.31,0.39,0.62,0.58,0.92c0,0-1.16-2.2-1.76-3.17c-0.43-0.69-0.93-1.35-1.4-2.01c-0.39-0.55-0.78-1.1-1.18-1.64
|
||||
c-0.13-0.18-0.28-0.34-0.46-0.55c0.23,0,0.41-0.01,0.59,0c0.18,0.01,0.31,0.12,0.3,0.3c-0.01,0.21,0.1,0.31,0.24,0.41
|
||||
c0.12,0.09,0.22,0.19,0.32,0.29c0.39,0.39,0.76,0.81,1.16,1.19c0.64,0.61,1.23,1.26,1.75,1.98c0.36,0.5,0.72,1.01,1.08,1.51
|
||||
c0.11,0.16,0.28,0.32,0.31,0.5c0.05,0.34,0.27,0.54,0.45,0.79c0.31,0.43,0.64,0.84,0.93,1.28c0.36,0.54,0.72,1.1,1.05,1.66
|
||||
c0.35,0.62,0.69,1.24,0.99,1.89c0.3,0.64,0.55,1.31,0.84,1.96c0.08,0.18,0.18,0.34,0.27,0.51c0.03-0.01-0.09-0.36-0.13-0.52
|
||||
c-0.04-0.16-0.86-2.36-0.97-2.59c-0.41-0.84-0.8-1.69-1.22-2.52c-0.33-0.66-0.69-1.31-1.03-1.96c-0.03-0.06-0.09-0.11-0.09-0.17
|
||||
c-0.02-0.38-0.29-0.62-0.5-0.89c-0.42-0.55-0.85-1.1-1.29-1.65c-0.29-0.36-0.58-0.71-0.88-1.07c-0.26-0.31-0.51-0.63-0.76-0.95
|
||||
c-0.01-0.01,0.51,0.26,0.72,0.44c0.68,0.59,1.38,1.17,2.02,1.81c0.47,0.46,0.9,0.96,1.28,1.49c0.31,0.43,0.68,0.79,1.1,1.12
|
||||
c-0.47-0.81-1.12-1.5-1.64-2.28c0.39,0.29,0.77,0.59,1.1,0.94c0.62,0.66,1.21,1.34,1.81,2.03c0.43,0.5,1.99,2.35,2.1,2.43
|
||||
c0.05-0.33-0.2-0.42-0.31-0.58c-0.39-0.55-0.79-1.09-1.2-1.62c-0.43-0.56-0.85-1.13-1.34-1.63c-1.2-1.21-2.43-2.38-3.65-3.56
|
||||
c-0.39-0.38-0.77-0.76-1.16-1.13c-0.34-0.32-0.73-0.61-1.03-0.96c-0.26-0.31-0.57-0.48-0.94-0.59c-0.06-0.02-0.1-0.09-0.21-0.19
|
||||
c0.44-0.04,0.8-0.07,1.16-0.11c-0.13,0.24-0.11,0.24-0.08,0.48c0.03,0.25,0.21,0.27,0.38,0.33c0.05,0.02,0.1,0.05,0.14,0.08
|
||||
c0.53,0.36,1.06,0.72,1.59,1.1c0.75,0.53,1.37,1.2,2,1.87c0.39,0.42,0.77,0.86,1.15,1.29c0.01,0.01,0.04,0.01,0.06,0.02
|
||||
c0.01-0.04,0.02-0.07,0.04-0.18c0.35,0.31,0.67,0.6,0.99,0.88c-0.45-0.63-1.01-1.16-1.45-1.8c0.3,0.19,3.84,3.56,4.54,3.85
|
||||
c-0.92-1.18-1.9-2.25-2.95-3.25c0.44,0.24,0.81,0.58,1.17,0.91c0.38,0.35,0.75,0.71,1.11,1.07c0.34,0.35,1.06,0.96,1.38,1.33
|
||||
c-0.06-0.11-0.43-0.55-0.5-0.67c0.05-0.02,0.09-0.04,0.11-0.06c0.03-0.02,0.04-0.06,0.07-0.09c-0.03-0.05-2.63-2.88-4.14-4
|
||||
c-0.69-0.51-1.37-1.03-2.06-1.54c-0.54-0.4-1.09-0.78-1.64-1.17c-0.16-0.12-0.31-0.25-0.47-0.38c0.03-0.05,0.05-0.14,0.09-0.14
|
||||
c0.1-0.01,0.22-0.02,0.31,0.02c0.22,0.11,0.43,0.25,0.63,0.38c0.66,0.41,1.31,0.83,1.96,1.25c0.58,0.37,1.16,0.73,1.73,1.1
|
||||
c0.12,0.07,0.21,0.18,0.32,0.26c-0.02,0.02-0.03,0.04-0.05,0.06c-0.35-0.19-0.71-0.37-1.06-0.57c-0.32-0.18-0.62-0.39-0.93-0.57
|
||||
c-0.09-0.05-0.19-0.08-0.29-0.12c0.11,0.11,0.21,0.23,0.33,0.32c0.57,0.44,1.16,0.87,1.74,1.3c0.15,0.11,0.48,0.29,0.5,0.27
|
||||
c-0.32-0.26-0.64-0.53-0.96-0.79c0.1-0.03,0.15-0.01,0.21,0.03c0.93,0.56,1.81,1.19,2.66,1.87c0.07,0.06,0.15,0.11,0.22,0.16
|
||||
c-0.12-0.16-0.07-0.43-0.19-0.59c1.18,0.92,5.41,4.74,5.46,4.77c0.53,0.58,1.5,1.54,1.6,1.65c0.09,0.1,1.46,1.8,2,2.45
|
||||
c0.81,0.98,1.59,1.99,2.36,3c0.34,0.45,0.63,0.94,0.94,1.41c0.1,0.15,0.19,0.38,0.33,0.42c0.24,0.08,0.32,0.26,0.43,0.42
|
||||
c0.29,0.44,0.57,0.88,0.84,1.33c0.67,1.13,1.34,2.26,2.01,3.38c0.03,0.06,0.1,0.09,0.15,0.13c-0.01-0.06-0.02-0.12-0.04-0.17
|
||||
C109.64,73,109.43,72.64,109.23,72.27z M79.01,37.91c1.07,0.05,2.8-0.03,4.27,0.05c-0.04,0.44-0.08,0.85-0.11,1.26
|
||||
c-0.01,0.13-0.03,0.26-0.02,0.39c0.07,1.09,0.14,2.18,0.21,3.27c0.01,0.24,0.01,0.47,0.01,0.71c0.01,0.16,0.01,0.3,0.14,0.44
|
||||
c0.06,0.06,0.05,0.21,0.04,0.32c-0.01,0.27-0.1,0.54,0.05,0.81c-0.09-0.07-0.19-0.15-0.28-0.22c-0.54,0.17-3.35,0.29-4.2,0.21
|
||||
c0.02-0.57-0.11-4.24-0.12-5.2c0-0.51,0.02-1.03,0.01-1.54C79.01,38.24,78.97,38.14,79.01,37.91z M78.3,37.95
|
||||
c0.2,0.39,0.08,0.75,0.08,1.1c0,0.32,0,0.63,0,0.95c0,0.67-0.01,1.34,0.01,2.01c0.02,0.67,0.06,1.34,0.12,2.01
|
||||
c0.03,0.39,0.13,0.77,0.19,1.16c-0.33,0-0.68,0-1.06,0c-0.04-0.47-0.05-0.92-0.11-1.37c-0.14-1.05-0.07-2.1-0.13-3.15
|
||||
c-0.02-0.29-0.05-0.58-0.05-0.87c0-0.43,0.01-0.86,0.04-1.3c0.01-0.19-0.01-0.36-0.2-0.53C77.59,37.95,77.95,37.95,78.3,37.95z
|
||||
M72.16,37.32c-0.41-0.06-2.45-0.08-2.8,0.03c-0.29-0.21-0.23-0.4-0.27-0.58c0.5-0.13,2.1-0.05,2.17-0.12
|
||||
c0.29,0.03,0.74,0.01,1.01,0.05c0.08,0.01,0.38,0.11,0.39,0.15C72.67,37.01,72.26,37.34,72.16,37.32z M75.34,37.93
|
||||
c0.35,0,0.83,0.04,1.2,0.04c0,0.29,0.11,0.59-0.08,0.87c-0.04,0.05-0.04,0.16-0.01,0.22c0.21,0.39,0.12,0.83,0.16,1.24
|
||||
c0.06,0.65,0.16,1.29,0.16,1.95c0.01,0.96,0.1,1.93,0.1,2.96c-0.32,0-0.91-0.05-1.25-0.05c-0.14-0.86-0.18-1.42-0.22-2.32
|
||||
c-0.04-0.91,0-1.93-0.05-2.84C75.31,39.4,75.25,38.54,75.34,37.93z M73.38,37.88c0.42,0,0.8,0,1.23,0c0,0.24,0,0.46,0,0.68
|
||||
c0.01,0.33,0.03,0.65,0.03,0.98c0,0.66,0.04,1.32-0.01,1.97c-0.05,0.66-0.01,1.31,0.08,1.96c0.06,0.4,0.11,0.81,0.19,1.21
|
||||
c0.03,0.13,0.12,0.25,0.19,0.38c-0.23,0.13-0.9,0.14-1.72,0.04c0.06-0.14,0.16-0.28,0.17-0.43c0.02-0.16-0.06-0.34-0.06-0.51
|
||||
c-0.02-0.64-0.03-1.29-0.04-1.93c-0.01-0.34-0.01-0.68-0.01-1.02c0.01-0.75,0.02-1.5,0.03-2.25c0-0.21,0.03-0.41-0.07-0.62
|
||||
C73.33,38.23,73.38,38.06,73.38,37.88z M76.21,48.65c-0.8,0.02-1.59,0.03-2.39,0.02c-0.52-0.01-3.97-0.05-5.17-0.06
|
||||
c-0.51,0-1.01-0.06-1.61-0.1c0.56-0.39,1.09-0.67,1.49-1.07c0.39-0.4,0.73-0.89,0.91-1.49c0.41-0.06,0.84-0.17,1.31-0.08
|
||||
c-0.11,0.45-0.24,0.82-0.68,0.99c-0.15,0.06-0.3,0.21-0.36,0.35c-0.12,0.31-0.38,0.46-0.61,0.66c-0.1,0.09-0.19,0.2-0.29,0.31
|
||||
c0.17,0.13,0.28,0.08,0.4,0c0.73-0.51,1.34-1.13,1.79-1.9c0.13-0.22,0.28-0.33,0.52-0.34c0.27-0.02,0.54-0.06,0.82-0.1
|
||||
c-0.13,0.68-0.45,1.24-0.93,1.71c-0.14,0.14-0.19,0.23-0.06,0.4c0.44-0.1,0.73-0.39,0.98-0.72c0.1-0.14,0.24-0.25,0.32-0.4
|
||||
c0.06-0.1,0.12-0.24,0.1-0.35c-0.04-0.27-0.06-0.27,0.1-0.58c0.53,0,1.05,0.01,1.58,0c0.57-0.01,1.13-0.04,1.7-0.05
|
||||
c0.08,0,0.17,0.03,0.25,0.05c-0.06,0.13-0.11,0.25-0.04,0.41c0.18,0.43,0.13,0.88,0.12,1.33c-0.01,0.25-0.04,0.5,0.15,0.71
|
||||
c0.03,0.04,0.02,0.13,0.03,0.22C76.49,48.6,76.35,48.65,76.21,48.65z M37.01,93.47c-0.14,0.67-0.15,1.36-0.2,2.04
|
||||
c0,0.04-0.01,0.08-0.02,0.12c-0.67,0-1.32,0-2,0c0-0.19-0.02-0.4,0-0.62c0.13-1.15,0.23-2.31,0.4-3.46
|
||||
c0.12-0.79,0.32-1.57,0.49-2.36c0.04-0.18,0.06-0.36,0.1-0.54c0.19-0.87,0.35-1.76,0.6-2.62c0.25-0.88,0.59-1.75,0.89-2.62
|
||||
c0.18-0.53,0.35-1.07,0.56-1.59c0.32-0.78,0.68-1.54,1.03-2.3c0.28-0.61,0.57-1.21,0.88-1.81c0.22-0.43,1.18-2.44,1.29-2.65
|
||||
c1.48-2.85,4.81-7.4,5.22-7.84c1.2-1.86,4.38-4.84,4.8-5.28c0.2-0.21,4.78-4.13,5.35-4.66c0.66-0.6,1.33-1.2,2.06-1.72
|
||||
c0.58-0.41,1.11-0.89,1.67-1.33c0.29-0.23,0.59-0.44,0.89-0.65c0.09-0.06,0.2-0.08,0.31-0.11c0.01,0.03,0.03,0.05,0.04,0.08
|
||||
c-0.26,0.21-0.53,0.42-0.79,0.64c-1.14,0.93-2.32,1.83-3.41,2.82c-0.8,0.73-1.63,1.41-2.41,2.15c-0.58,0.54-1.89,1.93-2,2.06
|
||||
c-0.94,1.06-3.29,3.75-3.51,4.05c-0.37,0.51-0.77,1.01-1.17,1.49c-0.28,0.34-1.16,1.44-1.31,1.67c-0.43,0.65-0.85,1.3-1.28,1.94
|
||||
c-0.5,0.74-1.46,2.37-1.48,2.41c-0.17,0.31-0.33,0.62-0.5,0.93c-0.36,0.66-0.73,1.31-1.07,1.97c-0.26,0.5-0.51,1.01-0.76,1.52
|
||||
c-0.31,0.62-0.64,1.23-0.93,1.86c-0.23,0.5-0.41,1.02-0.62,1.53c-0.14,0.34-0.29,0.67-0.43,1.01c-0.04,0.08-0.08,0.16-0.11,0.25
|
||||
c-0.39,1.16-1.25,3.96-1.31,4.2c-0.07,0.29-0.14,0.58-0.21,0.87c-0.03,0.13-0.54,2.56-0.59,2.94
|
||||
C37.36,90.77,37.07,93.17,37.01,93.47z M42.41,92.75c-0.16,0.61-0.25,2.7-0.25,3.16c-1.44-0.09-3-0.26-4.39-0.34
|
||||
c-0.12-0.88,0.21-4.01,0.27-4.43c0.09-0.58,0.39-2.07,0.52-2.65c0.17-0.76,0.51-2.2,1-4.26c0.69-2.61,3.05-7.51,3.45-8.34
|
||||
c0.26-0.54,0.57-1.06,0.86-1.59c0.06-0.12,0.11-0.24,0.18-0.35c0.12-0.2,0.25-0.39,0.38-0.59c0.14-0.21,0.3-0.4,0.42-0.62
|
||||
c0.47-0.9,1-1.76,1.57-2.59c0.44-0.64,0.81-1.32,1.22-1.98c0.1-0.17,0.23-0.32,0.35-0.48c0.31-0.4,0.62-0.81,0.95-1.2
|
||||
c0.35-0.41,0.7-0.82,1.07-1.22c0.42-0.46,0.88-0.87,1.25-1.36c0.27-0.35,0.53-0.71,0.84-1.03c0.57-0.59,1.11-1.19,1.72-1.73
|
||||
c0.35-0.31,0.63-0.69,0.99-0.99c0.31-0.26,0.58-0.57,0.88-0.84c0.44-0.4,0.88-0.79,1.33-1.18c0.67-0.59,1.34-1.19,2.01-1.78
|
||||
c0.23-0.2,0.46-0.4,0.68-0.6c0.42-0.38,0.85-0.74,1.37-0.98c0.13-0.06,0.22-0.18,0.34-0.26c0.59-0.4,1.18-0.79,1.77-1.19
|
||||
c0.23-0.15,0.47-0.29,0.67-0.47c0.1-0.09,0.12-0.26,0.2-0.44c0.54-0.23,1.01-0.73,1.77-0.9c-0.32,0.29-0.56,0.54-0.82,0.75
|
||||
c-0.72,0.59-1.41,1.23-2.09,1.87c-0.55,0.53-1.13,1.02-1.67,1.57c-0.72,0.73-1.41,1.5-2.11,2.24c-0.5,0.54-1.02,1.06-1.52,1.6
|
||||
c-0.35,0.38-0.68,0.77-1.01,1.16c-0.76,0.89-1.53,1.78-2.27,2.69c-0.5,0.61-0.95,1.26-1.42,1.89c-0.17,0.23-0.37,0.44-0.54,0.67
|
||||
c-0.23,0.3-0.44,0.61-0.67,0.92c-0.22,0.3-0.45,0.58-0.65,0.88c-0.36,0.58-0.71,1.17-1.05,1.76c-0.14,0.24-0.27,0.48-0.39,0.73
|
||||
c-0.19,0.38-0.38,0.77-0.57,1.15c-0.12,0.23-0.24,0.46-0.37,0.69c-0.3,0.57-0.61,1.13-0.92,1.7c-0.13,0.23-0.28,0.45-0.38,0.69
|
||||
c-0.23,0.51-0.45,1.04-0.67,1.55c-0.16,0.37-0.35,0.74-0.49,1.12c-0.16,0.44-0.28,0.91-0.44,1.35c-0.19,0.53-0.42,1.04-0.63,1.57
|
||||
c-0.06,0.15-0.29,0.31-0.02,0.48c0.01,0.01-0.02,0.1-0.04,0.15c-0.33,0.95-0.66,1.89-0.98,2.84c-0.29,0.86-0.46,1.75-0.62,2.65
|
||||
c-0.1,0.58-0.3,1.15-0.47,1.71c-0.12,0.42-0.49,3.92-0.5,3.98C42.5,92.18,42.48,92.48,42.41,92.75z M48.47,83.35
|
||||
c-0.06-0.23-0.06-0.22,0.18-0.66C48.59,82.89,48.54,83.1,48.47,83.35z M51.59,73.28c-0.11,0.21-0.21,0.43-0.32,0.64
|
||||
c-0.1,0.2-0.21,0.39-0.32,0.59c0.13-0.67,0.5-1.23,0.81-1.89C51.77,72.94,51.77,72.93,51.59,73.28z M61.6,57.9
|
||||
c0.27-0.46,0.64-0.83,1.03-1.19C62.45,57.24,61.99,57.54,61.6,57.9z M66.67,52.56c-0.94,1.06-2.04,1.95-3.1,2.88
|
||||
c-0.81,0.71-1.55,1.47-2.2,2.31c-0.36,0.47-0.68,0.97-1.02,1.45c-0.15,0.2-0.33,0.37-0.49,0.57c-0.05,0.06-0.08,0.15-0.11,0.23
|
||||
c0.05,0.01,0.09,0.01,0.17,0.02c-0.03,0.07-0.04,0.13-0.08,0.17c-0.61,0.68-1.04,1.48-1.55,2.23c-0.46,0.67-0.97,1.31-1.44,1.96
|
||||
c-0.13,0.18-0.25,0.37-0.36,0.57c-0.26,0.49-0.57,0.94-0.93,1.37c-0.29,0.35-0.53,0.75-0.79,1.13c-0.54,0.79-1.11,1.56-1.62,2.38
|
||||
c-0.77,1.24-1.55,2.48-2.11,3.84c-0.17,0.41-0.33,0.8-0.37,1.24c-0.04,0.52-0.39,0.92-0.55,1.39c-0.23,0.66-0.47,1.31-0.7,1.96
|
||||
c-0.08,0.24-0.17,0.47-0.24,0.71c-0.33,1.15-0.71,2.29-0.98,3.46c-0.24,1.02-0.4,2.07-0.56,3.11c-0.14,0.91-0.22,1.83-0.38,2.74
|
||||
c-0.1,0.62-0.43,2.58-0.44,2.67c-0.03,0.46-0.2,2.1-0.22,2.47c-0.03,0.54-0.06,1.08-0.08,1.61c-0.01,0.27,0,0.55,0,0.83
|
||||
c-0.41,0.14-2.74,0.19-3.81,0.08c0-0.25,0.01-0.49,0-0.74c-0.02-0.65,0.1-1.28,0.21-1.91c0.08-0.48,0.03-0.98,0.1-1.46
|
||||
c0.09-0.62,0.23-1.24,0.34-1.86c0.09-0.52,0.16-1.04,0.26-1.56c0.06-0.34,0.16-0.66,0.24-1c0.11-0.5,0.21-1,0.31-1.51
|
||||
c0.08-0.39,0.17-0.77,0.24-1.16c0.09-0.46,0.16-0.93,0.25-1.39c0.01-0.06,0.03-0.13,0.05-0.19c0.16-0.43,0.31-0.86,0.47-1.29
|
||||
c0.14-0.38,0.27-0.77,0.41-1.15c0.3-0.8,0.58-1.61,0.9-2.4c0.13-0.34,0.33-0.65,0.52-1.04c-0.11,0.05-0.17,0.08-0.29,0.14
|
||||
c0.05-0.14,0.06-0.25,0.11-0.34c0.43-0.89,0.86-1.78,1.31-2.66c0.21-0.41,0.46-0.8,0.67-1.2c0.24-0.45,0.45-0.92,0.71-1.36
|
||||
c0.67-1.13,1.33-2.27,2.05-3.37c0.63-0.96,1.34-1.86,2.03-2.78c0.67-0.89,1.34-1.79,2.05-2.65c0.88-1.07,1.79-2.11,2.69-3.16
|
||||
c0.2-0.23,0.44-0.41,0.66-0.62c0.05-0.04,0.13-0.08,0.14-0.13c0.08-0.41,0.44-0.57,0.69-0.83c0.47-0.48,0.97-0.93,1.41-1.43
|
||||
c0.9-1.03,1.83-2.04,2.95-2.84c0.39-0.28,0.81-0.53,1.2-0.82c0.27-0.2,0.52-0.44,0.77-0.67c0.18-0.16,0.36-0.29,0.67-0.21
|
||||
C66.76,52.45,66.72,52.51,66.67,52.56z M66.72,52.04c0.29-0.18,0.58-0.37,0.87-0.55c0.02,0.03,0.03,0.06,0.05,0.08
|
||||
C67.38,51.82,67.19,52.15,66.72,52.04z M65.2,58.25c0.02-0.23,0.12-0.41,0.31-0.54C65.49,57.94,65.34,58.09,65.2,58.25z
|
||||
M65.55,57.66c0.14-0.27,0.43-0.72,0.45-0.7C65.94,57.24,65.77,57.47,65.55,57.66z M66.19,56.55c0.08-0.11,0.16-0.23,0.24-0.34
|
||||
C66.46,56.39,66.35,56.49,66.19,56.55z M70.23,51.47c-0.29,0.32-0.62,0.61-0.89,0.95c-0.56,0.73-1.1,1.48-1.64,2.22
|
||||
c-0.32,0.44-0.64,0.87-0.96,1.31c0.47-1.13,3.09-4.52,3.67-4.74C70.35,51.29,70.3,51.39,70.23,51.47z M59.1,81.62
|
||||
c-0.03-0.01-0.06-0.01-0.09-0.02c0.04-0.18,0.07-0.36,0.11-0.54c0.03,0.01,0.07,0.02,0.1,0.03C59.18,81.26,59.14,81.44,59.1,81.62
|
||||
z M59.27,80.66c-0.02,0-0.04-0.01-0.06-0.01c0.01-0.07,0.02-0.14,0.03-0.21c0.03,0.01,0.05,0.01,0.08,0.02
|
||||
C59.3,80.52,59.28,80.59,59.27,80.66z M59.33,80.19c-0.02,0-0.03,0-0.05-0.01c0.01-0.09,0.01-0.18,0.02-0.27
|
||||
c0.03,0,0.05,0.01,0.08,0.01C59.37,80.02,59.35,80.11,59.33,80.19z M65.87,81.69c0.02-0.1,0.04-0.2,0.06-0.3
|
||||
c0.02,0,0.05,0.01,0.07,0.01C66,81.5,65.91,81.69,65.87,81.69z M71.12,76.9c-0.03,0-0.07,0-0.1,0c0.01-0.12,0.01-0.25,0.02-0.37
|
||||
c0.03,0,0.05,0,0.08,0C71.12,76.65,71.12,76.77,71.12,76.9z M73.15,62.99c-0.13-0.31,0.17-1.66,0.42-1.86
|
||||
C73.43,61.75,73.29,62.37,73.15,62.99z M74.11,58.4c-0.14,0.74-0.29,1.47-0.44,2.21c-0.02,0.1-0.06,0.19-0.12,0.34
|
||||
c-0.06-0.09,0.32-2.14,0.53-3.13c0.01-0.04,0.05-0.07,0.08-0.11C74.15,57.94,74.15,58.17,74.11,58.4z M74.17,57.7
|
||||
c-0.02-0.39,0.14-0.73,0.28-1.08C74.37,56.98,74.47,57.39,74.17,57.7z M81.01,69.6c0.01,0.07,0.02,0.14,0.03,0.21
|
||||
c0,0.01-0.01,0.03-0.02,0.03c-0.01,0-0.02-0.01-0.05-0.02c-0.01-0.06-0.03-0.13-0.05-0.2C80.95,69.61,80.98,69.61,81.01,69.6z
|
||||
M80.97,69.06c-0.2-0.4-0.16-0.82-0.21-1.24c0.04-0.01,0.07-0.01,0.11-0.02C80.9,68.22,80.93,68.64,80.97,69.06z M80.77,67.26
|
||||
c0.02,0.1,0.04,0.21,0.06,0.31c-0.03,0.01-0.06,0.01-0.08,0.02c-0.03-0.11-0.05-0.22-0.08-0.34
|
||||
C80.7,67.25,80.74,67.26,80.77,67.26z M85.06,69.33c0.01,0,0.02,0,0.03,0c0.01,0.08,0.02,0.15,0.03,0.23c-0.02,0-0.03,0-0.05,0
|
||||
C85.06,69.49,85.06,69.41,85.06,69.33z M79.18,49.68c0.03-0.14-0.09-0.44-0.06-0.57c0.32,0,0.62-0.05,1.09,0.17
|
||||
c0.02,0.17,0.01,0.14,0.03,0.31C79.88,49.53,79.54,49.83,79.18,49.68z M80.84,50.72c0.25,0.02,0.25,0.02,0.31,0.35
|
||||
C81.06,50.96,80.96,50.85,80.84,50.72z M88.63,57.84c-0.29-0.15-0.35-0.25-0.49-0.69C88.29,57.37,88.44,57.58,88.63,57.84z
|
||||
M87.78,56.63c0.06,0.1,0.15,0.32,0.13,0.34C87.84,56.88,87.75,56.66,87.78,56.63z M84.87,52.11c-0.16-0.15-0.32-0.29-0.48-0.44
|
||||
C84.7,51.65,84.9,52.09,84.87,52.11z"/>
|
||||
<path d="M25.56,105.87c0.2,0.01,0.25,0.16,0.35,0.28c0.14,0.19,0.31,0.29,0.57,0.26c0.92-0.09,6.82-0.11,7.38-0.13
|
||||
c0.21-0.01,2.94-0.11,2.94-0.08c-0.44,0.02,5.63,0.05,6.54,0.06c0.21,0,2.65,0.28,2.86,0.26c0,0,0,0,0,0
|
||||
c-0.05-0.01-0.25-0.05-0.9-0.24c0.13-0.03,0.19-0.06,0.25-0.05c0.79,0.1,20.85-0.11,22.37-0.1c0.75,0,2.98,0.01,3.15,0.03
|
||||
c0.16,0.02,9.08-0.31,9.95-0.21c0.55,0.06,1.1,0.11,1.65,0.16c0.65,0.06,1.31,0.1,1.96,0.15c0.02,0,0.05-0.01,0.09-0.02
|
||||
c0.02-0.05,0.04-0.11,0.08-0.21c0.65,0,1.33-0.01,2,0c0.65,0.01,1.29,0.05,1.94,0.07c0.25,0.01,0.5,0.01,0.74-0.01
|
||||
c0.25-0.02,0.5-0.05,0.76-0.04c0.82,0.04,1.65,0.04,2.48,0.05c0.13,0,0.26-0.01,0.4-0.02c0.43-0.01,0.87-0.07,1.3-0.01
|
||||
c0.78,0.1,1.55,0.04,2.33,0c0.76-0.04,1.53-0.09,2.29-0.11c0.89-0.02,1.78,0,2.66-0.01c0.72,0,1.44-0.01,2.16-0.02
|
||||
c0.08,0,0.16,0,0.23-0.03c0.4-0.17,0.4-0.18,0.67-0.16c0.06,0,0.12-0.01,0.18-0.02c-0.01-0.03-0.02-0.05-0.02-0.05
|
||||
c-1.16-0.06-2.31-0.15-3.47-0.17c-1.44-0.03-2.87,0-4.31-0.01c-0.35,0-0.71-0.05-1.06-0.07c-0.26-0.01-0.53,0-0.79-0.01
|
||||
c-0.72-0.04-1.45-0.1-2.17-0.12c-1.45-0.04-2.9-0.06-4.35-0.1c-0.41-0.01-0.82-0.04-1.22-0.05c-1.5-0.04-3-0.09-4.51-0.1
|
||||
c-1.12,0-2.24,0.1-3.36,0.1c-1.49,0.01-2.98-0.03-4.47-0.03c-1.41,0-2.82,0.03-4.23,0.04c-0.47,0-0.94,0-1.42,0
|
||||
c0.02-0.25,0.07-0.48,0.07-0.72c0.02-1.19,0.04-2.38,0.03-3.56c0-0.39-0.04-0.8-0.13-1.18c-0.06-0.25-0.24-0.47-0.18-0.8
|
||||
c0.54-0.02,1.06-0.06,1.59-0.05c1.07,0.02,10.22-0.03,10.28-0.03c1.38,0.05,2.77-0.17,4.15,0.01c0.07,0.01,0.19,0,0.2-0.04
|
||||
c0.08-0.2,0.25-0.11,0.38-0.12c0.14-0.01,0.29,0.03,0.43,0.02c0.51-0.04,1.02-0.09,1.52-0.14c0.03,0,0.06-0.04,0.11-0.07
|
||||
c-0.09-0.18-0.19-0.26-0.38-0.22c-0.2,0.04-0.41,0.08-0.62,0.08c-2.67-0.02-5.34-0.04-8-0.06c-2.52-0.02-10.01,0.1-11,0.14
|
||||
c-0.83,0.03-1.66,0-2.49-0.02c-0.88-0.03-9.45,0.04-9.77,0.03c-0.47-0.02-6.98,0.09-8.01,0.08c-0.87,0-6.47-0.14-7.47-0.1
|
||||
c-0.72,0.03-2.75-0.1-3.47-0.28c-0.1-0.03-0.26-0.02-0.33,0.04c-0.17,0.16-0.36,0.15-0.55,0.14c-0.28-0.01-0.55,0-0.83-0.01
|
||||
c-0.43-0.01-0.87,0-1.3-0.04c-0.58-0.06-0.59-0.07-0.55,0.54c0.5,0.05,0.57,0.12,0.69,0.63c0.02,0.1,0.06,0.21,0.06,0.31
|
||||
c0.04,0.86,0.1,1.71,0.12,2.57c0.01,0.84-0.03,1.69-0.05,2.53c-0.01,0.22-0.03,0.44-0.05,0.65c-0.21,0-0.38,0-0.55,0
|
||||
c-0.71,0-1.43,0-2.14-0.02c-0.63-0.02-2.97-0.1-3.51-0.1c-0.39,0-0.78-0.03-1.18-0.05c-0.08,0-0.17-0.06-0.22-0.03
|
||||
c-0.24,0.13-0.49,0.16-0.76,0.15c-0.08,0-0.2,0.12-0.23,0.21C25.38,105.71,25.5,105.87,25.56,105.87z M68.77,98.95
|
||||
c0.06,0.17,0.14,0.33,0.18,0.5c0.03,0.15,0.03,0.31,0.03,0.46c0,0.37,0,0.73,0,1.1c-0.01,0.97,0.11,1.94-0.06,2.91
|
||||
c-0.01,0.07-0.03,0.17,0,0.22c0.18,0.32,0.08,0.67,0.11,1.05c-1.35,0.13-2.69,0.07-4.07,0.16c0-0.23-0.01-0.41,0-0.59
|
||||
c0.05-0.85,0.1-1.71,0.15-2.56c0.03-0.5,0.07-1,0.09-1.49c0.01-0.13,0.02-0.28-0.04-0.39c-0.21-0.4-0.13-0.85-0.28-1.28
|
||||
C66.21,98.9,67.5,99.01,68.77,98.95z M57.87,104.08c0.08-1.08,0.15-2.16,0.13-3.24c-0.01-0.53-0.06-1.05-0.09-1.62
|
||||
c1.1-0.04,2.18-0.15,3.26-0.1c1.08,0.05,2.14-0.14,3.27-0.1c-0.03,0.51-0.05,0.95-0.07,1.4c-0.06,0.95-0.19,1.9,0.04,2.85
|
||||
c0.01,0.04,0.01,0.08,0.01,0.12c0.02,0.63,0.04,1.26,0.06,1.92c-2.16,0.15-4.33-0.05-6.51,0.02
|
||||
C57.8,104.91,57.84,104.48,57.87,104.08z M57.5,104.89c0.18,0.18,0.2,0.3,0.11,0.46C57.4,105.22,57.49,105.09,57.5,104.89z
|
||||
M53.51,102.12c0-0.42,0.05-0.84,0.02-1.26c-0.04-0.46,0.06-0.91,0.02-1.37c-0.01-0.09,0.03-0.18,0.05-0.31
|
||||
c0.25,0.03,0.92-0.12,1.06-0.12c0.75,0.02,1.5,0.04,2.24,0.06c0.34,1.04,0.22,2.1,0.13,3.16c-0.04,0.55-0.09,1.11-0.05,1.66
|
||||
c0.03,0.41,0.04,0.85,0.33,1.2c0.02,0.03,0.04,0.07,0.05,0.11c0.01,0.02,0,0.05-0.01,0.07c-0.58-0.02-3.16,0.05-3.54,0.05
|
||||
c-0.25,0-0.32-0.08-0.32-0.34C53.5,104.06,53.5,103.09,53.51,102.12z M47.58,99.17c1.74-0.17,3.4-0.03,5.01-0.01
|
||||
c0.27,1.94,0.23,3.85,0.35,5.77c0.2,0.12,0.2,0.12,0.18,0.47c-0.14,0.01-0.28,0.02-0.42,0.03c-1.62,0.03-3.23,0.25-4.86,0.14
|
||||
c-0.1-0.01-0.2-0.05-0.34-0.09C47.41,103.38,47.58,101.29,47.58,99.17z M47.13,105.08c0.13,0.19,0.14,0.31,0.03,0.45
|
||||
C46.99,105.4,47.07,105.28,47.13,105.08z M42.65,104.74c-0.01-0.06-0.02-0.15,0.01-0.19c0.25-0.27,0.19-0.62,0.25-0.94
|
||||
c0.03-0.14,0.03-0.29,0.03-0.43c0.01-0.19,0-0.39-0.06-0.59c-0.03,0.32-0.07,0.63-0.1,0.95c-0.03,0-0.05,0-0.08,0.01
|
||||
c-0.01-0.05-0.04-0.1-0.04-0.15c0.03-0.62,0.06-1.24,0.1-1.86c0.04-0.52,0.1-1.05,0.14-1.57c0.02-0.3,0-0.6,0-0.94
|
||||
c1.32,0.04,2.6,0.08,3.86,0.11c-0.03,0.58-0.08,1.13-0.09,1.68c-0.01,0.54,0.02,1.08,0.03,1.62c0,0.16-0.02,0.31-0.02,0.47
|
||||
c0.01,0.84,0.03,1.69,0.05,2.56c-1.13,0.13-3.98,0.11-4.34-0.05c0.13-0.07,0.23-0.12,0.34-0.17
|
||||
C42.71,105.08,42.68,104.91,42.65,104.74z M37.42,104.01c0.03-0.8,0.09-1.6,0.11-2.41c0.01-0.4,0.03-0.79,0.18-1.16
|
||||
c0.04-0.1,0.07-0.19-0.04-0.28c-0.04-0.03-0.05-0.12-0.05-0.18c0-0.35,0-0.71,0-1.1c1.5,0.02,2.95,0.06,4.46,0.24
|
||||
c0.06,0.81,0.14,1.6,0.16,2.39c0.02,0.58-0.04,1.16-0.05,1.73c-0.01,0.43,0,0.87,0.01,1.3c0,0.12,0.04,0.24,0.08,0.34
|
||||
c0.08,0.19,0.09,0.38-0.01,0.57c-0.25,0.08-3.47,0.02-4.86-0.09C37.42,104.92,37.41,104.46,37.42,104.01z M34.42,105.29
|
||||
c0.31-0.18,0.29-0.17,0.28-0.51c-0.02-0.68-0.01-1.37-0.02-2.05c-0.01-0.74-0.01-1.48-0.01-2.22c0-0.3,0.01-0.61,0.01-0.91
|
||||
c0-0.2-0.02-0.4-0.03-0.64c0.67,0,1.37,0,2.07,0c-0.02,1.16,0.13,2.29,0.14,3.43c0.01,0.54-0.05,1.08-0.06,1.62
|
||||
c-0.01,0.46,0,0.91,0,1.43c-0.84,0.02-1.66,0.05-2.57,0.07C34.33,105.39,34.36,105.32,34.42,105.29z"/>
|
||||
<path d="M69.48,109.95c0.3,0,0.61-0.02,0.91-0.01c0.3,0.01,8.9-0.12,9.77-0.12c0.83,0,3.33-0.12,3.31-0.17c0.63,0,1.25,0,1.91,0
|
||||
c-0.15-0.16-0.44-0.1-0.45-0.37c0-0.02-0.09-0.05-0.14-0.05c-0.32,0.02-0.61-0.08-0.88-0.24c-0.1-0.06-1.32-0.09-1.44-0.09
|
||||
c-0.35-0.02-6.27,0.18-6.37,0.13c-0.39-0.17-15.17,0.27-15.24,0.27c-0.26,0.01-0.53,0.02-0.79,0.02c-1.89,0-5.86-0.02-5.96-0.03
|
||||
c-0.03-0.2-0.11-0.52-0.13-0.52c-0.02,0.16-0.04,0.33-0.07,0.52c-0.27,0.26-0.57,0.17-1.07,0.19c-0.82,0.03-2.09-0.1-2.91-0.08
|
||||
c-1,0.02-1.99-0.03-2.99,0.01c-1.59,0.06-3.19-0.01-4.79-0.02c-0.62-0.01-12.52-0.01-12.7-0.03c-0.58-0.08-1.16-0.07-1.73,0.04
|
||||
c-0.11,0.02-0.26,0.09-0.31,0.19c-0.08,0.14,0,0.28,0.17,0.34c0.02,0.01,0.03,0.04,0.05,0.06c-0.12,0.27-0.12,0.27,0,0.57
|
||||
c0.2,0,0.4,0,0.63,0c0.03,0.23,0.06,0.44,0.07,0.65c0.03,0.67,0.13,4.09,0.15,5.13c0.02,0.95,0.21,4.76,0.17,5.43
|
||||
c-0.02,0.42-0.04,0.84-0.34,1.19c-0.07,0.08-0.07,0.21-0.11,0.35c0.61-0.05,0.52-0.6,0.78-0.88c-0.04,0.45-0.09,0.91-0.14,1.42
|
||||
c0.05,0.03,0.13,0.05,0.18,0.1c0.11,0.11,0.48-0.15,0.5-0.27c0.02-0.17-0.13-2.33-0.13-2.62c0-0.38-0.06-0.77-0.02-1.15
|
||||
c0.13-1.13,0.09-2.27,0.14-3.4c0.08-1.82,0.18-3.64,0.27-5.46c0.01-0.19,0.05-0.39,0.07-0.61c0.13-0.01,0.25-0.03,0.36-0.02
|
||||
c0.51,0.02,6.93-0.05,7.76,0c0.34,0.02,1-0.02,1.37,0c0.03,0.44,0.26,1.08,0.05,1.5c-0.05,0.11-0.01,1.12,0.01,1.49
|
||||
c0.02,0.46,0.09,3.16,0.06,3.83c-0.04,1.37-0.1,2.74-0.16,4.11c-0.03,0.7,0.35,2.18,0.53,2.24c0.06-0.14,0.15-0.26,0.17-0.4
|
||||
c0.06-0.44,0.12-0.89,0.13-1.34c0.02-1.41,0.03-2.82,0.03-4.23c0-1.77-0.01-3.54-0.03-5.3c0-0.21-0.04-0.41-0.07-0.6
|
||||
c-0.13-0.04-0.23-0.07-0.33-0.1c0-0.32,0-0.62,0-0.95c0.42,0,0.82,0,1.22,0c-0.37-0.17-0.79-0.03-1.14-0.26c0.68,0,1.35,0,2.05,0
|
||||
c0.02,0.15,0.06,0.28,0.06,0.41c0.01,0.61,0.01,1.21,0.01,1.82c0,0.32,0.04,0.64-0.01,0.95c-0.1,0.64-0.09,1.28-0.08,1.93
|
||||
c0.02,0.82-0.02,1.63-0.05,2.45c-0.03,0.75-0.07,1.5-0.11,2.25c-0.02,0.4-0.05,0.79-0.06,1.19c-0.01,0.43,0.01,0.87,0.02,1.3
|
||||
c0.01,0.28,0.13,1.13,0.32,1.1c0.28-0.33,0.04-0.74,0.22-1.16c0.04,0.22,0.07,0.36,0.1,0.52c0.13-0.12,0.32-10.71,0.11-10.97
|
||||
c-0.02-0.03-0.01-0.08-0.01-0.12c-0.11-0.59,0.13-1.14,0.2-1.73c0.28,0,6.86-0.03,7.09-0.03c0.76,0.01,1.53,0.03,2.29,0.04
|
||||
c0.63,0.01,1.26,0,1.93,0c-0.02,0.53-0.04,1.04-0.06,1.58c-0.49-0.23-0.58-0.18-0.63,0.31c-0.04,0.43-0.14,0.86-0.09,1.29
|
||||
c0.01,0.13,0.02,0.26,0.02,0.39c0,0.87-0.01,1.74-0.01,2.6c0,0.3,0,0.6,0,0.91c-0.01,1-0.03,2-0.02,3c0,0.45,0.05,0.89,0.06,1.34
|
||||
c0.01,0.28,0.58,1.94,0.7,1.98c0.12-0.11,0.24-1.2,0.25-1.66c0.02-1.31,0.09-2.61,0.1-3.92c0.01-1.49-0.02-2.98-0.05-4.47
|
||||
c-0.01-0.7-0.08-1.39-0.1-2.09c-0.01-0.42,0.01-0.83,0.02-1.31c0.59,0,7.36-0.22,7.92-0.24c0.85-0.03,1.71-0.11,2.56-0.11
|
||||
c0.92,0,3.89,0.11,4.47,0.11c0,0.2,0,0.36,0,0.55c-0.08,0-0.15-0.01-0.21,0c-0.3,0.05-0.35,0.11-0.34,0.42
|
||||
c0,0.03,0.01,0.05,0,0.08c-0.02,0.4,0.16,0.84-0.18,1.2c-0.03,0.03-0.02,0.1-0.01,0.15c0.01,0.1,0.07,6.5,0.08,6.79
|
||||
c0.03,1.01,0.06,2.03,0.1,3.04c0.02,0.57,0.06,1.13,0.1,1.69c0.01,0.11,0.07,0.22,0.15,0.31c0.12,0.14,0.23,0.12,0.29-0.04
|
||||
c0.05-0.12,0.07-0.25,0.12-0.41c-0.1,0.03-0.16,0.05-0.23,0.07c-0.09-0.37-0.08-0.36,0.09-0.67c0.1-0.18,0.23-2.42,0.19-2.44
|
||||
c-0.2-0.15-0.14-1.02-0.14-1.25c0.02-1.65,0.24-7.9,0.16-9.37C69.25,110.02,69.32,109.95,69.48,109.95z M29.47,112.76
|
||||
c-0.02,0-0.03,0-0.05,0.01c-0.01-0.06-0.03-0.12-0.03-0.19c0.01-0.48,0.04-0.97,0.04-1.45c0-0.1-0.04-0.2-0.06-0.3
|
||||
c-0.03-0.11-0.07-0.21-0.14-0.38c0.19,0.02,0.31,0.04,0.45,0.06C29.61,111.28,29.54,112.02,29.47,112.76z M42.11,120.74
|
||||
c-0.01,0-0.02,0-0.03,0c0-0.1,0-0.19,0-0.29c0.01,0,0.02,0,0.03,0C42.11,120.55,42.11,120.65,42.11,120.74z M42.27,111.08
|
||||
c-0.16-0.22-0.15-0.45-0.16-0.71c0.12,0,0.2,0,0.33,0C42.38,110.62,42.32,110.85,42.27,111.08z"/>
|
||||
<path d="M107.71,125.36c-0.01-0.23-0.02-0.43-0.04-0.64c-0.03-0.3-0.1-0.36-0.41-0.34c-1.22,0.08-2.45,0.16-3.67,0.24
|
||||
c-0.83,0.05-4.02,0.15-4.78,0.17c-0.26,0.01-9.43,0.02-9.81,0.03c-0.79,0.03-1.58,0.11-2.37,0.11c-1.3,0-2.61-0.05-3.91-0.08
|
||||
c-0.45-0.01-0.9,0-1.35-0.02c-1.62-0.05-11.08,0.02-11.54,0.03c-1.27,0.02-4.37,0.19-4.64,0.19c-0.76,0-1.52-0.02-2.29,0.01
|
||||
c-1.11,0.05-2.21,0.05-3.32,0.09c-0.19,0.01-0.36,0.01-0.52-0.12c-0.07-0.06-0.66-0.06-0.84-0.05c-0.76,0.05-1.53,0.16-2.29,0.15
|
||||
c-1.48-0.02-17.35-0.61-18.93-0.65c-1.27-0.03-8.68,0.01-9.73-0.02c-0.78-0.02-3.1-0.01-3.48,0.02c-0.31,0.03-0.62,0.1-0.94,0.13
|
||||
c-0.25,0.02-0.42,0.11-0.53,0.4c0.28,0.12,2.77,0.3,3.75,0.29c0.96-0.02,1.92-0.04,2.88-0.03c0.8,0.01,1.61,0.05,2.41,0.08
|
||||
c0.87,0.03,1.74,0.05,2.61,0.08c0.13,0,11.7,0.41,12.82,0.46c0.82,0.04,3.9,0.1,4.62,0.1c0.76,0,1.53,0.01,2.29,0.02
|
||||
c0.24,0,0.72-0.06,0.72-0.09c-0.28-0.02-0.55-0.03-0.83-0.05c0-0.03,0-0.06,0-0.08c0.46-0.06,0.92-0.02,1.38,0.04
|
||||
c0.02,0,3.43-0.02,7.29-0.04c0-0.02,0-0.03,0-0.05c0.07,0.01,0.15,0.02,0.22,0.03c-0.05,0.01-0.1,0.01-0.15,0.02
|
||||
c4.88-0.03,10.43-0.07,10.73-0.07c1.29,0,2.58,0.01,3.88,0.02c0.63,0.01,5.69-0.03,6.23,0c0.79,0.03,2.65,0.01,2.79,0.03
|
||||
c0.39,0.03,2.88-0.02,3.73-0.04c0.66-0.02,8.65-0.05,10.52-0.08c0.46-0.01,0.92,0,1.39,0.01c0.49,0.01,0.97,0.06,1.46,0.06
|
||||
c0.8-0.01,1.6-0.05,2.4-0.06c0.74-0.02,1.48-0.01,2.21-0.03c0.28-0.01,0.55-0.08,0.83-0.12c0-0.03-0.01-0.05-0.01-0.08
|
||||
C108.24,125.4,107.97,125.38,107.71,125.36z M49.63,125.76c-0.01,0-0.03-0.03-0.1-0.09c0.11,0.02,0.16,0.03,0.21,0.04
|
||||
C49.7,125.73,49.67,125.75,49.63,125.76z"/>
|
||||
<path d="M69.9,31.26c0.2-0.01,0.43-0.09,0.58,0c0.22,0.12,0.4,0.05,0.6,0.02c0.16-0.02,0.31-0.04,0.47-0.04
|
||||
c0.69-0.02,1.37-0.03,2.06-0.04c1.24-0.02,2.48,0.09,3.72-0.03c0.59-0.06,1.18,0.03,1.78,0.04c0.84,0.02,1.69,0.05,2.53,0.03
|
||||
c0.47-0.01,0.97,0.02,1.38-0.4c-0.11-0.08-0.21-0.16-0.25-0.19c-0.05-0.16-0.06-0.29-0.13-0.36c-0.16-0.17-0.34-0.34-0.53-0.47
|
||||
c-0.15-0.1-0.22-0.23-0.24-0.39c-0.03-0.22-0.05-0.44-0.06-0.67c-0.02-0.28-0.1-0.42-0.25-0.44c-0.21-0.02-0.35,0.12-0.39,0.41
|
||||
c-0.02,0.19,0,0.39,0,0.63c-0.19-0.02-0.39-0.06-0.59-0.04c-0.22,0.02-0.39,0.01-0.45-0.25c-0.14,0.02-0.26,0.03-0.37,0.05
|
||||
c-0.16-0.37,0.18-0.68,0.07-1.05c-0.26-0.12-0.46-0.02-0.65,0.2c0.04,0.11,0.09,0.21,0.11,0.29c-0.21,0.21-0.4,0.4-0.59,0.58
|
||||
c-0.2-0.03-0.41-0.05-0.59-0.07c-0.03-0.1-0.04-0.2-0.09-0.27c-0.19-0.28-0.32-0.57-0.3-0.92c0.01-0.21-0.13-0.3-0.34-0.27
|
||||
c-0.11,0.01-0.22,0.04-0.36,0.06c-0.02-0.14-0.03-0.24-0.04-0.35c-0.06-0.53,0.06-1.09-0.24-1.58c-0.02-0.03-0.01-0.08-0.01-0.12
|
||||
c0.01-0.41,0.03-0.82,0.03-1.22c0-0.49,0.19-0.96,0.08-1.45c-0.02-0.08,0.04-0.18,0.06-0.25c0.59,0.22,0.65,0.85,1.07,1.2
|
||||
c-0.05-0.25-0.14-0.47-0.21-0.71c-0.14-0.45-0.21-0.77-0.26-1.23c-0.14-0.82-0.35-1.61-0.37-2.36c-0.01-0.22,0.27-0.35,0.46-0.43
|
||||
c0.22-0.09,0.86-0.68,0.83-1.58c-0.03-0.84-0.41-1.39-0.91-1.59c-0.87-0.54-1.96,0-2.22,0.3c-0.26,0.23-0.58,0.7-0.54,1.38
|
||||
c0.13,0.57,0.3,1.03,0.72,1.34c0.09,0.03,0.28,0.2,0.37,0.23c0.19,0.06,0.24,0.21,0.22,0.37c-0.04,0.3-0.1,0.6-0.16,0.89
|
||||
c-0.06,0.31-0.23,1.36-0.31,1.67c-0.56,2.6-1.76,4.36-2.31,5.2c-0.06,0.1-0.15,0.19-0.25,0.24c-0.15,0.08-0.18,0.19-0.23,0.34
|
||||
c-0.11,0.39-0.32,0.7-0.75,0.81c-0.02,0.01-0.04,0.03-0.06,0.05c-0.03,0.11-0.06,0.22-0.11,0.39c-0.08-0.1-0.15-0.15-0.15-0.19
|
||||
c0.01-0.12,0.08-0.23,0.09-0.34c0.03-0.32,0.07-0.65,0.06-0.98c-0.01-0.23-0.28-0.34-0.47-0.24c-0.27,0.14-0.48,0.7-0.4,0.99
|
||||
c0.06,0.2,0.09,0.4,0.15,0.64c-0.25,0.02-0.44,0.03-0.63,0.05c-0.23,0.03-0.27,0.11-0.22,0.41c0.25-0.11,0.37,0.04,0.48,0.22
|
||||
c-0.02,0.04-0.03,0.07-0.05,0.08c-0.1,0.08-0.18,0.17-0.1,0.3c0.07,0.12,0.2,0.13,0.32,0.08c0.19-0.08,0.41-0.14,0.57-0.26
|
||||
c0.23-0.18,0.44-0.2,0.69-0.1c0.09,0.03,0.23,0.06,0.29,0.01c0.3-0.25,0.66-0.24,1.01-0.22c0.46,0.02,0.92,0.07,1.37,0.08
|
||||
c1.25,0.01,2.5,0.01,3.76-0.01c0.24,0,0.43,0.14,0.64,0.15c0.23,0,0.46-0.12,0.69-0.12c0.44,0.01,0.88,0.07,1.32,0.11
|
||||
c0.02-0.02,0.21,0.05,0.22,0.05c0.38,0.29,0.84,0.1,0.98,0.75c-0.38,0.03-0.69,0.07-1,0.08c-0.11,0-0.26,0-0.34-0.07
|
||||
c-0.24-0.2-0.55-0.02-0.81,0c-0.09,0.01-0.18,0.03-0.27,0.03c-1.35,0-2.71,0.01-4.07,0.01c-0.66,0-1.32-0.03-1.98-0.05
|
||||
c-0.8-0.02-1.61-0.05-2.41-0.08c-0.41-0.01-0.82,0.02-1.22-0.06c-0.32-0.07-0.54,0.05-0.76,0.19c-0.16,0.11-0.16,0.19-0.11,0.39
|
||||
C69.57,31.31,69.74,31.27,69.9,31.26z M75.86,18.67c-0.32-0.24-0.58-0.76-0.57-1.13c0.03-0.67,0.41-1.1,0.97-1.22
|
||||
c0.6-0.12,1.24,0.22,1.47,0.79c0.3,0.99-0.23,1.68-0.69,1.71C76.75,18.84,76.18,18.91,75.86,18.67z M76.2,22.28
|
||||
c0.09-0.37,0.15-0.75,0.22-1.12c0.12,0.2,0.23,1.19,0.19,1.69c-0.29,0.04-0.29,0.04-0.72,0.58C76.01,23,76.12,22.64,76.2,22.28z
|
||||
M74.61,27.82c-0.06,0.42-0.32,0.83-0.04,1.28c-0.31,0.02-0.54,0.04-0.78,0.05c-0.24,0.01-0.29-0.07-0.23-0.32
|
||||
c0.02-0.1-0.11-0.3-0.12-0.4c-0.03-0.39,0.18-0.4,0.47-0.61c0.17-0.12,0.46-0.68,0.58-0.86c0.3-0.44,0.58-1.06,0.74-1.56
|
||||
c0.21-0.66,0.32-0.97,0.55-1.67c0.11,0.27,0.26,0.56,0.25,0.79c-0.01,0.24,0,0.72,0,0.96c0,0.85-0.27,1.46-0.68,2.22
|
||||
c-0.06-0.01-0.32-0.12-0.38-0.13C74.77,27.52,74.64,27.63,74.61,27.82z M76.22,29.15c-0.33-0.02-0.67,0.07-1.02-0.06
|
||||
c0.03-0.07,0.05-0.13,0.08-0.15c0.48-0.24,0.61-0.71,0.73-1.17c0.15-0.55,0.26-1.11,0.38-1.67
|
||||
C76.32,27.11,76.55,28.14,76.22,29.15z M76.55,24.07c-0.17-0.32,0.05-0.61,0.05-0.91C76.65,23.47,76.71,23.78,76.55,24.07z
|
||||
M77.85,29.11c-0.81,0.04-0.53-0.38-0.51-0.65c0.14,0.05,0.51,0.51,0.65,0.56C77.92,29.07,77.89,29.11,77.85,29.11z"/>
|
||||
<path d="M53.59,108c0.02,0,0.05,0.02,0.07,0.03c0.22,0.03,0.44,0.09,0.66,0.09c0.38-0.01,11.62-0.25,12.26-0.3
|
||||
c0.26-0.02,0.53,0,0.79-0.02c0.7-0.05,1.38-0.12,2.09-0.06c0.98,0.09,1.97,0.11,2.96,0.15c0.44,0.02,0.88,0,1.32-0.09
|
||||
c-0.04-0.36-0.19-0.55-0.54-0.52c-0.14,0.01-0.29-0.02-0.43-0.05c-0.1-0.02-0.22-0.05-0.27-0.12c-0.14-0.18-0.31-0.17-0.5-0.15
|
||||
c-0.64,0.07-3.8,0.31-4.73,0.3c-1.56-0.02-3.11-0.11-4.67-0.12c-1.29-0.01-2.59,0.07-3.88,0.11c-1.05,0.04-12.43,0-13.64,0.01
|
||||
c-0.44,0-2.73,0.09-3.44,0.07c-1.07-0.03-12.25-0.1-14.17-0.15c-0.07,0-0.15-0.02-0.19,0.02c-0.22,0.2-0.49,0.18-0.75,0.18
|
||||
c-0.28,0-0.47,0.09-0.62,0.33c-0.06,0.1-0.19,0.19-0.1,0.3c0.06,0.07,0.2,0.11,0.3,0.12c0.79,0.03,1.58,0.06,2.37,0.06
|
||||
c1.15,0,2.3-0.02,3.44-0.04c0.92-0.02,9-0.21,10.03-0.02c0.04,0.01,2.15-0.01,3.16,0.01c1.95,0.04,3.9,0.09,5.86,0.14
|
||||
c0.51,0.01,1.03,0.04,1.54,0.05c0.6,0.01,1.19,0,1.79-0.01c-0.15-0.04-0.3-0.09-0.46-0.11c-0.41-0.03-0.81-0.04-1.22-0.07
|
||||
c-0.06,0-0.13-0.04-0.19-0.06c0-0.03,0.01-0.06,0.01-0.08C52.81,108,53.2,108,53.59,108z M72.09,107.5c0.22,0,0.46-0.06,0.66,0.14
|
||||
c-0.23-0.01-0.45-0.03-0.68-0.04C72.08,107.57,72.09,107.53,72.09,107.5z"/>
|
||||
<path d="M32.43,97.78c0.07,0.02,0.15,0.04,0.23,0.04c0.63,0.02,8.8-0.05,9.92-0.07c0.51-0.01,2.91-0.02,3.59-0.02
|
||||
c0.29,0,0.58-0.05,0.87-0.05c1.07,0.01,2.13,0.03,3.2,0.04c2.73,0.01,5.47,0.03,8.2,0.01c2.74-0.02,15.51-0.2,16.56-0.24
|
||||
c0.37-0.01,0.74,0,1.11,0c1.25-0.01,14,0.08,16.9-0.07c0.92-0.05,3.78-0.04,4.02-0.04c-0.03-0.31-0.19-0.42-0.4-0.44
|
||||
c-0.31-0.03-0.63-0.03-0.95-0.03c-0.54-0.01-1.08-0.01-1.61-0.01c-0.16,0-0.32-0.01-0.47,0c-0.54,0.03-3.02,0.09-3.57,0.11
|
||||
c-1.13,0.04-2.27,0.08-3.4,0.08c-9.18,0.01-31.34,0.12-31.84,0.13c-0.71,0.01-1.42,0.02-2.13,0.02c-0.54,0-12.79-0.05-14.62-0.06
|
||||
c-0.2,0-3.64-0.13-3.83-0.17c-0.61-0.13-1.18,0.08-1.75,0.2c-0.15,0.03-0.25,0.15-0.25,0.32C32.17,97.69,32.3,97.74,32.43,97.78z"
|
||||
/>
|
||||
<path d="M80.49,83.44c0.03-0.15,0.05-0.3,0.08-0.45c-1.44-1.09-2.89-2.18-4.33-3.27c-0.25,0.17-0.27,0.24-0.12,0.45
|
||||
c0.06,0.08,0.15,0.14,0.22,0.22c0.1,0.11,0.2,0.22,0.29,0.33c-0.01,0.03-0.03,0.05-0.04,0.08c-0.2-0.18-0.4-0.28-0.64-0.1
|
||||
c-0.57,0.43-1.15,0.83-1.7,1.29c-0.37,0.31-0.68,0.68-1.02,1.03c-0.21,0.21-0.44,0.42-0.65,0.63c-0.09,0.09-0.2,0.18-0.26,0.3
|
||||
c-0.04,0.08-0.03,0.2,0.01,0.28c0.03,0.06,0.15,0.1,0.22,0.1c0.13,0,0.25-0.04,0.4-0.08c0.02,0.21,0.03,0.38,0.05,0.57
|
||||
c0.15-0.02,0.26-0.03,0.33-0.04c0.19,0.21-0.03,0.46,0.17,0.71c0.03-0.15,0.06-0.24,0.06-0.32c0-0.39,0.12-0.75,0.24-1.12
|
||||
c0.14-0.46,0.45-0.8,0.8-1.11c0.06-0.05,0.14-0.1,0.21-0.1c0.29-0.03,0.58-0.05,0.87-0.07c0.06,0,0.12,0.04,0.27,0.1
|
||||
c-0.44,0.12-0.7,0.4-1.08,0.45c-0.19,0.03-0.36,0.15-0.53,0.25c-0.06,0.04-0.1,0.15-0.1,0.23c0,0.05,0.1,0.1,0.16,0.14
|
||||
c0.04,0.02,0.1,0.01,0.16,0c0.6-0.07,1.2-0.16,1.81-0.2c0.35-0.03,0.71,0.03,1.1,0.05c-0.05,0.07-0.06,0.11-0.07,0.12
|
||||
c-0.4,0.11-2.07,0.36-2.35,0.29c-0.11,0.27-0.05,0.51-0.05,0.76c0,0.42-0.03,1.42,0.05,1.54c0.09,0.13,0.11,0.31,0.17,0.5
|
||||
c0.27-0.16,1.26-0.3,1.51-0.24c0.13,0.03,0.29,0.02,0.42-0.03c0.25-0.09,1.36-0.09,1.41,0.06c0.37-0.14,0.51-0.42,0.37-0.73
|
||||
c-0.18-0.4-0.16-1.82-0.2-1.98c-0.13,0.01-0.22,0.02-0.31,0.03c-0.12,0-0.28-0.05-0.37,0.01c-0.09,0.06-0.12,0.22-0.17,0.34
|
||||
c-0.04,0.08-0.05,0.18-0.1,0.26c-0.03,0.05-0.09,0.07-0.2,0.15c0.08-0.34-0.08-0.43-0.31-0.48c-0.06-0.01-0.1-0.08-0.18-0.15
|
||||
c0.21-0.06,0.37-0.11,0.54-0.15c0.23-0.05,0.24-0.23,0.21-0.39c0.37-0.03,0.71-0.05,1.04-0.07c0.32,0.23,0.27,0.63,0.45,0.9
|
||||
c0.4-0.01,0.48-0.1,0.35-0.43c-0.11-0.27-0.25-0.52-0.02-0.85C79.88,83.49,80.19,83.5,80.49,83.44z M75.58,85.07
|
||||
c-0.02-0.1-0.04-0.21-0.07-0.32C75.8,84.76,75.8,84.77,75.58,85.07z M76.65,84.47c0.15,0.04,0.27,0.06,0.38,0.11
|
||||
c0.03,0.02,0.07,0.13,0.04,0.16c-0.11,0.18-0.27,0.06-0.42,0.08C76.65,84.7,76.65,84.61,76.65,84.47z M76.72,85.22
|
||||
c0.08-0.01,0.17-0.01,0.29-0.02c0,0.16,0,0.31,0,0.45C76.73,85.59,76.73,85.59,76.72,85.22z M77.66,85.04
|
||||
c0.04,0.26,0.06,0.46,0.09,0.67C77.48,85.62,77.45,85.42,77.66,85.04z M77.31,86.23c-0.15,0.05-0.24,0.08-0.33,0.1
|
||||
c-0.02-0.05-0.05-0.08-0.04-0.09c0.04-0.07,0.09-0.14,0.13-0.2C77.14,86.09,77.2,86.14,77.31,86.23z M78.06,82.3
|
||||
c-0.22-0.02-0.39-0.03-0.56-0.04C77.72,81.99,77.77,82,78.06,82.3z M76.05,82.08c0.23-0.29,0.4-0.34,0.74-0.24
|
||||
c0.12,0.04,0.26,0.05,0.38,0.07c0,0.03,0,0.07-0.01,0.1C76.8,82.03,76.43,82.06,76.05,82.08z M78.21,83.01
|
||||
c-0.18,0.01-0.36,0.03-0.55,0.03c-0.18,0-0.36-0.03-0.54-0.05c0.52-0.1,1.02-0.17,1.6-0.15C78.57,83.1,78.38,83,78.21,83.01z"/>
|
||||
<path d="M56.07,124.61c0.04-0.27,0.08-0.52,0.13-0.77c0.08,0,0.39-0.44,0.46-0.66c0.07-0.21,0.05-1.55,0.07-1.77
|
||||
c0.11-1.13,0.32-1.13,0.39-2.26c0.06-0.83,0.09-1.66,0.13-2.49c0.01-0.12,0-0.25,0-0.37c-0.03,0-0.06,0-0.1-0.01
|
||||
c-0.1,0.67-0.2,1.35-0.3,2.02c-0.01,0-0.03,0-0.04,0c-0.01-0.66-0.02-1.31-0.04-1.97c-0.03-0.76-0.07-1.53-0.11-2.29
|
||||
c-0.02-0.42-0.12-0.85-0.06-1.26c0.05-0.32-0.1-0.59-0.05-0.89c0.01-0.03-0.03-0.09-0.05-0.1c-0.21-0.09-0.17-0.28-0.17-0.44
|
||||
c0-0.11,0.01-0.21-0.01-0.32c-0.03-0.14-0.23-0.22-0.31-0.11c-0.1,0.14-0.23,0.32-0.22,0.48c0.01,0.87,0.07,1.73,0.1,2.6
|
||||
c0.02,0.58,0.06,1.16,0.01,1.73c-0.06,0.71-0.08,1.41-0.06,2.12c0.04,1.2,0.03,2.39,0.04,3.59c0.01,0.92,0.03,1.84,0.05,2.76
|
||||
C55.94,124.35,55.91,124.5,56.07,124.61z M56.51,118.93c0.02,0,0.03,0,0.05,0c0,0.35,0,0.69,0,1.04c-0.02,0-0.03,0-0.05,0
|
||||
C56.51,119.63,56.51,119.28,56.51,118.93z"/>
|
||||
<path d="M45.67,116.82c-0.27,0.53-0.03,1.06-0.27,1.52c-0.06-0.87-0.09-1.74-0.1-2.6c-0.01-0.87,0.09-1.74-0.02-2.6
|
||||
c-0.02-0.18,0.05-0.39-0.02-0.54c-0.14-0.32-0.1-0.64-0.1-0.97c0-0.14,0-0.29,0-0.43c0-0.13,0-0.26,0-0.39c-0.03,0-0.06,0-0.08,0
|
||||
c-0.03,0.15-0.06,0.3-0.09,0.44c-0.22,0.12-0.49,0.21-0.49,0.5c0.01,0.58-0.09,1.15-0.07,1.73c0.05,1.37,0.03,2.74,0.04,4.1
|
||||
c0.01,0.88,0.04,1.75,0.01,2.63c-0.04,1.08,0.01,2.16,0.12,3.24c0.02,0.17,0.05,0.34,0.22,0.38c0.17,0.03,0.2,0.13,0.23,0.27
|
||||
c0.02,0.06,0.08,0.1,0.16,0.19c0.05-0.13,0.06-0.24,0.12-0.29c0.29-0.22,0.25-0.54,0.26-0.83c0.01-0.55-0.04-1.1-0.03-1.65
|
||||
c0.02-1.13,0.08-2.27,0.11-3.4C45.68,117.7,45.67,117.3,45.67,116.82z"/>
|
||||
<path d="M51.8,115.49c0.05-1.46-0.06-2.91-0.09-4.37c0-0.09-0.08-0.22-0.15-0.24c-0.06-0.02-0.16,0.08-0.25,0.12
|
||||
c-0.09,0.04-0.17,0.08-0.25,0.11c0.26,1.72,0.23,3.43-0.09,5.14c-0.05-1.69-0.09-3.37-0.14-5.06c-0.09,0.18-0.15,0.37-0.15,0.56
|
||||
c0,0.71,0.01,1.42,0.02,2.13c0.02,0.79,0.04,1.58,0.07,2.37c0.04,0.86,0.12,1.71,0.16,2.57c0.03,0.84,0.03,1.69,0.03,2.53
|
||||
c0,0.44,0.06,2.54,0.06,2.79c0,0.15,0.09,0.21,0.21,0.25c0.15,0.05,0.58-1.14,0.57-1.37c-0.02-0.91-0.04-1.81-0.03-2.72
|
||||
c0-0.59,0.06-1.19,0.06-1.78C51.83,117.52,51.76,116.51,51.8,115.49z"/>
|
||||
<path d="M64.66,115.81c-0.03-0.37-0.03-0.73-0.03-1.1c0-0.74,0.02-1.48,0.02-2.22c0-0.44-0.21-0.59-0.66-0.47
|
||||
c0.03-0.48,0.05-0.95,0.08-1.49c-0.19,0.21-0.25,0.39-0.26,0.59c-0.01,0.54-0.02,1.08-0.04,1.62c-0.01,0.3-0.02,0.6-0.02,0.91
|
||||
c-0.01,0.59-0.01,1.18-0.01,1.77c0,0.14-0.02,0.29-0.02,0.43c0.01,0.8,0.03,1.61,0.04,2.41c0.01,0.75,0.03,1.5,0.04,2.25
|
||||
c0.01,0.76,0,1.53-0.01,2.29c0,0.43,0,0.87,0,1.33c0.15,0.05,0.27,0.09,0.39,0.13c0.06,0.01,0.13,0.02,0.18,0
|
||||
c0.17-0.08,0.3-0.64,0.16-0.74c-0.15-0.1-0.14-0.21-0.13-0.33c0.03-0.46,0.06-0.92,0.11-1.38
|
||||
C64.77,119.82,64.81,117.82,64.66,115.81z"/>
|
||||
<path d="M33.57,122.74c-0.01-0.22-0.01-0.45-0.05-0.67c-0.13-0.67,0.04-1.33,0.01-2c-0.06-1.14-0.03-2.29-0.02-3.44
|
||||
c0.01-1.38,0.03-2.77,0.04-4.15c0-0.27-0.02-0.53-0.04-0.82c-0.08,0.05-0.11,0.06-0.17,0.1c-0.03-0.22-0.06-0.41-0.08-0.61
|
||||
c-0.01-0.13-0.09-0.21-0.2-0.19c-0.09,0.01-0.18,0.09-0.23,0.16c-0.05,0.08-0.03,0.24-0.1,0.28c-0.23,0.15-0.23,0.37-0.23,0.59
|
||||
c0.02,0.63,0.06,1.26,0.06,1.89c0,1.19-0.02,2.37-0.03,3.56c0,0.6-0.03,1.21,0.11,1.8c0.06,0.25,0.11,0.51,0.13,0.77
|
||||
c0.04,0.62,0.06,1.24,0.09,1.85c0.02,0.29,0.02,0.58,0.07,0.87c0.05,0.25,0.15,0.5,0.24,0.74c0.02,0.06,0.12,0.09,0.24,0.17
|
||||
c0.01-0.2-0.03-0.4,0.03-0.44C33.68,123.09,33.58,122.91,33.57,122.74z"/>
|
||||
<path d="M81.35,34.96c0.21-0.23,0.72-0.07,0.72-0.58c0-0.02,0.04-0.05,0.06-0.05c0.25,0,0.19-0.22,0.22-0.35
|
||||
c0.05-0.18-0.04-0.28-0.23-0.27c-0.24,0.01-0.47,0.03-0.71,0.07c-1.14,0.2-8.62,0.01-9.41,0.03c-0.31,0.01-0.77-0.1-1.28,0.04
|
||||
c-0.12,0.19-0.06,0.34,0.07,0.41c0.15,0.09,0.33,0.18,0.5,0.19c0.93,0.06,1.86-0.04,2.79,0.08c0.74,0.1,1.5,0.08,2.25,0.09
|
||||
c1.56,0.01,3.11,0,4.67,0.01c0.12,0,0.25,0,0.41,0c-0.06,0.13-0.09,0.19-0.12,0.26C81.27,34.96,81.3,35.05,81.35,34.96z"/>
|
||||
<path d="M66.68,117.21c0.03-0.89,0.08-1.78,0.07-2.67c-0.01-0.5,0-1,0-1.5c0.01-0.42,0.03-0.84,0.04-1.26c0.01-0.45,0-0.9,0-1.35
|
||||
c-0.03,0-0.06,0-0.09-0.01c-0.03,0.16-0.05,0.32-0.09,0.52c-0.11,0-0.24,0.02-0.36,0c-0.24-0.06-0.35-0.02-0.38,0.22
|
||||
c-0.02,0.16,0.01,0.34,0.02,0.51c0.02,0.22,0.11,0.43-0.05,0.64c-0.04,0.06-0.04,0.18-0.01,0.26c0.19,0.56,0.16,1.14,0.16,1.72
|
||||
c0,0.77-0.04,1.55-0.02,2.32c0.02,1.05,0.11,2.1,0.12,3.16c0.01,1-0.05,2-0.04,2.99c0,0.23,0.13,0.47,0.25,0.68
|
||||
c0.1,0.17,0.31,0.13,0.36-0.05c0.06-0.2,0.12-0.42,0.09-0.62c-0.09-0.46-0.12-0.92-0.15-1.39c-0.02-0.41,0.01-0.82,0.01-1.23
|
||||
C66.64,119.19,66.64,118.2,66.68,117.21z"/>
|
||||
<path d="M31.42,115.57c0.01-0.87-0.04-1.73-0.08-2.6c-0.02-0.42-0.08-0.84-0.13-1.25c-0.02-0.14-0.03-0.3-0.23-0.32
|
||||
c-0.18-0.01-0.36,0.12-0.41,0.31c-0.03,0.11-0.03,0.24-0.02,0.35c0.03,1.13,0.06,2.27,0.08,3.4c0.02,1.15,0.04,2.29,0.06,3.44
|
||||
c0.03,1.38,0.05,2.77,0.09,4.15c0.01,0.31,0.08,0.61,0.13,0.93c0.35-0.07,0.3-0.31,0.32-0.47c0.14-0.13,0.19-0.6,0.16-0.71
|
||||
c-0.05-0.19-0.1-0.39-0.1-0.58c-0.01-1.1-0.01-2.19,0.01-3.29C31.33,117.81,31.41,116.69,31.42,115.57z"/>
|
||||
<path d="M74.57,68.98c-0.17,0.34-0.36,0.67-0.51,1.01c-0.14,0.32-0.07,0.65,0.13,0.94c0.05,0.08,0.18,0.17,0.23,0.16
|
||||
c0.09-0.03,0.19-0.14,0.22-0.23c0.05-0.17,0.06-0.36,0.07-0.55c0.01-0.3,0.07-0.6,0.19-0.87c0.29,0,0.51-0.08,0.65-0.33
|
||||
c0.06-0.11,0.18-0.19,0.27-0.29c0.12-0.15,0.25-0.23,0.47-0.18c0.26,0.05,0.53,0.03,0.85,0.05c0.03,0.06,0.09,0.17,0.16,0.31
|
||||
c-0.59,0.05-1.12,0.15-1.61,0.41c0.06,0.28,0.05,0.3,0.18,0.32c0.59,0.11,1.19,0.4,1.81,0.01c0.05,0.18,0.09,0.34,0.14,0.5
|
||||
c0.28-0.16-0.06-0.58,0.36-0.66c0.12,0.24,0.25,0.48,0.36,0.73c0.08,0.18-0.01,0.4-0.19,0.52c-0.16,0.1-0.3,0.06-0.38-0.11
|
||||
c-0.04-0.08-0.05-0.18-0.09-0.26c-0.07-0.14-0.2-0.18-0.33-0.09c-0.13,0.09-0.14,0.22-0.07,0.35c0.16,0.27,0.65,0.49,0.88,0.37
|
||||
c0.31-0.16,0.62-0.48,0.48-0.9c-0.14-0.43-0.35-0.85-0.56-1.25c-0.3-0.55-0.81-0.85-1.37-1.09c0.12-0.32-0.24-0.28-0.33-0.49
|
||||
c0.06-0.17,0.04-0.32-0.17-0.43c-0.39-0.21-0.76-0.08-0.85,0.36c-0.05,0.23-0.01,0.49-0.01,0.81
|
||||
C75.2,68.32,74.79,68.54,74.57,68.98z M77.63,69.59c-0.16-0.07-0.28-0.13-0.44-0.2c0.08-0.1,0.14-0.18,0.21-0.27
|
||||
C77.61,69.21,77.62,69.38,77.63,69.59z"/>
|
||||
<path d="M74.01,32.7c0.82-0.01,1.63-0.04,2.45-0.03c0.69,0,1.37,0.04,2.06,0.07c0.38,0.02,0.76,0.08,1.14,0.11
|
||||
c0.33,0.03,0.66,0.05,0.99,0.05c0.17,0,0.34-0.09,0.5-0.13c0-0.1,0.01-0.12,0-0.13c-0.03-0.04-0.07-0.08-0.1-0.12
|
||||
c-0.18-0.3-0.43-0.4-0.79-0.37c-0.56,0.05-1.13,0.04-1.7,0.04c-0.46,0-0.92-0.04-1.38-0.04c-0.72,0-1.45,0.04-2.17,0.04
|
||||
c-1.19-0.01-2.37-0.05-3.56-0.08c-0.22-0.01-0.42,0.01-0.63,0.16c0.03,0.14-0.13,0.25,0.11,0.48c0.46,0.01,0.9,0.02,1.35,0.01
|
||||
C72.86,32.73,73.43,32.71,74.01,32.7z"/>
|
||||
<path d="M63.95,83.37c0.1-0.14,0.08-0.25,0.04-0.4c-0.13-0.44-0.51-0.66-0.8-0.95c-0.18-0.18-0.41-0.21-0.66-0.16
|
||||
c-0.51,0.11-0.98,0.28-1.4,0.61c-0.28,0.22-0.58,0.41-0.86,0.63c-0.15,0.12-0.3,0.27-0.38,0.44c-0.06,0.14-0.04,0.33-0.02,0.49
|
||||
c0.01,0.11,0.07,0.21,0.13,0.31c0.04,0.06,0.12,0.15,0.16,0.14c0.09-0.03,0.18-0.09,0.23-0.17c0.05-0.07,0.02-0.2,0.07-0.25
|
||||
c0.3-0.29,0.62-0.57,0.94-0.83c0.11-0.09,0.26-0.13,0.42,0.01c-0.02,0.11-0.03,0.24-0.05,0.36c0.25,0.16,0.47,0.27,0.78,0.21
|
||||
c0.28-0.05,0.57-0.01,0.86,0c0.14,0,0.28,0.02,0.4,0.04C63.87,83.64,63.87,83.48,63.95,83.37z M61.99,82.88
|
||||
c0.26-0.05,0.45-0.08,0.64-0.11C62.54,83.15,62.42,83.18,61.99,82.88z M62.77,82.81c0.39-0.05,0.48,0.05,0.59,0.57
|
||||
C63.02,83.29,63.13,82.88,62.77,82.81z"/>
|
||||
<path d="M63.48,84.96c-0.24-0.11-0.45-0.06-0.65-0.03c-0.49,0.06-0.98,0.2-1.48,0.05c-0.06-0.02-0.15,0.03-0.23,0.05
|
||||
c-0.02,0.12-0.04,0.23-0.06,0.32c-0.17,0.09-0.39-0.01-0.46,0.26c-0.04,0.17-0.27,0.3-0.12,0.5c0.75,0,1.49,0,2.23,0
|
||||
c-0.39,0.21-0.8,0.35-1.24,0.23c-0.07-0.02-0.16-0.01-0.24-0.01c-0.1,0-0.23-0.03-0.3,0.02c-0.08,0.06-0.1,0.19-0.16,0.29
|
||||
c-0.17,0.04-0.35,0.07-0.52,0.1c-0.06,0.21-0.04,0.34,0.2,0.35c0.26,0.01,0.53,0.01,0.79,0.04c0.55,0.07,1.06,0.04,1.51-0.37
|
||||
c0.15-0.14,0.3-0.23,0.25-0.45c0.07-0.03,0.12-0.05,0.17-0.06c0.12-0.04,0.25-0.1,0.19-0.24c-0.03-0.07-0.14-0.14-0.23-0.16
|
||||
c-0.19-0.05-0.38-0.07-0.59-0.11c0.03-0.14,0.05-0.26,0.08-0.36C62.9,85.22,63.26,85.27,63.48,84.96z"/>
|
||||
<path d="M52.25,85.24c-0.01-0.25,0.18-0.32,0.33-0.44c0.05-0.04,0.11-0.14,0.09-0.19c-0.02-0.07-0.1-0.14-0.17-0.18
|
||||
c-0.06-0.04-0.18,0-0.22-0.05c-0.28-0.28-0.55-0.12-0.83-0.02c-0.21,0.08-0.42,0.15-0.63,0.19c-0.33,0.06-0.38,0.09-0.43,0.38
|
||||
c-0.16,0.12-0.29,0.21-0.42,0.3c0.07,0.09,0.15,0.18,0.23,0.28c-0.02,0.09-0.05,0.22-0.06,0.34c-0.05,0.34,0.02,0.45,0.31,0.63
|
||||
c0.09,0.06,0.17,0.15,0.27,0.23c0.3-0.14,0.61-0.28,0.91-0.43c0.04-0.02,0.09-0.08,0.09-0.12c0.01-0.2,0.17-0.41-0.15-0.58
|
||||
c0.23-0.09,0.38-0.15,0.52-0.21C52.14,85.34,52.25,85.29,52.25,85.24z"/>
|
||||
<path d="M58.16,66.65c-0.16-0.14-0.35-0.12-0.52-0.03c-0.22,0.12-0.42,0.27-0.62,0.41c-0.18,0.13-0.35,0.27-0.51,0.42
|
||||
c-0.06,0.06-0.1,0.15-0.13,0.23c-0.1,0.28-0.19,0.57-0.29,0.87c-0.22-0.1-0.43-0.11-0.58,0.1c-0.01,0.21,0.13,0.33,0.29,0.31
|
||||
c0.3-0.03,0.6-0.09,0.88-0.19c0.22-0.08,0.25-0.24,0.15-0.48c-0.09-0.21-0.04-0.39,0.08-0.55c0.1-0.14,0.21-0.27,0.34-0.38
|
||||
c0.28-0.25,0.51-0.22,0.82,0.1c-0.13,0.01-0.24,0.03-0.35,0.04c0,0.03,0,0.06-0.01,0.09c0.1,0.03,0.19,0.07,0.29,0.07
|
||||
c0.3,0.01,0.38-0.1,0.3-0.4c-0.01-0.05-0.04-0.11-0.02-0.14C58.43,66.92,58.3,66.78,58.16,66.65z"/>
|
||||
<path d="M54.73,70.12c0.03,0.18,0.13,0.29,0.32,0.28c0.25-0.02,0.49-0.04,0.7-0.22c0.08-0.07,0.2-0.12,0.3-0.16
|
||||
c0.09-0.04,0.19-0.07,0.29-0.1c-0.14-0.27-0.14-0.28-0.54-0.42c-0.16-0.05-0.33-0.05-0.5-0.04c-0.05,0-0.1,0.13-0.12,0.21
|
||||
c-0.01,0.06,0.04,0.14,0.08,0.25c-0.12-0.03-0.19-0.05-0.26-0.06C54.79,69.82,54.7,69.91,54.73,70.12z"/>
|
||||
<path d="M57.42,69.01c0.02,0.11,0.05,0.22,0.09,0.4c0.14-0.13,0.24-0.19,0.3-0.28c0.05-0.06,0.09-0.21,0.07-0.23
|
||||
c-0.08-0.06-0.19-0.12-0.29-0.12C57.48,68.79,57.4,68.88,57.42,69.01z"/>
|
||||
<path d="M88.02,51.18c0.4,0.27,0.79,0.54,1.19,0.8c0.01,0,0.02,0,0.02,0c-0.06-0.08-0.11-0.17-0.19-0.22
|
||||
c-0.25-0.19-0.51-0.37-0.77-0.55C88.21,51.17,88.04,51.16,88.02,51.18z"/>
|
||||
<path d="M94.66,55.8c0.23,0.22,0.45,0.44,0.69,0.65c0.1,0.09,0.23,0.16,0.35,0.23c-0.04-0.05-0.08-0.1-0.12-0.15
|
||||
C95.3,56.24,95.05,55.93,94.66,55.8z"/>
|
||||
<path d="M94.63,55.76c-0.12-0.27-0.33-0.43-0.6-0.52C94.23,55.41,94.43,55.59,94.63,55.76z"/>
|
||||
<path d="M109.9,73.57c-0.01,0.24,0.13,0.39,0.25,0.57L109.9,73.57z"/>
|
||||
<path d="M91,69.63c0.01-0.23-0.24-1.14-0.41-1.28C90.64,68.53,90.93,69.42,91,69.63z"/>
|
||||
<path d="M42.67,83.42c0.19-0.23,0.17-0.31-0.13-0.39c-0.4-0.11-0.78-0.05-1.16,0.11c-0.29,0.13-0.3,0.16-0.15,0.45
|
||||
c-0.17,0.08-0.34,0.15-0.58,0.26c0.21,0.19,0.37,0.32,0.51,0.45c-0.19,0.05-0.41,0.04-0.51,0.15c-0.12,0.12-0.14,0.35-0.2,0.53
|
||||
c-0.02,0.02-0.05,0.05-0.07,0.07c0.1,0.06,0.19,0.17,0.3,0.18c0.36,0.04,0.71,0.24,1.06,0.12c0.18,0.2,0.35,0.38,0.5,0.55
|
||||
c0.4-0.19,0.46-0.25,0.43-0.47c-0.01-0.14-0.02-0.28-0.04-0.42c-0.05-0.37,0.05-0.76-0.2-1.09c-0.02-0.03-0.01-0.11,0.01-0.15
|
||||
C42.51,83.66,42.58,83.53,42.67,83.42z M42.02,83.49c-0.12,0.06-0.25,0.08-0.38,0.12c-0.01-0.04-0.02-0.08-0.03-0.11
|
||||
c0.22-0.16,0.46-0.24,0.76-0.2C42.36,83.6,42.13,83.43,42.02,83.49z"/>
|
||||
<path d="M43.56,82.45c0.11-0.34,0.04-0.52-0.23-0.64c-0.17-0.07-0.34-0.15-0.51-0.2c-0.52-0.15-1.04-0.28-1.57-0.07
|
||||
c-0.19,0.08-0.35,0.19-0.42,0.39c-0.03,0.08-0.03,0.19,0,0.26c0.09,0.18,0.21,0.35,0.32,0.52c0.05,0.08,0.12,0.15,0.22,0.01
|
||||
c-0.05-0.17-0.14-0.36-0.02-0.46c0.35-0.06,0.62-0.13,0.89-0.15c0.26-0.02,0.56-0.1,0.72,0.27
|
||||
C43.18,82.15,43.37,82.23,43.56,82.45z"/>
|
||||
<path d="M74.64,46c-0.1,0.16-0.2,0.28-0.25,0.43c-0.08,0.22-0.13,0.45-0.21,0.67c-0.13,0.33-0.27,0.65-0.41,0.98
|
||||
c-0.07,0.16-0.07,0.28,0.15,0.32c0.03-0.06,0.08-0.11,0.1-0.16c0.1-0.36,0.28-0.67,0.49-0.98c0.14-0.19,0.24-0.41,0.35-0.62
|
||||
C74.97,46.39,74.9,46.15,74.64,46z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 64 KiB |
BIN
logo/logo_white.png
Normal file
BIN
logo/logo_white.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
581
logo/logo_white.svg
Normal file
581
logo/logo_white.svg
Normal file
@@ -0,0 +1,581 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Livello_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 298.9 141.89" style="enable-background:new 0 0 298.9 141.89;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M160.3,88.5c-0.86,0-1.29-0.43-1.29-1.29v-5.94c-3.01,4.22-7.83,8.09-14.81,8.09
|
||||
c-11.71,0-19.8-10.07-19.8-22.38c0-12.31,8.09-22.38,19.8-22.38c6.89,0,11.62,3.61,14.81,8.18v-6.03c0-0.86,0.43-1.29,1.29-1.29
|
||||
h7.14c0.86,0,1.29,0.43,1.29,1.29v40.46c0,0.86-0.43,1.29-1.29,1.29H160.3z M146.7,80.07c7.14,0,12.48-5.85,12.48-13.08
|
||||
c0-7.23-5.34-13.08-12.48-13.08c-7.23,0-12.57,5.85-12.57,13.08C134.13,74.21,139.47,80.07,146.7,80.07z"/>
|
||||
<path class="st0" d="M223.74,86.95c0,13.51-9.64,23.5-23.33,23.5c-7.14,0-13.6-2.93-17.39-6.8c-0.52-0.52-0.52-1.12-0.09-1.72
|
||||
l3.79-4.99c0.26-0.34,0.6-0.52,0.86-0.52c0.26,0,0.52,0.17,0.77,0.34c2.84,2.67,6.97,4.39,11.79,4.39c9.12,0,13.86-6.2,13.86-13.34
|
||||
v-6.97c-3.01,4.22-7.75,8.09-14.89,8.09c-11.62,0-19.71-9.73-19.71-22.03s8.09-22.29,19.71-22.29c6.89,0,11.71,3.61,14.89,8.18
|
||||
v-6.03c0-0.86,0.43-1.29,1.29-1.29h7.14c0.86,0,1.29,0.43,1.29,1.29V86.95z M201.7,79.64c7.23,0,12.57-5.42,12.57-12.74
|
||||
c0-7.32-5.34-13-12.57-13c-7.23,0-12.57,5.85-12.57,13C189.13,74.21,194.47,79.64,201.7,79.64z"/>
|
||||
<path class="st0" d="M267.64,78.35c0.26-0.17,0.6-0.26,0.86-0.26c0.43,0,0.77,0.26,1.03,0.6l3.18,4.56
|
||||
c0.43,0.77,0.34,1.29-0.34,1.72c-4.22,2.67-9.38,4.39-15.92,4.39c-12.22,0-22.04-10.07-22.04-22.38c0-11.71,8.18-22.38,20.92-22.38
|
||||
c13.43,0,21.17,11.02,21.17,24.19c0,0.95-0.43,1.55-1.46,1.55h-31.42c1.12,5.85,6.02,10.76,13.08,10.76
|
||||
C261.18,81.1,264.28,80.15,267.64,78.35z M267.21,63.2c-1.38-6.54-5.25-10.93-11.88-10.93c-6.2,0-10.5,4.3-11.62,10.93H267.21z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M142.92,126.01c-0.12,0-0.18-0.06-0.18-0.16v-7.36c0-0.11,0.06-0.16,0.18-0.16h5.26
|
||||
c0.12,0,0.18,0.06,0.18,0.16v0.9c0,0.11-0.06,0.16-0.18,0.16h-4.05v2.18h3.51c0.12,0,0.18,0.05,0.18,0.16v0.87
|
||||
c0,0.09-0.05,0.16-0.18,0.16h-3.51v2.92c0,0.11-0.06,0.16-0.18,0.16H142.92z"/>
|
||||
<path class="st0" d="M152.11,126.01c-0.12,0-0.18-0.06-0.18-0.16v-7.36c0-0.11,0.06-0.16,0.18-0.16h1.02
|
||||
c0.12,0,0.18,0.06,0.18,0.16v7.36c0,0.11-0.06,0.16-0.18,0.16H152.11z"/>
|
||||
<path class="st0" d="M157.89,126.01c-0.12,0-0.18-0.06-0.18-0.16v-7.36c0-0.11,0.06-0.16,0.18-0.16h1.02
|
||||
c0.12,0,0.18,0.06,0.18,0.16v6.31h3.26c0.12,0,0.18,0.06,0.18,0.16v0.89c0,0.11-0.06,0.16-0.18,0.16H157.89z"/>
|
||||
<path class="st0" d="M171.38,118.32c0.13,0,0.18,0.08,0.18,0.16v0.9c0,0.11-0.06,0.16-0.18,0.16h-3.64v2.02h3.21
|
||||
c0.12,0,0.18,0.06,0.18,0.16v0.87c0,0.11-0.06,0.16-0.18,0.16h-3.21v2.01h3.65c0.12,0,0.18,0.06,0.18,0.16v0.9
|
||||
c0,0.11-0.06,0.16-0.18,0.16h-4.86c-0.12,0-0.18-0.06-0.18-0.16v-7.36c0-0.11,0.06-0.16,0.18-0.16H171.38z"/>
|
||||
<path class="st0" d="M187.4,118.32c0.13,0,0.18,0.08,0.18,0.16v0.9c0,0.11-0.06,0.16-0.18,0.16h-3.64v2.02h3.21
|
||||
c0.12,0,0.18,0.06,0.18,0.16v0.87c0,0.11-0.06,0.16-0.18,0.16h-3.21v2.01h3.65c0.12,0,0.18,0.06,0.18,0.16v0.9
|
||||
c0,0.11-0.06,0.16-0.18,0.16h-4.86c-0.12,0-0.18-0.06-0.18-0.16v-7.36c0-0.11,0.06-0.16,0.18-0.16H187.4z"/>
|
||||
<path class="st0" d="M197.47,126.01c-0.16,0-0.22-0.04-0.31-0.16l-4.17-5.33v5.33c0,0.11-0.06,0.16-0.18,0.16h-1.03
|
||||
c-0.12,0-0.18-0.06-0.18-0.16v-7.36c0-0.11,0.06-0.16,0.18-0.16h1.04c0.16,0,0.23,0.07,0.32,0.16l4.16,5.33v-5.33
|
||||
c0-0.11,0.06-0.16,0.18-0.16h1.04c0.12,0,0.18,0.06,0.18,0.16v7.36c0,0.11-0.06,0.16-0.18,0.16H197.47z"/>
|
||||
<path class="st0" d="M208.78,119.78c-0.04,0.05-0.1,0.09-0.18,0.09c-0.04,0-0.07-0.01-0.12-0.03c-0.48-0.26-0.94-0.39-1.56-0.39
|
||||
c-1.71,0-2.98,1.22-2.98,2.74c0,1.52,1.27,2.73,2.98,2.73c0.53,0,1.12-0.12,1.6-0.42c0.05-0.03,0.09-0.04,0.12-0.04
|
||||
c0.06,0,0.11,0.03,0.16,0.09l0.54,0.75c0.07,0.1,0.05,0.19-0.06,0.25c-0.7,0.39-1.48,0.59-2.36,0.59c-2.53,0-4.37-1.71-4.37-3.95
|
||||
c0-2.22,1.84-3.97,4.37-3.97c0.92,0,1.71,0.24,2.33,0.59c0.11,0.06,0.13,0.13,0.07,0.23L208.78,119.78z"/>
|
||||
<path class="st0" d="M219.34,125.83c0.09,0.12,0.06,0.18-0.1,0.18h-1.15c-0.13,0-0.24-0.06-0.33-0.16l-1.88-2.47h-1.07v2.47
|
||||
c0,0.11-0.06,0.16-0.18,0.16h-1.03c-0.12,0-0.18-0.06-0.18-0.16v-7.36c0-0.11,0.06-0.16,0.18-0.16h2.84c1.67,0,2.8,1.12,2.8,2.53
|
||||
c0,1.44-1.22,2.15-1.9,2.33L219.34,125.83z M214.81,119.47v2.75h1.32c1.11,0,1.71-0.61,1.71-1.37c0-0.76-0.6-1.37-1.71-1.37
|
||||
H214.81z"/>
|
||||
<path class="st0" d="M225.65,121.16l1.92-2.75c0.05-0.07,0.12-0.1,0.21-0.1h1.2c0.1,0,0.12,0.09,0.07,0.16l-2.68,3.78v3.58
|
||||
c0,0.11-0.06,0.16-0.18,0.16h-1.04c-0.12,0-0.18-0.06-0.18-0.16v-3.58l-2.7-3.78c-0.05-0.08-0.02-0.16,0.07-0.16h1.21
|
||||
c0.1,0,0.17,0.03,0.21,0.1L225.65,121.16z"/>
|
||||
<path class="st0" d="M232.88,126.01c-0.12,0-0.18-0.06-0.18-0.16v-7.36c0-0.11,0.06-0.16,0.18-0.16h2.81
|
||||
c1.7,0,2.78,1.12,2.78,2.53c0,1.36-1.16,2.56-2.78,2.56h-1.6v2.44c0,0.11-0.06,0.16-0.18,0.16H232.88z M234.09,122.26h1.29
|
||||
c1.02,0,1.68-0.55,1.68-1.42c0-0.88-0.66-1.37-1.68-1.37h-1.29V122.26z"/>
|
||||
<path class="st0" d="M244.19,126.01c-0.12,0-0.18-0.06-0.18-0.16v-6.3h-2.36c-0.12,0-0.18-0.06-0.18-0.16v-0.9
|
||||
c0-0.11,0.06-0.16,0.18-0.16h6.1c0.12,0,0.18,0.06,0.18,0.16v0.9c0,0.11-0.06,0.16-0.18,0.16h-2.36v6.3
|
||||
c0,0.11-0.06,0.16-0.18,0.16H244.19z"/>
|
||||
<path class="st0" d="M251.84,126.01c-0.12,0-0.18-0.06-0.18-0.16v-7.36c0-0.11,0.06-0.16,0.18-0.16h1.02
|
||||
c0.12,0,0.18,0.06,0.18,0.16v7.36c0,0.11-0.06,0.16-0.18,0.16H251.84z"/>
|
||||
<path class="st0" d="M265.64,122.17c0,2.2-1.9,3.95-4.38,3.95c-2.48,0-4.37-1.71-4.37-3.95c0-2.22,1.89-3.97,4.37-3.97
|
||||
C263.74,118.21,265.64,119.95,265.64,122.17z M264.24,122.17c0-1.52-1.27-2.74-2.98-2.74c-1.71,0-2.98,1.22-2.98,2.74
|
||||
c0,1.52,1.27,2.73,2.98,2.73C262.95,124.9,264.24,123.69,264.24,122.17z"/>
|
||||
<path class="st0" d="M275.35,126.01c-0.16,0-0.22-0.04-0.31-0.16l-4.17-5.33v5.33c0,0.11-0.06,0.16-0.18,0.16h-1.03
|
||||
c-0.12,0-0.18-0.06-0.18-0.16v-7.36c0-0.11,0.06-0.16,0.18-0.16h1.04c0.16,0,0.23,0.07,0.32,0.16l4.16,5.33v-5.33
|
||||
c0-0.11,0.06-0.16,0.18-0.16h1.04c0.12,0,0.18,0.06,0.18,0.16v7.36c0,0.11-0.06,0.16-0.18,0.16H275.35z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M109.23,72.27c-0.38-0.68-0.75-1.38-1.15-2.06c-0.34-0.57-0.67-1.34-1.04-1.9c-0.45-0.69-0.71-1.22-1.19-1.9
|
||||
c-0.52-0.74-0.9-1.28-1.47-1.98c-0.63-0.79-1.47-1.74-2.14-2.5c-0.52-0.59-1.3-1.42-1.83-2.01c-0.16-0.18-2.04-2.14-3.51-3.45
|
||||
c-2.03-1.76-4.38-3.5-5.64-4.39c-0.94-0.72-1.83-1.25-2.92-2.02c-0.08-0.04-0.19-0.09-0.21-0.16c-0.08-0.33-0.33-0.33-0.59-0.32
|
||||
c-0.63,0.03-1.26,0.07-1.89,0.06c-0.71-0.01-1.42,0.04-2.13,0.08c-0.29,0.01-0.54-0.03-0.74-0.23c-0.13,0.07-0.24,0.18-0.35,0.19
|
||||
c-0.52,0.03-1.05,0.03-1.58,0.04c-0.2,0.01-0.35-0.07-0.4-0.25c-0.06-0.18-0.17-0.29-0.34-0.38c1.27-0.08,2.52-0.14,3.77-0.18
|
||||
c0.18-0.01,3.41,0.01,3.57-0.2c0.12-0.16,0.07-0.28-0.13-0.36c-0.08-0.03-0.18-0.06-0.23-0.12c-0.14-0.15-0.27-0.12-0.43-0.03
|
||||
c-0.14,0.07-0.29,0.14-0.44,0.15c-0.62,0.03-1.24,0.04-1.85,0.06c-0.55,0.02-3.59,0.11-4.58,0.08c-0.01-0.13-0.07-0.27-0.03-0.35
|
||||
c0.13-0.22,0.03-0.41-0.03-0.6c-0.1-0.34-0.19-0.68-0.16-1.04c0.02-0.3-0.1-0.45-0.45-0.57c-0.04,0.21-0.05,0.41-0.12,0.61
|
||||
c-0.05,0.14-0.02,0.24,0.03,0.37c0.21,0.52,0.4,1.04,0.61,1.63c-0.91,0-1.76,0-2.66,0c0.06-0.71-0.05-1.42,0.01-2.13
|
||||
c-0.21-0.32-0.35-0.33-0.6-0.49c0.36-0.21,0.72-0.12,1.09,0.04c0.11-0.24,0.32-0.21,0.52-0.21c0.72-0.01,1.44,0,2.16-0.03
|
||||
c0.77-0.03,1.54-0.08,2.32,0.02c0.44,0.06,0.87,0.12,1.28,0.18c0.14,0.79,0.61,1.09,1.09,1.59c0.26,0.27,0.69,0.61,1.05,0.73
|
||||
c0.02-0.02,0.03-0.04,0.05-0.07c-0.53-0.72-1.06-1.45-1.59-2.17c0-0.04,0-0.08,0.01-0.12c0.43-0.04,0.4-0.32,0.15-0.83
|
||||
c-0.07-0.16-0.1-0.33-0.2-0.43c-0.2-0.21-0.13-2.15-0.14-2.45c0-0.26,0.03-0.53,0.04-0.79c0.02-0.9-0.01-1.8,0.06-2.69
|
||||
c0.03-0.36-0.04-0.7-0.06-1.05c0-0.07-0.09-0.16-0.16-0.19c-0.09-0.04-0.21-0.03-0.31-0.03c-0.59,0.03-1.17,0.06-1.76,0.09
|
||||
c-0.03,0-0.43,0-0.46,0c-0.56-0.07-0.84-0.08-1.4-0.1c-0.92-0.04-1.51,0.05-2.43,0.04c-0.79,0-1.71-0.01-2.5-0.03
|
||||
c-0.45-0.01-1.04-0.03-1.48-0.05c-0.28-0.01-0.6-0.01-0.97-0.03c0.49-0.39,1.06-0.67,1.56-0.6c0.42,0.06,0.82,0.1,1.25,0.04
|
||||
c0.99-0.14,1.99-0.15,2.99-0.09c0.83,0.05,1.65,0.15,2.47,0.31c0.36,0.07,1.06,0.07,1.34,0c0-0.03-0.62-0.03-0.88-0.09
|
||||
c-0.25-0.05-0.64-0.09-0.89-0.14c-0.25-0.05-0.51-0.08-0.83-0.14c-0.11-0.17,0.29-0.14,0.31-0.14c0.8,0.01,1.43,0.07,2.23,0.1
|
||||
c0.85,0.03,1.7,0.07,2.56,0.1c0.22,0.01,0.36-0.11,0.5-0.34c0.03-0.3-0.65-0.21-0.95-0.31c-0.01-0.1-0.03-0.19-0.04-0.29
|
||||
c-0.18-0.12-0.35-0.1-0.51-0.08c-0.27,0.04-0.42-0.1-0.57-0.27c-0.16-0.17-0.33-0.34-0.48-0.51c-0.11-0.12-0.24-0.15-0.41-0.07
|
||||
c-0.01,0.25-0.01,0.64,0.13,0.94c-0.46-0.01-1.31,0.01-1.54,0.01c-0.76,0.01-1.06-0.01-1.86,0c-0.64,0.06-1.4,0.21-1.86,0.01
|
||||
c-0.09-0.17-0.16-0.64-0.32-0.66c-0.33,0.12-0.33,0.12-0.38,0.5C76.47,35.92,76.5,36,76.25,36c-0.24,0.04-0.42,0.03-0.61-0.07
|
||||
c0.06-0.21,0.11-0.35,0.13-0.49c0.04-0.21-0.07-0.33-0.28-0.33c-0.21-0.01-0.28,0.14-0.37,0.3c-0.29,0.52-0.43,0.58-1.01,0.44
|
||||
c0.01-0.04,0-0.08,0.02-0.11c0.12-0.15,0.13-0.32-0.04-0.41c-0.12-0.06-0.35-0.09-0.44-0.02c-0.21,0.15-0.37,0.38-0.57,0.6
|
||||
c-0.23,0-0.5,0-0.78,0c0-0.06-0.01-0.1,0-0.14c0.04-0.14,0.04-0.29-0.11-0.34c-0.15-0.05-0.29-0.01-0.38,0.18
|
||||
c-0.09,0.22-0.3,0.33-0.54,0.33c-0.24,0-0.49,0-0.8,0c0.24-0.27,0.43-0.51,0.64-0.74c0.17-0.19,0.18-0.2-0.01-0.45
|
||||
c-0.17,0.02-0.33,0.05-0.51,0.08c-0.11,0.65-0.61,0.91-1.13,1.1c-0.36,0.13-0.76,0.17-1.15,0.25c-0.13,0.02-0.23,0.06-0.25,0.2
|
||||
c-0.01,0.13,0.06,0.19,0.17,0.22c0.2,0.06,0.28,0.19,0.3,0.41c0.04,0.42,0.49,0.58,0.63,0.94c0.16-0.01,0.15,0.1,0.16,0.21
|
||||
c0.03,0.33,0.08,0.65,0.11,0.98c0.06,0.56,0.18,1.13,0.14,1.69c-0.08,1.06-0.01,2.11,0.01,3.16c0,0.26,0.03,0.52,0.04,0.76
|
||||
c0.47,0.34,0.73,0.19,0.79-0.5c0-0.01,0-0.03,0-0.04c-0.07-1.09-0.07-2.18-0.21-3.26c-0.12-0.91-0.07-1.84,0.12-2.76
|
||||
c0.02-0.09,0.05-0.17,0.07-0.27c0.2-0.01,0.38-0.02,0.61-0.03c0,0.18,0,0.33,0,0.49c-0.01,0.87-0.02,1.74-0.02,2.61
|
||||
c0.01,0.94,0.03,1.87,0.05,2.81c0,0.17,0,0.34,0.03,0.51c0.01,0.08,0.07,0.15,0.16,0.32c0.06-0.16,0.1-0.24,0.13-0.33
|
||||
c0.09,0.1,0.12,0.17,0.16,0.18c0.12,0.04,0.25,0.1,0.36,0.07c0.06-0.02,0.11-0.19,0.12-0.29c0-0.25-0.01-0.5-0.04-0.75
|
||||
c-0.06-0.5-0.16-0.99-0.19-1.49c-0.04-0.93-0.04-1.87-0.05-2.8c0-0.18,0.04-0.36,0.06-0.54c0.05-0.03,0.09-0.06,0.12-0.07
|
||||
c0.14-0.02,0.27-0.04,0.27-0.22c0-0.15-0.1-0.18-0.23-0.21c-0.11-0.03-0.2-0.14-0.3-0.21c0.01-0.03,0.02-0.05,0.03-0.08
|
||||
c0.29,0,0.59,0,0.9,0c0.04,0.17,0.12,0.36,0.13,0.55c0.06,0.89,0.12,1.79,0.16,2.69c0.05,1.11,0.07,2.21,0.11,3.32
|
||||
c0.01,0.23,0.03,0.46,0.05,0.72c-1.57,0-3.1,0-4.62,0c-0.07,0.33-0.04,0.38,0.22,0.52c0.4,0.2,0.46,0.47,0.17,0.82
|
||||
c-0.17,0.2-0.34,0.4-0.52,0.59c-0.12,0.13-0.24,0.25-0.37,0.35c-0.2,0.15-0.42,0.27-0.63,0.41c-0.39,0.27-0.79,0.51-1.29,0.55
|
||||
c-0.27,0.02-0.54,0.09-0.79,0.14c-0.12,0.37-0.12,0.37,0.09,0.61c-0.09,0.08,9.04-0.12,13.65-0.05c-0.04,0.11,0.02,0.53-0.02,0.67
|
||||
c-0.4,0-0.8,0.01-1.19,0c-0.16-0.01-0.32-0.06-0.54-0.11c-0.17,0.22-0.48,0.17-0.79,0.17c-0.46,0.01-0.92,0.04-1.38,0.05
|
||||
c-0.14,0-0.28-0.02-0.43-0.04c0-0.11,0-0.2,0-0.29c-0.36-0.13-0.43,0.25-0.59,0.33c-0.33-0.04-7.18,0.04-7.44,0.03
|
||||
c-0.34-0.01-0.68-0.06-1.02-0.08c-0.35-0.02-0.69,0.14-1.04-0.04c-0.13-0.07-0.25,0.02-0.34,0.14c-0.08,0.12-0.05,0.23,0.05,0.32
|
||||
c0.04,0.04,0.12,0.05,0.23,0.1c-0.41,0.24-0.7,0.4-0.98,0.59c-1.21,0.82-2.53,1.47-3.66,2.4c-0.34,0.28-0.73,0.5-1.07,0.78
|
||||
c-0.32,0.27-2.14,1.65-2.7,2.09c-0.6,0.47-5.88,5.57-6.81,6.58c-0.39,0.43-0.75,0.88-1.16,1.28c-0.38,0.37-1.25,1.52-1.37,1.67
|
||||
c-0.88,1.06-4.49,5.8-5.9,8.45c-1.36,2.37-3.84,8-4.06,8.63c-0.26,0.76-0.52,1.51-0.67,2.3c-0.1,0.49-0.92,3.62-1.05,4.36
|
||||
c-0.12,0.69-0.44,3.2-0.47,3.45c-0.1,0.9-0.19,1.81-0.28,2.75c-0.2,0.03-0.41,0.07-0.63,0.1c-0.16,0.02-0.25,0.12-0.26,0.26
|
||||
c-0.01,0.08,0.07,0.18,0.12,0.26c0.04,0.06,0.12,0.1,0.16,0.16c0.16,0.21,0.38,0.26,0.63,0.25c0.78-0.03,1.55-0.09,2.33-0.1
|
||||
c0.75-0.01,1.5,0,2.25,0.04c0.71,0.03,11.13,0.14,15.64,0.14c0.72,0,3.91-0.06,4.78-0.06c0.3,0,2.91-0.1,3.91-0.16
|
||||
c1-0.06,11.01-0.24,11.59-0.23c0.05,0,4.25,0.13,5.42,0.14c0.21,0,2.03,0.06,2.72,0.02c1.26-0.08,15.17,0.11,16.68,0.09
|
||||
c0.63-0.01,6.52-0.07,7.29-0.1c0.55-0.02,1.11-0.02,1.66-0.04c0.09,0,0.19-0.04,0.3-0.07c-0.07-0.25-0.22-0.33-0.41-0.38
|
||||
c-0.14-0.04-0.27-0.09-0.41-0.14c-0.15-0.04-0.29-0.11-0.44-0.12c-0.25-0.02-3.64,0.06-4.56,0.11c-0.43,0.02-3.52-0.02-3.93-0.01
|
||||
c-0.08,0-0.16-0.01-0.24-0.02c-0.25-0.02-0.49-0.05-0.74-0.07c-0.24-0.02-0.48-0.04-0.71-0.03c-0.83,0.02-5.75,0.01-6.65,0.02
|
||||
c-0.63,0.01-5.53-0.01-5.83-0.01c-0.81-0.02-18.52,0.09-19.63,0.08c-0.33,0-4.97,0.16-5.02,0.16c-0.28,0.01-5.25-0.03-5.59-0.03
|
||||
c-0.87,0.01-3.11,0-3.36,0.01c-0.51,0.01-2.73,0.01-3.32,0.02c-0.33,0-0.36,0-0.42-0.32c-0.17-0.9-0.18-1.81-0.15-2.72
|
||||
c0.03-0.71,0.18-1.42,0.2-2.12c0.03-1.3,0.29-2.56,0.56-3.83c0.02-0.08,0.04-0.22,0-0.25c-0.11-0.08-0.06-0.16-0.04-0.24
|
||||
c0.24-0.97,0.49-1.94,0.73-2.92c0.24-0.96,0.46-1.92,0.71-2.88c0.2-0.74,0.41-1.47,0.65-2.2c0.22-0.68,0.44-1.36,0.73-2
|
||||
c0.4-0.9,0.87-1.78,1.32-2.66c0.51-1,1.03-1.99,1.53-2.99c0.33-0.66,0.63-1.34,1.07-1.93c0.04-0.05,0.09-0.15,0.07-0.17
|
||||
c-0.2-0.18-0.02-0.3,0.06-0.43c0.25-0.4,0.51-0.8,0.77-1.2c0.49-0.75,0.97-1.5,1.47-2.25c0.73-1.08,3.38-4.74,4.06-5.43
|
||||
c0.59-0.59,2.32-2.61,2.62-2.92c0.42-0.43,1.09-1.41,0.97-1.48c0.33-0.34,0.62-0.67,0.95-0.98c0.47-0.44,0.96-0.86,1.43-1.29
|
||||
c0.27-0.25,1.37-1.21,1.53-1.57c0.01-0.03,0.08-0.05,0.13-0.06c0.35-0.08,0.66-0.23,0.83-0.56c0.17,0.01,0.33,0.01,0.54,0.02
|
||||
c-0.08,0.09-0.12,0.14-0.16,0.19c-0.25,0.3-0.51,0.6-0.76,0.9c-0.34,0.42-0.67,0.84-1.01,1.27c-0.43,0.54-0.91,1.04-1.21,1.68
|
||||
c-0.19,0.4-0.49,0.75-0.73,1.13c-0.7,1.12-1.41,2.22-2.04,3.39c-0.59,1.1-4.34,7.71-4.36,7.76c-0.18,0.37-0.35,0.74-0.52,1.1
|
||||
c-0.13,0.29-0.26,0.58-0.4,0.86c-0.32,0.63-0.66,1.26-0.97,1.9c-0.21,0.44-0.41,0.88-0.58,1.34c-0.43,1.11-0.84,2.22-1.25,3.33
|
||||
c-0.27,0.73-0.51,1.47-0.75,2.2c-0.21,0.65-0.42,1.31-0.6,1.96c-0.17,0.63-0.32,1.28-0.47,1.92c-0.02,0.1-0.05,0.21-0.07,0.31
|
||||
c-0.02,0.1-0.04,0.2-0.05,0.3c0.02,0,0.03,0.01,0.05,0.01c0.06-0.14,0.13-0.28,0.19-0.42c-0.04,0.35-0.12,0.67-0.21,1
|
||||
c-0.05,0.19-0.12,0.37-0.17,0.52c-0.22-0.14-0.42-0.3-0.65-0.41c-0.26-0.13-0.48-0.27-0.61-0.54c-0.05-0.1-0.14-0.19-0.24-0.26
|
||||
c-0.15-0.11-0.3-0.19-0.38-0.37c-0.03-0.07-0.18-0.12-0.26-0.11c-0.14,0.02-0.28,0.07-0.39,0.15c-0.53,0.34-1.05,0.7-1.59,1.02
|
||||
c-0.19,0.11-0.3,0.24-0.33,0.45c-0.02,0.22,0.15,0.29,0.31,0.34c-0.09,0.22-0.37,0.33-0.26,0.6c0.47,0.02,0.93,0.05,1.21-0.45
|
||||
c-0.13-0.01-0.24-0.02-0.35-0.02c0.16-0.67,0.56-1.06,1.23-1.18c0.25,0.18,0.51,0.37,0.8,0.58c-0.06,0.57,0.94,0.86,1.43,0.48
|
||||
c0,0.08,0.01,0.14,0,0.21c-0.1,0.52-0.2,1.03-0.29,1.55c-0.15,0.83-0.3,1.66-0.44,2.49c-0.06,0.36-0.1,0.73-0.13,1.1
|
||||
c-0.04,0.58-0.03,1.16-0.08,1.73c-0.05,0.56-0.31,4-0.28,4.67c0,0,0.03,0.4,0.03,0.19c0.08-0.17,0.09-0.34,0.14-0.5
|
||||
c0.22-0.76,0.31-1.55,0.39-2.33c0.09-0.84,0.19-1.68,0.31-2.51c0.17-1.09,0.37-2.18,0.55-3.27c0.15-0.9,0.3-1.79,0.46-2.69
|
||||
c0.15-0.83,0.29-1.66,0.47-2.48c0.12-0.54,0.32-1.06,0.47-1.59c0.16-0.6,0.3-1.2,0.46-1.8c0.24-0.88,0.49-1.75,0.9-2.57
|
||||
c0.15-0.3,0.27-0.6,0.4-0.91c0.02-0.06,0.06-0.17,0.04-0.18c-0.29-0.16-0.06-0.37,0-0.5c0.29-0.72,0.62-1.43,0.93-2.14
|
||||
c0.06-0.13,0.12-0.26,0.18-0.39c0.06-0.12,1.18-2.1,1.63-3.04c0.57-1.19,1.11-2.39,1.72-3.56c0.36-0.7,0.7-1.42,1.23-2.02
|
||||
c0.03-0.04,0.04-0.09,0.06-0.14c0.07-0.17,0.12-0.35,0.21-0.5c0.38-0.68,0.79-1.34,1.17-2.02c0.56-1.02,1.24-1.96,1.92-2.9
|
||||
c0.36-0.5,0.7-1.02,1.04-1.54c0.25-0.38,0.46-0.8,0.76-1.15c0.5-0.6,1.05-1.17,1.59-1.76c-0.09-0.22-0.09-0.24,0.11-0.43
|
||||
c0.4-0.38,1.05-1.28,0.96-1.32c0.23-0.21,0.42-0.31,0.61-0.42c0.38-0.21,0.38-0.21,0.62-0.15c-0.12,0.14-0.23,0.27-0.34,0.39
|
||||
c-0.44,0.51-0.88,1.01-1.3,1.54c-0.28,0.35-0.48,0.76-0.76,1.11c-0.8,1.03-1.48,2.14-2.16,3.25c-0.37,0.59-0.78,1.16-1.12,1.77
|
||||
c-0.53,0.95-1.05,1.92-1.53,2.9c-0.32,0.66-0.63,1.33-0.84,2.03c-0.19,0.63-0.39,1.26-0.66,1.86c-0.2,0.44-0.42,0.88-0.59,1.33
|
||||
c-0.32,0.82-0.6,1.66-0.93,2.48c-0.44,1.09-1.87,5.3-2.1,6.34c-0.19,0.86-0.35,1.73-0.58,2.58c-0.25,0.94-0.55,1.87-0.74,2.83
|
||||
c-0.09,0.45-0.26,0.89-0.32,1.34c-0.13,0.99-0.17,2-0.33,2.98c-0.18,1.09-0.14,2.18-0.21,3.27c-0.06,0.83,0.03,2.61,0.06,2.74
|
||||
c0.3-1.16,0.13-2.28,0.36-3.36c0.02,0.14,0.05,0.28,0.07,0.42c0.11-0.3,0.15-0.59,0.18-0.88c0.21-2.3,0.63-4.57,1.12-6.82
|
||||
c0.34-1.56,0.71-3.11,1.09-4.66c0.34-1.39,0.69-2.78,1.25-4.1c0.02-0.06,0.01-0.13,0.02-0.19c0.01-0.12,0.01-0.24,0.04-0.35
|
||||
c0.31-1.2,0.77-2.35,1.24-3.5c0.45-1.08,0.93-2.15,1.41-3.23c0.48-1.07,0.97-2.14,1.46-3.2c0.28-0.6,0.57-1.19,0.86-1.78
|
||||
c0.24-0.48,0.47-0.98,0.73-1.44c0.35-0.61,0.75-1.2,1.08-1.81c0.55-1.01,1.23-1.93,1.91-2.86c0.11-0.15,1.38-1.64,2-2.29
|
||||
c0.22-0.23,0.48-0.42,0.54-0.79c0.09,0.01,0.19,0.02,0.3,0.03c-0.08,0.14-0.16,0.25-0.21,0.36c-0.37,0.82-0.73,1.64-1.09,2.45
|
||||
c-0.34,0.76-0.56,1.56-0.84,2.34c-0.27,0.79-0.45,1.59-0.68,2.39c-0.13,0.48-0.27,0.96-0.41,1.44c-0.3,1.05-0.61,2.1-0.91,3.15
|
||||
c-0.19,0.68-0.36,1.37-0.56,2.06c-0.19,0.67-0.4,1.33-0.61,2c-0.15,0.48-0.32,0.96-0.47,1.4c-0.35-0.21-0.71-0.38-1.03-0.6
|
||||
c-0.46-0.32-0.97-0.32-1.48-0.26c-0.28,0.03-0.57,0.13-0.72,0.42c-0.27,0.51-0.53,1.03-0.78,1.55c-0.13,0.27-0.07,0.41,0.21,0.63
|
||||
c0.34-0.19,0.65-0.39,0.69-0.84c0.01-0.11,0.12-0.24,0.21-0.32c0.2-0.17,0.42-0.32,0.63-0.48c0.41-0.31,0.71-0.25,0.96,0.2
|
||||
c0.09,0.16,0.18,0.32,0.42,0.32c0-0.19,0-0.37,0-0.57c0.27,0.11,0.48,0.36,0.8,0.19c-0.12,0.43-0.22,0.8-0.32,1.18
|
||||
c-0.15,0.58-1.63,9.63-1.69,10.38c-0.05,0.6-0.12,1.21-0.2,1.81c-0.07,0.57-0.12,1.15-0.23,1.72c-0.15,0.76-0.16,1.52-0.16,2.28
|
||||
c0,0.34,0.12,0.69,0.07,1.01c-0.1,0.57-0.09,1.14-0.07,1.72c0.01,0.25,0.01,0.5-0.02,0.75c-0.04,0.31-0.11,0.62-0.16,0.93
|
||||
c-0.01,0.06,0.03,0.13,0.05,0.25c0.08-0.52,0.17-0.97,0.23-1.43c0.08-0.6,0.14-1.21,0.21-1.81c0.08-0.63,0.17-1.25,0.24-1.88
|
||||
c0.07-0.72,0.12-1.44,0.18-2.17c0.01-0.09,0.05-0.17,0.07-0.26c0.02,0.01,0.04,0.69,0.03,1.02c0.07-0.12,0.12-0.24,0.13-0.37
|
||||
c0.08-0.77,0.15-1.55,0.21-2.33c0.11-1.31,0.27-2.62,0.5-3.91c0-0.03,0.01-0.05,0.01-0.08c0.27-1.02,0.55-2.03,0.82-3.05
|
||||
c0.06-0.22,0.14-0.45,0.1-0.76c-0.19,0.3-0.17,0.64-0.42,0.83c0.09-0.41,0.27-0.8,0.3-1.21c0.06-0.71,0.47-1.33,0.41-2.06
|
||||
c0-0.01,0.01-0.03,0.01-0.04c0.08-0.26,0.09-0.52,0.13-0.78c0.1-0.6,0.24-1.19,0.39-1.78c0.42-1.63,0.84-3.26,1.27-4.89
|
||||
c0.21-0.8,0.36-1.62,0.77-2.36c0.03-0.05,0.04-0.14,0.01-0.17c-0.12-0.11-0.06-0.21-0.03-0.32c0.06-0.23,0.1-0.46,0.17-0.69
|
||||
c0.26-0.83,0.52-1.66,0.79-2.48c0.06-0.19,0.17-0.35,0.24-0.54c0.1-0.27,0.16-0.55,0.27-0.82c0.3-0.73,0.58-1.47,0.92-2.19
|
||||
c0.32-0.68,0.47-1.43,0.97-2.02c0.02-0.03,0.02-0.08,0.04-0.11c0.19-0.31,0.37-0.63,0.57-0.94c0.04-0.06,0.12-0.11,0.19-0.12
|
||||
c0.21-0.01,0.43-0.01,0.66-0.01c-0.09,0.3-0.17,0.57-0.25,0.83c-0.15,0.49-0.31,0.97-0.44,1.47c-0.25,0.94-0.46,1.89-0.71,2.83
|
||||
c-0.23,0.84-1.89,7.97-1.98,8.69c-0.07,0.56-1.18,5.38-1.35,9.4c-0.03,0.97-0.13,1.94-0.21,2.92c-0.02,0.3-0.04,0.6-0.07,0.9
|
||||
c-0.05,0.48-0.14,0.96-0.16,1.45c-0.04,0.99-0.05,1.98-0.08,2.97c-0.02,0.79-0.06,1.58-0.08,2.37c-0.01,0.49,0,0.97,0,1.46
|
||||
c0.1,0.07,0.22-0.88,0.29-1.4c0.01-0.08,0.02-0.17,0.06-0.22c0.11-0.12,0.08-0.22,0.06-0.36c-0.05-0.31-0.02-0.62,0.28-0.82
|
||||
c-0.16-0.33-0.11-0.65-0.06-0.98c0.06-0.46,0.11-0.91,0.14-1.37c0.06-0.98,0.1-1.97,0.16-2.95c0.01-0.23,0.04-0.47,0.19-0.77
|
||||
c0,0.72,0,1.35,0,1.98c0.05-0.17,0.04-0.33,0.05-0.5c0.03-0.46,0.06-0.92,0.09-1.38c0.07-1.13,0.12-2.26,0.23-3.39
|
||||
c0.09-0.9,0.24-1.8,0.38-2.7c0.03-0.2,1.07-5.9,1.25-6.86c0.2-1.04,0.35-2.1,0.56-3.14c0.3-1.46,0.63-2.91,0.95-4.36
|
||||
c0.23-1.06,0.49-2.1,0.94-3.09c0.05-0.1,0.03-0.23,0.05-0.35c-0.02,0-0.05-0.01-0.07-0.01c-0.09,0.16-0.18,0.32-0.26,0.48
|
||||
c0.1-0.85,0.67-2.45,1.06-3.12c0.2,0.01,0.41,0.02,0.6,0.03c0.06,0.21,0.11,0.37,0.16,0.53c0.27,0.81,0.55,1.62,0.82,2.43
|
||||
c0.33,0.97,0.66,1.94,0.92,2.93c0.14,0.56,0.28,1.12,0.4,1.69c0.11,0.53,0.18,1.06,0.27,1.59c0.07,0.4,0.16,0.79,0.23,1.19
|
||||
c0.12,0.65,0.25,1.29,0.33,1.94c0.03,0.27,0.18,0.5,0.21,0.76c0.08,0.61,0.19,1.22,0.24,1.84c0.09,1.07,0.14,2.15,0.23,3.22
|
||||
c0.06,0.76,0.14,1.52,0.23,2.28c0.09,0.76,0.27,1.49,0.45,2.23c0.17,0.69,0.34,1.39,0.28,2.1c-0.08,0.87,0.01,2.53,0.11,3.48
|
||||
c0.06-0.3,0.11-1.32,0.16-1.53c0.09-0.03-0.05-2.85-0.11-4.23c0.03,0,0.05-0.01,0.08-0.01c0.04,0.23,0.08,0.46,0.12,0.69
|
||||
c0.01-0.42,0.01-0.84-0.04-1.25c-0.1-0.88-0.23-1.75-0.35-2.62c-0.01-0.09-0.04-0.18-0.06-0.27c-0.05-0.19-0.11-0.38-0.15-0.57
|
||||
c-0.16-0.92-0.3-1.85-0.45-2.77c-0.08-0.48-0.17-0.96-0.24-1.44c-0.11-0.72-0.2-1.43-0.3-2.15c-0.07-0.51-0.15-1.01-0.24-1.52
|
||||
c-0.09-0.51-0.05-1.02-0.12-1.53c-0.07-0.48-0.11-0.96-0.23-1.43c-0.25-1-0.54-1.99-0.76-2.99c-0.28-1.25-0.52-2.5-0.93-3.72
|
||||
c-0.09-0.26-0.09-0.56-0.13-0.84c0.2-0.03,0.34-0.04,0.48-0.08c0.22-0.06,0.35,0,0.44,0.21c0.11,0.26,0.26,0.51,0.39,0.76
|
||||
c0.08,0.15,0.2,0.28,0.26,0.43c0.14,0.38,0.43,0.7,0.44,1.13c0,0.05,0.04,0.1,0.07,0.14c0.41,0.65,1.79,3.87,1.82,3.96
|
||||
c0.36,0.85,0.54,1.76,0.8,2.64c0.04,0.13,0.06,0.25,0.09,0.38c0.05,0.21,0.08,0.42,0.15,0.61c0.27,0.78,0.54,1.57,0.55,2.41
|
||||
c0,0.07,0.01,0.13,0.03,0.2c0.29,1.12,0.65,2.22,0.8,3.38c0.07,0.52,0.19,1.03,0.27,1.55c0.14,0.86,0.26,1.72,0.39,2.58
|
||||
c0.02,0.13,0.1,1.12,0.23,1.1c0.06-0.49-0.21-1.9-0.11-2.39c0.25,0.93,0.25,1.91,0.53,2.83c-0.04-0.69-0.1-1.37-0.2-2.05
|
||||
c-0.26-1.71-0.53-3.41-0.81-5.11c-0.16-0.96-0.34-1.92-0.51-2.88c-0.08-0.42-1.13-4.27-1.44-5.08c-0.45-1.17-0.88-2.35-1.35-3.51
|
||||
c-0.3-0.74-0.68-1.46-1-2.19c-0.15-0.35-0.39-0.69-0.38-1.12c0.23-0.05,0.42-0.06,0.59,0.12c0.37,0.39,2.71,3.45,3.14,3.98
|
||||
c0.81,1,1.58,2.04,2.14,3.21c0.29,0.61,0.64,1.18,0.93,1.79c0.38,0.77,0.75,1.54,1.09,2.33c0.52,1.18,1.01,2.37,1.52,3.56
|
||||
c0.2,0.46,0.39,0.92,0.59,1.38c0.07,0.15,0.12,0.31,0.19,0.46c0.12,0.28,0.11,0.62,0.4,0.82c0.02-0.04,0.04-0.09,0.03-0.13
|
||||
c-0.28-0.71-0.55-1.42-0.84-2.12c-0.49-1.22-0.99-2.43-1.49-3.65c-0.28-0.68-0.59-1.35-0.88-2.03c-0.28-0.66-0.53-1.33-0.82-1.99
|
||||
c-0.16-0.36-0.37-0.7-0.55-1.04c-0.12-0.23-0.25-0.47-0.37-0.7c0.02-0.01,0.04-0.03,0.07-0.04c0.13,0.19,0.27,0.37,0.39,0.57
|
||||
c0.2,0.31,0.39,0.62,0.58,0.92c0,0-1.16-2.2-1.76-3.17c-0.43-0.69-0.93-1.35-1.4-2.01c-0.39-0.55-0.78-1.1-1.18-1.64
|
||||
c-0.13-0.18-0.28-0.34-0.46-0.55c0.23,0,0.41-0.01,0.59,0c0.18,0.01,0.31,0.12,0.3,0.3c-0.01,0.21,0.1,0.31,0.24,0.41
|
||||
c0.12,0.09,0.22,0.19,0.32,0.29c0.39,0.39,0.76,0.81,1.16,1.19c0.64,0.61,1.23,1.26,1.75,1.98c0.36,0.5,0.72,1.01,1.08,1.51
|
||||
c0.11,0.16,0.28,0.32,0.31,0.5c0.05,0.34,0.27,0.54,0.45,0.79c0.31,0.43,0.64,0.84,0.93,1.28c0.36,0.54,0.72,1.1,1.05,1.66
|
||||
c0.35,0.62,0.69,1.24,0.99,1.89c0.3,0.64,0.55,1.31,0.84,1.96c0.08,0.18,0.18,0.34,0.27,0.51c0.03-0.01-0.09-0.36-0.13-0.52
|
||||
c-0.04-0.16-0.86-2.36-0.97-2.59c-0.41-0.84-0.8-1.69-1.22-2.52c-0.33-0.66-0.69-1.31-1.03-1.96c-0.03-0.06-0.09-0.11-0.09-0.17
|
||||
c-0.02-0.38-0.29-0.62-0.5-0.89c-0.42-0.55-0.85-1.1-1.29-1.65c-0.29-0.36-0.58-0.71-0.88-1.07c-0.26-0.31-0.51-0.63-0.76-0.95
|
||||
c-0.01-0.01,0.51,0.26,0.72,0.44c0.68,0.59,1.38,1.17,2.02,1.81c0.47,0.46,0.9,0.96,1.28,1.49c0.31,0.43,0.68,0.79,1.1,1.12
|
||||
c-0.47-0.81-1.12-1.5-1.64-2.28c0.39,0.29,0.77,0.59,1.1,0.94c0.62,0.66,1.21,1.34,1.81,2.03c0.43,0.5,1.99,2.35,2.1,2.43
|
||||
c0.05-0.33-0.2-0.42-0.31-0.58c-0.39-0.55-0.79-1.09-1.2-1.62c-0.43-0.56-0.85-1.13-1.34-1.63c-1.2-1.21-2.43-2.38-3.65-3.56
|
||||
c-0.39-0.38-0.77-0.76-1.16-1.13c-0.34-0.32-0.73-0.61-1.03-0.96c-0.26-0.31-0.57-0.48-0.94-0.59c-0.06-0.02-0.1-0.09-0.21-0.19
|
||||
c0.44-0.04,0.8-0.07,1.16-0.11c-0.13,0.24-0.11,0.24-0.08,0.48c0.03,0.25,0.21,0.27,0.38,0.33c0.05,0.02,0.1,0.05,0.14,0.08
|
||||
c0.53,0.36,1.06,0.72,1.59,1.1c0.75,0.53,1.37,1.2,2,1.87c0.39,0.42,0.77,0.86,1.15,1.29c0.01,0.01,0.04,0.01,0.06,0.02
|
||||
c0.01-0.04,0.02-0.07,0.04-0.18c0.35,0.31,0.67,0.6,0.99,0.88c-0.45-0.63-1.01-1.16-1.45-1.8c0.3,0.19,3.84,3.56,4.54,3.85
|
||||
c-0.92-1.18-1.9-2.25-2.95-3.25c0.44,0.24,0.81,0.58,1.17,0.91c0.38,0.35,0.75,0.71,1.11,1.07c0.34,0.35,1.06,0.96,1.38,1.33
|
||||
c-0.06-0.11-0.43-0.55-0.5-0.67c0.05-0.02,0.09-0.04,0.11-0.06c0.03-0.02,0.04-0.06,0.07-0.09c-0.03-0.05-2.63-2.88-4.14-4
|
||||
c-0.69-0.51-1.37-1.03-2.06-1.54c-0.54-0.4-1.09-0.78-1.64-1.17c-0.16-0.12-0.31-0.25-0.47-0.38c0.03-0.05,0.05-0.14,0.09-0.14
|
||||
c0.1-0.01,0.22-0.02,0.31,0.02c0.22,0.11,0.43,0.25,0.63,0.38c0.66,0.41,1.31,0.83,1.96,1.25c0.58,0.37,1.16,0.73,1.73,1.1
|
||||
c0.12,0.07,0.21,0.18,0.32,0.26c-0.02,0.02-0.03,0.04-0.05,0.06c-0.35-0.19-0.71-0.37-1.06-0.57c-0.32-0.18-0.62-0.39-0.93-0.57
|
||||
c-0.09-0.05-0.19-0.08-0.29-0.12c0.11,0.11,0.21,0.23,0.33,0.32c0.57,0.44,1.16,0.87,1.74,1.3c0.15,0.11,0.48,0.29,0.5,0.27
|
||||
c-0.32-0.26-0.64-0.53-0.96-0.79c0.1-0.03,0.15-0.01,0.21,0.03c0.93,0.56,1.81,1.19,2.66,1.87c0.07,0.06,0.15,0.11,0.22,0.16
|
||||
c-0.12-0.16-0.07-0.43-0.19-0.59c1.18,0.92,5.41,4.74,5.46,4.77c0.53,0.58,1.5,1.54,1.6,1.65c0.09,0.1,1.46,1.8,2,2.45
|
||||
c0.81,0.98,1.59,1.99,2.36,3c0.34,0.45,0.63,0.94,0.94,1.41c0.1,0.15,0.19,0.38,0.33,0.42c0.24,0.08,0.32,0.26,0.43,0.42
|
||||
c0.29,0.44,0.57,0.88,0.84,1.33c0.67,1.13,1.34,2.26,2.01,3.38c0.03,0.06,0.1,0.09,0.15,0.13c-0.01-0.06-0.02-0.12-0.04-0.17
|
||||
C109.64,73,109.43,72.64,109.23,72.27z M79.01,37.91c1.07,0.05,2.8-0.03,4.27,0.05c-0.04,0.44-0.08,0.85-0.11,1.26
|
||||
c-0.01,0.13-0.03,0.26-0.02,0.39c0.07,1.09,0.14,2.18,0.21,3.27c0.01,0.24,0.01,0.47,0.01,0.71c0.01,0.16,0.01,0.3,0.14,0.44
|
||||
c0.06,0.06,0.05,0.21,0.04,0.32c-0.01,0.27-0.1,0.54,0.05,0.81c-0.09-0.07-0.19-0.15-0.28-0.22c-0.54,0.17-3.35,0.29-4.2,0.21
|
||||
c0.02-0.57-0.11-4.24-0.12-5.2c0-0.51,0.02-1.03,0.01-1.54C79.01,38.24,78.97,38.14,79.01,37.91z M78.3,37.95
|
||||
c0.2,0.39,0.08,0.75,0.08,1.1c0,0.32,0,0.63,0,0.95c0,0.67-0.01,1.34,0.01,2.01c0.02,0.67,0.06,1.34,0.12,2.01
|
||||
c0.03,0.39,0.13,0.77,0.19,1.16c-0.33,0-0.68,0-1.06,0c-0.04-0.47-0.05-0.92-0.11-1.37c-0.14-1.05-0.07-2.1-0.13-3.15
|
||||
c-0.02-0.29-0.05-0.58-0.05-0.87c0-0.43,0.01-0.86,0.04-1.3c0.01-0.19-0.01-0.36-0.2-0.53C77.59,37.95,77.95,37.95,78.3,37.95z
|
||||
M72.16,37.32c-0.41-0.06-2.45-0.08-2.8,0.03c-0.29-0.21-0.23-0.4-0.27-0.58c0.5-0.13,2.1-0.05,2.17-0.12
|
||||
c0.29,0.03,0.74,0.01,1.01,0.05c0.08,0.01,0.38,0.11,0.39,0.15C72.67,37.01,72.26,37.34,72.16,37.32z M75.34,37.93
|
||||
c0.35,0,0.83,0.04,1.2,0.04c0,0.29,0.11,0.59-0.08,0.87c-0.04,0.05-0.04,0.16-0.01,0.22c0.21,0.39,0.12,0.83,0.16,1.24
|
||||
c0.06,0.65,0.16,1.29,0.16,1.95c0.01,0.96,0.1,1.93,0.1,2.96c-0.32,0-0.91-0.05-1.25-0.05c-0.14-0.86-0.18-1.42-0.22-2.32
|
||||
c-0.04-0.91,0-1.93-0.05-2.84C75.31,39.4,75.25,38.54,75.34,37.93z M73.38,37.88c0.42,0,0.8,0,1.23,0c0,0.24,0,0.46,0,0.68
|
||||
c0.01,0.33,0.03,0.65,0.03,0.98c0,0.66,0.04,1.32-0.01,1.97c-0.05,0.66-0.01,1.31,0.08,1.96c0.06,0.4,0.11,0.81,0.19,1.21
|
||||
c0.03,0.13,0.12,0.25,0.19,0.38c-0.23,0.13-0.9,0.14-1.72,0.04c0.06-0.14,0.16-0.28,0.17-0.43c0.02-0.16-0.06-0.34-0.06-0.51
|
||||
c-0.02-0.64-0.03-1.29-0.04-1.93c-0.01-0.34-0.01-0.68-0.01-1.02c0.01-0.75,0.02-1.5,0.03-2.25c0-0.21,0.03-0.41-0.07-0.62
|
||||
C73.33,38.23,73.38,38.06,73.38,37.88z M76.21,48.65c-0.8,0.02-1.59,0.03-2.39,0.02c-0.52-0.01-3.97-0.05-5.17-0.06
|
||||
c-0.51,0-1.01-0.06-1.61-0.1c0.56-0.39,1.09-0.67,1.49-1.07c0.39-0.4,0.73-0.89,0.91-1.49c0.41-0.06,0.84-0.17,1.31-0.08
|
||||
c-0.11,0.45-0.24,0.82-0.68,0.99c-0.15,0.06-0.3,0.21-0.36,0.35c-0.12,0.31-0.38,0.46-0.61,0.66c-0.1,0.09-0.19,0.2-0.29,0.31
|
||||
c0.17,0.13,0.28,0.08,0.4,0c0.73-0.51,1.34-1.13,1.79-1.9c0.13-0.22,0.28-0.33,0.52-0.34c0.27-0.02,0.54-0.06,0.82-0.1
|
||||
c-0.13,0.68-0.45,1.24-0.93,1.71c-0.14,0.14-0.19,0.23-0.06,0.4c0.44-0.1,0.73-0.39,0.98-0.72c0.1-0.14,0.24-0.25,0.32-0.4
|
||||
c0.06-0.1,0.12-0.24,0.1-0.35c-0.04-0.27-0.06-0.27,0.1-0.58c0.53,0,1.05,0.01,1.58,0c0.57-0.01,1.13-0.04,1.7-0.05
|
||||
c0.08,0,0.17,0.03,0.25,0.05c-0.06,0.13-0.11,0.25-0.04,0.41c0.18,0.43,0.13,0.88,0.12,1.33c-0.01,0.25-0.04,0.5,0.15,0.71
|
||||
c0.03,0.04,0.02,0.13,0.03,0.22C76.49,48.6,76.35,48.65,76.21,48.65z M37.01,93.47c-0.14,0.67-0.15,1.36-0.2,2.04
|
||||
c0,0.04-0.01,0.08-0.02,0.12c-0.67,0-1.32,0-2,0c0-0.19-0.02-0.4,0-0.62c0.13-1.15,0.23-2.31,0.4-3.46
|
||||
c0.12-0.79,0.32-1.57,0.49-2.36c0.04-0.18,0.06-0.36,0.1-0.54c0.19-0.87,0.35-1.76,0.6-2.62c0.25-0.88,0.59-1.75,0.89-2.62
|
||||
c0.18-0.53,0.35-1.07,0.56-1.59c0.32-0.78,0.68-1.54,1.03-2.3c0.28-0.61,0.57-1.21,0.88-1.81c0.22-0.43,1.18-2.44,1.29-2.65
|
||||
c1.48-2.85,4.81-7.4,5.22-7.84c1.2-1.86,4.38-4.84,4.8-5.28c0.2-0.21,4.78-4.13,5.35-4.66c0.66-0.6,1.33-1.2,2.06-1.72
|
||||
c0.58-0.41,1.11-0.89,1.67-1.33c0.29-0.23,0.59-0.44,0.89-0.65c0.09-0.06,0.2-0.08,0.31-0.11c0.01,0.03,0.03,0.05,0.04,0.08
|
||||
c-0.26,0.21-0.53,0.42-0.79,0.64c-1.14,0.93-2.32,1.83-3.41,2.82c-0.8,0.73-1.63,1.41-2.41,2.15c-0.58,0.54-1.89,1.93-2,2.06
|
||||
c-0.94,1.06-3.29,3.75-3.51,4.05c-0.37,0.51-0.77,1.01-1.17,1.49c-0.28,0.34-1.16,1.44-1.31,1.67c-0.43,0.65-0.85,1.3-1.28,1.94
|
||||
c-0.5,0.74-1.46,2.37-1.48,2.41c-0.17,0.31-0.33,0.62-0.5,0.93c-0.36,0.66-0.73,1.31-1.07,1.97c-0.26,0.5-0.51,1.01-0.76,1.52
|
||||
c-0.31,0.62-0.64,1.23-0.93,1.86c-0.23,0.5-0.41,1.02-0.62,1.53c-0.14,0.34-0.29,0.67-0.43,1.01c-0.04,0.08-0.08,0.16-0.11,0.25
|
||||
c-0.39,1.16-1.25,3.96-1.31,4.2c-0.07,0.29-0.14,0.58-0.21,0.87c-0.03,0.13-0.54,2.56-0.59,2.94
|
||||
C37.36,90.77,37.07,93.17,37.01,93.47z M42.41,92.75c-0.16,0.61-0.25,2.7-0.25,3.16c-1.44-0.09-3-0.26-4.39-0.34
|
||||
c-0.12-0.88,0.21-4.01,0.27-4.43c0.09-0.58,0.39-2.07,0.52-2.65c0.17-0.76,0.51-2.2,1-4.26c0.69-2.61,3.05-7.51,3.45-8.34
|
||||
c0.26-0.54,0.57-1.06,0.86-1.59c0.06-0.12,0.11-0.24,0.18-0.35c0.12-0.2,0.25-0.39,0.38-0.59c0.14-0.21,0.3-0.4,0.42-0.62
|
||||
c0.47-0.9,1-1.76,1.57-2.59c0.44-0.64,0.81-1.32,1.22-1.98c0.1-0.17,0.23-0.32,0.35-0.48c0.31-0.4,0.62-0.81,0.95-1.2
|
||||
c0.35-0.41,0.7-0.82,1.07-1.22c0.42-0.46,0.88-0.87,1.25-1.36c0.27-0.35,0.53-0.71,0.84-1.03c0.57-0.59,1.11-1.19,1.72-1.73
|
||||
c0.35-0.31,0.63-0.69,0.99-0.99c0.31-0.26,0.58-0.57,0.88-0.84c0.44-0.4,0.88-0.79,1.33-1.18c0.67-0.59,1.34-1.19,2.01-1.78
|
||||
c0.23-0.2,0.46-0.4,0.68-0.6c0.42-0.38,0.85-0.74,1.37-0.98c0.13-0.06,0.22-0.18,0.34-0.26c0.59-0.4,1.18-0.79,1.77-1.19
|
||||
c0.23-0.15,0.47-0.29,0.67-0.47c0.1-0.09,0.12-0.26,0.2-0.44c0.54-0.23,1.01-0.73,1.77-0.9c-0.32,0.29-0.56,0.54-0.82,0.75
|
||||
c-0.72,0.59-1.41,1.23-2.09,1.87c-0.55,0.53-1.13,1.02-1.67,1.57c-0.72,0.73-1.41,1.5-2.11,2.24c-0.5,0.54-1.02,1.06-1.52,1.6
|
||||
c-0.35,0.38-0.68,0.77-1.01,1.16c-0.76,0.89-1.53,1.78-2.27,2.69c-0.5,0.61-0.95,1.26-1.42,1.89c-0.17,0.23-0.37,0.44-0.54,0.67
|
||||
c-0.23,0.3-0.44,0.61-0.67,0.92c-0.22,0.3-0.45,0.58-0.65,0.88c-0.36,0.58-0.71,1.17-1.05,1.76c-0.14,0.24-0.27,0.48-0.39,0.73
|
||||
c-0.19,0.38-0.38,0.77-0.57,1.15c-0.12,0.23-0.24,0.46-0.37,0.69c-0.3,0.57-0.61,1.13-0.92,1.7c-0.13,0.23-0.28,0.45-0.38,0.69
|
||||
c-0.23,0.51-0.45,1.04-0.67,1.55c-0.16,0.37-0.35,0.74-0.49,1.12c-0.16,0.44-0.28,0.91-0.44,1.35c-0.19,0.53-0.42,1.04-0.63,1.57
|
||||
c-0.06,0.15-0.29,0.31-0.02,0.48c0.01,0.01-0.02,0.1-0.04,0.15c-0.33,0.95-0.66,1.89-0.98,2.84c-0.29,0.86-0.46,1.75-0.62,2.65
|
||||
c-0.1,0.58-0.3,1.15-0.47,1.71c-0.12,0.42-0.49,3.92-0.5,3.98C42.5,92.18,42.48,92.48,42.41,92.75z M48.47,83.35
|
||||
c-0.06-0.23-0.06-0.22,0.18-0.66C48.59,82.89,48.54,83.1,48.47,83.35z M51.59,73.28c-0.11,0.21-0.21,0.43-0.32,0.64
|
||||
c-0.1,0.2-0.21,0.39-0.32,0.59c0.13-0.67,0.5-1.23,0.81-1.89C51.77,72.94,51.77,72.93,51.59,73.28z M61.6,57.9
|
||||
c0.27-0.46,0.64-0.83,1.03-1.19C62.45,57.24,61.99,57.54,61.6,57.9z M66.67,52.56c-0.94,1.06-2.04,1.95-3.1,2.88
|
||||
c-0.81,0.71-1.55,1.47-2.2,2.31c-0.36,0.47-0.68,0.97-1.02,1.45c-0.15,0.2-0.33,0.37-0.49,0.57c-0.05,0.06-0.08,0.15-0.11,0.23
|
||||
c0.05,0.01,0.09,0.01,0.17,0.02c-0.03,0.07-0.04,0.13-0.08,0.17c-0.61,0.68-1.04,1.48-1.55,2.23c-0.46,0.67-0.97,1.31-1.44,1.96
|
||||
c-0.13,0.18-0.25,0.37-0.36,0.57c-0.26,0.49-0.57,0.94-0.93,1.37c-0.29,0.35-0.53,0.75-0.79,1.13c-0.54,0.79-1.11,1.56-1.62,2.38
|
||||
c-0.77,1.24-1.55,2.48-2.11,3.84c-0.17,0.41-0.33,0.8-0.37,1.24c-0.04,0.52-0.39,0.92-0.55,1.39c-0.23,0.66-0.47,1.31-0.7,1.96
|
||||
c-0.08,0.24-0.17,0.47-0.24,0.71c-0.33,1.15-0.71,2.29-0.98,3.46c-0.24,1.02-0.4,2.07-0.56,3.11c-0.14,0.91-0.22,1.83-0.38,2.74
|
||||
c-0.1,0.62-0.43,2.58-0.44,2.67c-0.03,0.46-0.2,2.1-0.22,2.47c-0.03,0.54-0.06,1.08-0.08,1.61c-0.01,0.27,0,0.55,0,0.83
|
||||
c-0.41,0.14-2.74,0.19-3.81,0.08c0-0.25,0.01-0.49,0-0.74c-0.02-0.65,0.1-1.28,0.21-1.91c0.08-0.48,0.03-0.98,0.1-1.46
|
||||
c0.09-0.62,0.23-1.24,0.34-1.86c0.09-0.52,0.16-1.04,0.26-1.56c0.06-0.34,0.16-0.66,0.24-1c0.11-0.5,0.21-1,0.31-1.51
|
||||
c0.08-0.39,0.17-0.77,0.24-1.16c0.09-0.46,0.16-0.93,0.25-1.39c0.01-0.06,0.03-0.13,0.05-0.19c0.16-0.43,0.31-0.86,0.47-1.29
|
||||
c0.14-0.38,0.27-0.77,0.41-1.15c0.3-0.8,0.58-1.61,0.9-2.4c0.13-0.34,0.33-0.65,0.52-1.04c-0.11,0.05-0.17,0.08-0.29,0.14
|
||||
c0.05-0.14,0.06-0.25,0.11-0.34c0.43-0.89,0.86-1.78,1.31-2.66c0.21-0.41,0.46-0.8,0.67-1.2c0.24-0.45,0.45-0.92,0.71-1.36
|
||||
c0.67-1.13,1.33-2.27,2.05-3.37c0.63-0.96,1.34-1.86,2.03-2.78c0.67-0.89,1.34-1.79,2.05-2.65c0.88-1.07,1.79-2.11,2.69-3.16
|
||||
c0.2-0.23,0.44-0.41,0.66-0.62c0.05-0.04,0.13-0.08,0.14-0.13c0.08-0.41,0.44-0.57,0.69-0.83c0.47-0.48,0.97-0.93,1.41-1.43
|
||||
c0.9-1.03,1.83-2.04,2.95-2.84c0.39-0.28,0.81-0.53,1.2-0.82c0.27-0.2,0.52-0.44,0.77-0.67c0.18-0.16,0.36-0.29,0.67-0.21
|
||||
C66.76,52.45,66.72,52.51,66.67,52.56z M66.72,52.04c0.29-0.18,0.58-0.37,0.87-0.55c0.02,0.03,0.03,0.06,0.05,0.08
|
||||
C67.38,51.82,67.19,52.15,66.72,52.04z M65.2,58.25c0.02-0.23,0.12-0.41,0.31-0.54C65.49,57.94,65.34,58.09,65.2,58.25z
|
||||
M65.55,57.66c0.14-0.27,0.43-0.72,0.45-0.7C65.94,57.24,65.77,57.47,65.55,57.66z M66.19,56.55c0.08-0.11,0.16-0.23,0.24-0.34
|
||||
C66.46,56.39,66.35,56.49,66.19,56.55z M70.23,51.47c-0.29,0.32-0.62,0.61-0.89,0.95c-0.56,0.73-1.1,1.48-1.64,2.22
|
||||
c-0.32,0.44-0.64,0.87-0.96,1.31c0.47-1.13,3.09-4.52,3.67-4.74C70.35,51.29,70.3,51.39,70.23,51.47z M59.1,81.62
|
||||
c-0.03-0.01-0.06-0.01-0.09-0.02c0.04-0.18,0.07-0.36,0.11-0.54c0.03,0.01,0.07,0.02,0.1,0.03C59.18,81.26,59.14,81.44,59.1,81.62z
|
||||
M59.27,80.66c-0.02,0-0.04-0.01-0.06-0.01c0.01-0.07,0.02-0.14,0.03-0.21c0.03,0.01,0.05,0.01,0.08,0.02
|
||||
C59.3,80.52,59.28,80.59,59.27,80.66z M59.33,80.19c-0.02,0-0.03,0-0.05-0.01c0.01-0.09,0.01-0.18,0.02-0.27
|
||||
c0.03,0,0.05,0.01,0.08,0.01C59.37,80.02,59.35,80.11,59.33,80.19z M65.87,81.69c0.02-0.1,0.04-0.2,0.06-0.3
|
||||
c0.02,0,0.05,0.01,0.07,0.01C66,81.5,65.91,81.69,65.87,81.69z M71.12,76.9c-0.03,0-0.07,0-0.1,0c0.01-0.12,0.01-0.25,0.02-0.37
|
||||
c0.03,0,0.05,0,0.08,0C71.12,76.65,71.12,76.77,71.12,76.9z M73.15,62.99c-0.13-0.31,0.17-1.66,0.42-1.86
|
||||
C73.43,61.75,73.29,62.37,73.15,62.99z M74.11,58.4c-0.14,0.74-0.29,1.47-0.44,2.21c-0.02,0.1-0.06,0.19-0.12,0.34
|
||||
c-0.06-0.09,0.32-2.14,0.53-3.13c0.01-0.04,0.05-0.07,0.08-0.11C74.15,57.94,74.15,58.17,74.11,58.4z M74.17,57.7
|
||||
c-0.02-0.39,0.14-0.73,0.28-1.08C74.37,56.98,74.47,57.39,74.17,57.7z M81.01,69.6c0.01,0.07,0.02,0.14,0.03,0.21
|
||||
c0,0.01-0.01,0.03-0.02,0.03c-0.01,0-0.02-0.01-0.05-0.02c-0.01-0.06-0.03-0.13-0.05-0.2C80.95,69.61,80.98,69.61,81.01,69.6z
|
||||
M80.97,69.06c-0.2-0.4-0.16-0.82-0.21-1.24c0.04-0.01,0.07-0.01,0.11-0.02C80.9,68.22,80.93,68.64,80.97,69.06z M80.77,67.26
|
||||
c0.02,0.1,0.04,0.21,0.06,0.31c-0.03,0.01-0.06,0.01-0.08,0.02c-0.03-0.11-0.05-0.22-0.08-0.34C80.7,67.25,80.74,67.26,80.77,67.26
|
||||
z M85.06,69.33c0.01,0,0.02,0,0.03,0c0.01,0.08,0.02,0.15,0.03,0.23c-0.02,0-0.03,0-0.05,0C85.06,69.49,85.06,69.41,85.06,69.33z
|
||||
M79.18,49.68c0.03-0.14-0.09-0.44-0.06-0.57c0.32,0,0.62-0.05,1.09,0.17c0.02,0.17,0.01,0.14,0.03,0.31
|
||||
C79.88,49.53,79.54,49.83,79.18,49.68z M80.84,50.72c0.25,0.02,0.25,0.02,0.31,0.35C81.06,50.96,80.96,50.85,80.84,50.72z
|
||||
M88.63,57.84c-0.29-0.15-0.35-0.25-0.49-0.69C88.29,57.37,88.44,57.58,88.63,57.84z M87.78,56.63c0.06,0.1,0.15,0.32,0.13,0.34
|
||||
C87.84,56.88,87.75,56.66,87.78,56.63z M84.87,52.11c-0.16-0.15-0.32-0.29-0.48-0.44C84.7,51.65,84.9,52.09,84.87,52.11z"/>
|
||||
<path class="st0" d="M25.56,105.87c0.2,0.01,0.25,0.16,0.35,0.28c0.14,0.19,0.31,0.29,0.57,0.26c0.92-0.09,6.82-0.11,7.38-0.13
|
||||
c0.21-0.01,2.94-0.11,2.94-0.08c-0.44,0.02,5.63,0.05,6.54,0.06c0.21,0,2.65,0.28,2.86,0.26c0,0,0,0,0,0
|
||||
c-0.05-0.01-0.25-0.05-0.9-0.24c0.13-0.03,0.19-0.06,0.25-0.05c0.79,0.1,20.85-0.11,22.37-0.1c0.75,0,2.98,0.01,3.15,0.03
|
||||
c0.16,0.02,9.08-0.31,9.95-0.21c0.55,0.06,1.1,0.11,1.65,0.16c0.65,0.06,1.31,0.1,1.96,0.15c0.02,0,0.05-0.01,0.09-0.02
|
||||
c0.02-0.05,0.04-0.11,0.08-0.21c0.65,0,1.33-0.01,2,0c0.65,0.01,1.29,0.05,1.94,0.07c0.25,0.01,0.5,0.01,0.74-0.01
|
||||
c0.25-0.02,0.5-0.05,0.76-0.04c0.82,0.04,1.65,0.04,2.48,0.05c0.13,0,0.26-0.01,0.4-0.02c0.43-0.01,0.87-0.07,1.3-0.01
|
||||
c0.78,0.1,1.55,0.04,2.33,0c0.76-0.04,1.53-0.09,2.29-0.11c0.89-0.02,1.78,0,2.66-0.01c0.72,0,1.44-0.01,2.16-0.02
|
||||
c0.08,0,0.16,0,0.23-0.03c0.4-0.17,0.4-0.18,0.67-0.16c0.06,0,0.12-0.01,0.18-0.02c-0.01-0.03-0.02-0.05-0.02-0.05
|
||||
c-1.16-0.06-2.31-0.15-3.47-0.17c-1.44-0.03-2.87,0-4.31-0.01c-0.35,0-0.71-0.05-1.06-0.07c-0.26-0.01-0.53,0-0.79-0.01
|
||||
c-0.72-0.04-1.45-0.1-2.17-0.12c-1.45-0.04-2.9-0.06-4.35-0.1c-0.41-0.01-0.82-0.04-1.22-0.05c-1.5-0.04-3-0.09-4.51-0.1
|
||||
c-1.12,0-2.24,0.1-3.36,0.1c-1.49,0.01-2.98-0.03-4.47-0.03c-1.41,0-2.82,0.03-4.23,0.04c-0.47,0-0.94,0-1.42,0
|
||||
c0.02-0.25,0.07-0.48,0.07-0.72c0.02-1.19,0.04-2.38,0.03-3.56c0-0.39-0.04-0.8-0.13-1.18c-0.06-0.25-0.24-0.47-0.18-0.8
|
||||
c0.54-0.02,1.06-0.06,1.59-0.05c1.07,0.02,10.22-0.03,10.28-0.03c1.38,0.05,2.77-0.17,4.15,0.01c0.07,0.01,0.19,0,0.2-0.04
|
||||
c0.08-0.2,0.25-0.11,0.38-0.12c0.14-0.01,0.29,0.03,0.43,0.02c0.51-0.04,1.02-0.09,1.52-0.14c0.03,0,0.06-0.04,0.11-0.07
|
||||
c-0.09-0.18-0.19-0.26-0.38-0.22c-0.2,0.04-0.41,0.08-0.62,0.08c-2.67-0.02-5.34-0.04-8-0.06c-2.52-0.02-10.01,0.1-11,0.14
|
||||
c-0.83,0.03-1.66,0-2.49-0.02c-0.88-0.03-9.45,0.04-9.77,0.03c-0.47-0.02-6.98,0.09-8.01,0.08c-0.87,0-6.47-0.14-7.47-0.1
|
||||
c-0.72,0.03-2.75-0.1-3.47-0.28c-0.1-0.03-0.26-0.02-0.33,0.04c-0.17,0.16-0.36,0.15-0.55,0.14c-0.28-0.01-0.55,0-0.83-0.01
|
||||
c-0.43-0.01-0.87,0-1.3-0.04c-0.58-0.06-0.59-0.07-0.55,0.54c0.5,0.05,0.57,0.12,0.69,0.63c0.02,0.1,0.06,0.21,0.06,0.31
|
||||
c0.04,0.86,0.1,1.71,0.12,2.57c0.01,0.84-0.03,1.69-0.05,2.53c-0.01,0.22-0.03,0.44-0.05,0.65c-0.21,0-0.38,0-0.55,0
|
||||
c-0.71,0-1.43,0-2.14-0.02c-0.63-0.02-2.97-0.1-3.51-0.1c-0.39,0-0.78-0.03-1.18-0.05c-0.08,0-0.17-0.06-0.22-0.03
|
||||
c-0.24,0.13-0.49,0.16-0.76,0.15c-0.08,0-0.2,0.12-0.23,0.21C25.38,105.71,25.5,105.87,25.56,105.87z M68.77,98.95
|
||||
c0.06,0.17,0.14,0.33,0.18,0.5c0.03,0.15,0.03,0.31,0.03,0.46c0,0.37,0,0.73,0,1.1c-0.01,0.97,0.11,1.94-0.06,2.91
|
||||
c-0.01,0.07-0.03,0.17,0,0.22c0.18,0.32,0.08,0.67,0.11,1.05c-1.35,0.13-2.69,0.07-4.07,0.16c0-0.23-0.01-0.41,0-0.59
|
||||
c0.05-0.85,0.1-1.71,0.15-2.56c0.03-0.5,0.07-1,0.09-1.49c0.01-0.13,0.02-0.28-0.04-0.39c-0.21-0.4-0.13-0.85-0.28-1.28
|
||||
C66.21,98.9,67.5,99.01,68.77,98.95z M57.87,104.08c0.08-1.08,0.15-2.16,0.13-3.24c-0.01-0.53-0.06-1.05-0.09-1.62
|
||||
c1.1-0.04,2.18-0.15,3.26-0.1c1.08,0.05,2.14-0.14,3.27-0.1c-0.03,0.51-0.05,0.95-0.07,1.4c-0.06,0.95-0.19,1.9,0.04,2.85
|
||||
c0.01,0.04,0.01,0.08,0.01,0.12c0.02,0.63,0.04,1.26,0.06,1.92c-2.16,0.15-4.33-0.05-6.51,0.02
|
||||
C57.8,104.91,57.84,104.48,57.87,104.08z M57.5,104.89c0.18,0.18,0.2,0.3,0.11,0.46C57.4,105.22,57.49,105.09,57.5,104.89z
|
||||
M53.51,102.12c0-0.42,0.05-0.84,0.02-1.26c-0.04-0.46,0.06-0.91,0.02-1.37c-0.01-0.09,0.03-0.18,0.05-0.31
|
||||
c0.25,0.03,0.92-0.12,1.06-0.12c0.75,0.02,1.5,0.04,2.24,0.06c0.34,1.04,0.22,2.1,0.13,3.16c-0.04,0.55-0.09,1.11-0.05,1.66
|
||||
c0.03,0.41,0.04,0.85,0.33,1.2c0.02,0.03,0.04,0.07,0.05,0.11c0.01,0.02,0,0.05-0.01,0.07c-0.58-0.02-3.16,0.05-3.54,0.05
|
||||
c-0.25,0-0.32-0.08-0.32-0.34C53.5,104.06,53.5,103.09,53.51,102.12z M47.58,99.17c1.74-0.17,3.4-0.03,5.01-0.01
|
||||
c0.27,1.94,0.23,3.85,0.35,5.77c0.2,0.12,0.2,0.12,0.18,0.47c-0.14,0.01-0.28,0.02-0.42,0.03c-1.62,0.03-3.23,0.25-4.86,0.14
|
||||
c-0.1-0.01-0.2-0.05-0.34-0.09C47.41,103.38,47.58,101.29,47.58,99.17z M47.13,105.08c0.13,0.19,0.14,0.31,0.03,0.45
|
||||
C46.99,105.4,47.07,105.28,47.13,105.08z M42.65,104.74c-0.01-0.06-0.02-0.15,0.01-0.19c0.25-0.27,0.19-0.62,0.25-0.94
|
||||
c0.03-0.14,0.03-0.29,0.03-0.43c0.01-0.19,0-0.39-0.06-0.59c-0.03,0.32-0.07,0.63-0.1,0.95c-0.03,0-0.05,0-0.08,0.01
|
||||
c-0.01-0.05-0.04-0.1-0.04-0.15c0.03-0.62,0.06-1.24,0.1-1.86c0.04-0.52,0.1-1.05,0.14-1.57c0.02-0.3,0-0.6,0-0.94
|
||||
c1.32,0.04,2.6,0.08,3.86,0.11c-0.03,0.58-0.08,1.13-0.09,1.68c-0.01,0.54,0.02,1.08,0.03,1.62c0,0.16-0.02,0.31-0.02,0.47
|
||||
c0.01,0.84,0.03,1.69,0.05,2.56c-1.13,0.13-3.98,0.11-4.34-0.05c0.13-0.07,0.23-0.12,0.34-0.17
|
||||
C42.71,105.08,42.68,104.91,42.65,104.74z M37.42,104.01c0.03-0.8,0.09-1.6,0.11-2.41c0.01-0.4,0.03-0.79,0.18-1.16
|
||||
c0.04-0.1,0.07-0.19-0.04-0.28c-0.04-0.03-0.05-0.12-0.05-0.18c0-0.35,0-0.71,0-1.1c1.5,0.02,2.95,0.06,4.46,0.24
|
||||
c0.06,0.81,0.14,1.6,0.16,2.39c0.02,0.58-0.04,1.16-0.05,1.73c-0.01,0.43,0,0.87,0.01,1.3c0,0.12,0.04,0.24,0.08,0.34
|
||||
c0.08,0.19,0.09,0.38-0.01,0.57c-0.25,0.08-3.47,0.02-4.86-0.09C37.42,104.92,37.41,104.46,37.42,104.01z M34.42,105.29
|
||||
c0.31-0.18,0.29-0.17,0.28-0.51c-0.02-0.68-0.01-1.37-0.02-2.05c-0.01-0.74-0.01-1.48-0.01-2.22c0-0.3,0.01-0.61,0.01-0.91
|
||||
c0-0.2-0.02-0.4-0.03-0.64c0.67,0,1.37,0,2.07,0c-0.02,1.16,0.13,2.29,0.14,3.43c0.01,0.54-0.05,1.08-0.06,1.62
|
||||
c-0.01,0.46,0,0.91,0,1.43c-0.84,0.02-1.66,0.05-2.57,0.07C34.33,105.39,34.36,105.32,34.42,105.29z"/>
|
||||
<path class="st0" d="M69.48,109.95c0.3,0,0.61-0.02,0.91-0.01c0.3,0.01,8.9-0.12,9.77-0.12c0.83,0,3.33-0.12,3.31-0.17
|
||||
c0.63,0,1.25,0,1.91,0c-0.15-0.16-0.44-0.1-0.45-0.37c0-0.02-0.09-0.05-0.14-0.05c-0.32,0.02-0.61-0.08-0.88-0.24
|
||||
c-0.1-0.06-1.32-0.09-1.44-0.09c-0.35-0.02-6.27,0.18-6.37,0.13c-0.39-0.17-15.17,0.27-15.24,0.27c-0.26,0.01-0.53,0.02-0.79,0.02
|
||||
c-1.89,0-5.86-0.02-5.96-0.03c-0.03-0.2-0.11-0.52-0.13-0.52c-0.02,0.16-0.04,0.33-0.07,0.52c-0.27,0.26-0.57,0.17-1.07,0.19
|
||||
c-0.82,0.03-2.09-0.1-2.91-0.08c-1,0.02-1.99-0.03-2.99,0.01c-1.59,0.06-3.19-0.01-4.79-0.02c-0.62-0.01-12.52-0.01-12.7-0.03
|
||||
c-0.58-0.08-1.16-0.07-1.73,0.04c-0.11,0.02-0.26,0.09-0.31,0.19c-0.08,0.14,0,0.28,0.17,0.34c0.02,0.01,0.03,0.04,0.05,0.06
|
||||
c-0.12,0.27-0.12,0.27,0,0.57c0.2,0,0.4,0,0.63,0c0.03,0.23,0.06,0.44,0.07,0.65c0.03,0.67,0.13,4.09,0.15,5.13
|
||||
c0.02,0.95,0.21,4.76,0.17,5.43c-0.02,0.42-0.04,0.84-0.34,1.19c-0.07,0.08-0.07,0.21-0.11,0.35c0.61-0.05,0.52-0.6,0.78-0.88
|
||||
c-0.04,0.45-0.09,0.91-0.14,1.42c0.05,0.03,0.13,0.05,0.18,0.1c0.11,0.11,0.48-0.15,0.5-0.27c0.02-0.17-0.13-2.33-0.13-2.62
|
||||
c0-0.38-0.06-0.77-0.02-1.15c0.13-1.13,0.09-2.27,0.14-3.4c0.08-1.82,0.18-3.64,0.27-5.46c0.01-0.19,0.05-0.39,0.07-0.61
|
||||
c0.13-0.01,0.25-0.03,0.36-0.02c0.51,0.02,6.93-0.05,7.76,0c0.34,0.02,1-0.02,1.37,0c0.03,0.44,0.26,1.08,0.05,1.5
|
||||
c-0.05,0.11-0.01,1.12,0.01,1.49c0.02,0.46,0.09,3.16,0.06,3.83c-0.04,1.37-0.1,2.74-0.16,4.11c-0.03,0.7,0.35,2.18,0.53,2.24
|
||||
c0.06-0.14,0.15-0.26,0.17-0.4c0.06-0.44,0.12-0.89,0.13-1.34c0.02-1.41,0.03-2.82,0.03-4.23c0-1.77-0.01-3.54-0.03-5.3
|
||||
c0-0.21-0.04-0.41-0.07-0.6c-0.13-0.04-0.23-0.07-0.33-0.1c0-0.32,0-0.62,0-0.95c0.42,0,0.82,0,1.22,0
|
||||
c-0.37-0.17-0.79-0.03-1.14-0.26c0.68,0,1.35,0,2.05,0c0.02,0.15,0.06,0.28,0.06,0.41c0.01,0.61,0.01,1.21,0.01,1.82
|
||||
c0,0.32,0.04,0.64-0.01,0.95c-0.1,0.64-0.09,1.28-0.08,1.93c0.02,0.82-0.02,1.63-0.05,2.45c-0.03,0.75-0.07,1.5-0.11,2.25
|
||||
c-0.02,0.4-0.05,0.79-0.06,1.19c-0.01,0.43,0.01,0.87,0.02,1.3c0.01,0.28,0.13,1.13,0.32,1.1c0.28-0.33,0.04-0.74,0.22-1.16
|
||||
c0.04,0.22,0.07,0.36,0.1,0.52c0.13-0.12,0.32-10.71,0.11-10.97c-0.02-0.03-0.01-0.08-0.01-0.12c-0.11-0.59,0.13-1.14,0.2-1.73
|
||||
c0.28,0,6.86-0.03,7.09-0.03c0.76,0.01,1.53,0.03,2.29,0.04c0.63,0.01,1.26,0,1.93,0c-0.02,0.53-0.04,1.04-0.06,1.58
|
||||
c-0.49-0.23-0.58-0.18-0.63,0.31c-0.04,0.43-0.14,0.86-0.09,1.29c0.01,0.13,0.02,0.26,0.02,0.39c0,0.87-0.01,1.74-0.01,2.6
|
||||
c0,0.3,0,0.6,0,0.91c-0.01,1-0.03,2-0.02,3c0,0.45,0.05,0.89,0.06,1.34c0.01,0.28,0.58,1.94,0.7,1.98c0.12-0.11,0.24-1.2,0.25-1.66
|
||||
c0.02-1.31,0.09-2.61,0.1-3.92c0.01-1.49-0.02-2.98-0.05-4.47c-0.01-0.7-0.08-1.39-0.1-2.09c-0.01-0.42,0.01-0.83,0.02-1.31
|
||||
c0.59,0,7.36-0.22,7.92-0.24c0.85-0.03,1.71-0.11,2.56-0.11c0.92,0,3.89,0.11,4.47,0.11c0,0.2,0,0.36,0,0.55
|
||||
c-0.08,0-0.15-0.01-0.21,0c-0.3,0.05-0.35,0.11-0.34,0.42c0,0.03,0.01,0.05,0,0.08c-0.02,0.4,0.16,0.84-0.18,1.2
|
||||
c-0.03,0.03-0.02,0.1-0.01,0.15c0.01,0.1,0.07,6.5,0.08,6.79c0.03,1.01,0.06,2.03,0.1,3.04c0.02,0.57,0.06,1.13,0.1,1.69
|
||||
c0.01,0.11,0.07,0.22,0.15,0.31c0.12,0.14,0.23,0.12,0.29-0.04c0.05-0.12,0.07-0.25,0.12-0.41c-0.1,0.03-0.16,0.05-0.23,0.07
|
||||
c-0.09-0.37-0.08-0.36,0.09-0.67c0.1-0.18,0.23-2.42,0.19-2.44c-0.2-0.15-0.14-1.02-0.14-1.25c0.02-1.65,0.24-7.9,0.16-9.37
|
||||
C69.25,110.02,69.32,109.95,69.48,109.95z M29.47,112.76c-0.02,0-0.03,0-0.05,0.01c-0.01-0.06-0.03-0.12-0.03-0.19
|
||||
c0.01-0.48,0.04-0.97,0.04-1.45c0-0.1-0.04-0.2-0.06-0.3c-0.03-0.11-0.07-0.21-0.14-0.38c0.19,0.02,0.31,0.04,0.45,0.06
|
||||
C29.61,111.28,29.54,112.02,29.47,112.76z M42.11,120.74c-0.01,0-0.02,0-0.03,0c0-0.1,0-0.19,0-0.29c0.01,0,0.02,0,0.03,0
|
||||
C42.11,120.55,42.11,120.65,42.11,120.74z M42.27,111.08c-0.16-0.22-0.15-0.45-0.16-0.71c0.12,0,0.2,0,0.33,0
|
||||
C42.38,110.62,42.32,110.85,42.27,111.08z"/>
|
||||
<path class="st0" d="M107.71,125.36c-0.01-0.23-0.02-0.43-0.04-0.64c-0.03-0.3-0.1-0.36-0.41-0.34c-1.22,0.08-2.45,0.16-3.67,0.24
|
||||
c-0.83,0.05-4.02,0.15-4.78,0.17c-0.26,0.01-9.43,0.02-9.81,0.03c-0.79,0.03-1.58,0.11-2.37,0.11c-1.3,0-2.61-0.05-3.91-0.08
|
||||
c-0.45-0.01-0.9,0-1.35-0.02c-1.62-0.05-11.08,0.02-11.54,0.03c-1.27,0.02-4.37,0.19-4.64,0.19c-0.76,0-1.52-0.02-2.29,0.01
|
||||
c-1.11,0.05-2.21,0.05-3.32,0.09c-0.19,0.01-0.36,0.01-0.52-0.12c-0.07-0.06-0.66-0.06-0.84-0.05c-0.76,0.05-1.53,0.16-2.29,0.15
|
||||
c-1.48-0.02-17.35-0.61-18.93-0.65c-1.27-0.03-8.68,0.01-9.73-0.02c-0.78-0.02-3.1-0.01-3.48,0.02c-0.31,0.03-0.62,0.1-0.94,0.13
|
||||
c-0.25,0.02-0.42,0.11-0.53,0.4c0.28,0.12,2.77,0.3,3.75,0.29c0.96-0.02,1.92-0.04,2.88-0.03c0.8,0.01,1.61,0.05,2.41,0.08
|
||||
c0.87,0.03,1.74,0.05,2.61,0.08c0.13,0,11.7,0.41,12.82,0.46c0.82,0.04,3.9,0.1,4.62,0.1c0.76,0,1.53,0.01,2.29,0.02
|
||||
c0.24,0,0.72-0.06,0.72-0.09c-0.28-0.02-0.55-0.03-0.83-0.05c0-0.03,0-0.06,0-0.08c0.46-0.06,0.92-0.02,1.38,0.04
|
||||
c0.02,0,3.43-0.02,7.29-0.04c0-0.02,0-0.03,0-0.05c0.07,0.01,0.15,0.02,0.22,0.03c-0.05,0.01-0.1,0.01-0.15,0.02
|
||||
c4.88-0.03,10.43-0.07,10.73-0.07c1.29,0,2.58,0.01,3.88,0.02c0.63,0.01,5.69-0.03,6.23,0c0.79,0.03,2.65,0.01,2.79,0.03
|
||||
c0.39,0.03,2.88-0.02,3.73-0.04c0.66-0.02,8.65-0.05,10.52-0.08c0.46-0.01,0.92,0,1.39,0.01c0.49,0.01,0.97,0.06,1.46,0.06
|
||||
c0.8-0.01,1.6-0.05,2.4-0.06c0.74-0.02,1.48-0.01,2.21-0.03c0.28-0.01,0.55-0.08,0.83-0.12c0-0.03-0.01-0.05-0.01-0.08
|
||||
C108.24,125.4,107.97,125.38,107.71,125.36z M49.63,125.76c-0.01,0-0.03-0.03-0.1-0.09c0.11,0.02,0.16,0.03,0.21,0.04
|
||||
C49.7,125.73,49.67,125.75,49.63,125.76z"/>
|
||||
<path class="st0" d="M69.9,31.26c0.2-0.01,0.43-0.09,0.58,0c0.22,0.12,0.4,0.05,0.6,0.02c0.16-0.02,0.31-0.04,0.47-0.04
|
||||
c0.69-0.02,1.37-0.03,2.06-0.04c1.24-0.02,2.48,0.09,3.72-0.03c0.59-0.06,1.18,0.03,1.78,0.04c0.84,0.02,1.69,0.05,2.53,0.03
|
||||
c0.47-0.01,0.97,0.02,1.38-0.4c-0.11-0.08-0.21-0.16-0.25-0.19c-0.05-0.16-0.06-0.29-0.13-0.36c-0.16-0.17-0.34-0.34-0.53-0.47
|
||||
c-0.15-0.1-0.22-0.23-0.24-0.39c-0.03-0.22-0.05-0.44-0.06-0.67c-0.02-0.28-0.1-0.42-0.25-0.44c-0.21-0.02-0.35,0.12-0.39,0.41
|
||||
c-0.02,0.19,0,0.39,0,0.63c-0.19-0.02-0.39-0.06-0.59-0.04c-0.22,0.02-0.39,0.01-0.45-0.25c-0.14,0.02-0.26,0.03-0.37,0.05
|
||||
c-0.16-0.37,0.18-0.68,0.07-1.05c-0.26-0.12-0.46-0.02-0.65,0.2c0.04,0.11,0.09,0.21,0.11,0.29c-0.21,0.21-0.4,0.4-0.59,0.58
|
||||
c-0.2-0.03-0.41-0.05-0.59-0.07c-0.03-0.1-0.04-0.2-0.09-0.27c-0.19-0.28-0.32-0.57-0.3-0.92c0.01-0.21-0.13-0.3-0.34-0.27
|
||||
c-0.11,0.01-0.22,0.04-0.36,0.06c-0.02-0.14-0.03-0.24-0.04-0.35c-0.06-0.53,0.06-1.09-0.24-1.58c-0.02-0.03-0.01-0.08-0.01-0.12
|
||||
c0.01-0.41,0.03-0.82,0.03-1.22c0-0.49,0.19-0.96,0.08-1.45c-0.02-0.08,0.04-0.18,0.06-0.25c0.59,0.22,0.65,0.85,1.07,1.2
|
||||
c-0.05-0.25-0.14-0.47-0.21-0.71c-0.14-0.45-0.21-0.77-0.26-1.23c-0.14-0.82-0.35-1.61-0.37-2.36c-0.01-0.22,0.27-0.35,0.46-0.43
|
||||
c0.22-0.09,0.86-0.68,0.83-1.58c-0.03-0.84-0.41-1.39-0.91-1.59c-0.87-0.54-1.96,0-2.22,0.3c-0.26,0.23-0.58,0.7-0.54,1.38
|
||||
c0.13,0.57,0.3,1.03,0.72,1.34c0.09,0.03,0.28,0.2,0.37,0.23c0.19,0.06,0.24,0.21,0.22,0.37c-0.04,0.3-0.1,0.6-0.16,0.89
|
||||
c-0.06,0.31-0.23,1.36-0.31,1.67c-0.56,2.6-1.76,4.36-2.31,5.2c-0.06,0.1-0.15,0.19-0.25,0.24c-0.15,0.08-0.18,0.19-0.23,0.34
|
||||
c-0.11,0.39-0.32,0.7-0.75,0.81c-0.02,0.01-0.04,0.03-0.06,0.05c-0.03,0.11-0.06,0.22-0.11,0.39c-0.08-0.1-0.15-0.15-0.15-0.19
|
||||
c0.01-0.12,0.08-0.23,0.09-0.34c0.03-0.32,0.07-0.65,0.06-0.98c-0.01-0.23-0.28-0.34-0.47-0.24c-0.27,0.14-0.48,0.7-0.4,0.99
|
||||
c0.06,0.2,0.09,0.4,0.15,0.64c-0.25,0.02-0.44,0.03-0.63,0.05c-0.23,0.03-0.27,0.11-0.22,0.41c0.25-0.11,0.37,0.04,0.48,0.22
|
||||
c-0.02,0.04-0.03,0.07-0.05,0.08c-0.1,0.08-0.18,0.17-0.1,0.3c0.07,0.12,0.2,0.13,0.32,0.08c0.19-0.08,0.41-0.14,0.57-0.26
|
||||
c0.23-0.18,0.44-0.2,0.69-0.1c0.09,0.03,0.23,0.06,0.29,0.01c0.3-0.25,0.66-0.24,1.01-0.22c0.46,0.02,0.92,0.07,1.37,0.08
|
||||
c1.25,0.01,2.5,0.01,3.76-0.01c0.24,0,0.43,0.14,0.64,0.15c0.23,0,0.46-0.12,0.69-0.12c0.44,0.01,0.88,0.07,1.32,0.11
|
||||
c0.02-0.02,0.21,0.05,0.22,0.05c0.38,0.29,0.84,0.1,0.98,0.75c-0.38,0.03-0.69,0.07-1,0.08c-0.11,0-0.26,0-0.34-0.07
|
||||
c-0.24-0.2-0.55-0.02-0.81,0c-0.09,0.01-0.18,0.03-0.27,0.03c-1.35,0-2.71,0.01-4.07,0.01c-0.66,0-1.32-0.03-1.98-0.05
|
||||
c-0.8-0.02-1.61-0.05-2.41-0.08c-0.41-0.01-0.82,0.02-1.22-0.06c-0.32-0.07-0.54,0.05-0.76,0.19c-0.16,0.11-0.16,0.19-0.11,0.39
|
||||
C69.57,31.31,69.74,31.27,69.9,31.26z M75.86,18.67c-0.32-0.24-0.58-0.76-0.57-1.13c0.03-0.67,0.41-1.1,0.97-1.22
|
||||
c0.6-0.12,1.24,0.22,1.47,0.79c0.3,0.99-0.23,1.68-0.69,1.71C76.75,18.84,76.18,18.91,75.86,18.67z M76.2,22.28
|
||||
c0.09-0.37,0.15-0.75,0.22-1.12c0.12,0.2,0.23,1.19,0.19,1.69c-0.29,0.04-0.29,0.04-0.72,0.58C76.01,23,76.12,22.64,76.2,22.28z
|
||||
M74.61,27.82c-0.06,0.42-0.32,0.83-0.04,1.28c-0.31,0.02-0.54,0.04-0.78,0.05c-0.24,0.01-0.29-0.07-0.23-0.32
|
||||
c0.02-0.1-0.11-0.3-0.12-0.4c-0.03-0.39,0.18-0.4,0.47-0.61c0.17-0.12,0.46-0.68,0.58-0.86c0.3-0.44,0.58-1.06,0.74-1.56
|
||||
c0.21-0.66,0.32-0.97,0.55-1.67c0.11,0.27,0.26,0.56,0.25,0.79c-0.01,0.24,0,0.72,0,0.96c0,0.85-0.27,1.46-0.68,2.22
|
||||
c-0.06-0.01-0.32-0.12-0.38-0.13C74.77,27.52,74.64,27.63,74.61,27.82z M76.22,29.15c-0.33-0.02-0.67,0.07-1.02-0.06
|
||||
c0.03-0.07,0.05-0.13,0.08-0.15c0.48-0.24,0.61-0.71,0.73-1.17c0.15-0.55,0.26-1.11,0.38-1.67C76.32,27.11,76.55,28.14,76.22,29.15
|
||||
z M76.55,24.07c-0.17-0.32,0.05-0.61,0.05-0.91C76.65,23.47,76.71,23.78,76.55,24.07z M77.85,29.11c-0.81,0.04-0.53-0.38-0.51-0.65
|
||||
c0.14,0.05,0.51,0.51,0.65,0.56C77.92,29.07,77.89,29.11,77.85,29.11z"/>
|
||||
<path class="st0" d="M53.59,108c0.02,0,0.05,0.02,0.07,0.03c0.22,0.03,0.44,0.09,0.66,0.09c0.38-0.01,11.62-0.25,12.26-0.3
|
||||
c0.26-0.02,0.53,0,0.79-0.02c0.7-0.05,1.38-0.12,2.09-0.06c0.98,0.09,1.97,0.11,2.96,0.15c0.44,0.02,0.88,0,1.32-0.09
|
||||
c-0.04-0.36-0.19-0.55-0.54-0.52c-0.14,0.01-0.29-0.02-0.43-0.05c-0.1-0.02-0.22-0.05-0.27-0.12c-0.14-0.18-0.31-0.17-0.5-0.15
|
||||
c-0.64,0.07-3.8,0.31-4.73,0.3c-1.56-0.02-3.11-0.11-4.67-0.12c-1.29-0.01-2.59,0.07-3.88,0.11c-1.05,0.04-12.43,0-13.64,0.01
|
||||
c-0.44,0-2.73,0.09-3.44,0.07c-1.07-0.03-12.25-0.1-14.17-0.15c-0.07,0-0.15-0.02-0.19,0.02c-0.22,0.2-0.49,0.18-0.75,0.18
|
||||
c-0.28,0-0.47,0.09-0.62,0.33c-0.06,0.1-0.19,0.19-0.1,0.3c0.06,0.07,0.2,0.11,0.3,0.12c0.79,0.03,1.58,0.06,2.37,0.06
|
||||
c1.15,0,2.3-0.02,3.44-0.04c0.92-0.02,9-0.21,10.03-0.02c0.04,0.01,2.15-0.01,3.16,0.01c1.95,0.04,3.9,0.09,5.86,0.14
|
||||
c0.51,0.01,1.03,0.04,1.54,0.05c0.6,0.01,1.19,0,1.79-0.01c-0.15-0.04-0.3-0.09-0.46-0.11c-0.41-0.03-0.81-0.04-1.22-0.07
|
||||
c-0.06,0-0.13-0.04-0.19-0.06c0-0.03,0.01-0.06,0.01-0.08C52.81,108,53.2,108,53.59,108z M72.09,107.5c0.22,0,0.46-0.06,0.66,0.14
|
||||
c-0.23-0.01-0.45-0.03-0.68-0.04C72.08,107.57,72.09,107.53,72.09,107.5z"/>
|
||||
<path class="st0" d="M32.43,97.78c0.07,0.02,0.15,0.04,0.23,0.04c0.63,0.02,8.8-0.05,9.92-0.07c0.51-0.01,2.91-0.02,3.59-0.02
|
||||
c0.29,0,0.58-0.05,0.87-0.05c1.07,0.01,2.13,0.03,3.2,0.04c2.73,0.01,5.47,0.03,8.2,0.01c2.74-0.02,15.51-0.2,16.56-0.24
|
||||
c0.37-0.01,0.74,0,1.11,0c1.25-0.01,14,0.08,16.9-0.07c0.92-0.05,3.78-0.04,4.02-0.04c-0.03-0.31-0.19-0.42-0.4-0.44
|
||||
c-0.31-0.03-0.63-0.03-0.95-0.03c-0.54-0.01-1.08-0.01-1.61-0.01c-0.16,0-0.32-0.01-0.47,0c-0.54,0.03-3.02,0.09-3.57,0.11
|
||||
c-1.13,0.04-2.27,0.08-3.4,0.08c-9.18,0.01-31.34,0.12-31.84,0.13c-0.71,0.01-1.42,0.02-2.13,0.02c-0.54,0-12.79-0.05-14.62-0.06
|
||||
c-0.2,0-3.64-0.13-3.83-0.17c-0.61-0.13-1.18,0.08-1.75,0.2c-0.15,0.03-0.25,0.15-0.25,0.32C32.17,97.69,32.3,97.74,32.43,97.78z"
|
||||
/>
|
||||
<path class="st0" d="M80.49,83.44c0.03-0.15,0.05-0.3,0.08-0.45c-1.44-1.09-2.89-2.18-4.33-3.27c-0.25,0.17-0.27,0.24-0.12,0.45
|
||||
c0.06,0.08,0.15,0.14,0.22,0.22c0.1,0.11,0.2,0.22,0.29,0.33c-0.01,0.03-0.03,0.05-0.04,0.08c-0.2-0.18-0.4-0.28-0.64-0.1
|
||||
c-0.57,0.43-1.15,0.83-1.7,1.29c-0.37,0.31-0.68,0.68-1.02,1.03c-0.21,0.21-0.44,0.42-0.65,0.63c-0.09,0.09-0.2,0.18-0.26,0.3
|
||||
c-0.04,0.08-0.03,0.2,0.01,0.28c0.03,0.06,0.15,0.1,0.22,0.1c0.13,0,0.25-0.04,0.4-0.08c0.02,0.21,0.03,0.38,0.05,0.57
|
||||
c0.15-0.02,0.26-0.03,0.33-0.04c0.19,0.21-0.03,0.46,0.17,0.71c0.03-0.15,0.06-0.24,0.06-0.32c0-0.39,0.12-0.75,0.24-1.12
|
||||
c0.14-0.46,0.45-0.8,0.8-1.11c0.06-0.05,0.14-0.1,0.21-0.1c0.29-0.03,0.58-0.05,0.87-0.07c0.06,0,0.12,0.04,0.27,0.1
|
||||
c-0.44,0.12-0.7,0.4-1.08,0.45c-0.19,0.03-0.36,0.15-0.53,0.25c-0.06,0.04-0.1,0.15-0.1,0.23c0,0.05,0.1,0.1,0.16,0.14
|
||||
c0.04,0.02,0.1,0.01,0.16,0c0.6-0.07,1.2-0.16,1.81-0.2c0.35-0.03,0.71,0.03,1.1,0.05c-0.05,0.07-0.06,0.11-0.07,0.12
|
||||
c-0.4,0.11-2.07,0.36-2.35,0.29c-0.11,0.27-0.05,0.51-0.05,0.76c0,0.42-0.03,1.42,0.05,1.54c0.09,0.13,0.11,0.31,0.17,0.5
|
||||
c0.27-0.16,1.26-0.3,1.51-0.24c0.13,0.03,0.29,0.02,0.42-0.03c0.25-0.09,1.36-0.09,1.41,0.06c0.37-0.14,0.51-0.42,0.37-0.73
|
||||
c-0.18-0.4-0.16-1.82-0.2-1.98c-0.13,0.01-0.22,0.02-0.31,0.03c-0.12,0-0.28-0.05-0.37,0.01c-0.09,0.06-0.12,0.22-0.17,0.34
|
||||
c-0.04,0.08-0.05,0.18-0.1,0.26c-0.03,0.05-0.09,0.07-0.2,0.15c0.08-0.34-0.08-0.43-0.31-0.48c-0.06-0.01-0.1-0.08-0.18-0.15
|
||||
c0.21-0.06,0.37-0.11,0.54-0.15c0.23-0.05,0.24-0.23,0.21-0.39c0.37-0.03,0.71-0.05,1.04-0.07c0.32,0.23,0.27,0.63,0.45,0.9
|
||||
c0.4-0.01,0.48-0.1,0.35-0.43c-0.11-0.27-0.25-0.52-0.02-0.85C79.88,83.49,80.19,83.5,80.49,83.44z M75.58,85.07
|
||||
c-0.02-0.1-0.04-0.21-0.07-0.32C75.8,84.76,75.8,84.77,75.58,85.07z M76.65,84.47c0.15,0.04,0.27,0.06,0.38,0.11
|
||||
c0.03,0.02,0.07,0.13,0.04,0.16c-0.11,0.18-0.27,0.06-0.42,0.08C76.65,84.7,76.65,84.61,76.65,84.47z M76.72,85.22
|
||||
c0.08-0.01,0.17-0.01,0.29-0.02c0,0.16,0,0.31,0,0.45C76.73,85.59,76.73,85.59,76.72,85.22z M77.66,85.04
|
||||
c0.04,0.26,0.06,0.46,0.09,0.67C77.48,85.62,77.45,85.42,77.66,85.04z M77.31,86.23c-0.15,0.05-0.24,0.08-0.33,0.1
|
||||
c-0.02-0.05-0.05-0.08-0.04-0.09c0.04-0.07,0.09-0.14,0.13-0.2C77.14,86.09,77.2,86.14,77.31,86.23z M78.06,82.3
|
||||
c-0.22-0.02-0.39-0.03-0.56-0.04C77.72,81.99,77.77,82,78.06,82.3z M76.05,82.08c0.23-0.29,0.4-0.34,0.74-0.24
|
||||
c0.12,0.04,0.26,0.05,0.38,0.07c0,0.03,0,0.07-0.01,0.1C76.8,82.03,76.43,82.06,76.05,82.08z M78.21,83.01
|
||||
c-0.18,0.01-0.36,0.03-0.55,0.03c-0.18,0-0.36-0.03-0.54-0.05c0.52-0.1,1.02-0.17,1.6-0.15C78.57,83.1,78.38,83,78.21,83.01z"/>
|
||||
<path class="st0" d="M56.07,124.61c0.04-0.27,0.08-0.52,0.13-0.77c0.08,0,0.39-0.44,0.46-0.66c0.07-0.21,0.05-1.55,0.07-1.77
|
||||
c0.11-1.13,0.32-1.13,0.39-2.26c0.06-0.83,0.09-1.66,0.13-2.49c0.01-0.12,0-0.25,0-0.37c-0.03,0-0.06,0-0.1-0.01
|
||||
c-0.1,0.67-0.2,1.35-0.3,2.02c-0.01,0-0.03,0-0.04,0c-0.01-0.66-0.02-1.31-0.04-1.97c-0.03-0.76-0.07-1.53-0.11-2.29
|
||||
c-0.02-0.42-0.12-0.85-0.06-1.26c0.05-0.32-0.1-0.59-0.05-0.89c0.01-0.03-0.03-0.09-0.05-0.1c-0.21-0.09-0.17-0.28-0.17-0.44
|
||||
c0-0.11,0.01-0.21-0.01-0.32c-0.03-0.14-0.23-0.22-0.31-0.11c-0.1,0.14-0.23,0.32-0.22,0.48c0.01,0.87,0.07,1.73,0.1,2.6
|
||||
c0.02,0.58,0.06,1.16,0.01,1.73c-0.06,0.71-0.08,1.41-0.06,2.12c0.04,1.2,0.03,2.39,0.04,3.59c0.01,0.92,0.03,1.84,0.05,2.76
|
||||
C55.94,124.35,55.91,124.5,56.07,124.61z M56.51,118.93c0.02,0,0.03,0,0.05,0c0,0.35,0,0.69,0,1.04c-0.02,0-0.03,0-0.05,0
|
||||
C56.51,119.63,56.51,119.28,56.51,118.93z"/>
|
||||
<path class="st0" d="M45.67,116.82c-0.27,0.53-0.03,1.06-0.27,1.52c-0.06-0.87-0.09-1.74-0.1-2.6c-0.01-0.87,0.09-1.74-0.02-2.6
|
||||
c-0.02-0.18,0.05-0.39-0.02-0.54c-0.14-0.32-0.1-0.64-0.1-0.97c0-0.14,0-0.29,0-0.43c0-0.13,0-0.26,0-0.39c-0.03,0-0.06,0-0.08,0
|
||||
c-0.03,0.15-0.06,0.3-0.09,0.44c-0.22,0.12-0.49,0.21-0.49,0.5c0.01,0.58-0.09,1.15-0.07,1.73c0.05,1.37,0.03,2.74,0.04,4.1
|
||||
c0.01,0.88,0.04,1.75,0.01,2.63c-0.04,1.08,0.01,2.16,0.12,3.24c0.02,0.17,0.05,0.34,0.22,0.38c0.17,0.03,0.2,0.13,0.23,0.27
|
||||
c0.02,0.06,0.08,0.1,0.16,0.19c0.05-0.13,0.06-0.24,0.12-0.29c0.29-0.22,0.25-0.54,0.26-0.83c0.01-0.55-0.04-1.1-0.03-1.65
|
||||
c0.02-1.13,0.08-2.27,0.11-3.4C45.68,117.7,45.67,117.3,45.67,116.82z"/>
|
||||
<path class="st0" d="M51.8,115.49c0.05-1.46-0.06-2.91-0.09-4.37c0-0.09-0.08-0.22-0.15-0.24c-0.06-0.02-0.16,0.08-0.25,0.12
|
||||
c-0.09,0.04-0.17,0.08-0.25,0.11c0.26,1.72,0.23,3.43-0.09,5.14c-0.05-1.69-0.09-3.37-0.14-5.06c-0.09,0.18-0.15,0.37-0.15,0.56
|
||||
c0,0.71,0.01,1.42,0.02,2.13c0.02,0.79,0.04,1.58,0.07,2.37c0.04,0.86,0.12,1.71,0.16,2.57c0.03,0.84,0.03,1.69,0.03,2.53
|
||||
c0,0.44,0.06,2.54,0.06,2.79c0,0.15,0.09,0.21,0.21,0.25c0.15,0.05,0.58-1.14,0.57-1.37c-0.02-0.91-0.04-1.81-0.03-2.72
|
||||
c0-0.59,0.06-1.19,0.06-1.78C51.83,117.52,51.76,116.51,51.8,115.49z"/>
|
||||
<path class="st0" d="M64.66,115.81c-0.03-0.37-0.03-0.73-0.03-1.1c0-0.74,0.02-1.48,0.02-2.22c0-0.44-0.21-0.59-0.66-0.47
|
||||
c0.03-0.48,0.05-0.95,0.08-1.49c-0.19,0.21-0.25,0.39-0.26,0.59c-0.01,0.54-0.02,1.08-0.04,1.62c-0.01,0.3-0.02,0.6-0.02,0.91
|
||||
c-0.01,0.59-0.01,1.18-0.01,1.77c0,0.14-0.02,0.29-0.02,0.43c0.01,0.8,0.03,1.61,0.04,2.41c0.01,0.75,0.03,1.5,0.04,2.25
|
||||
c0.01,0.76,0,1.53-0.01,2.29c0,0.43,0,0.87,0,1.33c0.15,0.05,0.27,0.09,0.39,0.13c0.06,0.01,0.13,0.02,0.18,0
|
||||
c0.17-0.08,0.3-0.64,0.16-0.74c-0.15-0.1-0.14-0.21-0.13-0.33c0.03-0.46,0.06-0.92,0.11-1.38
|
||||
C64.77,119.82,64.81,117.82,64.66,115.81z"/>
|
||||
<path class="st0" d="M33.57,122.74c-0.01-0.22-0.01-0.45-0.05-0.67c-0.13-0.67,0.04-1.33,0.01-2c-0.06-1.14-0.03-2.29-0.02-3.44
|
||||
c0.01-1.38,0.03-2.77,0.04-4.15c0-0.27-0.02-0.53-0.04-0.82c-0.08,0.05-0.11,0.06-0.17,0.1c-0.03-0.22-0.06-0.41-0.08-0.61
|
||||
c-0.01-0.13-0.09-0.21-0.2-0.19c-0.09,0.01-0.18,0.09-0.23,0.16c-0.05,0.08-0.03,0.24-0.1,0.28c-0.23,0.15-0.23,0.37-0.23,0.59
|
||||
c0.02,0.63,0.06,1.26,0.06,1.89c0,1.19-0.02,2.37-0.03,3.56c0,0.6-0.03,1.21,0.11,1.8c0.06,0.25,0.11,0.51,0.13,0.77
|
||||
c0.04,0.62,0.06,1.24,0.09,1.85c0.02,0.29,0.02,0.58,0.07,0.87c0.05,0.25,0.15,0.5,0.24,0.74c0.02,0.06,0.12,0.09,0.24,0.17
|
||||
c0.01-0.2-0.03-0.4,0.03-0.44C33.68,123.09,33.58,122.91,33.57,122.74z"/>
|
||||
<path class="st0" d="M81.35,34.96c0.21-0.23,0.72-0.07,0.72-0.58c0-0.02,0.04-0.05,0.06-0.05c0.25,0,0.19-0.22,0.22-0.35
|
||||
c0.05-0.18-0.04-0.28-0.23-0.27c-0.24,0.01-0.47,0.03-0.71,0.07c-1.14,0.2-8.62,0.01-9.41,0.03c-0.31,0.01-0.77-0.1-1.28,0.04
|
||||
c-0.12,0.19-0.06,0.34,0.07,0.41c0.15,0.09,0.33,0.18,0.5,0.19c0.93,0.06,1.86-0.04,2.79,0.08c0.74,0.1,1.5,0.08,2.25,0.09
|
||||
c1.56,0.01,3.11,0,4.67,0.01c0.12,0,0.25,0,0.41,0c-0.06,0.13-0.09,0.19-0.12,0.26C81.27,34.96,81.3,35.05,81.35,34.96z"/>
|
||||
<path class="st0" d="M66.68,117.21c0.03-0.89,0.08-1.78,0.07-2.67c-0.01-0.5,0-1,0-1.5c0.01-0.42,0.03-0.84,0.04-1.26
|
||||
c0.01-0.45,0-0.9,0-1.35c-0.03,0-0.06,0-0.09-0.01c-0.03,0.16-0.05,0.32-0.09,0.52c-0.11,0-0.24,0.02-0.36,0
|
||||
c-0.24-0.06-0.35-0.02-0.38,0.22c-0.02,0.16,0.01,0.34,0.02,0.51c0.02,0.22,0.11,0.43-0.05,0.64c-0.04,0.06-0.04,0.18-0.01,0.26
|
||||
c0.19,0.56,0.16,1.14,0.16,1.72c0,0.77-0.04,1.55-0.02,2.32c0.02,1.05,0.11,2.1,0.12,3.16c0.01,1-0.05,2-0.04,2.99
|
||||
c0,0.23,0.13,0.47,0.25,0.68c0.1,0.17,0.31,0.13,0.36-0.05c0.06-0.2,0.12-0.42,0.09-0.62c-0.09-0.46-0.12-0.92-0.15-1.39
|
||||
c-0.02-0.41,0.01-0.82,0.01-1.23C66.64,119.19,66.64,118.2,66.68,117.21z"/>
|
||||
<path class="st0" d="M31.42,115.57c0.01-0.87-0.04-1.73-0.08-2.6c-0.02-0.42-0.08-0.84-0.13-1.25c-0.02-0.14-0.03-0.3-0.23-0.32
|
||||
c-0.18-0.01-0.36,0.12-0.41,0.31c-0.03,0.11-0.03,0.24-0.02,0.35c0.03,1.13,0.06,2.27,0.08,3.4c0.02,1.15,0.04,2.29,0.06,3.44
|
||||
c0.03,1.38,0.05,2.77,0.09,4.15c0.01,0.31,0.08,0.61,0.13,0.93c0.35-0.07,0.3-0.31,0.32-0.47c0.14-0.13,0.19-0.6,0.16-0.71
|
||||
c-0.05-0.19-0.1-0.39-0.1-0.58c-0.01-1.1-0.01-2.19,0.01-3.29C31.33,117.81,31.41,116.69,31.42,115.57z"/>
|
||||
<path class="st0" d="M74.57,68.98c-0.17,0.34-0.36,0.67-0.51,1.01c-0.14,0.32-0.07,0.65,0.13,0.94c0.05,0.08,0.18,0.17,0.23,0.16
|
||||
c0.09-0.03,0.19-0.14,0.22-0.23c0.05-0.17,0.06-0.36,0.07-0.55c0.01-0.3,0.07-0.6,0.19-0.87c0.29,0,0.51-0.08,0.65-0.33
|
||||
c0.06-0.11,0.18-0.19,0.27-0.29c0.12-0.15,0.25-0.23,0.47-0.18c0.26,0.05,0.53,0.03,0.85,0.05c0.03,0.06,0.09,0.17,0.16,0.31
|
||||
c-0.59,0.05-1.12,0.15-1.61,0.41c0.06,0.28,0.05,0.3,0.18,0.32c0.59,0.11,1.19,0.4,1.81,0.01c0.05,0.18,0.09,0.34,0.14,0.5
|
||||
c0.28-0.16-0.06-0.58,0.36-0.66c0.12,0.24,0.25,0.48,0.36,0.73c0.08,0.18-0.01,0.4-0.19,0.52c-0.16,0.1-0.3,0.06-0.38-0.11
|
||||
c-0.04-0.08-0.05-0.18-0.09-0.26c-0.07-0.14-0.2-0.18-0.33-0.09c-0.13,0.09-0.14,0.22-0.07,0.35c0.16,0.27,0.65,0.49,0.88,0.37
|
||||
c0.31-0.16,0.62-0.48,0.48-0.9c-0.14-0.43-0.35-0.85-0.56-1.25c-0.3-0.55-0.81-0.85-1.37-1.09c0.12-0.32-0.24-0.28-0.33-0.49
|
||||
c0.06-0.17,0.04-0.32-0.17-0.43c-0.39-0.21-0.76-0.08-0.85,0.36c-0.05,0.23-0.01,0.49-0.01,0.81
|
||||
C75.2,68.32,74.79,68.54,74.57,68.98z M77.63,69.59c-0.16-0.07-0.28-0.13-0.44-0.2c0.08-0.1,0.14-0.18,0.21-0.27
|
||||
C77.61,69.21,77.62,69.38,77.63,69.59z"/>
|
||||
<path class="st0" d="M74.01,32.7c0.82-0.01,1.63-0.04,2.45-0.03c0.69,0,1.37,0.04,2.06,0.07c0.38,0.02,0.76,0.08,1.14,0.11
|
||||
c0.33,0.03,0.66,0.05,0.99,0.05c0.17,0,0.34-0.09,0.5-0.13c0-0.1,0.01-0.12,0-0.13c-0.03-0.04-0.07-0.08-0.1-0.12
|
||||
c-0.18-0.3-0.43-0.4-0.79-0.37c-0.56,0.05-1.13,0.04-1.7,0.04c-0.46,0-0.92-0.04-1.38-0.04c-0.72,0-1.45,0.04-2.17,0.04
|
||||
c-1.19-0.01-2.37-0.05-3.56-0.08c-0.22-0.01-0.42,0.01-0.63,0.16c0.03,0.14-0.13,0.25,0.11,0.48c0.46,0.01,0.9,0.02,1.35,0.01
|
||||
C72.86,32.73,73.43,32.71,74.01,32.7z"/>
|
||||
<path class="st0" d="M63.95,83.37c0.1-0.14,0.08-0.25,0.04-0.4c-0.13-0.44-0.51-0.66-0.8-0.95c-0.18-0.18-0.41-0.21-0.66-0.16
|
||||
c-0.51,0.11-0.98,0.28-1.4,0.61c-0.28,0.22-0.58,0.41-0.86,0.63c-0.15,0.12-0.3,0.27-0.38,0.44c-0.06,0.14-0.04,0.33-0.02,0.49
|
||||
c0.01,0.11,0.07,0.21,0.13,0.31c0.04,0.06,0.12,0.15,0.16,0.14c0.09-0.03,0.18-0.09,0.23-0.17c0.05-0.07,0.02-0.2,0.07-0.25
|
||||
c0.3-0.29,0.62-0.57,0.94-0.83c0.11-0.09,0.26-0.13,0.42,0.01c-0.02,0.11-0.03,0.24-0.05,0.36c0.25,0.16,0.47,0.27,0.78,0.21
|
||||
c0.28-0.05,0.57-0.01,0.86,0c0.14,0,0.28,0.02,0.4,0.04C63.87,83.64,63.87,83.48,63.95,83.37z M61.99,82.88
|
||||
c0.26-0.05,0.45-0.08,0.64-0.11C62.54,83.15,62.42,83.18,61.99,82.88z M62.77,82.81c0.39-0.05,0.48,0.05,0.59,0.57
|
||||
C63.02,83.29,63.13,82.88,62.77,82.81z"/>
|
||||
<path class="st0" d="M63.48,84.96c-0.24-0.11-0.45-0.06-0.65-0.03c-0.49,0.06-0.98,0.2-1.48,0.05c-0.06-0.02-0.15,0.03-0.23,0.05
|
||||
c-0.02,0.12-0.04,0.23-0.06,0.32c-0.17,0.09-0.39-0.01-0.46,0.26c-0.04,0.17-0.27,0.3-0.12,0.5c0.75,0,1.49,0,2.23,0
|
||||
c-0.39,0.21-0.8,0.35-1.24,0.23c-0.07-0.02-0.16-0.01-0.24-0.01c-0.1,0-0.23-0.03-0.3,0.02c-0.08,0.06-0.1,0.19-0.16,0.29
|
||||
c-0.17,0.04-0.35,0.07-0.52,0.1c-0.06,0.21-0.04,0.34,0.2,0.35c0.26,0.01,0.53,0.01,0.79,0.04c0.55,0.07,1.06,0.04,1.51-0.37
|
||||
c0.15-0.14,0.3-0.23,0.25-0.45c0.07-0.03,0.12-0.05,0.17-0.06c0.12-0.04,0.25-0.1,0.19-0.24c-0.03-0.07-0.14-0.14-0.23-0.16
|
||||
c-0.19-0.05-0.38-0.07-0.59-0.11c0.03-0.14,0.05-0.26,0.08-0.36C62.9,85.22,63.26,85.27,63.48,84.96z"/>
|
||||
<path class="st0" d="M52.25,85.24c-0.01-0.25,0.18-0.32,0.33-0.44c0.05-0.04,0.11-0.14,0.09-0.19c-0.02-0.07-0.1-0.14-0.17-0.18
|
||||
c-0.06-0.04-0.18,0-0.22-0.05c-0.28-0.28-0.55-0.12-0.83-0.02c-0.21,0.08-0.42,0.15-0.63,0.19c-0.33,0.06-0.38,0.09-0.43,0.38
|
||||
c-0.16,0.12-0.29,0.21-0.42,0.3c0.07,0.09,0.15,0.18,0.23,0.28c-0.02,0.09-0.05,0.22-0.06,0.34c-0.05,0.34,0.02,0.45,0.31,0.63
|
||||
c0.09,0.06,0.17,0.15,0.27,0.23c0.3-0.14,0.61-0.28,0.91-0.43c0.04-0.02,0.09-0.08,0.09-0.12c0.01-0.2,0.17-0.41-0.15-0.58
|
||||
c0.23-0.09,0.38-0.15,0.52-0.21C52.14,85.34,52.25,85.29,52.25,85.24z"/>
|
||||
<path class="st0" d="M58.16,66.65c-0.16-0.14-0.35-0.12-0.52-0.03c-0.22,0.12-0.42,0.27-0.62,0.41c-0.18,0.13-0.35,0.27-0.51,0.42
|
||||
c-0.06,0.06-0.1,0.15-0.13,0.23c-0.1,0.28-0.19,0.57-0.29,0.87c-0.22-0.1-0.43-0.11-0.58,0.1c-0.01,0.21,0.13,0.33,0.29,0.31
|
||||
c0.3-0.03,0.6-0.09,0.88-0.19c0.22-0.08,0.25-0.24,0.15-0.48c-0.09-0.21-0.04-0.39,0.08-0.55c0.1-0.14,0.21-0.27,0.34-0.38
|
||||
c0.28-0.25,0.51-0.22,0.82,0.1c-0.13,0.01-0.24,0.03-0.35,0.04c0,0.03,0,0.06-0.01,0.09c0.1,0.03,0.19,0.07,0.29,0.07
|
||||
c0.3,0.01,0.38-0.1,0.3-0.4c-0.01-0.05-0.04-0.11-0.02-0.14C58.43,66.92,58.3,66.78,58.16,66.65z"/>
|
||||
<path class="st0" d="M54.73,70.12c0.03,0.18,0.13,0.29,0.32,0.28c0.25-0.02,0.49-0.04,0.7-0.22c0.08-0.07,0.2-0.12,0.3-0.16
|
||||
c0.09-0.04,0.19-0.07,0.29-0.1c-0.14-0.27-0.14-0.28-0.54-0.42c-0.16-0.05-0.33-0.05-0.5-0.04c-0.05,0-0.1,0.13-0.12,0.21
|
||||
c-0.01,0.06,0.04,0.14,0.08,0.25c-0.12-0.03-0.19-0.05-0.26-0.06C54.79,69.82,54.7,69.91,54.73,70.12z"/>
|
||||
<path class="st0" d="M57.42,69.01c0.02,0.11,0.05,0.22,0.09,0.4c0.14-0.13,0.24-0.19,0.3-0.28c0.05-0.06,0.09-0.21,0.07-0.23
|
||||
c-0.08-0.06-0.19-0.12-0.29-0.12C57.48,68.79,57.4,68.88,57.42,69.01z"/>
|
||||
<path class="st0" d="M88.02,51.18c0.4,0.27,0.79,0.54,1.19,0.8c0.01,0,0.02,0,0.02,0c-0.06-0.08-0.11-0.17-0.19-0.22
|
||||
c-0.25-0.19-0.51-0.37-0.77-0.55C88.21,51.17,88.04,51.16,88.02,51.18z"/>
|
||||
<path class="st0" d="M94.66,55.8c0.23,0.22,0.45,0.44,0.69,0.65c0.1,0.09,0.23,0.16,0.35,0.23c-0.04-0.05-0.08-0.1-0.12-0.15
|
||||
C95.3,56.24,95.05,55.93,94.66,55.8z"/>
|
||||
<path class="st0" d="M94.63,55.76c-0.12-0.27-0.33-0.43-0.6-0.52C94.23,55.41,94.43,55.59,94.63,55.76z"/>
|
||||
<path class="st0" d="M109.9,73.57c-0.01,0.24,0.13,0.39,0.25,0.57L109.9,73.57z"/>
|
||||
<path class="st0" d="M91,69.63c0.01-0.23-0.24-1.14-0.41-1.28C90.64,68.53,90.93,69.42,91,69.63z"/>
|
||||
<path class="st0" d="M42.67,83.42c0.19-0.23,0.17-0.31-0.13-0.39c-0.4-0.11-0.78-0.05-1.16,0.11c-0.29,0.13-0.3,0.16-0.15,0.45
|
||||
c-0.17,0.08-0.34,0.15-0.58,0.26c0.21,0.19,0.37,0.32,0.51,0.45c-0.19,0.05-0.41,0.04-0.51,0.15c-0.12,0.12-0.14,0.35-0.2,0.53
|
||||
c-0.02,0.02-0.05,0.05-0.07,0.07c0.1,0.06,0.19,0.17,0.3,0.18c0.36,0.04,0.71,0.24,1.06,0.12c0.18,0.2,0.35,0.38,0.5,0.55
|
||||
c0.4-0.19,0.46-0.25,0.43-0.47c-0.01-0.14-0.02-0.28-0.04-0.42c-0.05-0.37,0.05-0.76-0.2-1.09c-0.02-0.03-0.01-0.11,0.01-0.15
|
||||
C42.51,83.66,42.58,83.53,42.67,83.42z M42.02,83.49c-0.12,0.06-0.25,0.08-0.38,0.12c-0.01-0.04-0.02-0.08-0.03-0.11
|
||||
c0.22-0.16,0.46-0.24,0.76-0.2C42.36,83.6,42.13,83.43,42.02,83.49z"/>
|
||||
<path class="st0" d="M43.56,82.45c0.11-0.34,0.04-0.52-0.23-0.64c-0.17-0.07-0.34-0.15-0.51-0.2c-0.52-0.15-1.04-0.28-1.57-0.07
|
||||
c-0.19,0.08-0.35,0.19-0.42,0.39c-0.03,0.08-0.03,0.19,0,0.26c0.09,0.18,0.21,0.35,0.32,0.52c0.05,0.08,0.12,0.15,0.22,0.01
|
||||
c-0.05-0.17-0.14-0.36-0.02-0.46c0.35-0.06,0.62-0.13,0.89-0.15c0.26-0.02,0.56-0.1,0.72,0.27C43.18,82.15,43.37,82.23,43.56,82.45
|
||||
z"/>
|
||||
<path class="st0" d="M74.64,46c-0.1,0.16-0.2,0.28-0.25,0.43c-0.08,0.22-0.13,0.45-0.21,0.67c-0.13,0.33-0.27,0.65-0.41,0.98
|
||||
c-0.07,0.16-0.07,0.28,0.15,0.32c0.03-0.06,0.08-0.11,0.1-0.16c0.1-0.36,0.28-0.67,0.49-0.98c0.14-0.19,0.24-0.41,0.35-0.62
|
||||
C74.97,46.39,74.9,46.15,74.64,46z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 64 KiB |
6
parse.go
6
parse.go
@@ -1,8 +1,6 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Copyright 2021 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package age
|
||||
|
||||
|
||||
451
plugin/client.go
Normal file
451
plugin/client.go
Normal file
@@ -0,0 +1,451 @@
|
||||
// Copyright 2021 Google LLC
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
// Package plugin implements the age plugin protocol.
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
exec "golang.org/x/sys/execabs"
|
||||
|
||||
"filippo.io/age"
|
||||
"filippo.io/age/internal/format"
|
||||
)
|
||||
|
||||
type Recipient struct {
|
||||
name string
|
||||
encoding string
|
||||
ui *ClientUI
|
||||
|
||||
// identity is true when encoding is an identity string.
|
||||
identity bool
|
||||
}
|
||||
|
||||
var _ age.Recipient = &Recipient{}
|
||||
var _ age.RecipientWithLabels = &Recipient{}
|
||||
|
||||
func NewRecipient(s string, ui *ClientUI) (*Recipient, error) {
|
||||
name, _, err := ParseRecipient(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Recipient{
|
||||
name: name, encoding: s, ui: ui,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Name returns the plugin name, which is used in the recipient ("age1name1...")
|
||||
// and identity ("AGE-PLUGIN-NAME-1...") encodings, as well as in the plugin
|
||||
// binary name ("age-plugin-name").
|
||||
func (r *Recipient) Name() string {
|
||||
return r.name
|
||||
}
|
||||
|
||||
func (r *Recipient) Wrap(fileKey []byte) (stanzas []*age.Stanza, err error) {
|
||||
stanzas, _, err = r.WrapWithLabels(fileKey)
|
||||
return
|
||||
}
|
||||
|
||||
func (r *Recipient) WrapWithLabels(fileKey []byte) (stanzas []*age.Stanza, labels []string, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s plugin: %w", r.name, err)
|
||||
}
|
||||
}()
|
||||
|
||||
conn, err := openClientConnection(r.name, "recipient-v1")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("couldn't start plugin: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Phase 1: client sends recipient or identity and file key
|
||||
addType := "add-recipient"
|
||||
if r.identity {
|
||||
addType = "add-identity"
|
||||
}
|
||||
if err := writeStanza(conn, addType, r.encoding); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := writeStanza(conn, fmt.Sprintf("grease-%x", rand.Int())); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := writeStanzaWithBody(conn, "wrap-file-key", fileKey); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := writeStanza(conn, "extension-labels"); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := writeStanza(conn, "done"); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Phase 2: plugin responds with stanzas
|
||||
sr := format.NewStanzaReader(bufio.NewReader(conn))
|
||||
ReadLoop:
|
||||
for {
|
||||
s, err := r.ui.readStanza(r.name, sr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
switch s.Type {
|
||||
case "recipient-stanza":
|
||||
if len(s.Args) < 2 {
|
||||
return nil, nil, fmt.Errorf("malformed recipient stanza: unexpected argument count")
|
||||
}
|
||||
n, err := strconv.Atoi(s.Args[0])
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("malformed recipient stanza: invalid index")
|
||||
}
|
||||
// We only send a single file key, so the index must be 0.
|
||||
if n != 0 {
|
||||
return nil, nil, fmt.Errorf("malformed recipient stanza: unexpected index")
|
||||
}
|
||||
|
||||
stanzas = append(stanzas, &age.Stanza{
|
||||
Type: s.Args[1],
|
||||
Args: s.Args[2:],
|
||||
Body: s.Body,
|
||||
})
|
||||
|
||||
if err := writeStanza(conn, "ok"); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
case "labels":
|
||||
if labels != nil {
|
||||
return nil, nil, fmt.Errorf("repeated labels stanza")
|
||||
}
|
||||
labels = s.Args
|
||||
|
||||
if err := writeStanza(conn, "ok"); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
case "error":
|
||||
if err := writeStanza(conn, "ok"); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf("%s", s.Body)
|
||||
case "done":
|
||||
break ReadLoop
|
||||
default:
|
||||
if ok, err := r.ui.handle(r.name, conn, s); err != nil {
|
||||
return nil, nil, err
|
||||
} else if !ok {
|
||||
if err := writeStanza(conn, "unsupported"); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(stanzas) == 0 {
|
||||
return nil, nil, fmt.Errorf("received zero recipient stanzas")
|
||||
}
|
||||
|
||||
return stanzas, labels, nil
|
||||
}
|
||||
|
||||
type Identity struct {
|
||||
name string
|
||||
encoding string
|
||||
ui *ClientUI
|
||||
}
|
||||
|
||||
var _ age.Identity = &Identity{}
|
||||
|
||||
func NewIdentity(s string, ui *ClientUI) (*Identity, error) {
|
||||
name, _, err := ParseIdentity(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Identity{
|
||||
name: name, encoding: s, ui: ui,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewIdentityWithoutData(name string, ui *ClientUI) (*Identity, error) {
|
||||
s := EncodeIdentity(name, nil)
|
||||
return &Identity{
|
||||
name: name, encoding: s, ui: ui,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Name returns the plugin name, which is used in the recipient ("age1name1...")
|
||||
// and identity ("AGE-PLUGIN-NAME-1...") encodings, as well as in the plugin
|
||||
// binary name ("age-plugin-name").
|
||||
func (i *Identity) Name() string {
|
||||
return i.name
|
||||
}
|
||||
|
||||
// Recipient returns a Recipient wrapping this identity. When that Recipient is
|
||||
// used to encrypt a file key, the identity encoding is provided as-is to the
|
||||
// plugin, which is expected to support encrypting to identities.
|
||||
func (i *Identity) Recipient() *Recipient {
|
||||
return &Recipient{
|
||||
name: i.name,
|
||||
encoding: i.encoding,
|
||||
identity: true,
|
||||
ui: i.ui,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Identity) Unwrap(stanzas []*age.Stanza) (fileKey []byte, err error) {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("%s plugin: %w", i.name, err)
|
||||
}
|
||||
}()
|
||||
|
||||
conn, err := openClientConnection(i.name, "identity-v1")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't start plugin: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Phase 1: client sends the plugin the identity string and the stanzas
|
||||
if err := writeStanza(conn, "add-identity", i.encoding); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := writeStanza(conn, fmt.Sprintf("grease-%x", rand.Int())); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, rs := range stanzas {
|
||||
s := &format.Stanza{
|
||||
Type: "recipient-stanza",
|
||||
Args: append([]string{"0", rs.Type}, rs.Args...),
|
||||
Body: rs.Body,
|
||||
}
|
||||
if err := s.Marshal(conn); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := writeStanza(conn, "done"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Phase 2: plugin responds with various commands and a file key
|
||||
sr := format.NewStanzaReader(bufio.NewReader(conn))
|
||||
ReadLoop:
|
||||
for {
|
||||
s, err := i.ui.readStanza(i.name, sr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch s.Type {
|
||||
case "file-key":
|
||||
if len(s.Args) != 1 {
|
||||
return nil, fmt.Errorf("malformed file-key stanza: unexpected arguments count")
|
||||
}
|
||||
n, err := strconv.Atoi(s.Args[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed file-key stanza: invalid index")
|
||||
}
|
||||
// We only send a single file key, so the index must be 0.
|
||||
if n != 0 {
|
||||
return nil, fmt.Errorf("malformed file-key stanza: unexpected index")
|
||||
}
|
||||
if fileKey != nil {
|
||||
return nil, fmt.Errorf("received duplicated file-key stanza")
|
||||
}
|
||||
|
||||
fileKey = s.Body
|
||||
|
||||
if err := writeStanza(conn, "ok"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case "error":
|
||||
if err := writeStanza(conn, "ok"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%s", s.Body)
|
||||
case "done":
|
||||
break ReadLoop
|
||||
default:
|
||||
if ok, err := i.ui.handle(i.name, conn, s); err != nil {
|
||||
return nil, err
|
||||
} else if !ok {
|
||||
if err := writeStanza(conn, "unsupported"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if fileKey == nil {
|
||||
return nil, age.ErrIncorrectIdentity
|
||||
}
|
||||
return fileKey, nil
|
||||
}
|
||||
|
||||
// ClientUI holds callbacks that will be invoked by (Un)Wrap if the plugin
|
||||
// wishes to interact with the user. If any of them is nil or returns an error,
|
||||
// failure will be reported to the plugin, but note that the error is otherwise
|
||||
// discarded. Implementations are encouraged to display errors to the user
|
||||
// before returning them.
|
||||
type ClientUI struct {
|
||||
// DisplayMessage displays the message, which is expected to have lowercase
|
||||
// initials and no final period.
|
||||
DisplayMessage func(name, message string) error
|
||||
|
||||
// RequestValue requests a secret or public input, with the provided prompt.
|
||||
RequestValue func(name, prompt string, secret bool) (string, error)
|
||||
|
||||
// Confirm requests a confirmation with the provided prompt. The yes and no
|
||||
// value are the choices provided to the user. no may be empty. The return
|
||||
// value indicates whether the user selected the yes or no option.
|
||||
Confirm func(name, prompt, yes, no string) (choseYes bool, err error)
|
||||
|
||||
// WaitTimer is invoked once (Un)Wrap has been waiting for 5 seconds on the
|
||||
// plugin, for example because the plugin is waiting for an external event
|
||||
// (e.g. a hardware token touch). Unlike the other callbacks, WaitTimer runs
|
||||
// in a separate goroutine, and if missing it's simply ignored.
|
||||
WaitTimer func(name string)
|
||||
}
|
||||
|
||||
func (c *ClientUI) handle(name string, conn *clientConnection, s *format.Stanza) (ok bool, err error) {
|
||||
switch s.Type {
|
||||
case "msg":
|
||||
if c.DisplayMessage == nil {
|
||||
return true, writeStanza(conn, "fail")
|
||||
}
|
||||
if err := c.DisplayMessage(name, string(s.Body)); err != nil {
|
||||
return true, writeStanza(conn, "fail")
|
||||
}
|
||||
return true, writeStanza(conn, "ok")
|
||||
case "request-secret", "request-public":
|
||||
if c.RequestValue == nil {
|
||||
return true, writeStanza(conn, "fail")
|
||||
}
|
||||
secret, err := c.RequestValue(name, string(s.Body), s.Type == "request-secret")
|
||||
if err != nil {
|
||||
return true, writeStanza(conn, "fail")
|
||||
}
|
||||
return true, writeStanzaWithBody(conn, "ok", []byte(secret))
|
||||
case "confirm":
|
||||
if len(s.Args) != 1 && len(s.Args) != 2 {
|
||||
return true, fmt.Errorf("malformed confirm stanza: unexpected number of arguments")
|
||||
}
|
||||
if c.Confirm == nil {
|
||||
return true, writeStanza(conn, "fail")
|
||||
}
|
||||
yes, err := format.DecodeString(s.Args[0])
|
||||
if err != nil {
|
||||
return true, fmt.Errorf("malformed confirm stanza: invalid YES option encoding")
|
||||
}
|
||||
var no []byte
|
||||
if len(s.Args) == 2 {
|
||||
no, err = format.DecodeString(s.Args[1])
|
||||
if err != nil {
|
||||
return true, fmt.Errorf("malformed confirm stanza: invalid NO option encoding")
|
||||
}
|
||||
}
|
||||
choseYes, err := c.Confirm(name, string(s.Body), string(yes), string(no))
|
||||
if err != nil {
|
||||
return true, writeStanza(conn, "fail")
|
||||
}
|
||||
result := "yes"
|
||||
if !choseYes {
|
||||
result = "no"
|
||||
}
|
||||
return true, writeStanza(conn, "ok", result)
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// readStanza calls r.ReadStanza and, if set, invokes WaitTimer in a separate
|
||||
// goroutine if the call takes longer than 5 seconds.
|
||||
func (c *ClientUI) readStanza(name string, r *format.StanzaReader) (*format.Stanza, error) {
|
||||
if c.WaitTimer != nil {
|
||||
defer time.AfterFunc(5*time.Second, func() { c.WaitTimer(name) }).Stop()
|
||||
}
|
||||
return r.ReadStanza()
|
||||
}
|
||||
|
||||
type clientConnection struct {
|
||||
cmd *exec.Cmd
|
||||
io.Reader // stdout
|
||||
io.Writer // stdin
|
||||
stderr bytes.Buffer
|
||||
close func()
|
||||
}
|
||||
|
||||
var testOnlyPluginPath string
|
||||
|
||||
func openClientConnection(name, protocol string) (*clientConnection, error) {
|
||||
path := "age-plugin-" + name
|
||||
if testOnlyPluginPath != "" {
|
||||
path = filepath.Join(testOnlyPluginPath, path)
|
||||
}
|
||||
cmd := exec.Command(path, "--age-plugin="+protocol)
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cc := &clientConnection{
|
||||
cmd: cmd,
|
||||
Reader: stdout,
|
||||
Writer: stdin,
|
||||
close: func() {
|
||||
stdin.Close()
|
||||
stdout.Close()
|
||||
},
|
||||
}
|
||||
|
||||
if os.Getenv("AGEDEBUG") == "plugin" {
|
||||
cc.Reader = io.TeeReader(cc.Reader, os.Stderr)
|
||||
cc.Writer = io.MultiWriter(cc.Writer, os.Stderr)
|
||||
cmd.Stderr = os.Stderr
|
||||
}
|
||||
|
||||
// We don't want the plugins to rely on the working directory for anything
|
||||
// as different clients might treat it differently, so we set it to an empty
|
||||
// temporary directory.
|
||||
cmd.Dir = os.TempDir()
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cc, nil
|
||||
}
|
||||
|
||||
func (cc *clientConnection) Close() error {
|
||||
// Close stdin and stdout and send SIGINT (if supported) to the plugin,
|
||||
// then wait for it to cleanup and exit.
|
||||
cc.close()
|
||||
cc.cmd.Process.Signal(os.Interrupt)
|
||||
return cc.cmd.Wait()
|
||||
}
|
||||
|
||||
func writeStanza(conn io.Writer, t string, args ...string) error {
|
||||
s := &format.Stanza{Type: t, Args: args}
|
||||
return s.Marshal(conn)
|
||||
}
|
||||
|
||||
func writeStanzaWithBody(conn io.Writer, t string, body []byte) error {
|
||||
s := &format.Stanza{Type: t, Body: body}
|
||||
return s.Marshal(conn)
|
||||
}
|
||||
133
plugin/client_test.go
Normal file
133
plugin/client_test.go
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2023 The age Authors
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"filippo.io/age"
|
||||
"filippo.io/age/internal/bech32"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
switch filepath.Base(os.Args[0]) {
|
||||
// TODO: deduplicate from cmd/age TestMain.
|
||||
case "age-plugin-test":
|
||||
switch os.Args[1] {
|
||||
case "--age-plugin=recipient-v1":
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanner.Scan() // add-recipient
|
||||
scanner.Scan() // body
|
||||
scanner.Scan() // grease
|
||||
scanner.Scan() // body
|
||||
scanner.Scan() // wrap-file-key
|
||||
scanner.Scan() // body
|
||||
fileKey := scanner.Text()
|
||||
scanner.Scan() // extension-labels
|
||||
scanner.Scan() // body
|
||||
scanner.Scan() // done
|
||||
scanner.Scan() // body
|
||||
os.Stdout.WriteString("-> recipient-stanza 0 test\n")
|
||||
os.Stdout.WriteString(fileKey + "\n")
|
||||
scanner.Scan() // ok
|
||||
scanner.Scan() // body
|
||||
os.Stdout.WriteString("-> done\n\n")
|
||||
os.Exit(0)
|
||||
default:
|
||||
panic(os.Args[1])
|
||||
}
|
||||
case "age-plugin-testpqc":
|
||||
switch os.Args[1] {
|
||||
case "--age-plugin=recipient-v1":
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanner.Scan() // add-recipient
|
||||
scanner.Scan() // body
|
||||
scanner.Scan() // grease
|
||||
scanner.Scan() // body
|
||||
scanner.Scan() // wrap-file-key
|
||||
scanner.Scan() // body
|
||||
fileKey := scanner.Text()
|
||||
scanner.Scan() // extension-labels
|
||||
scanner.Scan() // body
|
||||
scanner.Scan() // done
|
||||
scanner.Scan() // body
|
||||
os.Stdout.WriteString("-> recipient-stanza 0 test\n")
|
||||
os.Stdout.WriteString(fileKey + "\n")
|
||||
scanner.Scan() // ok
|
||||
scanner.Scan() // body
|
||||
os.Stdout.WriteString("-> labels postquantum\n\n")
|
||||
scanner.Scan() // ok
|
||||
scanner.Scan() // body
|
||||
os.Stdout.WriteString("-> done\n\n")
|
||||
os.Exit(0)
|
||||
default:
|
||||
panic(os.Args[1])
|
||||
}
|
||||
default:
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLabels(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("Windows support is TODO")
|
||||
}
|
||||
temp := t.TempDir()
|
||||
testOnlyPluginPath = temp
|
||||
t.Cleanup(func() { testOnlyPluginPath = "" })
|
||||
ex, err := os.Executable()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Link(ex, filepath.Join(temp, "age-plugin-test")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Chmod(filepath.Join(temp, "age-plugin-test"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Link(ex, filepath.Join(temp, "age-plugin-testpqc")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Chmod(filepath.Join(temp, "age-plugin-testpqc"), 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
name, err := bech32.Encode("age1test", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testPlugin, err := NewRecipient(name, &ClientUI{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
namePQC, err := bech32.Encode("age1testpqc", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
testPluginPQC, err := NewRecipient(namePQC, &ClientUI{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if _, err := age.Encrypt(io.Discard, testPluginPQC); err != nil {
|
||||
t.Errorf("expected one pqc to work, got %v", err)
|
||||
}
|
||||
if _, err := age.Encrypt(io.Discard, testPluginPQC, testPluginPQC); err != nil {
|
||||
t.Errorf("expected two pqc to work, got %v", err)
|
||||
}
|
||||
if _, err := age.Encrypt(io.Discard, testPluginPQC, testPlugin); err == nil {
|
||||
t.Errorf("expected one pqc and one normal to fail")
|
||||
}
|
||||
if _, err := age.Encrypt(io.Discard, testPlugin, testPluginPQC); err == nil {
|
||||
t.Errorf("expected one pqc and one normal to fail")
|
||||
}
|
||||
}
|
||||
55
plugin/encode.go
Normal file
55
plugin/encode.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2023 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"filippo.io/age/internal/bech32"
|
||||
)
|
||||
|
||||
// EncodeIdentity encodes a plugin identity string for a plugin with the given
|
||||
// name. If the name is invalid, it returns an empty string.
|
||||
func EncodeIdentity(name string, data []byte) string {
|
||||
s, _ := bech32.Encode("AGE-PLUGIN-"+strings.ToUpper(name)+"-", data)
|
||||
return s
|
||||
}
|
||||
|
||||
// ParseIdentity decodes a plugin identity string. It returns the plugin name
|
||||
// in lowercase and the encoded data.
|
||||
func ParseIdentity(s string) (name string, data []byte, err error) {
|
||||
hrp, data, err := bech32.Decode(s)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("invalid identity encoding: %v", err)
|
||||
}
|
||||
if !strings.HasPrefix(hrp, "AGE-PLUGIN-") || !strings.HasSuffix(hrp, "-") {
|
||||
return "", nil, fmt.Errorf("not a plugin identity: %v", err)
|
||||
}
|
||||
name = strings.TrimSuffix(strings.TrimPrefix(hrp, "AGE-PLUGIN-"), "-")
|
||||
name = strings.ToLower(name)
|
||||
return name, data, nil
|
||||
}
|
||||
|
||||
// EncodeRecipient encodes a plugin recipient string for a plugin with the given
|
||||
// name. If the name is invalid, it returns an empty string.
|
||||
func EncodeRecipient(name string, data []byte) string {
|
||||
s, _ := bech32.Encode("age1"+strings.ToLower(name), data)
|
||||
return s
|
||||
}
|
||||
|
||||
// ParseRecipient decodes a plugin recipient string. It returns the plugin name
|
||||
// in lowercase and the encoded data.
|
||||
func ParseRecipient(s string) (name string, data []byte, err error) {
|
||||
hrp, data, err := bech32.Decode(s)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("invalid recipient encoding: %v", err)
|
||||
}
|
||||
if !strings.HasPrefix(hrp, "age1") {
|
||||
return "", nil, fmt.Errorf("not a plugin recipient: %v", err)
|
||||
}
|
||||
name = strings.TrimPrefix(hrp, "age1")
|
||||
return name, data, nil
|
||||
}
|
||||
24
plugin/encode_go1.20.go
Normal file
24
plugin/encode_go1.20.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2023 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.20
|
||||
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
"fmt"
|
||||
|
||||
"filippo.io/age/internal/bech32"
|
||||
)
|
||||
|
||||
// EncodeX25519Recipient encodes a native X25519 recipient from a
|
||||
// [crypto/ecdh.X25519] public key. It's meant for plugins that implement
|
||||
// identities that are compatible with native recipients.
|
||||
func EncodeX25519Recipient(pk *ecdh.PublicKey) (string, error) {
|
||||
if pk.Curve() != ecdh.X25519() {
|
||||
return "", fmt.Errorf("wrong ecdh Curve")
|
||||
}
|
||||
return bech32.Encode("age", pk.Bytes())
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Copyright 2019 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package age
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Copyright 2019 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package age_test
|
||||
|
||||
|
||||
45
scrypt.go
45
scrypt.go
@@ -1,15 +1,15 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Copyright 2019 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package age
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"filippo.io/age/internal/format"
|
||||
@@ -88,6 +88,29 @@ func (r *ScryptRecipient) Wrap(fileKey []byte) ([]*Stanza, error) {
|
||||
return []*Stanza{l}, nil
|
||||
}
|
||||
|
||||
// WrapWithLabels implements [age.RecipientWithLabels], returning a random
|
||||
// label. This ensures a ScryptRecipient can't be mixed with other recipients
|
||||
// (including other ScryptRecipients).
|
||||
//
|
||||
// Users reasonably expect files encrypted to a passphrase to be [authenticated]
|
||||
// by that passphrase, i.e. for it to be impossible to produce a file that
|
||||
// decrypts successfully with a passphrase without knowing it. If a file is
|
||||
// encrypted to other recipients, those parties can produce different files that
|
||||
// would break that expectation.
|
||||
//
|
||||
// [authenticated]: https://words.filippo.io/dispatches/age-authentication/
|
||||
func (r *ScryptRecipient) WrapWithLabels(fileKey []byte) (stanzas []*Stanza, labels []string, err error) {
|
||||
stanzas, err = r.Wrap(fileKey)
|
||||
|
||||
random := make([]byte, 16)
|
||||
if _, err := rand.Read(random); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
labels = []string{hex.EncodeToString(random)}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ScryptIdentity is a password-based identity.
|
||||
type ScryptIdentity struct {
|
||||
password []byte
|
||||
@@ -122,9 +145,16 @@ func (i *ScryptIdentity) SetMaxWorkFactor(logN int) {
|
||||
}
|
||||
|
||||
func (i *ScryptIdentity) Unwrap(stanzas []*Stanza) ([]byte, error) {
|
||||
for _, s := range stanzas {
|
||||
if s.Type == "scrypt" && len(stanzas) != 1 {
|
||||
return nil, errors.New("an scrypt recipient must be the only one")
|
||||
}
|
||||
}
|
||||
return multiUnwrap(i.unwrap, stanzas)
|
||||
}
|
||||
|
||||
var digitsRe = regexp.MustCompile(`^[1-9][0-9]*$`)
|
||||
|
||||
func (i *ScryptIdentity) unwrap(block *Stanza) ([]byte, error) {
|
||||
if block.Type != "scrypt" {
|
||||
return nil, ErrIncorrectIdentity
|
||||
@@ -139,6 +169,9 @@ func (i *ScryptIdentity) unwrap(block *Stanza) ([]byte, error) {
|
||||
if len(salt) != scryptSaltSize {
|
||||
return nil, errors.New("invalid scrypt recipient block")
|
||||
}
|
||||
if w := block.Args[1]; !digitsRe.MatchString(w) {
|
||||
return nil, fmt.Errorf("scrypt work factor encoding invalid: %q", w)
|
||||
}
|
||||
logN, err := strconv.Atoi(block.Args[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse scrypt work factor: %v", err)
|
||||
@@ -146,13 +179,13 @@ func (i *ScryptIdentity) unwrap(block *Stanza) ([]byte, error) {
|
||||
if logN > i.maxWorkFactor {
|
||||
return nil, fmt.Errorf("scrypt work factor too large: %v", logN)
|
||||
}
|
||||
if logN <= 0 {
|
||||
if logN <= 0 { // unreachable
|
||||
return nil, fmt.Errorf("invalid scrypt work factor: %v", logN)
|
||||
}
|
||||
|
||||
salt = append([]byte(scryptLabel), salt...)
|
||||
k, err := scrypt.Key(i.password, salt, 1<<logN, 8, 1, chacha20poly1305.KeySize)
|
||||
if err != nil {
|
||||
if err != nil { // unreachable
|
||||
return nil, fmt.Errorf("failed to generate scrypt hash: %v", err)
|
||||
}
|
||||
|
||||
|
||||
274
testkit_test.go
Normal file
274
testkit_test.go
Normal file
@@ -0,0 +1,274 @@
|
||||
// Copyright 2022 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build go1.18
|
||||
// +build go1.18
|
||||
|
||||
package age_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"filippo.io/age"
|
||||
"filippo.io/age/armor"
|
||||
"filippo.io/age/internal/format"
|
||||
"filippo.io/age/internal/stream"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
|
||||
agetest "c2sp.org/CCTV/age"
|
||||
)
|
||||
|
||||
func forEachVector(t *testing.T, f func(t *testing.T, v *vector)) {
|
||||
tests, err := fs.ReadDir(agetest.Vectors, ".")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, test := range tests {
|
||||
name := test.Name()
|
||||
contents, err := fs.ReadFile(agetest.Vectors, name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
f(t, parseVector(t, contents))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type vector struct {
|
||||
expect string
|
||||
payloadHash *[32]byte
|
||||
fileKey *[16]byte
|
||||
identities []age.Identity
|
||||
armored bool
|
||||
file []byte
|
||||
}
|
||||
|
||||
func parseVector(t *testing.T, test []byte) *vector {
|
||||
v := &vector{file: test}
|
||||
for {
|
||||
line, rest, ok := bytes.Cut(v.file, []byte("\n"))
|
||||
if !ok {
|
||||
t.Fatal("invalid test file: no payload")
|
||||
}
|
||||
v.file = rest
|
||||
if len(line) == 0 {
|
||||
break
|
||||
}
|
||||
key, value, _ := strings.Cut(string(line), ": ")
|
||||
switch key {
|
||||
case "expect":
|
||||
switch value {
|
||||
case "success":
|
||||
case "HMAC failure":
|
||||
case "header failure":
|
||||
case "armor failure":
|
||||
case "payload failure":
|
||||
case "no match":
|
||||
default:
|
||||
t.Fatal("invalid test file: unknown expect value:", value)
|
||||
}
|
||||
v.expect = value
|
||||
case "payload":
|
||||
h, err := hex.DecodeString(value)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v.payloadHash = (*[32]byte)(h)
|
||||
case "file key":
|
||||
h, err := hex.DecodeString(value)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v.fileKey = (*[16]byte)(h)
|
||||
case "identity":
|
||||
i, err := age.ParseX25519Identity(value)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v.identities = append(v.identities, i)
|
||||
case "passphrase":
|
||||
i, err := age.NewScryptIdentity(value)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
v.identities = append(v.identities, i)
|
||||
case "armored":
|
||||
v.armored = true
|
||||
case "comment":
|
||||
t.Log(value)
|
||||
default:
|
||||
t.Fatal("invalid test file: unknown header key:", key)
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func TestVectors(t *testing.T) {
|
||||
forEachVector(t, testVector)
|
||||
}
|
||||
|
||||
func testVector(t *testing.T, v *vector) {
|
||||
var in io.Reader = bytes.NewReader(v.file)
|
||||
if v.armored {
|
||||
in = armor.NewReader(in)
|
||||
}
|
||||
r, err := age.Decrypt(in, v.identities...)
|
||||
if err != nil && strings.HasSuffix(err.Error(), "bad header MAC") {
|
||||
if v.expect == "HMAC failure" {
|
||||
t.Log(err)
|
||||
return
|
||||
}
|
||||
t.Fatalf("expected %s, got HMAC error", v.expect)
|
||||
} else if e := new(armor.Error); errors.As(err, &e) {
|
||||
if v.expect == "armor failure" {
|
||||
t.Log(err)
|
||||
return
|
||||
}
|
||||
t.Fatalf("expected %s, got: %v", v.expect, err)
|
||||
} else if _, ok := err.(*age.NoIdentityMatchError); ok {
|
||||
if v.expect == "no match" {
|
||||
t.Log(err)
|
||||
return
|
||||
}
|
||||
t.Fatalf("expected %s, got: %v", v.expect, err)
|
||||
} else if err != nil {
|
||||
if v.expect == "header failure" {
|
||||
t.Log(err)
|
||||
return
|
||||
}
|
||||
t.Fatalf("expected %s, got: %v", v.expect, err)
|
||||
} else if v.expect != "success" && v.expect != "payload failure" &&
|
||||
v.expect != "armor failure" {
|
||||
t.Fatalf("expected %s, got success", v.expect)
|
||||
}
|
||||
out, err := io.ReadAll(r)
|
||||
if err != nil && v.expect == "success" {
|
||||
t.Fatalf("expected %s, got: %v", v.expect, err)
|
||||
} else if err != nil {
|
||||
t.Log(err)
|
||||
if v.expect == "armor failure" {
|
||||
if e := new(armor.Error); !errors.As(err, &e) {
|
||||
t.Errorf("expected armor.Error, got %T", err)
|
||||
}
|
||||
}
|
||||
if v.payloadHash != nil && sha256.Sum256(out) != *v.payloadHash {
|
||||
t.Error("partial payload hash mismatch")
|
||||
}
|
||||
return
|
||||
} else if v.expect != "success" {
|
||||
t.Fatalf("expected %s, got success", v.expect)
|
||||
}
|
||||
if sha256.Sum256(out) != *v.payloadHash {
|
||||
t.Error("payload hash mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
// TestVectorsRoundTrip checks that any (valid) armor, header, and/or STREAM
|
||||
// payload in the test vectors re-encodes identically.
|
||||
func TestVectorsRoundTrip(t *testing.T) {
|
||||
forEachVector(t, testVectorRoundTrip)
|
||||
}
|
||||
|
||||
func testVectorRoundTrip(t *testing.T, v *vector) {
|
||||
if v.armored {
|
||||
if v.expect == "armor failure" {
|
||||
t.SkipNow()
|
||||
}
|
||||
t.Run("armor", func(t *testing.T) {
|
||||
payload, err := io.ReadAll(armor.NewReader(bytes.NewReader(v.file)))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
w := armor.NewWriter(buf)
|
||||
if _, err := w.Write(payload); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Armor format is not perfectly strict: CRLF ↔ LF and trailing and
|
||||
// leading spaces are allowed and won't round-trip.
|
||||
expect := bytes.Replace(v.file, []byte("\r\n"), []byte("\n"), -1)
|
||||
expect = bytes.TrimSpace(expect)
|
||||
expect = append(expect, '\n')
|
||||
if !bytes.Equal(buf.Bytes(), expect) {
|
||||
t.Error("got a different armor encoding")
|
||||
}
|
||||
})
|
||||
// Armor tests are not interesting beyond their armor encoding.
|
||||
return
|
||||
}
|
||||
|
||||
if v.expect == "header failure" {
|
||||
t.SkipNow()
|
||||
}
|
||||
hdr, p, err := format.Parse(bytes.NewReader(v.file))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
payload, err := io.ReadAll(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Run("header", func(t *testing.T) {
|
||||
buf := &bytes.Buffer{}
|
||||
if err := hdr.Marshal(buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
buf.Write(payload)
|
||||
if !bytes.Equal(buf.Bytes(), v.file) {
|
||||
t.Error("got a different header+payload encoding")
|
||||
}
|
||||
})
|
||||
|
||||
if v.expect == "success" {
|
||||
t.Run("STREAM", func(t *testing.T) {
|
||||
nonce, payload := payload[:16], payload[16:]
|
||||
key := streamKey(v.fileKey[:], nonce)
|
||||
r, err := stream.NewReader(key, bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
plaintext, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
w, err := stream.NewWriter(key, buf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := w.Write(plaintext); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), payload) {
|
||||
t.Error("got a different STREAM ciphertext")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func streamKey(fileKey, nonce []byte) []byte {
|
||||
h := hkdf.New(sha256.New, fileKey, nonce, []byte("payload"))
|
||||
streamKey := make([]byte, chacha20poly1305.KeySize)
|
||||
if _, err := io.ReadFull(h, streamKey); err != nil {
|
||||
panic("age: internal error: failed to read from HKDF: " + err.Error())
|
||||
}
|
||||
return streamKey
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// Copyright 2019 The age Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://developers.google.com/open-source/licenses/bsd
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package age
|
||||
|
||||
|
||||
Reference in New Issue
Block a user