174 Commits

Author SHA1 Message Date
Filippo Valsorda
b6b5f4300f cmd/age: disable golang/go#61779 tests workaround 2024-06-16 13:52:42 +02:00
Filippo Valsorda
627e6bc9d8 cmd/age: create file for empty decryptions
Fixes #555
Updates #159
Updates #57
2024-06-16 13:50:52 +02:00
Filippo Valsorda
7ed486868a .github/workflows: apparently setup-go has no defaults 2024-06-16 06:03:13 -04:00
Filippo Valsorda
2a761fcb8c .github/workflows: update GitHub Actions 2024-06-16 06:03:13 -04:00
Filippo Valsorda
98e7afcbac all: upgrade dependencies 2024-06-16 06:03:13 -04:00
Filippo Valsorda
5ef63b6153 .github/workflows: install bootstrap Go 2024-06-16 06:03:13 -04:00
Filippo Valsorda
bc21ece498 .github/workflows: disable environment
It's very spammy, just move the secret to a repository secret.
2024-06-16 06:03:13 -04:00
Filippo Valsorda
69c21b83fb README: fix scoop command
Closes #564
2024-06-16 05:06:58 -04:00
Filippo Valsorda
35cf02b1d0 .github: link maintenance policy from CONTRIBUTING.md 2024-06-16 05:03:03 -04:00
Jakub Wilk
29b68c20fc README: fix typo (#534) 2024-01-10 06:40:17 -05:00
Filippo Valsorda
101cc86763 README: Debian 12 installation instructions 2023-09-20 08:41:00 -04:00
Filippo Valsorda
6ad4560f4a .github/workflows: drop FreeBSD tests
This is unfortunate, but without a live platform to test on,
I can't investigate issues, and CI is now failing with just

   ?   	filippo.io/age/cmd/age-keygen	[no test files]
  Killed

which really could be anything.
2023-08-07 18:44:57 -04:00
Filippo Valsorda
93055632ad cmd/age: fix FreeBSD tests 2023-08-06 19:39:31 +02:00
Filippo Valsorda
294b0aa1e3 plugin: skip execution tests on Windows for now 2023-08-06 19:03:27 +02:00
Filippo Valsorda
f1f96c25e0 plugin: build tag EncodeX25519Recipient which uses crypto/ecdh 2023-08-06 18:36:38 +02:00
Filippo Valsorda
9fd564d543 .github/workflows: update and fix CI 2023-08-06 18:29:16 +02:00
Filippo Valsorda
c89f0b932e age,plugin: add RecipientWithLabels 2023-08-05 21:34:47 +02:00
Filippo Valsorda
dd733c5c0f cmd/age: grease the client-controlled plugin phases 2023-08-05 21:34:14 +02:00
Filippo Valsorda
004b544d83 plugin: add EncodeX25519Recipient 2023-08-05 21:34:14 +02:00
Filippo Valsorda
02181d83e9 plugin: add identity and recipient encoding 2023-08-05 21:34:14 +02:00
Filippo Valsorda
6976c5fca5 plugin: expose package 2023-08-05 21:34:14 +02:00
Filippo Valsorda
980763a16e age: make TestVectorsRoundTrip a little stricter 2023-07-23 00:54:40 +02:00
Filippo Valsorda
4740a92ef9 age: use testkit vectors to test armor, header, and STREAM round-trips
Before

	filippo.io/age/armor	coverage: 72.3% of statements in filippo.io/age/...
	filippo.io/age/internal/format	coverage: 86.8% of statements in filippo.io/age/...
	filippo.io/age/internal/stream	coverage: 83.9% of statements in filippo.io/age/...

After

	filippo.io/age/armor	coverage: 88.0% of statements in filippo.io/age/...
	filippo.io/age/internal/format	coverage: 87.6% of statements in filippo.io/age/...
	filippo.io/age/internal/stream	coverage: 86.0% of statements in filippo.io/age/...
2023-07-23 00:18:41 +02:00
Stepan
6c36e167c8 README: update release download link (#512) 2023-06-30 09:05:44 -04:00
Filippo Valsorda
9f0a2d25ac README: add link to awesome age list 2023-04-22 16:40:45 -04:00
Helio Machado
b6537b1865 .github/workflows: trigger interop. tests using gh (#481) 2023-04-22 06:01:29 -04:00
GitHub Actions
486b6dac96 doc: regenerate groff and html man pages 2023-04-22 09:53:44 +00:00
zhsj
877ca247e3 .github/workflows: update ronn to 0.9 (#483)
This fixes apostrophes in generated manpage.
Apostrophes should be entered as `'` ; using `\'` produces an acute accent.

ronn in Ubuntu is from https://github.com/apjanke/ronn-ng
2023-04-22 05:53:03 -04:00
andros21
502b180b17 README: dark/light mode logo (#500)
https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#specifying-the-theme-an-image-is-shown-to
2023-04-22 05:46:50 -04:00
Filippo Valsorda
8e3f74c283 cmd/age: deflake TestScript and update testscript 2023-01-02 13:34:35 +01:00
Berk D. Demir
edf7388f77 age: depend on c2sp.org/CCTV/age for TestVectors
Simplifies importing test data from CCTV without needing to invoke
"go mod download" from TestVectors. Makes life easier for package
builders with no networking, like Nixpkgs.
2022-12-30 18:24:08 -05:00
Filippo Valsorda
5471e05672 Revert "all: temporarily disable testscript tests"
This reverts commit 90a446549a.
2022-12-29 21:53:31 +01:00
Filippo Valsorda
c6dcfa1efc all: temporarily disable testscript tests
They require a replace directive that breaks "go install". Will revert
this after tagging a new latest release.
2022-12-26 15:36:58 -05:00
Filippo Valsorda
a1fabee4c8 all: upgrade dependencies 2022-12-26 15:36:58 -05:00
Filippo Valsorda
7354aa0d08 internal/bech32: remove data length limit
Fixes #453
2022-12-23 12:28:06 +01:00
Filippo Valsorda
bf8d2a3911 age: move testkit to CCTV 2022-10-28 20:11:09 +02:00
Helio Machado
5d5c9c48d8 .github/workflows: parallelize build process (#456) 2022-10-10 07:41:06 -04:00
Helio Machado
e05ce267a6 .github/workflows: use gh to upload release artifacts (#455)
* .github/workflows: use gh to upload release artifacts

https://cli.github.com/manual/gh_release_upload

* .github/workflows: remove unnecessary braces

When you use expressions in an if conditional, you may omit the expression syntax ${{ }} because GitHub automatically evaluates the if conditional as an expression.

https://docs.github.com/en/actions/learn-github-actions/expressions
2022-10-09 22:38:36 -04:00
GitHub Actions
000e931101 doc: regenerate groff and html man pages 2022-09-28 09:47:53 +00:00
a1346054
ebf48f1bbc doc: trim trailing whitespace 2022-09-28 05:46:34 -04:00
a1346054
3f2209cab9 doc: fix typos 2022-09-28 05:46:34 -04:00
Filippo Valsorda
bbac0a501c README: add a couple links to the heading 2022-09-26 08:16:06 -04:00
Mehrnoosh Beniss
02a01c6a6f README: switch order of installation and usage (#448) 2022-09-26 08:10:01 -04:00
Filippo Valsorda
8328d19d3e .github/workflows: update Go and FreeBSD in test 2022-09-13 13:18:49 +02:00
Simone Ragusa
1f84a45175 .github/workflows: change Go LICENSE URL from golang.org to go.dev (#449)
Co-authored-by: Filippo Valsorda <github@bip.filippo.io>
2022-09-11 15:54:32 -04:00
Filippo Valsorda
084c974f53 all: use better implementation of testscript timeouts 2022-07-28 14:21:43 +02:00
Filippo Valsorda
36ae5671cf cmd/age: buffer armored ciphertext before prompting
A partial solution, still missing bracketed paste support.

Updates #364
2022-07-12 22:30:19 +02:00
Filippo Valsorda
8a02f4801f cmd/age: improve terminal-related testscripts 2022-07-12 22:26:51 +02:00
Filippo Valsorda
e41463e117 cmd/age: fix autogenerated password log line
The newline was dropped in 0ab5c73.
2022-07-12 22:26:51 +02:00
Filippo Valsorda
f66877cfa5 README: update installation instructions to provide v1.0.0+
Updates #371
2022-07-07 12:11:02 -04:00
Filippo Valsorda
891be91d42 armor: add FuzzMalleability test 2022-07-03 12:48:48 +02:00
Filippo Valsorda
a5d38ae6ce armor: correct whitespace and trailing garbage handling
Ignore whitespace before and after file, reject trailing garbage and
whitespace within the file.
2022-07-03 12:48:48 +02:00
Filippo Valsorda
799c2bf8e8 tests: add armor tests 2022-07-03 12:48:48 +02:00
Filippo Valsorda
e84d74239e age: wrap decryption errors through and add armor.Error 2022-07-03 12:48:48 +02:00
Filippo Valsorda
95ba0188d1 age: keep age lowercase in all docs 2022-07-03 12:48:48 +02:00
Filippo Valsorda
0ab5c738fb cmd/age: ensure TUI output goes all to the terminal 2022-07-03 12:48:48 +02:00
Filippo Valsorda
de7c1fb565 agessh: support PKCS#8-encoded Ed25519 private keys
OpenSSH never generated them (unencrypted, and golang.org/x/crypto/ssh
doesn't support encrypted PKCS#8 for now, so the encrypted_keys.go
change is technically superfluous) but there are other systems that
produce them (for example, 1Password). Unfortunately, ParseRawPrivateKey
returns a value type for PKCS#8 and a pointer type for the OpenSSH
format (golang/go#51974), so we need to handle both.

Fixes #429
2022-07-03 12:48:48 +02:00
Filippo Valsorda
92fb4d508c agessh: properly detect public key mismatch for encrypted keys 2022-07-03 12:48:48 +02:00
Filippo Valsorda
331b242a9c cmd/age: add scrypt testscript 2022-07-03 12:48:48 +02:00
Filippo Valsorda
c50f1ae2e1 cmd/age: replace tests with testscript 2022-06-21 12:41:38 +01:00
Filippo Valsorda
8023f06ce2 all: build tag tests that require Go 1.18 2022-06-20 22:33:31 +01:00
Filippo Valsorda
4f7bb44386 internal/format: add malleability fuzz test 2022-06-20 01:18:48 +01:00
Filippo Valsorda
3f56ac13fb internal/stream: reject trailing data (no EOF) after end of stream 2022-06-19 23:11:14 +02:00
Filippo Valsorda
2e090545df age: reject leading zeroes and sign in scrypt work factor 2022-06-19 17:52:30 +02:00
Filippo Valsorda
2088adf268 tests: add expected no match and minor additions 2022-06-19 00:11:23 +02:00
Filippo Valsorda
eaa4e03cfe tests: finish parsing and X25519 tests, distinguish HMAC errors
If the implementation re-encodes the header before checking the HMAC,
that would mask malleability issues: the HMAC check would fail because
the tests HMAC'd the original header, but an attacker could also produce
the right HMAC. Instead of duplicating every parsing tests (with the
original and re-encoded HMAC), we make the test framework distinguish
HMAC errors, which ensures bad encodings are recognized as such and not
bypassable HMAC errors.
2022-06-18 13:47:00 +02:00
Filippo Valsorda
f8a121dd87 tests: add more tests for hmac and X25519 encodings 2022-06-16 15:55:29 +02:00
Filippo Valsorda
bb4493a7cd tests: add X25519 low order point tests 2022-06-16 11:47:27 +02:00
Filippo Valsorda
92713afd1e tests: add scrypt tests and move Go files from testdata 2022-06-15 20:38:59 +02:00
Filippo Valsorda
787044bdb6 testdata: convert last chunk tests to testkit 2022-06-15 19:38:18 +02:00
Filippo Valsorda
a787511e01 testdata: add a few more testkit vectors 2022-06-15 18:54:09 +02:00
Filippo Valsorda
e49b1f9afe internal/testkit: new test framework 2022-06-15 18:53:07 +02:00
GitHub Actions
78bedc2873 doc: regenerate groff and html man pages 2022-06-11 15:09:45 +00:00
GitHub Actions
f4112110f1 doc: regenerate groff and html man pages 2022-05-24 13:59:08 +00:00
Filippo Valsorda
acb1170bbc doc: clarify -e -i docs
Fixes #339
2022-05-24 15:57:42 +02:00
Filippo Valsorda
56f6acca37 cmd/age: reject passphrase-encrypted files if -i is used
Passphrase-encrypted files make age(1) block, which would be unexpected
when decrypting files in a script using -i.
2022-05-24 15:57:42 +02:00
Filippo Valsorda
5824a13b57 cmd/age,internal/plugin: print a message if waiting on a plugin for too long 2022-05-24 15:57:42 +02:00
Filippo Valsorda
c0e80ef2c9 cmd/age: improve confirm dialog
Don't require enter after the selection number, print errors as
warnings, and retry if an unexpected selection is made.
2022-05-24 15:56:22 +02:00
Filippo Valsorda
eeb9a079da cmd/age: add -j option for data-less plugins
See str4d/rage#237 and str4d/rage#236
2022-05-24 15:56:22 +02:00
Filippo Valsorda
fbe51d501c doc: document plugins in age(1) 2022-05-24 15:56:22 +02:00
Filippo Valsorda
87a982b72e internal/plugin: refactor plugin handling code 2022-05-24 15:56:22 +02:00
Filippo Valsorda
5a0da177e9 internal/plugin,cmd/age: implement confirm protocol verb 2022-05-24 15:56:22 +02:00
Filippo Valsorda
349ed5ed3f cmd/age: clean up the terminal UI 2022-05-24 15:56:22 +02:00
Filippo Valsorda
384d0393e0 internal/plugin,cmd/age: add support for encrypting to plugin identities 2022-05-24 15:56:22 +02:00
Filippo Valsorda
7bad9c2ad8 internal/plugin: update to latest plugin spec
The main change is that phase 2 of the wrapping state machine is
interactive to accommodate symmetric plugins.
2022-05-24 15:56:22 +02:00
Filippo Valsorda
01b56b117c internal/plugin: complete experimental plugin support 2022-05-24 15:56:22 +02:00
Filippo Valsorda
f6a5b94705 internal/plugin,cmd/age: implement preliminary plugin client support 2022-05-24 15:56:22 +02:00
GitHub Actions
cff70cffe2 doc: regenerate groff and html man pages 2022-05-24 13:51:06 +00:00
Filippo Valsorda
73416d1ac5 .github/workflows: fix generation of groff man pages 2022-05-24 15:49:39 +02:00
Filippo Valsorda
30d8e65e03 internal/stream: disallow empty final chunks
A non-empty payload of length a multiple of the chunk size can be
encrypted in two ways: with the last chunk full, or with an extra empty
last chunk. This is mostly an oversight in the original spec.

Both age and rage generate full last chunks, so we should be still in
time to pick one of the two, and avoid the underspecification. It's not
the one I would have picked originally, maybe, because disallowing full
last chunks would have avoided the trial decryption, but oh well.
2022-05-24 15:47:13 +02:00
Filippo Valsorda
765400f0c1 internal/format: factor out a StanzaReader for use in the plugin protocol 2022-05-22 11:54:38 +02:00
hakerdefo
cb539f931c README: add openSUSE Tumbleweed and sort installation table entries (#421)
Adding the installation instructions for openSUSE Tumbleweed and sorting the table entries in alphabetical order.
2022-05-20 07:14:08 -04:00
Filippo Valsorda
d6c77e0838 README: update badges 2022-05-09 06:03:01 -04:00
Simon Brand
ac31f5c935 cmd/age: fallback to stdin if /dev/tty cannot be opened (#414) 2022-05-04 06:44:48 -04:00
Filippo Valsorda
e8771b6d8a README: specify minimum Alpine repo version 2022-04-28 18:40:52 -04:00
Tionis
d8fa2fb0de README: add Alpine package to installation section (#412) 2022-04-28 18:39:25 -04:00
Filippo Valsorda
3e1aa11e9d .github: add CONTRIBUTING.md 2022-04-27 18:19:34 -04:00
Richard Ulmer
f7fcbef542 internal/stream: remove unused variable from testRoundTrip (#396) 2022-04-27 07:31:48 -04:00
Filippo Valsorda
299ec50c32 cmd/age: improve error message for out-of-order flags
Fixes #160
Closes #345

Co-authored-by: puenka <puenka@users.noreply.github.com>
2022-04-26 21:15:26 +02:00
Filippo Valsorda
2e20ca5fa7 .github/workflows: fix FreeBSD tests (#410) 2022-04-26 14:29:40 -04:00
Helio Machado
d717942b93 internal/format: improve error message for old header format (#398) 2022-04-26 13:30:35 -04:00
Brendan Fattig
3b4fb67296 README: change Go download link from golang.org to go.dev (#403) 2022-04-08 16:53:12 -04:00
Dimitris Apostolou
f01e37b4d7 doc: fix typo in age(1) (#395) 2022-02-24 04:37:53 -05:00
Filippo Valsorda
3411802309 cmd/age: offer a hint when the file was corrupted by PowerShell
I would still like to find a way to offer a warning while doing the
encryption, rather than at decryption time, but better than nothing.

Updates #290
2022-01-07 12:26:02 +01:00
Arnau Díaz
4169274d04 cmd/age-keygen: actually exit on errorf (#383)
Co-authored-by: Arnau Diaz <arnau.diaz@adevinta.com>
2022-01-04 13:39:38 -05:00
Filippo Valsorda
e4ae4cf884 age: fix typo in package docs 2022-01-01 08:02:58 -05:00
Kot
ab3707c085 README: add Scoop package (#378) 2021-12-18 12:25:55 -05:00
Eng Zer Jun
7665b87dc2 all: move from io/ioutil to io and os packages (#353)
The io/ioutil package has been deprecated as of Go 1.16, see
https://golang.org/doc/go1.16#ioutil. This commit replaces the existing
io/ioutil functions with their new definitions in io and os packages.

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2021-12-18 12:06:22 -05:00
Michael Cook
08f52cc125 .github/workflows: skip signing if key pass isn't available (#377)
With the exception of GITHUB_TOKEN, secrets are not passed to the
runner when a workflow is triggered from a forked repository.

See https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#pull-request-events-for-forked-repositories
2021-12-18 10:54:10 -05:00
Filippo Valsorda
a21c212358 logo: mention the logo designers 2021-11-24 12:28:59 +01:00
Filippo Valsorda
dfa2052cb7 all: move copyright owners to AUTHORS file 2021-11-24 11:29:15 +01:00
Filippo Valsorda
0c41827056 logo: add logo resources 2021-11-24 11:24:39 +01:00
Filippo Valsorda
3432b88db9 README: recommend "go install" for building from source 2021-11-12 14:38:51 -05:00
Filippo Valsorda
e2fc716c8b .github/workflows: use vmactions/freebsd-vm for FreeBSD (#344)
Replaces CirrusCI dependency.
2021-10-16 14:16:01 -04:00
Filippo Valsorda
3bd9ab8e9b .github/workflows: sign Windows binaries
Fixes #326
Closes #328

Co-authored-by: Joshua Small <technion@lolware.net>
2021-10-04 11:59:48 +02:00
Ross Light
4e1d7631e5 README: add instructions for Chocolatey install (#338)
Uses the repository at https://github.com/vaporwave9/age-chocolatey
2021-10-03 14:48:54 -04:00
GitHub Actions
1c95ceae09 doc: regenerate groff and html man pages 2021-09-30 13:01:16 +00:00
puenka
be70dba53e doc: fix typo in age(1) (#336) 2021-09-30 08:59:41 -04:00
GitHub Actions
50c2f22ba7 doc: regenerate groff and html man pages 2021-09-25 13:57:46 +00:00
y-yagi
878682b574 doc: fix typo in age(1) (#333) 2021-09-25 09:56:28 -04:00
Filippo Valsorda
3d7a7ff0b8 .github/workflows: add -trimpath to build and refactor a bit 2021-09-10 12:09:00 +02:00
Filippo Valsorda
776e1780a9 .github/workflows: merge test and gotip workflows 2021-09-10 11:38:48 +02:00
Filippo Valsorda
427edf35cc .github/workflows: downscope permissions where possible 2021-09-10 11:38:48 +02:00
GitHub Actions
8d88096476 doc: regenerate groff and html man pages 2021-09-08 10:35:24 +00:00
Filippo Valsorda
5cad72c62e doc: clarify backwards compatibility section 2021-09-08 12:33:45 +02:00
Filippo Valsorda
fd1b393f9c README: fix man page links 2021-09-07 19:24:40 +02:00
Caleb Maclennan
79211ba255 HomebrewFormula: drop formula, now in Homebrew’s repository (#318)
* HomebrewFormula: drop formula, now in Homebrew’s repository
* doc: update brew install, tap not required
2021-09-07 09:22:09 -04:00
Filippo Valsorda
552aa0a07d README: resize and center the logo 2021-09-06 12:45:08 -04:00
Filippo Valsorda
47d8133c52 README: add new logo 🏛
With a background of the color of the default desktop dark theme,
because there is no good way to use a transparent image.

https://github.community/t/support-theme-context-for-images-in-light-vs-dark-mode/147981/69
2021-09-06 12:29:21 -04:00
Filippo Valsorda
36b0a4f611 all: update dependencies and module version
This should bring in a faster golang.org/x/crypto/curve25519.
2021-09-05 01:55:46 +02:00
Filippo Valsorda
fda89073cd README: document new dl.filippo.io links 2021-09-05 01:24:14 +02:00
GitHub Actions
7756fbfe45 doc: regenerate groff and html man pages 2021-09-04 16:08:15 +00:00
Filippo Valsorda
57f6b8acae age,doc: document backwards compatibility policy
Fixes #216
2021-09-04 18:06:38 +02:00
Aaron Bieber
e08055f4e5 all: update x/sys to v0.0.0-20210630005230-0f9fa26af87c (#299)
This allows age to be built on OpenBSD/mips64!
2021-07-14 18:33:55 +02:00
NORlogik
7cb6b84758 README: mention official Void Linux package (#294) 2021-07-09 21:25:18 +02:00
Filippo Valsorda
4ea591b25f HomebrewFormula: update age to v1.0.0-rc.3 2021-06-15 14:27:09 +02:00
Filippo Valsorda
9d4b2ae7ac age: move the scrypt lone recipient check out of Decrypt
The important one is the decryption side one, because when a user types
a password they expect it to both decrypt and authenticate the file.
Moved that one out of Decrypt and into ScryptIdentity, now that
Identities get all the stanzas. special_cases--

This also opens the door to other Identity implementations that do allow
multiple scrypt recipients, if someone really wants that. The CLI will
never allow it, but an explicit choice by an API consumer feels like
something we shouldn't interfere with.

Moreover, this also allows alternative Identity implementations that use
different recipient types to replicate the behavior if they have the
same authentication semantics.

The encryption side one is only a courtesy, to stop API users from
making files that won't decrypt. Unfortunately, that one needs to stay
as a special case in Encrypt, as the Recipient can't see around itself.
However, changed it to a type assertion, so custom recipients can
generate multiple scrypt recipient stanzas, if they really want.
2021-06-15 14:00:10 +02:00
GitHub Actions
1ddf01df2c doc: regenerate groff and html man pages 2021-06-14 13:24:26 +02:00
Filippo Valsorda
f4e28fe809 .github/workflows: fix non-idempotent ronn email mangling 2021-06-14 13:24:26 +02:00
Filippo Valsorda
0703f86521 cmd/age,cmd/age-keygen: normalize errors, warnings, and hints 2021-06-14 13:24:26 +02:00
Filippo Valsorda
fb97277f8d cmd/age: add support for encrypted identity files
Updates #252
Closes #132
2021-06-14 13:24:26 +02:00
Filippo Valsorda
fa5b575ceb cmd/age: use CONIN$/CONOUT$ on Windows for password prompts
Fixes #128
Closes #274

Co-authored-by: codesoap <codesoap@mailbox.org>
2021-06-02 11:04:02 +02:00
GitHub Actions
cde103daae doc: regenerate groff and html man pages 2021-06-01 10:26:04 +00:00
Andreas Wachowski
b403e96be8 doc: fix typo in age-keygen(1) (#273) 2021-06-01 06:25:16 -04:00
GitHub Actions
329a7ece8f doc: regenerate groff and html man pages 2021-05-26 14:47:36 +00:00
Filippo Valsorda
3cd503dce9 doc: SEC 1 encoding is for ECDSA, which we don't support 2021-05-26 16:44:38 +02:00
Filippo Valsorda
cd28d56599 .github/workflows: don't run ronn on new tags 2021-05-26 15:28:21 +02:00
Filippo Valsorda
a94f3c3dc9 HomebrewFormula: update age to v1.0.0-rc.2 2021-05-26 13:52:31 +02:00
Filippo Valsorda
6596145a2c armor: don't leave an empty line before the footer
Closes #264
Fixes #263
2021-05-26 13:35:30 +02:00
Filippo Valsorda
7a262e1ffd agessh: use allowed RSA key size in tests 2021-05-25 21:15:04 +02:00
Filippo Valsorda
0b895a9340 HomebrewFormula: drop man pages that are not yet in the release 2021-05-25 20:54:31 +02:00
Filippo Valsorda
c9aca162ef README: add pkg.go.dev and man page badges 2021-05-25 20:45:46 +02:00
GitHub Actions
c7c3012437 doc: regenerate groff and html man pages 2021-05-25 20:36:23 +02:00
Filippo Valsorda
e58a8859b9 doc: add age(1) and age-keygen(1) man pages
Closes #131
2021-05-25 20:36:23 +02:00
Filippo Valsorda
fb293ef526 agessh: reject small ssh-rsa keys
Fixes #266
2021-05-24 10:58:50 +02:00
Caleb Maclennan
3d5b49a348 README.md: drop system upgrade args from Arch Linux install (#270) 2021-05-24 04:41:27 -04:00
mjkalyan
cd4b2476bc README.md: add Gentoo installation method (#269) 2021-05-24 04:40:36 -04:00
Ryan Castellucci
759a88d3e8 cmd/age-keygen: don't warn about world-readable output for public keys (#268)
Fixes #267
2021-05-18 20:35:29 -04:00
Filippo Valsorda
85763d390a age: remove recipient limit
Fixes #139
2021-05-02 18:44:21 -04:00
Filippo Valsorda
fff82986fa README: clarify pronunciation reference
Updates #103
2021-04-23 11:38:05 -04:00
Filippo Valsorda
67ce088a41 README: add pronunciation
Fixes #103
2021-04-23 03:27:04 -04:00
Filippo Valsorda
3ad0bbed99 README: dry up installation instructions into a table 2021-04-23 02:34:51 -04:00
Simone Ragusa
7a55783693 README: add NixOS/Nix installation instructions (#197) 2021-04-23 02:13:24 -04:00
Herby Gillot
d271e916cf README: add instructions for installing via MacPorts (#179) 2021-04-23 02:12:51 -04:00
Robert-André Mauchin
50254ff522 README: add Fedora installation instructions (#183) 2021-04-23 02:10:02 -04:00
Filippo Valsorda
7a335c9d5d cmd/age: allow reading both passphrase and input from a terminal
Fixes #196
Closes #258
2021-04-23 02:06:50 -04:00
Filippo Valsorda
ff1b4ffb08 cmd/age,cmd/age-keygen: check Close() error on output files
Fixes #81
2021-04-23 00:11:12 -04:00
Filippo Valsorda
e63c22e327 Reapply "agessh: use filippo.io/edwards25519 for Ed25519 to Curve25519 conversion"
This reverts commit 629b0dbbc9.
2021-04-22 22:27:35 -04:00
Filippo Valsorda
a6a173e24f .github/workflows: add freebsd/amd64 and darwin/arm64 builds
Fixes #189
2021-04-22 22:22:57 -04:00
Filippo Valsorda
b4e0d7718f README: remove mailing list mention
It wasn't very active and was replaced by GitHub Discussions.
2021-04-19 00:21:27 -04:00
Filippo Valsorda
9e65644c3f .github: update "New issue" page (#211) 2021-04-18 18:45:50 -04:00
Christian Rebischke
290a2fd5ec README: mention official Arch Linux package (#204)
Signed-off-by: Christian Rebischke <chris@shibumi.dev>
2021-04-05 11:19:17 -04:00
Richard Ulmer
bad2c0d2e0 cmd/age: use golang.org/x/term instead of deprecated package (#205) 2021-04-05 09:22:51 -04:00
Ben Banfield-Zanin
dabc470bfe HomebrewFormula: update age.rb to 1.0.0-rc.1 (#199) 2021-03-21 07:12:27 -04:00
88 changed files with 5585 additions and 784 deletions

View File

@@ -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
View File

@@ -1 +1,2 @@
*.age binary
testdata/testkit/* binary

34
.github/CONTRIBUTING.md vendored Normal file
View 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)! 💖

View File

@@ -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
View 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!

View File

@@ -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 -->

View File

@@ -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>
```

View File

@@ -1,72 +1,85 @@
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
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
with:
name: age-binaries
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@v2
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),
});
}
name: age-binaries
- 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
View 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

Binary file not shown.

42
.github/workflows/certs/uitacllc.key vendored Normal file
View 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-----

View File

@@ -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 ./...

View File

@@ -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
View 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@v3
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@v2
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

View File

@@ -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: 1.22
- 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
View 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

View File

@@ -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

View File

@@ -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.

216
README.md
View File

@@ -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>
[![pkg.go.dev](https://pkg.go.dev/badge/filippo.io/age)](https://pkg.go.dev/filippo.io/age)
[![Go Reference](https://pkg.go.dev/badge/filippo.io/age.svg)](https://pkg.go.dev/filippo.io/age)
[![man page](<https://img.shields.io/badge/age(1)-man%20page-lightgrey>)](https://filippo.io/age/age.1)
[![C2SP specification](https://img.shields.io/badge/%C2%A7%23-specification-blueviolet)](https://age-encryption.org/v1)
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,158 @@ $ 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>
</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 +183,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 +227,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 +263,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
View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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,

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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,18 @@ import (
"flag"
"fmt"
"io"
_log "log"
"os"
"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 +49,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 +67,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 +130,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 +141,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,47 +151,75 @@ 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")
}
}
@@ -175,29 +228,44 @@ func main() {
if name := flag.Arg(0); name != "" && 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 != "-" {
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 +275,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 +298,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 +314,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 +463,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 {
@@ -389,9 +532,3 @@ 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 ]")
}

View File

@@ -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.
})
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -1 +0,0 @@
now-major-idea-author-clerk-bronze-all-soul-uncover-glad

View File

@@ -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
View 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

View File

@@ -1,7 +0,0 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACB/aTuac9tiWRGrKEtixFlryYlGCPTOpdbmXN9RRmDF2gAAAKDgV/GC4Ffx
ggAAAAtzc2gtZWQyNTUxOQAAACB/aTuac9tiWRGrKEtixFlryYlGCPTOpdbmXN9RRmDF2g
AAAECvFoQXQzXgJLQ+Gz4PfEcfyZwC2gUjOiWTD//mTPyD8H9pO5pz22JZEasoS2LEWWvJ
iUYI9M6l1uZc31FGYMXaAAAAG2ZpbGlwcG9AQmlzdHJvbWF0aC1NMS5sb2NhbAEC
-----END OPENSSH PRIVATE KEY-----

View File

@@ -1 +0,0 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIH9pO5pz22JZEasoS2LEWWvJiUYI9M6l1uZc31FGYMXa

View File

@@ -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ÖWv?w8ZW

125
cmd/age/testdata/encrypted_keys.txt vendored Normal file
View File

@@ -0,0 +1,125 @@
# TODO: age-encrypted private keys, multiple identities, -i ordering, -e -i,
# age file password prompt during encryption
[!linux] [!darwin] skip # no pty support
# 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=

View File

@@ -1,7 +0,0 @@
age-encryption.org/v1
-> scrypt z8U9dYMQuK1fFdvtpQYLEQ 10
5SVjw1bbFCZLdI1FR7RqfTd3yWo4KS1ikOjvz60Bpqhrv0W6o6/2oszxZEm1gEUC
--- YXxSwONGPBbV7woMuEFTYuA03qTYUF1k0Y8j/NDEu1o
’Ì
Å!í‡ i<7F>Þ.f¦æš{ŸdËIE]© ñn”$È!b®2

View File

@@ -1,6 +0,0 @@
age-encryption.org/v1
-> X25519 UkSgrxSETNpdkHY8EwiiRivqks2QJLUzsNsVjUTDcmw
8yB9TqsBo4Ypchw07AtemV5TW4sGwyPDPMIfRg8Ve8rbDXt4tCwnnKcMq2K6aoqx
--- vUhLU0U9Dc8YhbKy4SxKuq0iSqqjBWGnHfZG+9+O4v4
タケ<EFBFBD><EFBFBD>€hエW諾餞I暠fッニ<EFBDAF>索Q;ミRh皷w

Binary file not shown.

View File

@@ -1,5 +0,0 @@
age-encryption.org/v1
-> scrypt 1Q6WlGmsRulbN7bmUw8A1Q 23
GP2lnzFuk1dgEkcMPmK6KkmuOm5gIWJzLeuwGcRsvAY
--- vXvOsVbDbMc0x5Js1FS6k1ViOJ3H2ZdSUZo9bfvbzmU
=ñÅÙœ£¨v>¹vhKMû'ÕßúÙ½NeçS†Äñ\(_ó

View File

@@ -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

View File

@@ -1,5 +0,0 @@
age-encryption.org/v1
-> X25519 Rp86RQ3LgUJpQy4X2RMUhURlBP28tCaLQ2ssysJfRhg
83YXad/lj3/wFM4n7vlGIiBSgfhG8lfiP5U7ajjK3HM
--- O2+UpzetsP2+7BPyGQ4C6VMTY6zwp5TiNpVcFy4qdyM
 璇漡<0E>(擔Q:|c蔑鞮<>=f<>6b坿!

54
cmd/age/testdata/output_file.txt vendored Normal file
View File

@@ -0,0 +1,54 @@
# 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
[!linux] [!darwin] skip # no pty support
# 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
View 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
View 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

View File

@@ -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
«)KVMARsSMÚëûØKí>ÕÕ«`¤ ~T0n·

View File

@@ -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=

View File

@@ -1 +0,0 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDULTit0KUehbigbhsQaZo2oXLgMug8nTfnzDhcf5cEzdOJZyF6i5aRQbqbXIOYeTS3Shpp/iE6d60qi93JBBfveAZqr76tK7lVK8fstvAAGgbo9L9Ru5nhWX0HTlZUOodUT2E0rgAeoFzfvcZTaajzPmikrESmuaJLcdJ3crbCIyovkTxc85KbSn+Ky5grntdVR7GXve0HuIgwuSXGNyO0/hEQyhCEco4w53LZDXl3Dlxgb52u0QCsFFiQJmZ3RWnbCz2mNjM9Vo5KRt2pJ4u74lVsfXCvAqG4GsDd5WXCNRNaQ7gUdw1eSnvvcy3hxeEqLNN9tHaFCjEuyPfXfTVd3t0SlXreyoNbV75rYvZ2F6aLxyMMys+5bpnX0tu2LSq4Bvj5xIsBokjgEd3UXyj1eErsQdUJ0gghAMpXOjVbISwPAGMvUsH2unGbjvfaxnK57S7KAXOYznjhBf5ayIleAc+4LVD/KeMtGbpkyXXozC9jwH9cZBbGU4BtElAbzh8=

59
cmd/age/testdata/scrypt.txt vendored Normal file
View File

@@ -0,0 +1,59 @@
[!linux] [!darwin] skip # no pty support
# 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 --

View File

@@ -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ÒÛ¨

56
cmd/age/testdata/terminal.txt vendored Normal file
View File

@@ -0,0 +1,56 @@
[!linux] [!darwin] skip # no pty support
# 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
View 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
View 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
View 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) }

View File

@@ -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
View 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" "April 2023" ""
.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
View 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'>April 2023</li>
<li class='tr'>age-keygen(1)</li>
</ol>
</div>
</body>
</html>

57
doc/age-keygen.1.ronn Normal file
View 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
View 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" "April 2023" ""
.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
View 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 &gt; 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 &gt; recipients.txt
# Alice
age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p
# Bob
age1lggyhqrw2nlhcxprm67z43rta597azn8gknawjehu9d9dl0jq3yqqvfafg
$ age -R recipients.txt example.jpg &gt; example.jpg.age
</code></pre>
<p>Encrypt and decrypt a file using a passphrase:</p>
<pre><code>$ age -p secrets.txt &gt; 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 &gt; secrets.txt
Enter passphrase:
</code></pre>
<p>Encrypt and decrypt with a passphrase-protected identity file:</p>
<pre><code>$ age-keygen | age -p &gt; 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 &gt; secrets.txt.age
$ age -d -i key.age secrets.txt.age &gt; 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 &gt; example.jpg.age
$ age -d -i ~/.ssh/id_ed25519 example.jpg.age &gt; 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 &gt; 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 &gt; 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'>April 2023</li>
<li class='tr'>age(1)</li>
</ol>
</div>
</body>
</html>

315
doc/age.1.ronn Normal file
View 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
View File

@@ -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
View File

@@ -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=

View File

@@ -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")
}

View File

@@ -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},

View File

@@ -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

View File

@@ -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")
}
})
}

View File

@@ -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

View File

@@ -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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

579
logo/logo.svg Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

581
logo/logo_white.svg Normal file
View 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

View File

@@ -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
View 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
View 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
View 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
View 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())
}

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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
}

View File

@@ -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