Compare commits

...

137 Commits

Author SHA1 Message Date
Vincent Breitmoser
3293dd8f78 version 2.0 2025-02-28 22:07:16 +01:00
Vincent Breitmoser
c7a032eb69 nix: add nix flake 2025-02-28 22:05:32 +01:00
Vincent Breitmoser
475bcbffb8 nginx: route all requests via hagrid 2025-02-28 22:05:32 +01:00
Vincent Breitmoser
dafed3d492 db: don't use sq's export logic for our certs 2025-02-28 22:05:32 +01:00
Vincent Breitmoser
a504b0ea12 hagridctl: update for sqlite 2025-02-28 22:05:32 +01:00
Vincent Breitmoser
df6bfb2d84 db: improve typings for sqlite 2025-02-28 22:05:32 +01:00
Vincent Breitmoser
b5b5879474 db: add DatabaseTransaction abstraction 2025-02-28 22:05:32 +01:00
Vincent Breitmoser
5778aaed84 db: work on sqlite, make tests pass 2025-02-28 22:05:32 +01:00
Vincent Breitmoser
7beb5209af db: add sqlite query tracing during tests 2025-02-28 22:05:32 +01:00
puzzlewolf
4787816581 db: start work on rusqlite 2025-02-28 22:05:32 +01:00
Vincent Breitmoser
359475f89f docker: add sqlite dep 2025-02-28 22:05:32 +01:00
Vincent Breitmoser
253d672d47 db: abstract over log path interface 2025-02-28 22:05:32 +01:00
Vincent Breitmoser
e0aeef7ddc mail: support sending via local smtp server 2025-02-28 21:53:25 +01:00
Vincent Breitmoser
44db398a1c cargo: downgrade sequoia-openpgp to 1.17.0 (for now)
Starting with 1.18.0, the retain_userids method starts working
differently, returning an empty cert if no signed user ids or direct key
signature is left. Since we need this, we'll stay on 1.17.0 for now.
2024-11-17 19:08:23 +01:00
Vincent Breitmoser
8ea89d3e0e hagrid: fix tokens test 2024-11-17 14:15:34 +01:00
Vincent Breitmoser
0d25da7138 cargo: cargo fmt --all 2024-11-17 14:03:12 +01:00
Vincent Breitmoser
e0f8352ac6 docker: update docker-build for new rust-toolchain 2024-11-17 13:49:44 +01:00
Vincent Breitmoser
dca8afa1e6 cargo: cargo update 2024-11-17 13:47:22 +01:00
Vincent Breitmoser
ea44f52a16 rust-toolchain: update to 1.82.0 2024-11-17 13:46:25 +01:00
Vincent Breitmoser
b4d92f0ec1 use rust-crypto instead of ring for sealed state
Newer versions of ring are very obscure, and I couldn't figure out how
to use its interface in a reasonable time. I used the rust-crypto
methods instead where things were straightforward.
2024-11-17 13:46:24 +01:00
Vincent Breitmoser
26ef2f6e1c db: fix tests 2024-03-24 23:50:56 +01:00
Vincent Breitmoser
cfd9fd8eb3 tester: add gen-reqs command 2024-03-24 13:09:04 +01:00
Vincent Breitmoser
13ddd4ff3a tester: add tester workspace, adding tools for testing 2024-03-24 13:09:04 +01:00
Vincent Breitmoser
a9440c6d0a hagridctl: add dump command to dump entire database 2024-03-24 13:09:04 +01:00
Vincent Breitmoser
fe2337507a hagridctl: import public keys publishing emails 2024-03-24 13:09:04 +01:00
Vincent Breitmoser
36dff563fc docker: use bullseye base image 2024-03-24 13:09:04 +01:00
Vincent Breitmoser
da5648488b ci: actually use correct dep package name 2024-01-27 10:24:26 +01:00
Vincent Breitmoser
7f304929ea ci: update gitlab for openssl dep 2024-01-27 10:22:47 +01:00
Vincent Breitmoser
7c7b15e37c version 1.3.0 2024-01-26 15:35:37 +01:00
Vincent Breitmoser
dfafe5cdb7 cargo: use openssl crypo backend 2024-01-26 15:35:37 +01:00
Vincent Breitmoser
45c6fcf216 cargo: simplify versions of hagridctl as well 2024-01-26 15:33:41 +01:00
Vincent Breitmoser
e85d3eab40 nix: update for 23.11 2024-01-12 12:15:11 +01:00
Vincent Breitmoser
defd2314be docker: add instructions to build for a Debian environment via docker 2023-12-28 13:52:49 +01:00
Vincent Breitmoser
da4665306e cargo: cargo update
This update requires a forked version of rocket_i18n to accommodate for
a trivial renaming in rocket v0.5.0. Can be changed back to upstream if
https://github.com/Plume-org/rocket_i18n/pull/24 is merged.
2023-12-28 13:37:45 +01:00
Vincent Breitmoser
d11de8a354 hagrid: don't panic on short token size 2023-12-28 12:54:04 +01:00
Vincent Breitmoser
1d1eedc319 about: update privacy policy
Also clear translated files for this particular template.
2023-06-30 13:30:19 +02:00
Vincent Breitmoser
e7ec0edf1e db: check that user ids contain a valid self-signature for publication 2023-06-10 14:36:04 +00:00
Vincent Breitmoser
ed624da4a4 cargo: cargo update 2023-06-10 16:16:04 +02:00
Vincent Breitmoser
831331fd2d about: add news entry about k.o.o governance 2023-06-10 16:04:06 +02:00
Vic Demuzere
7f92f1813b Fix links to renamed Gitlab project
Most of them redirected to the new location but the commit link at the
bottom of every page resulted in a 404.
2023-06-05 11:37:36 +02:00
Vincent Breitmoser
37d42e96d7 i18n: fix some issues with position arguments 2023-02-11 17:57:54 +01:00
Vincent Breitmoser
e96594ab26 remove some stray println statements 2023-02-11 17:39:37 +01:00
Vincent Breitmoser
55e54c8bab readme: add notice about maintenance 2023-02-11 17:39:37 +01:00
Vincent Breitmoser
04b4bc817a i18n: update some fuzzily misdetected strings 2022-12-10 17:42:56 +01:00
Vincent Breitmoser
5e08a7086e cargo: cargo update and fix for some deps 2022-12-10 17:42:50 +01:00
Vincent Breitmoser
875ab41c7d cargo: apply cargo fmt --all 2022-12-10 15:29:58 +01:00
Justus Winter
dc2d67d9eb Bump sequoia-openpgp to 1.11 and synchronize src/dump.rs. 2022-12-09 12:59:42 +01:00
Vincent Breitmoser
0a28c04f86 contrib: remove systemd service file 2022-06-29 10:57:13 +02:00
Vincent Breitmoser
aca0cdcdff version 1.2.1 2022-04-09 13:37:51 +02:00
Vincent Breitmoser
aa6474fd29 cargo: cargo update 2022-04-09 13:36:33 +02:00
Vincent Breitmoser
55ec155b30 mail: update to lettre-0.10.0-rc.5 and adapt 2022-04-09 13:36:33 +02:00
Vincent Breitmoser
39fae28f53 version 1.2.0 2022-03-12 15:30:51 +01:00
Vincent Breitmoser
427fe351d8 i18n: tx pull 2022-03-12 15:20:12 +01:00
Vincent Breitmoser
7e00c56a24 dist: small css fixes 2022-03-10 18:09:10 +01:00
Nora Widdecke
07bab9d11b hagridtcl: Allow clippy::needless_collect 2022-03-06 23:30:12 +00:00
Nora Widdecke
e00cae5a4e db,hagridctl: Autofix clippy issues 2022-03-06 23:30:12 +00:00
Nora Widdecke
1802cc6811 Run clippy on the whole workspace 2022-03-06 23:30:12 +00:00
Nora Widdecke
59c42c033d Apply clippy to the tests, too 2022-03-06 23:30:12 +00:00
Nora Widdecke
ed924f439b hkp: Cleanup pks/lookup route.
- Replace custom FromRequest implementation and query parsing with
    rocket builtin.

  - Remove Hkp::Invalid variant, replaced with Result
2022-02-28 17:47:41 +01:00
Nora Widdecke
3b2810dcf7 hkp: Fix pks/lookup with urlencoded parameter.
And test the pks/lookup route with a urlencoded parameter.

  Fixes #168.
2022-02-28 17:42:22 +01:00
Vincent Breitmoser
7b8f5e1462 i18n: commit translation files for rustfmt determined compilation order 2022-02-26 18:47:32 +01:00
Vincent Breitmoser
a9c4786d14 i18n: extract include_i18n macro use into method for consistent compilation order 2022-02-26 18:23:22 +01:00
Vincent Breitmoser
e4718d7598 ci: simplify ci pipeline, check formatting 2022-02-26 18:20:55 +01:00
Vincent Breitmoser
b29845b893 cargo: apply cargo fmt --all 2022-02-26 17:01:14 +01:00
Vincent Breitmoser
961559e154 cargo: remove rustfmt config 2022-02-26 17:01:14 +01:00
Percy
4d8bb36824 fix: the text in the search button is not displayed in the small screen 2022-02-26 15:44:33 +00:00
Percy
2ec10fc40a fix: link and image overflow 2022-02-26 15:44:33 +00:00
Nora Widdecke
c1a88f8840 web: handle wkd requests 2022-02-26 16:40:54 +01:00
Vincent Breitmoser
329a9c09b0 update gettext-macros to 0.6.1 2022-02-26 16:34:57 +01:00
Nora Widdecke
36b03ea608 ci: Run clippy 2022-02-25 10:25:26 +01:00
Nora Widdecke
b06c2c96bd lint: allow clippy::nonminimal_bool 2022-02-25 10:25:26 +01:00
Nora Widdecke
46646f1965 lint: raise too-many-arguments-threshold 2022-02-25 10:25:26 +01:00
Nora Widdecke
a329a1a89a lint: fix clippy::single_component_path_imports 2022-02-25 10:25:26 +01:00
Nora Widdecke
cd020cae40 lint: fix clippy::collapsible_if 2022-02-25 10:25:26 +01:00
Nora Widdecke
b2a7ca29b7 lint: fix clippy::clone_on_copy 2022-02-25 10:25:26 +01:00
Nora Widdecke
5a6d1a97fd lint: fix clippy::bind_instead_of_map 2022-02-25 10:25:26 +01:00
Nora Widdecke
d2f6d682ac lint: fix clippy::needless_lifetimes 2022-02-25 10:25:26 +01:00
Nora Widdecke
4d27f3f5b9 lint: fix clippy::single_match 2022-02-25 10:25:26 +01:00
Nora Widdecke
a46bd4ebee lint: fix clippy::redundant_closure 2022-02-25 10:25:26 +01:00
Nora Widdecke
3253f50127 lint: fix clippy::match_like_matches_macro 2022-02-25 10:25:26 +01:00
Nora Widdecke
a2ace61e71 lint: fix clippy::or_fun_call 2022-02-25 10:25:26 +01:00
Nora Widdecke
421f8a0908 lint: Apply clippy autofixes
clippy::needless_borrow

  clippy::single_char_pattern

  clippy::redundant_clone

  clippy::needless_return

  clippy::needless_question_mark

  clippy::useless_conversion

  clippy::to_string_in_format_args

  clippy::to_string_in_format_args

  clippy::useless_format

  clippy::useless_vec

  clippy::toplevel_ref_arg

  clippy::redundant_static_lifetimes

  clippy::try_err
2022-02-25 10:23:34 +01:00
Nora Widdecke
8eb3984560 cargo: use rocket_prometheus 0.10.0-rc.1
Fixes #167
2022-02-15 11:35:13 +01:00
Vincent Breitmoser
546e3b9452 readme: update for stable rust 2022-02-06 23:33:33 +01:00
Justus Winter
57efbe2937 hagridctl: drop backtrace from error messages 2022-02-06 22:58:29 +01:00
Justus Winter
81b5426544 hagridctl: drop feature and recursion_limit attribute 2022-02-06 22:58:29 +01:00
Justus Winter
f1078b3ccc hagridctl: adapt to rockets new profile names 2022-02-06 22:58:29 +01:00
Justus Winter
42260ff2e1 web: change profile names to match rocket's new convention
see https://rocket.rs/v0.5-rc/guide/configuration/#default-provider
2022-02-06 22:58:29 +01:00
Justus Winter
7b413150ca web: start from rocket's default config for the tests 2022-02-06 22:58:29 +01:00
Justus Winter
11f93c3249 web: fix extracting configuration values 2022-02-06 22:58:29 +01:00
Justus Winter
fec0cc4852 web: port to handlebars 3, the version used in rocket's dyn templates 2022-02-06 22:58:29 +01:00
Justus Winter
b97a06f51c web: rename request_origin -> origin, it is a mouthful already 2022-02-06 22:58:29 +01:00
Justus Winter
cf0abbe047 web: immutable responses 2022-02-06 22:58:29 +01:00
Justus Winter
2bf703a796 web: don't use consumed response 2022-02-06 22:58:29 +01:00
Justus Winter
a31d69d111 web: enable prometheus again 2022-02-06 22:58:29 +01:00
Justus Winter
b428116189 web: i18n and request origin everywhere 2022-02-06 22:58:29 +01:00
Justus Winter
24eb0b0d1b web: anyhow::Error doesn't implement Responder 2022-02-06 22:58:29 +01:00
Justus Winter
23fb3f9fb2 web: fix header composition 2022-02-06 22:58:29 +01:00
Justus Winter
f50ce6912a cargo: use published rocket_i18n, hyperx 2022-02-06 22:58:29 +01:00
Vincent Breitmoser
a2bc5f014c web: first iteration, update to rocket v0.5-rc1 2022-02-06 22:58:29 +01:00
Vincent Breitmoser
3f156ec8c2 cargo: update, and use rocket v0.5-rc1 2022-02-06 22:58:22 +01:00
Vincent Breitmoser
33224d1855 i18n: fix tests and strings 2022-01-04 15:55:01 +01:00
Vincent Breitmoser
23880d1386 db+web: remove x-accel optimization
This removes a shortcut to serve certificates from nginx by including an
X-Accel-Redirect header in the response.
2022-01-04 13:52:58 +01:00
Vincent Breitmoser
77407e03cc db: correctly abstract NamedTempFile as type trait 2022-01-04 13:28:26 +01:00
Vincent Breitmoser
6782c57520 fs: group abstract methods on top of Database trait 2022-01-04 13:23:37 +01:00
Vincent Breitmoser
0e0b5c160a cargo: cargo update, and use recent nightly 2022-01-04 12:24:44 +01:00
Vincent Breitmoser
3aa26c10f3 nix: update shell.nix 2022-01-04 12:17:18 +01:00
Vincent Breitmoser
9d5ec287a9 cargo: update gettext-macros to patched 0.6
This should work with rust stable, once we update rocket.

Uses a patched version of gettext-macros with a bugfix for
https://github.com/Plume-org/gettext-macros/issues/16
2022-01-04 12:06:16 +01:00
Vincent Breitmoser
43cdb28b97 i18n: tx pull 2021-11-28 14:32:14 +01:00
Justus Winter
b8ddf58977 update sequoia-openpgp to 1.5 2021-10-25 16:04:12 +02:00
Justus Winter
6db41b87f2 update dump.rs from sq, revert to its canonical form
Previously, the code was taken with the modifications from
dump.sequoia-pgp.org.  However, the canonical form is the one from the
sq tool.
2021-10-11 11:06:13 +02:00
Justus Winter
8dabd2c37a update sequoia-openpgp to 1.4 2021-10-11 10:23:45 +02:00
Vincent Breitmoser
90356ddb28 update changed files from rebuild 2021-07-21 09:44:43 +02:00
Justus Winter
bb9a3d8324 Strip non-exportable signatures and cert components.
If non-exportable signatures are uploaded to Hagrid, this is most
certainly an accident.  Handle this gracefully by stripping these
signatures (and certificate components that are only bound by
non-exportable signatures) when writing them to the database.

Fixes #155.
2021-07-15 19:21:44 +00:00
Vincent Breitmoser
0543e13b14 nginx: re-add missing proxy cache path directive 2021-07-14 12:32:14 +02:00
Vincent Breitmoser
3432fbe584 readme: small update
Mention use of ngx_http_lua_module for nginx config, and document move
of the IRC channel to OFTC.
2021-07-13 11:09:00 +02:00
Vincent Breitmoser
569a9df5a0 nginx: update nginx.conf, ditch nginx-site.conf 2021-07-13 11:05:44 +02:00
Vincent Breitmoser
58585dd41f version 1.1.0 2021-06-24 12:04:26 +02:00
Vincent Breitmoser
b7127a672e db: remove unused feature declaration 2021-06-24 11:59:55 +02:00
Justus Winter
39c0e12ac6 database: serve first-party attested third-party certifications
This implements support for third-party userid certifications.  To
prevent denial-of-service attacks, we only merge those certifications
that are attested by the key holder.

The key holder attests the certifications using an Attested Key
Signature containing the digests of the certifications in an Attested
Certifications subpacket as specified in RFC4880bis-10.

Fixes #124.
2021-06-13 13:30:53 +02:00
Justus Winter
3ecd264c59 sync the dumper code from sq 2021-06-13 10:48:59 +00:00
Justus Winter
c98c588064 update sequoia-openpgp to 1.3 2021-06-13 10:48:59 +00:00
Vincent Breitmoser
c85a7e2c14 about: move IRC channel to OFTC 2021-06-13 12:14:00 +02:00
Vincent Breitmoser
f0dd400a92 about: update rate limit info in api docs 2021-06-11 11:25:49 +02:00
Vincent Breitmoser
f0e0e179ce nginx: increase burst window for fpr lookups to 1000 2021-06-11 11:25:27 +02:00
Justus Winter
a9b1363d09 database: simplify tpk_to_string
Use the convenience function to armor certificates.  This also adds
comments to the armor blocks, making it easier to identify
certificates when casually inspecting them as text files.
2021-05-06 18:54:53 +00:00
Justus Winter
e1e88037e8 database: drop tpk_filter_userids
This function is now provided by Sequoia, and as we no longer have to
reparse the certificate, it is infallible.  Simplify
tpk_filter_alive_emails accordingly.
2021-05-06 18:54:53 +00:00
Justus Winter
5d23bc8c21 database: fix comment 2021-05-06 18:54:53 +00:00
Vincent Breitmoser
71ca5b2888 nginx: use application/octet-stream content-type for WKD routes
The format of keys returned on WKD routes is binary, however
`application/pgp-keys` is specified to contain keys in ASCII-armored
format. The WKD spec says the returned content-type SHOULD be
`application/octet-stream`, too.

references:
https://www.ietf.org/archive/id/draft-koch-openpgp-webkey-service-11.txt
https://tools.ietf.org/html/rfc3156#section-7
2021-04-28 13:22:37 +02:00
Vincent Breitmoser
a7b4eec1fe hkp: drop "upload" mails 2021-04-19 18:40:21 +02:00
Vincent Breitmoser
7011245414 db: don't quarantine degenerate keys 2021-04-15 00:25:18 +02:00
Vincent Breitmoser
7ad5746f52 db: fix check_link_fpr method 2021-04-15 00:25:09 +02:00
Vincent Breitmoser
a4d2197ac5 i18n: tx pull 2021-04-14 23:44:29 +02:00
Vincent Breitmoser
53270cfb04 nginx: add option for loose rate limiting 2021-03-05 13:23:51 +01:00
Vincent Breitmoser
3462a335dd i18n: tx pull 2021-03-03 15:55:44 +01:00
Vincent Breitmoser
ef14d709bd i18n: tx pull 2021-02-26 11:30:47 +01:00
Vincent Breitmoser
726d04aca7 web: fix return status for error page with localization 2021-02-20 14:25:15 +01:00
137 changed files with 11289 additions and 6679 deletions

1
.envrc
View File

@@ -1 +1,2 @@
watch_file rust-toolchain
use nix

2
.gitignore vendored
View File

@@ -7,3 +7,5 @@
target
*.po~
/dist/templates/localized
result

View File

@@ -1,14 +1,13 @@
stages:
- build
build:binary:
stage: build
tags:
- docker
image: "rustlang/rust:nightly"
build, test and lint:
image: "rust:1-bullseye"
interruptible: true
script:
- apt update -qy
- apt install -qy libclang-dev build-essential pkg-config clang nettle-dev gettext zsh
- apt install -qy build-essential pkg-config clang libclang-dev libssl-dev gettext zsh
- rustup component add clippy
- rustup component add rustfmt
- ./make-translated-templates
- RUST_BACKTRACE=full cargo build
- RUST_BACKTRACE=full cargo test --all
- cargo build
- cargo clippy --tests --no-deps --workspace
- cargo fmt --all -- --check
- cargo test --all

3870
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
[package]
name = "hagrid"
version = "1.0.0"
authors = ["Vincent Breitmoser <look@my.amazin.horse>", "Kai Michaelis <kai@sequoia-pgp.org>"]
version = "2.0.0"
authors = ["Vincent Breitmoser <look@my.amazin.horse>", "Kai Michaelis <kai@sequoia-pgp.org>", "Justus Winter <justus@sequoia-pgp.org>"]
build = "build.rs"
default-run = "hagrid"
edition = "2018"
@@ -10,52 +10,46 @@ edition = "2018"
members = [
"database",
"hagridctl",
"tester",
]
[dependencies]
hagrid-database = { path = "database" }
chrono = "0.4.10"
chrono = "0.4"
anyhow = "1"
rocket = "0"
rocket_codegen = "0"
sequoia-openpgp = { version = "1", default-features = false, features = ["crypto-nettle"] }
rocket = { version = "0.5", features = [ "json" ] }
rocket_dyn_templates = { version = "0.1", features = ["handlebars"] }
rocket_codegen = "0.5"
sequoia-openpgp = { version = "=1.17.0", default-features = false, features = ["crypto-openssl"] }
multipart = "0"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
serde = "1"
serde_derive = "1"
serde_json = "1"
time = "0.1"
tempfile = "3.0"
tempfile = "3"
structopt = "0.2"
url = "1.6"
handlebars = "1.1.0"
num_cpus = "1.0"
ring = "0.13"
url = "1"
num_cpus = "1"
aes-gcm = "0.10"
sha2 = "0.10"
base64 = "0.10"
uuid = { version = "0.7", features = [ "v4" ] }
rocket_prometheus = "0.2"
lazy_static = "1.3.0"
rocket_i18n = "0.4"
gettext-macros = "0.5"
runtime-fmt = "0.4"
rocket_prometheus = "0.10"
lazy_static = "1"
gettext-macros = "0.6"
gettext-utils = "0.1"
gettext = "0.4"
glob = "0.3"
rfc2047 = "0.1"
hyperx = "1.4"
# this is a slightly annoying update, so keeping this back for now
lettre = { version = "=0.10.0-rc.5", default-features = false, features = ["builder", "file-transport", "sendmail-transport", "smtp-transport"] }
[patch.crates-io]
runtime-fmt = { git = "https://github.com/Valodim/runtime-fmt", rev = "44c15d832cb327ef33f95548a9a964d98c006fe4" }
[dependencies.lettre]
version = "0.10.0-pre"
[dependencies.rocket_i18n]
# git = "https://github.com/Plume-org/rocket_i18n"
git = "https://github.com/Valodim/rocket_i18n"
branch = "go-async"
default-features = false
# smtp-transport doesn't build (openssl problem)
features = ["builder", "file-transport", "sendmail-transport"]
git = "https://github.com/lettre/lettre"
rev = "245c600c82ee18b766e8729f005ff453a55dce34"
[dependencies.rocket_contrib]
version = "0"
default-features = false
features = ["handlebars_templates", "json"]
features = ["rocket"]
[build-dependencies]
vergen = "3"

View File

@@ -6,6 +6,15 @@ Hagrid is a verifying OpenPGP key server.
You can find general instructions and an API documentation at the running
instance at [https://keys.openpgp.org](https://keys.openpgp.org).
Please note that Hagrid is built and maintained only for the service at
keys.openpgp.org. It is not maintained or officially supported as
deployable software.
Compatibility note: Hagrid v2.0 uses an sqlite certificate store instead of the
previous file based database. This means that it also no longer supports serving
certificates directly via reverse proxy. You can us hagridctl to dump and import
an old database.
License
-------
@@ -26,22 +35,20 @@ License along with Hagrid. If not, see
Quick Start
-----------
Building Hagrid requires a working nightly Rust toolchain. The
easiest way to get the toolchain is to download [rustup](https://rustup.rs).
Building Hagrid requires a working stable Rust toolchain.
The easiest way to get the toolchain is to download [rustup](https://rustup.rs).
Additionally, install external dependencies are required. Get them (on Debian or
Ubuntu) with
Additionally, some external dependencies are required.
Get them (on Debian or Ubuntu) with
```bash
sudo apt install gnutls-bin nettle-dev gcc llvm-dev libclang-dev build-essential pkg-config gettext
sudo apt install gnutls-bin libssl-dev gcc llvm-dev libclang-dev build-essential pkg-config gettext libsqlite3-dev
```
After rustup and all other dependencies are installed, get the nightly compiler and tools, copy the
config file, and simply compile and run:
After Rust and the other dependencies are installed, copy the config file, then simply compile and run:
```bash
cd hagrid
rustup override set nightly-2020-06-01
cp Rocket.toml.dist Rocket.toml
cargo run
```
@@ -53,17 +60,7 @@ will be statically built, and can be copied anywhere. You will also need to
adjust `Rocket.toml` accordingly. Hagrid uses `sendmail` for mailing, so you
also need a working local mailer setup.
Reverse Proxy
-------------
Hagrid is designed to defer lookups to reverse proxy server like Nginx
and Apache. Lookups via `/vks/v1/by-finingerprint`, `/vks/v1/by-keyid`, and
`/vks/v1/by-email` can be handled by a robust and performant HTTP server. A
sample configuration for nginx is part of the repository (`nginx.conf`,
`hagrid-routes.conf`).
Community
---------
We're in `#hagrid` on Freenode IRC, also reachable via Matrix as
`#hagrid:stratum0.org`.
We're in `#hagrid` on OFTC IRC, also reachable via Matrix as `#hagrid:stratum0.org`.

View File

@@ -2,7 +2,7 @@
address = "0.0.0.0"
port = 8080
[development]
[debug]
base-URI = "http://localhost:8080"
from = "noreply@localhost"
x-accel-redirect = false
@@ -36,7 +36,7 @@ maintenance_file = "maintenance"
enable_prometheus = false
email_template_dir = "email-templates"
[production]
[release]
base-URI = "https://keys.openpgp.org"
base-URI-Onion = "https://keys.openpgp.org"
from = "noreply@keys.openpgp.org"

View File

@@ -4,6 +4,5 @@ use vergen::{generate_cargo_keys, ConstantsFlags};
fn main() {
// Generate the 'cargo:' key output
generate_cargo_keys(ConstantsFlags::all())
.expect("Unable to generate the cargo keys!");
generate_cargo_keys(ConstantsFlags::all()).expect("Unable to generate the cargo keys!");
}

5
clippy.toml Normal file
View File

@@ -0,0 +1,5 @@
# Disabled until we use at least Rust 1.49.0, which is the first
# version that supports the msrv field.
msrv = "1.58.1"
too-many-arguments-threshold = 10

View File

@@ -1,13 +0,0 @@
[Unit]
Description=Hagrid Verifying Keyserver
After=network.target
[Service]
ExecStart=/opt/hagrid/target/release/hagrid /opt/hagrid/dist -D %i -F hagrid@%i
WorkingDirectory=/opt/hagrid
User=hagrid
Group=hagrid
StandardOutput=syslog
[Install]
WantedBy=multi-user.target

View File

@@ -5,24 +5,27 @@ authors = ["Kai Michaelis <kai@sequoia-pgp.org>"]
[dependencies]
anyhow = "1"
sequoia-openpgp = { version = "1", default-features = false, features = ["crypto-nettle"] }
multipart = "0"
sequoia-openpgp = { version = "=1.17.0", default-features = false, features = ["crypto-openssl"] }
log = "0"
rand = "0.6"
serde = { version = "1.0", features = ["derive"] }
serde_derive = "1.0"
serde_json = "1.0"
serde_derive = "1"
serde_json = "1"
time = "0.1"
tempfile = "3.0"
url = "1.6"
tempfile = "3"
url = "1"
hex = "0.3"
base64 = "0.10"
pathdiff = "0.1"
idna = "0.1"
fs2 = "0.4"
walkdir = "2.2"
walkdir = "2"
chrono = "0.4"
zbase32 = "0.1.2"
zbase32 = "0.1"
r2d2 = "0.8"
r2d2_sqlite = "0.24"
rusqlite = { version = "0.31", features = ["trace"] }
self_cell = "1"
[lib]
name = "hagrid_database"

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,6 @@
#![feature(proc_macro_hygiene, plugin, decl_macro)]
#![recursion_limit = "1024"]
use std::convert::TryFrom;
use std::path::PathBuf;
use std::str::FromStr;
use openpgp::serialize::SerializeInto;
@@ -16,42 +14,38 @@ extern crate fs2;
extern crate idna;
#[macro_use]
extern crate log;
extern crate chrono;
extern crate hex;
extern crate pathdiff;
extern crate r2d2_sqlite;
extern crate rand;
extern crate self_cell;
extern crate serde;
extern crate serde_json;
extern crate tempfile;
extern crate time;
extern crate url;
extern crate hex;
extern crate walkdir;
extern crate chrono;
extern crate zbase32;
use tempfile::NamedTempFile;
extern crate sequoia_openpgp as openpgp;
use openpgp::{
Cert,
packet::UserID,
parse::Parse,
types::KeyFlags,
};
use openpgp::{packet::UserID, parse::Parse, types::KeyFlags, Cert};
pub mod types;
use types::{Email, Fingerprint, KeyID};
pub mod wkd;
pub mod sync;
pub mod wkd;
mod fs;
pub use self::fs::Filesystem as KeyDatabase;
mod sqlite;
pub use self::sqlite::Sqlite as KeyDatabase;
mod stateful_tokens;
pub use stateful_tokens::StatefulTokens;
mod openpgp_utils;
use openpgp_utils::{tpk_filter_userids, tpk_filter_alive_emails, tpk_to_string, tpk_clean, is_status_revoked, POLICY};
use openpgp_utils::{is_status_revoked, tpk_clean, tpk_filter_alive_emails, tpk_to_string, POLICY};
#[cfg(test)]
mod test;
@@ -66,14 +60,20 @@ pub enum Query {
Invalid(),
}
impl Query {
pub fn is_invalid(&self) -> bool {
matches!(self, Query::Invalid() | Query::InvalidShort())
}
}
impl FromStr for Query {
type Err = anyhow::Error;
fn from_str(term: &str) -> Result<Self> {
use self::Query::*;
let looks_like_short_key_id = !term.contains('@') &&
(term.starts_with("0x") && term.len() < 16 || term.len() == 8);
let looks_like_short_key_id =
!term.contains('@') && (term.starts_with("0x") && term.len() < 16 || term.len() == 8);
if looks_like_short_key_id {
Ok(InvalidShort())
} else if let Ok(fp) = Fingerprint::from_str(term) {
@@ -88,7 +88,7 @@ impl FromStr for Query {
}
}
#[derive(Debug,PartialEq,Eq,PartialOrd,Ord)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum EmailAddressStatus {
Published,
NotPublished,
@@ -109,12 +109,20 @@ impl ImportResult {
ImportResult::Unchanged(status) => status,
}
}
pub fn as_tpk_status(&self) -> &TpkStatus {
match self {
ImportResult::New(status) => status,
ImportResult::Updated(status) => status,
ImportResult::Unchanged(status) => status,
}
}
}
#[derive(Debug,PartialEq)]
#[derive(Debug, PartialEq)]
pub struct TpkStatus {
pub is_revoked: bool,
pub email_status: Vec<(Email,EmailAddressStatus)>,
pub email_status: Vec<(Email, EmailAddressStatus)>,
pub unparsed_uids: usize,
}
@@ -123,14 +131,60 @@ pub enum RegenerateResult {
Unchanged,
}
pub trait Database: Sync + Send {
type MutexGuard;
pub trait DatabaseTransaction<'a> {
type TempCert;
fn commit(self) -> Result<()>;
fn link_email(&self, email: &Email, fpr: &Fingerprint) -> Result<()>;
fn unlink_email(&self, email: &Email, fpr: &Fingerprint) -> Result<()>;
fn link_fpr(&self, from: &Fingerprint, to: &Fingerprint) -> Result<()>;
fn unlink_fpr(&self, from: &Fingerprint, to: &Fingerprint) -> Result<()>;
fn write_to_temp(&self, content: &[u8]) -> Result<Self::TempCert>;
fn move_tmp_to_full(&self, content: Self::TempCert, fpr: &Fingerprint) -> Result<()>;
fn move_tmp_to_published(&self, content: Self::TempCert, fpr: &Fingerprint) -> Result<()>;
fn move_tmp_to_published_wkd(
&self,
content: Option<Self::TempCert>,
fpr: &Fingerprint,
) -> Result<()>;
fn write_to_quarantine(&self, fpr: &Fingerprint, content: &[u8]) -> Result<()>;
}
pub trait Database<'a>: Sync + Send {
type Transaction: DatabaseTransaction<'a>;
/// Lock the DB for a complex update.
///
/// All basic write operations are atomic so we don't need to lock
/// read operations to ensure that we return something sane.
fn lock(&self) -> Result<Self::MutexGuard>;
fn transaction(&'a self) -> Result<Self::Transaction>;
/// Queries the database using Fingerprint, KeyID, or
/// email-address, returning the primary fingerprint.
fn lookup_primary_fingerprint(&self, term: &Query) -> Option<Fingerprint>;
fn by_fpr(&self, fpr: &Fingerprint) -> Option<String>;
fn by_kid(&self, kid: &KeyID) -> Option<String>;
fn by_email(&self, email: &Email) -> Option<String>;
fn by_email_wkd(&self, email: &Email) -> Option<Vec<u8>>;
fn by_domain_and_hash_wkd(&self, domain: &str, hash: &str) -> Option<Vec<u8>>;
fn by_fpr_full(&self, fpr: &Fingerprint) -> Option<String>;
fn by_primary_fpr(&self, fpr: &Fingerprint) -> Option<String>;
fn get_last_log_entry(&self) -> Result<Fingerprint>;
fn write_log_append(&self, filename: &str, fpr_primary: &Fingerprint) -> Result<()>;
fn check_link_fpr(
&self,
fpr: &Fingerprint,
target: &Fingerprint,
) -> Result<Option<Fingerprint>>;
fn check_consistency(&self) -> Result<()>;
/// Queries the database using Fingerprint, KeyID, or
/// email-address.
@@ -139,7 +193,7 @@ pub trait Database: Sync + Send {
let armored = match term {
ByFingerprint(ref fp) => self.by_fpr(fp),
ByKeyID(ref keyid) => self.by_kid(keyid),
ByEmail(ref email) => self.by_email(&email),
ByEmail(ref email) => self.by_email(email),
_ => None,
};
@@ -149,27 +203,6 @@ pub trait Database: Sync + Send {
}
}
/// Queries the database using Fingerprint, KeyID, or
/// email-address, returning the primary fingerprint.
fn lookup_primary_fingerprint(&self, term: &Query) -> Option<Fingerprint>;
/// Gets the path to the underlying file, if any.
fn lookup_path(&self, term: &Query) -> Option<PathBuf> {
let _ = term;
None
}
fn link_email(&self, email: &Email, fpr: &Fingerprint) -> Result<()>;
fn unlink_email(&self, email: &Email, fpr: &Fingerprint) -> Result<()>;
fn link_fpr(&self, from: &Fingerprint, to: &Fingerprint) -> Result<()>;
fn unlink_fpr(&self, from: &Fingerprint, to: &Fingerprint) -> Result<()>;
fn by_fpr(&self, fpr: &Fingerprint) -> Option<String>;
fn by_kid(&self, kid: &KeyID) -> Option<String>;
fn by_email(&self, email: &Email) -> Option<String>;
fn by_email_wkd(&self, email: &Email) -> Option<Vec<u8>>;
/// Complex operation that updates a Cert in the database.
///
/// 1. Merge new Cert with old, full Cert
@@ -182,17 +215,18 @@ pub trait Database: Sync + Send {
/// - abort if any problems come up!
/// 5. Move full and published temporary Cert to their location
/// 6. Update all symlinks
fn merge(&self, new_tpk: Cert) -> Result<ImportResult> {
fn merge(&'a self, new_tpk: Cert) -> Result<ImportResult> {
let fpr_primary = Fingerprint::try_from(new_tpk.primary_key().fingerprint())?;
let _lock = self.lock()?;
let tx = self.transaction()?;
let known_uids: Vec<UserID> = new_tpk
.userids()
.map(|binding| binding.userid().clone())
.collect();
let full_tpk_old = self.by_fpr_full(&fpr_primary)
let full_tpk_old = self
.by_fpr_full(&fpr_primary)
.and_then(|bytes| Cert::from_bytes(bytes.as_bytes()).ok());
let is_update = full_tpk_old.is_some();
let (full_tpk_new, full_tpk_unchanged) = if let Some(full_tpk_old) = full_tpk_old {
@@ -205,18 +239,21 @@ pub trait Database: Sync + Send {
let is_revoked = is_status_revoked(full_tpk_new.revocation_status(&POLICY, None));
let is_ok = is_revoked ||
full_tpk_new.keys().subkeys().next().is_some() ||
full_tpk_new.userids().next().is_some();
let is_ok = is_revoked
|| full_tpk_new.keys().subkeys().next().is_some()
|| full_tpk_new.userids().next().is_some();
if !is_ok {
self.write_to_quarantine(&fpr_primary, &tpk_to_string(&full_tpk_new)?)?;
// self.write_to_quarantine(&fpr_primary, &tpk_to_string(&full_tpk_new)?)?;
return Err(anyhow!("Not a well-formed key!"));
}
let published_tpk_old = self
.by_fpr(&fpr_primary)
.and_then(|bytes| Cert::from_bytes(bytes.as_bytes()).ok());
let published_emails = published_tpk_old.as_ref().map(|cert| tpk_get_emails(cert)).unwrap_or_default();
let published_emails = published_tpk_old
.as_ref()
.map(tpk_get_emails)
.unwrap_or_default();
let unparsed_uids = full_tpk_new
.userids()
@@ -234,7 +271,10 @@ pub trait Database: Sync + Send {
}
})
.flatten()
.filter(|(binding, email)| known_uids.contains(binding.userid()) || published_emails.contains(email))
.filter(|(binding, _)| binding.self_signatures().next().is_some())
.filter(|(binding, email)| {
known_uids.contains(binding.userid()) || published_emails.contains(email)
})
.flat_map(|(binding, email)| {
if is_status_revoked(binding.revocation_status(&POLICY, None)) {
Some((email, EmailAddressStatus::Revoked))
@@ -252,14 +292,18 @@ pub trait Database: Sync + Send {
// Abort if no changes were made
if full_tpk_unchanged {
return Ok(ImportResult::Unchanged(TpkStatus { is_revoked, email_status, unparsed_uids }));
return Ok(ImportResult::Unchanged(TpkStatus {
is_revoked,
email_status,
unparsed_uids,
}));
}
let published_tpk_new = if is_revoked {
tpk_filter_alive_emails(&full_tpk_new, &[])
} else {
tpk_filter_alive_emails(&full_tpk_new, &published_emails)
}?;
};
let newly_revoked_emails: Vec<&Email> = published_emails
.iter()
@@ -272,33 +316,34 @@ pub trait Database: Sync + Send {
.flatten()
.any(|unrevoked_email| &unrevoked_email == *email);
!has_unrevoked_userid
}).collect();
})
.collect();
let fingerprints = tpk_get_linkable_fprs(&published_tpk_new);
let fpr_checks = fingerprints
.iter()
.map(|fpr| self.check_link_fpr(&fpr, &fpr_primary))
.map(|fpr| self.check_link_fpr(fpr, &fpr_primary))
.collect::<Vec<_>>()
.into_iter()
.collect::<Result<Vec<_>>>();
if fpr_checks.is_err() {
self.write_to_quarantine(&fpr_primary, &tpk_to_string(&full_tpk_new)?)?;
tx.write_to_quarantine(&fpr_primary, &tpk_to_string(&full_tpk_new)?)?;
}
let fpr_checks = fpr_checks?;
let fpr_not_linked = fpr_checks.into_iter().flatten();
let full_tpk_tmp = self.write_to_temp(&tpk_to_string(&full_tpk_new)?)?;
let full_tpk_tmp = tx.write_to_temp(&tpk_to_string(&full_tpk_new)?)?;
let published_tpk_clean = tpk_clean(&published_tpk_new)?;
let published_tpk_tmp = self.write_to_temp(&tpk_to_string(&published_tpk_clean)?)?;
let published_tpk_tmp = tx.write_to_temp(&tpk_to_string(&published_tpk_clean)?)?;
// these are very unlikely to fail. but if it happens,
// database consistency might be compromised!
self.move_tmp_to_full(full_tpk_tmp, &fpr_primary)?;
self.move_tmp_to_published(published_tpk_tmp, &fpr_primary)?;
self.regenerate_wkd(&fpr_primary, &published_tpk_clean)?;
tx.move_tmp_to_full(full_tpk_tmp, &fpr_primary)?;
tx.move_tmp_to_published(published_tpk_tmp, &fpr_primary)?;
self.regenerate_wkd(&tx, &fpr_primary, &published_tpk_clean)?;
let published_tpk_changed = published_tpk_old
.map(|tpk| tpk != published_tpk_clean)
@@ -308,29 +353,39 @@ pub trait Database: Sync + Send {
}
for fpr in fpr_not_linked {
if let Err(e) = self.link_fpr(&fpr, &fpr_primary) {
info!("Error ensuring symlink! {} {} {:?}",
&fpr, &fpr_primary, e);
if let Err(e) = tx.link_fpr(&fpr, &fpr_primary) {
info!("Error ensuring symlink! {} {} {:?}", &fpr, &fpr_primary, e);
}
}
for revoked_email in newly_revoked_emails {
if let Err(e) = self.unlink_email(&revoked_email, &fpr_primary) {
info!("Error ensuring symlink! {} {} {:?}",
&fpr_primary, &revoked_email, e);
if let Err(e) = tx.unlink_email(revoked_email, &fpr_primary) {
info!(
"Error ensuring symlink! {} {} {:?}",
&fpr_primary, &revoked_email, e
);
}
}
tx.commit()?;
if is_update {
Ok(ImportResult::Updated(TpkStatus { is_revoked, email_status, unparsed_uids }))
Ok(ImportResult::Updated(TpkStatus {
is_revoked,
email_status,
unparsed_uids,
}))
} else {
Ok(ImportResult::New(TpkStatus { is_revoked, email_status, unparsed_uids }))
Ok(ImportResult::New(TpkStatus {
is_revoked,
email_status,
unparsed_uids,
}))
}
}
fn update_write_log(&self, fpr_primary: &Fingerprint) {
let log_name = self.get_current_log_filename();
println!("{}", log_name);
if let Err(e) = self.write_log_append(&log_name, fpr_primary) {
error!("Error writing to log! {} {} {}", &log_name, &fpr_primary, e);
}
@@ -340,8 +395,13 @@ pub trait Database: Sync + Send {
Utc::now().format("%Y-%m-%d").to_string()
}
fn get_tpk_status(&self, fpr_primary: &Fingerprint, known_addresses: &[Email]) -> Result<TpkStatus> {
let tpk_full = self.by_fpr_full(&fpr_primary)
fn get_tpk_status(
&self,
fpr_primary: &Fingerprint,
known_addresses: &[Email],
) -> Result<TpkStatus> {
let tpk_full = self
.by_fpr_full(fpr_primary)
.ok_or_else(|| anyhow!("Key not in database!"))
.and_then(|bytes| Cert::from_bytes(bytes.as_bytes()))?;
@@ -354,15 +414,18 @@ pub trait Database: Sync + Send {
.count();
let published_uids: Vec<UserID> = self
.by_fpr(&fpr_primary)
.by_fpr(fpr_primary)
.and_then(|bytes| Cert::from_bytes(bytes.as_bytes()).ok())
.map(|tpk| tpk.userids()
.map(|binding| binding.userid().clone())
.collect()
).unwrap_or_default();
.map(|tpk| {
tpk.userids()
.map(|binding| binding.userid().clone())
.collect()
})
.unwrap_or_default();
let mut email_status: Vec<_> = tpk_full
.userids()
.filter(|binding| binding.self_signatures().next().is_some())
.flat_map(|binding| {
let uid = binding.userid();
if let Ok(email) = Email::try_from(uid) {
@@ -385,7 +448,11 @@ pub trait Database: Sync + Send {
// the same address, we keep the first.
email_status.dedup_by(|(e1, _), (e2, _)| e1 == e2);
Ok(TpkStatus { is_revoked, email_status, unparsed_uids })
Ok(TpkStatus {
is_revoked,
email_status,
unparsed_uids,
})
}
/// Complex operation that publishes some user id for a Cert already in the database.
@@ -401,74 +468,86 @@ pub trait Database: Sync + Send {
/// - abort if any problems come up!
/// 5. Move full and published temporary Cert to their location
/// 6. Update all symlinks
fn set_email_published(&self, fpr_primary: &Fingerprint, email_new: &Email) -> Result<()> {
let _lock = self.lock()?;
fn set_email_published(&'a self, fpr_primary: &Fingerprint, email_new: &Email) -> Result<()> {
let tx = self.transaction()?;
self.nolock_unlink_email_if_other(fpr_primary, email_new)?;
self.unlink_email_if_other(&tx, fpr_primary, email_new)?;
let full_tpk = self.by_fpr_full(&fpr_primary)
let full_tpk = self
.by_fpr_full(fpr_primary)
.ok_or_else(|| anyhow!("Key not in database!"))
.and_then(|bytes| Cert::from_bytes(bytes.as_bytes()))?;
let published_uids_old: Vec<UserID> = self
.by_fpr(&fpr_primary)
.by_fpr(fpr_primary)
.and_then(|bytes| Cert::from_bytes(bytes.as_bytes()).ok())
.map(|tpk| tpk.userids()
.map(|binding| binding.userid().clone())
.collect()
).unwrap_or_default();
let published_emails_old: Vec<Email> = published_uids_old.iter()
.map(|tpk| {
tpk.userids()
.map(|binding| binding.userid().clone())
.collect()
})
.unwrap_or_default();
let published_emails_old: Vec<Email> = published_uids_old
.iter()
.map(|uid| Email::try_from(uid).ok())
.flatten()
.collect();
// println!("publishing: {:?}", &uid_new);
if published_emails_old.contains(&email_new) {
if published_emails_old.contains(email_new) {
// UserID already published - just stop
return Ok(());
}
let mut published_emails = published_emails_old.clone();
let mut published_emails = published_emails_old;
published_emails.push(email_new.clone());
let published_tpk_new = tpk_filter_alive_emails(&full_tpk, &published_emails)?;
let published_tpk_new = tpk_filter_alive_emails(&full_tpk, &published_emails);
if !published_tpk_new
.userids()
.map(|binding| Email::try_from(binding.userid()))
.flatten()
.any(|email| email == *email_new) {
return Err(anyhow!("Requested UserID not found!"));
.any(|email| email == *email_new)
{
return Err(anyhow!("Requested UserID not found!"));
}
let published_tpk_clean = tpk_clean(&published_tpk_new)?;
let published_tpk_tmp = self.write_to_temp(&tpk_to_string(&published_tpk_clean)?)?;
let published_tpk_tmp = tx.write_to_temp(&tpk_to_string(&published_tpk_clean)?)?;
self.move_tmp_to_published(published_tpk_tmp, &fpr_primary)?;
self.regenerate_wkd(fpr_primary, &published_tpk_clean)?;
tx.move_tmp_to_published(published_tpk_tmp, fpr_primary)?;
self.regenerate_wkd(&tx, fpr_primary, &published_tpk_clean)?;
self.update_write_log(&fpr_primary);
self.update_write_log(fpr_primary);
if let Err(e) = self.link_email(&email_new, &fpr_primary) {
info!("Error ensuring email symlink! {} -> {} {:?}",
&email_new, &fpr_primary, e);
if let Err(e) = tx.link_email(email_new, fpr_primary) {
info!(
"Error ensuring email symlink! {} -> {} {:?}",
&email_new, &fpr_primary, e
);
}
tx.commit()?;
Ok(())
}
fn nolock_unlink_email_if_other(
fn unlink_email_if_other(
&self,
tx: &Self::Transaction,
fpr_primary: &Fingerprint,
unlink_email: &Email,
) -> Result<()> {
let current_link_fpr = self.lookup_primary_fingerprint(
&Query::ByEmail(unlink_email.clone()));
let current_link_fpr =
self.lookup_primary_fingerprint(&Query::ByEmail(unlink_email.clone()));
if let Some(current_fpr) = current_link_fpr {
if current_fpr != *fpr_primary {
self.nolock_set_email_unpublished_filter(&current_fpr,
|uid| Email::try_from(uid).map(|email| email != *unlink_email)
.unwrap_or(false))?;
self.set_email_unpublished_filter(&tx, &current_fpr, |uid| {
Email::try_from(uid)
.map(|email| email != *unlink_email)
.unwrap_or(false)
})?;
}
}
Ok(())
@@ -489,19 +568,12 @@ pub trait Database: Sync + Send {
/// 6. Update all symlinks
fn set_email_unpublished_filter(
&self,
tx: &Self::Transaction,
fpr_primary: &Fingerprint,
email_remove: impl Fn(&UserID) -> bool,
) -> Result<()> {
let _lock = self.lock()?;
self.nolock_set_email_unpublished_filter(fpr_primary, email_remove)
}
fn nolock_set_email_unpublished_filter(
&self,
fpr_primary: &Fingerprint,
email_remove: impl Fn(&UserID) -> bool,
) -> Result<()> {
let published_tpk_old = self.by_fpr(&fpr_primary)
let published_tpk_old = self
.by_fpr(fpr_primary)
.ok_or_else(|| anyhow!("Key not in database!"))
.and_then(|bytes| Cert::from_bytes(bytes.as_bytes()))?;
@@ -511,9 +583,7 @@ pub trait Database: Sync + Send {
.flatten()
.collect();
let published_tpk_new = {
tpk_filter_userids(&published_tpk_old, |uid| email_remove(uid.userid()))?
};
let published_tpk_new = published_tpk_old.retain_userids(|uid| email_remove(uid.userid()));
let published_emails_new: Vec<Email> = published_tpk_new
.userids()
@@ -526,17 +596,19 @@ pub trait Database: Sync + Send {
.filter(|email| !published_emails_new.contains(email));
let published_tpk_clean = tpk_clean(&published_tpk_new)?;
let published_tpk_tmp = self.write_to_temp(&tpk_to_string(&published_tpk_clean)?)?;
let published_tpk_tmp = tx.write_to_temp(&tpk_to_string(&published_tpk_clean)?)?;
self.move_tmp_to_published(published_tpk_tmp, &fpr_primary)?;
self.regenerate_wkd(fpr_primary, &published_tpk_clean)?;
tx.move_tmp_to_published(published_tpk_tmp, fpr_primary)?;
self.regenerate_wkd(&tx, fpr_primary, &published_tpk_clean)?;
self.update_write_log(&fpr_primary);
self.update_write_log(fpr_primary);
for unpublished_email in unpublished_emails {
if let Err(e) = self.unlink_email(&unpublished_email, &fpr_primary) {
info!("Error deleting email symlink! {} -> {} {:?}",
&unpublished_email, &fpr_primary, e);
if let Err(e) = tx.unlink_email(unpublished_email, fpr_primary) {
info!(
"Error deleting email symlink! {} -> {} {:?}",
&unpublished_email, &fpr_primary, e
);
}
}
@@ -544,28 +616,32 @@ pub trait Database: Sync + Send {
}
fn set_email_unpublished(
&self,
&'a self,
fpr_primary: &Fingerprint,
email_remove: &Email,
) -> Result<()> {
self.set_email_unpublished_filter(fpr_primary, |uid|
let tx = self.transaction().unwrap();
self.set_email_unpublished_filter(&tx, fpr_primary, |uid| {
Email::try_from(uid)
.map(|email| email != *email_remove)
.unwrap_or(false))
.unwrap_or(false)
})?;
tx.commit()?;
Ok(())
}
fn set_email_unpublished_all(
&self,
fpr_primary: &Fingerprint,
) -> Result<()> {
self.set_email_unpublished_filter(fpr_primary, |_| false)
fn set_email_unpublished_all(&'a self, fpr_primary: &Fingerprint) -> Result<()> {
let tx = self.transaction().unwrap();
self.set_email_unpublished_filter(&tx, fpr_primary, |_| false)?;
tx.commit()?;
Ok(())
}
fn regenerate_links(
&self,
fpr_primary: &Fingerprint,
) -> Result<RegenerateResult> {
let tpk = self.by_primary_fpr(&fpr_primary)
fn regenerate_links(&'a self, fpr_primary: &Fingerprint) -> Result<RegenerateResult> {
let tx = self.transaction().unwrap();
let tpk = self
.by_primary_fpr(fpr_primary)
.and_then(|bytes| Cert::from_bytes(bytes.as_bytes()).ok())
.ok_or_else(|| anyhow!("Key not in database!"))?;
@@ -575,13 +651,13 @@ pub trait Database: Sync + Send {
.flatten()
.collect();
self.regenerate_wkd(fpr_primary, &tpk)?;
self.regenerate_wkd(&tx, fpr_primary, &tpk)?;
let fingerprints = tpk_get_linkable_fprs(&tpk);
let fpr_checks = fingerprints
.into_iter()
.map(|fpr| self.check_link_fpr(&fpr, &fpr_primary))
.map(|fpr| self.check_link_fpr(&fpr, fpr_primary))
.collect::<Vec<_>>()
.into_iter()
.collect::<Result<Vec<_>>>()?;
@@ -593,14 +669,16 @@ pub trait Database: Sync + Send {
for fpr in fpr_not_linked {
keys_linked += 1;
self.link_fpr(&fpr, &fpr_primary)?;
tx.link_fpr(&fpr, fpr_primary)?;
}
for email in published_emails {
emails_linked += 1;
self.link_email(&email, &fpr_primary)?;
tx.link_email(&email, fpr_primary)?;
}
tx.commit()?;
if keys_linked != 0 || emails_linked != 0 {
Ok(RegenerateResult::Updated)
} else {
@@ -610,59 +688,49 @@ pub trait Database: Sync + Send {
fn regenerate_wkd(
&self,
tx: &Self::Transaction,
fpr_primary: &Fingerprint,
published_tpk: &Cert
published_tpk: &Cert,
) -> Result<()> {
let published_wkd_tpk_tmp = if published_tpk.userids().next().is_some() {
Some(self.write_to_temp(&published_tpk.to_vec()?)?)
Some(tx.write_to_temp(&published_tpk.export_to_vec()?)?)
} else {
None
};
self.move_tmp_to_published_wkd(published_wkd_tpk_tmp, fpr_primary)?;
tx.move_tmp_to_published_wkd(published_wkd_tpk_tmp, fpr_primary)?;
Ok(())
}
fn check_link_fpr(&self, fpr: &Fingerprint, target: &Fingerprint) -> Result<Option<Fingerprint>>;
fn by_fpr_full(&self, fpr: &Fingerprint) -> Option<String>;
fn by_primary_fpr(&self, fpr: &Fingerprint) -> Option<String>;
fn write_to_temp(&self, content: &[u8]) -> Result<NamedTempFile>;
fn move_tmp_to_full(&self, content: NamedTempFile, fpr: &Fingerprint) -> Result<()>;
fn move_tmp_to_published(&self, content: NamedTempFile, fpr: &Fingerprint) -> Result<()>;
fn move_tmp_to_published_wkd(&self, content: Option<NamedTempFile>, fpr: &Fingerprint) -> Result<()>;
fn write_to_quarantine(&self, fpr: &Fingerprint, content: &[u8]) -> Result<()>;
fn write_log_append(&self, filename: &str, fpr_primary: &Fingerprint) -> Result<()>;
fn check_consistency(&self) -> Result<()>;
}
fn tpk_get_emails(cert: &Cert) -> Vec<Email> {
cert
.userids()
cert.userids()
.map(|binding| Email::try_from(binding.userid()))
.flatten()
.collect()
}
pub fn tpk_get_linkable_fprs(tpk: &Cert) -> Vec<Fingerprint> {
let ref signing_capable = KeyFlags::empty()
.set_signing()
.set_certification();
let ref fpr_primary = Fingerprint::try_from(tpk.fingerprint()).unwrap();
tpk
.keys()
.into_iter()
.flat_map(|bundle| {
Fingerprint::try_from(bundle.key().fingerprint())
.map(|fpr| (fpr, bundle.binding_signature(&POLICY, None).ok().and_then(|sig| sig.key_flags())))
let signing_capable = &KeyFlags::empty().set_signing().set_certification();
let fpr_primary = &Fingerprint::try_from(tpk.fingerprint()).unwrap();
tpk.keys()
.into_iter()
.flat_map(|bundle| {
Fingerprint::try_from(bundle.key().fingerprint()).map(|fpr| {
(
fpr,
bundle
.binding_signature(&POLICY, None)
.ok()
.and_then(|sig| sig.key_flags()),
)
})
.filter(|(fpr, flags)| {
fpr == fpr_primary ||
flags.is_none() ||
!(signing_capable & flags.as_ref().unwrap()).is_empty()
})
.map(|(fpr,_)| fpr)
.collect()
})
.filter(|(fpr, flags)| {
fpr == fpr_primary
|| flags.is_none()
|| !(signing_capable & flags.as_ref().unwrap()).is_empty()
})
.map(|(fpr, _)| fpr)
.collect()
}

View File

@@ -2,13 +2,8 @@ use openpgp::Result;
use std::convert::TryFrom;
use openpgp::{
Cert,
cert::amalgamation::ComponentAmalgamation,
types::RevocationStatus,
armor::{Writer, Kind},
packet::UserID,
serialize::Serialize as OpenPgpSerialize,
policy::StandardPolicy,
cert::prelude::*, policy::StandardPolicy, serialize::SerializeInto as _,
types::RevocationStatus, Cert,
};
use Email;
@@ -24,13 +19,7 @@ pub fn is_status_revoked(status: RevocationStatus) -> bool {
}
pub fn tpk_to_string(tpk: &Cert) -> Result<Vec<u8>> {
let mut buf = Vec::new();
{
let mut armor_writer = Writer::new(&mut buf, Kind::PublicKey)?;
tpk.serialize(&mut armor_writer)?;
armor_writer.finalize()?;
}
Ok(buf)
tpk.armored().to_vec()
}
pub fn tpk_clean(tpk: &Cert) -> Result<Cert> {
@@ -41,33 +30,65 @@ pub fn tpk_clean(tpk: &Cert) -> Result<Cert> {
// The primary key and related signatures.
let pk_bundle = tpk.primary_key().bundle();
acc.push(pk_bundle.key().clone().into());
for s in pk_bundle.self_signatures() { acc.push(s.clone().into()) }
for s in pk_bundle.self_revocations() { acc.push(s.clone().into()) }
for s in pk_bundle.other_revocations() { acc.push(s.clone().into()) }
for s in pk_bundle.self_signatures() {
acc.push(s.clone().into())
}
for s in pk_bundle.self_revocations() {
acc.push(s.clone().into())
}
for s in pk_bundle.other_revocations() {
acc.push(s.clone().into())
}
// The subkeys and related signatures.
for skb in tpk.keys().subkeys() {
acc.push(skb.key().clone().into());
for s in skb.self_signatures() { acc.push(s.clone().into()) }
for s in skb.self_revocations() { acc.push(s.clone().into()) }
for s in skb.other_revocations() { acc.push(s.clone().into()) }
for s in skb.self_signatures() {
acc.push(s.clone().into())
}
for s in skb.self_revocations() {
acc.push(s.clone().into())
}
for s in skb.other_revocations() {
acc.push(s.clone().into())
}
}
// Updates for UserIDs fulfilling `filter`.
// The UserIDs.
for uidb in tpk.userids() {
acc.push(uidb.userid().clone().into());
for s in uidb.self_signatures() { acc.push(s.clone().into()) }
for s in uidb.self_revocations() { acc.push(s.clone().into()) }
for s in uidb.other_revocations() { acc.push(s.clone().into()) }
for s in uidb.self_signatures() {
acc.push(s.clone().into())
}
for s in uidb.self_revocations() {
acc.push(s.clone().into())
}
for s in uidb.other_revocations() {
acc.push(s.clone().into())
}
// Reasoning about the currently attested certifications
// requires a policy.
if let Ok(vuid) = uidb.with_policy(&POLICY, None) {
for s in vuid.attestation_key_signatures() {
acc.push(s.clone().into());
}
for s in vuid.attested_certifications() {
acc.push(s.clone().into());
}
}
}
Cert::from_packets(acc.into_iter())
}
/// Filters the Cert, keeping only UserIDs that aren't revoked, and whose emails match the given list
pub fn tpk_filter_alive_emails(tpk: &Cert, emails: &[Email]) -> Result<Cert> {
tpk_filter_userids(tpk, |uid| {
if is_status_revoked(uid.revocation_status(&POLICY, None)) {
pub fn tpk_filter_alive_emails(tpk: &Cert, emails: &[Email]) -> Cert {
tpk.clone().retain_userids(|uid| {
let is_exportable = uid.self_signatures().any(|s| s.exportable().is_ok());
if !is_exportable {
false
} else if is_status_revoked(uid.revocation_status(&POLICY, None)) {
false
} else if let Ok(email) = Email::try_from(uid.userid()) {
emails.contains(&email)
@@ -76,44 +97,3 @@ pub fn tpk_filter_alive_emails(tpk: &Cert, emails: &[Email]) -> Result<Cert> {
}
})
}
/// Filters the Cert, keeping only those UserIDs that fulfill the
/// predicate `filter`.
pub fn tpk_filter_userids<F>(tpk: &Cert, filter: F) -> Result<Cert>
where F: Fn(&ComponentAmalgamation<UserID>) -> bool
{
// Iterate over the Cert, pushing packets we want to merge
// into the accumulator.
let mut acc = Vec::new();
// The primary key and related signatures.
let pk_bundle = tpk.primary_key().bundle();
acc.push(pk_bundle.key().clone().into());
for s in pk_bundle.self_signatures() { acc.push(s.clone().into()) }
for s in pk_bundle.certifications() { acc.push(s.clone().into()) }
for s in pk_bundle.self_revocations() { acc.push(s.clone().into()) }
for s in pk_bundle.other_revocations() { acc.push(s.clone().into()) }
// The subkeys and related signatures.
for skb in tpk.keys().subkeys() {
acc.push(skb.key().clone().into());
for s in skb.self_signatures() { acc.push(s.clone().into()) }
for s in skb.certifications() { acc.push(s.clone().into()) }
for s in skb.self_revocations() { acc.push(s.clone().into()) }
for s in skb.other_revocations() { acc.push(s.clone().into()) }
}
// Updates for UserIDs fulfilling `filter`.
for uidb in tpk.userids() {
// Only include userids matching filter
if filter(&uidb) {
acc.push(uidb.userid().clone().into());
for s in uidb.self_signatures() { acc.push(s.clone().into()) }
for s in uidb.certifications() { acc.push(s.clone().into()) }
for s in uidb.self_revocations() { acc.push(s.clone().into()) }
for s in uidb.other_revocations() { acc.push(s.clone().into()) }
}
}
Cert::from_packets(acc.into_iter())
}

734
database/src/sqlite.rs Normal file
View File

@@ -0,0 +1,734 @@
use self_cell::self_cell;
use std::convert::TryFrom;
use std::fs::create_dir_all;
use std::path::PathBuf;
use std::str::FromStr;
use std::time::{SystemTime, UNIX_EPOCH};
use openpgp::policy::StandardPolicy;
use types::{Email, Fingerprint, KeyID};
use Result;
use {Database, Query};
use openpgp::Cert;
use r2d2_sqlite::rusqlite::params;
use r2d2_sqlite::rusqlite::OptionalExtension;
use r2d2_sqlite::rusqlite::ToSql;
use r2d2_sqlite::rusqlite::Transaction;
use r2d2_sqlite::SqliteConnectionManager;
use crate::{wkd, DatabaseTransaction};
pub const POLICY: StandardPolicy = StandardPolicy::new();
pub struct Sqlite {
pool: r2d2::Pool<SqliteConnectionManager>,
}
impl Sqlite {
pub fn new_file(base_dir: impl Into<PathBuf>) -> Result<Self> {
let base_dir: PathBuf = base_dir.into();
let db_file = base_dir.join("keys.sqlite");
let manager = SqliteConnectionManager::file(db_file);
Self::new_internal(base_dir, manager)
}
#[cfg(test)]
fn build_pool(manager: SqliteConnectionManager) -> Result<r2d2::Pool<SqliteConnectionManager>> {
#[derive(Copy, Clone, Debug)]
pub struct LogConnectionCustomizer;
impl<E> r2d2::CustomizeConnection<rusqlite::Connection, E> for LogConnectionCustomizer {
fn on_acquire(&self, conn: &mut rusqlite::Connection) -> std::result::Result<(), E> {
println!("Acquiring sqlite pool connection: {:?}", conn);
conn.trace(Some(|query| {
println!("{}", query);
}));
std::result::Result::Ok(())
}
fn on_release(&self, conn: rusqlite::Connection) {
println!("Releasing pool connection: {:?}", conn);
}
}
Ok(r2d2::Pool::builder()
.connection_customizer(Box::new(LogConnectionCustomizer {}))
.build(manager)?)
}
#[cfg(not(test))]
fn build_pool(manager: SqliteConnectionManager) -> Result<r2d2::Pool<SqliteConnectionManager>> {
Ok(r2d2::Pool::builder().build(manager)?)
}
fn new_internal(base_dir: PathBuf, manager: SqliteConnectionManager) -> Result<Self> {
let keys_dir_log = base_dir.join("log");
create_dir_all(&keys_dir_log)?;
let pool = Self::build_pool(manager)?;
let conn = pool.get()?;
conn.pragma_update(None, "journal_mode", "wal")?;
conn.pragma_update(None, "synchronous", "normal")?;
conn.pragma_update(None, "user_version", "1")?;
conn.execute_batch(
"
CREATE TABLE IF NOT EXISTS certs (
primary_fingerprint TEXT NOT NULL PRIMARY KEY,
full TEXT NOT NULL,
published TEXT,
published_not_armored BLOB,
updated_at TIMESTAMP NOT NULL,
created_at TIMESTAMP NOT NULL
);
CREATE TABLE IF NOT EXISTS cert_identifiers (
fingerprint TEXT NOT NULL PRIMARY KEY,
keyid TEXT NOT NULL,
primary_fingerprint TEXT NOT NULL,
created_at TIMESTAMP NOT NULL
);
CREATE TABLE IF NOT EXISTS emails (
email TEXT NOT NULL PRIMARY KEY,
domain TEXT NOT NULL,
wkd_hash TEXT NOT NULL,
primary_fingerprint TEXT NOT NULL,
created_at TIMESTAMP NOT NULL
);
",
)?;
Ok(Self { pool })
}
}
self_cell! {
pub struct SqliteTransaction {
owner: r2d2::PooledConnection<SqliteConnectionManager>,
#[covariant]
dependent: Transaction,
}
}
impl SqliteTransaction {
fn start(pool: &r2d2::Pool<SqliteConnectionManager>) -> Result<Self> {
let conn = pool.get()?;
Ok(Self::new(conn, |c| {
Transaction::new_unchecked(c, rusqlite::TransactionBehavior::Deferred).unwrap()
}))
}
fn tx(&self) -> &Transaction {
self.borrow_dependent()
}
}
fn query_simple<T: rusqlite::types::FromSql>(
conn: &r2d2::PooledConnection<SqliteConnectionManager>,
query: &str,
params: &[&dyn ToSql],
) -> Option<T> {
conn.prepare_cached(query)
.expect("query must be valid")
.query_row(params, |row| row.get(0))
.optional()
.expect("query exection must not fail")
}
impl<'a> DatabaseTransaction<'a> for SqliteTransaction {
type TempCert = Vec<u8>;
fn commit(self) -> Result<()> {
// we can't use tx().commit(), but we can cheat :)
self.tx().execute_batch("COMMIT")?;
Ok(())
}
fn write_to_temp(&self, content: &[u8]) -> Result<Self::TempCert> {
Ok(content.to_vec())
}
fn move_tmp_to_full(&self, file: Self::TempCert, fpr: &Fingerprint) -> Result<()> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_millis() as u64;
let file = String::from_utf8(file)?;
self.tx().execute(
"
INSERT INTO certs (primary_fingerprint, full, created_at, updated_at)
VALUES (?1, ?2, ?3, ?3)
ON CONFLICT(primary_fingerprint) DO UPDATE SET full=excluded.full, updated_at = excluded.updated_at
",
params![fpr, file, now],
)?;
Ok(())
}
fn move_tmp_to_published(&self, file: Self::TempCert, fpr: &Fingerprint) -> Result<()> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_millis() as u64;
let file = String::from_utf8(file)?;
self.tx().execute(
"UPDATE certs SET published = ?2, updated_at = ?3 WHERE primary_fingerprint = ?1",
params![fpr, file, now],
)?;
Ok(())
}
fn move_tmp_to_published_wkd(
&self,
file: Option<Self::TempCert>,
fpr: &Fingerprint,
) -> Result<()> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_millis() as u64;
self.tx().execute(
"UPDATE certs SET published_not_armored = ?2, updated_at = ?3 WHERE primary_fingerprint = ?1",
params![fpr, file, now],
)?;
Ok(())
}
fn write_to_quarantine(&self, _fpr: &Fingerprint, _content: &[u8]) -> Result<()> {
Ok(())
}
fn link_email(&self, email: &Email, fpr: &Fingerprint) -> Result<()> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_millis() as u64;
let (domain, wkd_hash) = wkd::encode_wkd(email.as_str()).expect("email must be vaild");
self.tx().execute(
"
INSERT INTO emails (email, wkd_hash, domain, primary_fingerprint, created_at)
VALUES (?1, ?2, ?3, ?4, ?5)
ON CONFLICT(email) DO UPDATE SET primary_fingerprint = excluded.primary_fingerprint
",
params![email, domain, wkd_hash, fpr, now],
)?;
Ok(())
}
fn unlink_email(&self, email: &Email, fpr: &Fingerprint) -> Result<()> {
self.tx()
.execute(
"DELETE FROM emails WHERE email = ?1 AND primary_fingerprint = ?2",
params![email, fpr],
)
.unwrap();
Ok(())
}
fn link_fpr(&self, from_fpr: &Fingerprint, primary_fpr: &Fingerprint) -> Result<()> {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_millis() as u64;
self.tx().execute(
"
INSERT INTO cert_identifiers (fingerprint, keyid, primary_fingerprint, created_at)
VALUES (?1, ?2, ?3, ?4)
ON CONFLICT(fingerprint) DO UPDATE SET primary_fingerprint = excluded.primary_fingerprint;
",
params![
from_fpr,
KeyID::from(from_fpr),
primary_fpr,
now,
],
)?;
Ok(())
}
fn unlink_fpr(&self, from_fpr: &Fingerprint, primary_fpr: &Fingerprint) -> Result<()> {
self.tx().execute(
"DELETE FROM cert_identifiers WHERE primary_fingerprint = ?1 AND fingerprint = ?2 AND keyid = ?3",
params![primary_fpr, from_fpr, KeyID::from(from_fpr)],
)?;
Ok(())
}
}
impl<'a> Database<'a> for Sqlite {
type Transaction = SqliteTransaction;
fn transaction(&'a self) -> Result<Self::Transaction> {
SqliteTransaction::start(&self.pool)
}
fn write_log_append(&self, _filename: &str, _fpr_primary: &Fingerprint) -> Result<()> {
// this is done implicitly via created_at in sqlite, no need to do anything here
Ok(())
}
fn lookup_primary_fingerprint(&self, term: &Query) -> Option<Fingerprint> {
use super::Query::*;
let conn = self.pool.get().unwrap();
match term {
ByFingerprint(ref fp) => query_simple(
&conn,
"SELECT primary_fingerprint FROM cert_identifiers WHERE fingerprint = ?1",
params![fp],
),
ByKeyID(ref keyid) => query_simple(
&conn,
"SELECT primary_fingerprint FROM cert_identifiers WHERE keyid = ?1",
params![keyid],
),
ByEmail(ref email) => query_simple(
&conn,
"SELECT primary_fingerprint FROM emails WHERE email = ?1",
params![email],
),
_ => return None,
}
}
// Lookup straight from certs table, no link resolution
fn by_fpr_full(&self, primary_fpr: &Fingerprint) -> Option<String> {
let conn = self.pool.get().unwrap();
query_simple(
&conn,
"SELECT full FROM certs WHERE primary_fingerprint = ?1",
params![primary_fpr],
)
}
// XXX: rename! to by_primary_fpr_published
// Lookup the published cert straight from certs table, no link resolution
fn by_primary_fpr(&self, primary_fpr: &Fingerprint) -> Option<String> {
let conn = self.pool.get().unwrap();
query_simple(
&conn,
"SELECT published FROM certs WHERE primary_fingerprint = ?1",
params![primary_fpr],
)
}
fn by_fpr(&self, fpr: &Fingerprint) -> Option<String> {
let conn = self.pool.get().unwrap();
query_simple::<Fingerprint>(
&conn,
"SELECT primary_fingerprint FROM cert_identifiers WHERE fingerprint = ?1",
params![fpr],
)
.and_then(|primary_fpr| {
query_simple(
&conn,
"SELECT published FROM certs WHERE primary_fingerprint = ?1",
params![&primary_fpr],
)
})
}
fn by_email(&self, email: &Email) -> Option<String> {
let conn = self.pool.get().unwrap();
query_simple::<Fingerprint>(
&conn,
"SELECT primary_fingerprint FROM emails WHERE email = ?1",
params![email],
)
.and_then(|primary_fpr| {
query_simple(
&conn,
"SELECT published FROM certs WHERE primary_fingerprint = ?1",
params![&primary_fpr],
)
})
}
fn by_email_wkd(&self, email: &Email) -> Option<Vec<u8>> {
let conn = self.pool.get().unwrap();
query_simple::<Fingerprint>(
&conn,
"SELECT primary_fingerprint FROM emails WHERE email = ?1",
params![email],
)
.and_then(|primary_fpr| {
query_simple(
&conn,
"SELECT published_not_armored FROM certs WHERE primary_fingerprint = ?1",
params![&primary_fpr],
)
})
}
fn by_kid(&self, kid: &KeyID) -> Option<String> {
let conn = self.pool.get().unwrap();
query_simple::<Fingerprint>(
&conn,
"SELECT primary_fingerprint FROM cert_identifiers WHERE keyid = ?1",
params![kid],
)
.and_then(|primary_fpr| {
query_simple(
&conn,
"SELECT published FROM certs WHERE primary_fingerprint = ?1",
params![primary_fpr],
)
})
}
fn by_domain_and_hash_wkd(&self, domain: &str, wkd_hash: &str) -> Option<Vec<u8>> {
let conn = self.pool.get().unwrap();
query_simple::<Fingerprint>(
&conn,
"SELECT primary_fingerprint FROM emails WHERE domain = ?1 AND wkd_hash = ?2",
params![domain, wkd_hash],
)
.and_then(|primary_fpr| {
query_simple(
&conn,
"SELECT published_not_armored FROM certs WHERE primary_fingerprint = ?1",
params![primary_fpr],
)
})
}
fn check_link_fpr(
&self,
fpr: &Fingerprint,
_fpr_target: &Fingerprint,
) -> Result<Option<Fingerprint>> {
// a desync here cannot happen structurally, so always return true here
Ok(Some(fpr.clone()))
}
/// Checks the database for consistency.
///
/// Note that this operation may take a long time, and is
/// generally only useful for testing.
fn check_consistency(&self) -> Result<()> {
let conn = self.pool.get().unwrap();
let mut stmt = conn.prepare("SELECT primary_fingerprint, published FROM certs")?;
let mut rows = stmt.query([])?;
while let Some(row) = rows.next()? {
let primary_fpr: Fingerprint = row.get(0)?;
let published: String = row.get(1)?;
let cert = Cert::from_str(&published).unwrap();
let mut cert_emails: Vec<Email> = cert
.userids()
.map(|uid| uid.userid().email2().unwrap())
.flatten()
.map(|email| Email::from_str(&email))
.flatten()
.collect();
let mut db_emails: Vec<Email> = conn
.prepare("SELECT email FROM emails WHERE primary_fingerprint = ?1")?
.query_map([&primary_fpr], |row| row.get::<_, String>(0))
.unwrap()
.map(|email| Email::from_str(&email.unwrap()))
.flatten()
.collect();
cert_emails.sort();
cert_emails.dedup();
db_emails.sort();
if cert_emails != db_emails {
return Err(format_err!(
"{:?} does not have correct emails indexed, cert ${:?} db {:?}",
&primary_fpr,
cert_emails,
db_emails,
));
}
let policy = &POLICY;
let mut cert_fprs: Vec<Fingerprint> = cert
.keys()
.with_policy(policy, None)
.for_certification()
.for_signing()
.map(|amalgamation| amalgamation.key().fingerprint())
.map(Fingerprint::try_from)
.flatten()
.collect();
let mut db_fprs: Vec<Fingerprint> = conn
.prepare("SELECT fingerprint FROM cert_identifiers WHERE primary_fingerprint = ?1")?
.query_map([&primary_fpr], |row| row.get::<_, Fingerprint>(0))
.unwrap()
.flatten()
.collect();
cert_fprs.sort();
db_fprs.sort();
if cert_fprs != db_fprs {
return Err(format_err!(
"{:?} does not have correct fingerprints indexed, cert ${:?} db {:?}",
&primary_fpr,
cert_fprs,
db_fprs,
));
}
}
Ok(())
}
fn get_last_log_entry(&self) -> Result<Fingerprint> {
let conn = self.pool.get().unwrap();
Ok(conn.query_row(
"SELECT primary_fingerprint FROM certs ORDER BY updated_at DESC LIMIT 1",
[],
|row| row.get::<_, Fingerprint>(0),
)?)
}
}
#[cfg(test)]
mod tests {
use super::*;
use openpgp::cert::CertBuilder;
use tempfile::TempDir;
use test;
const DATA_1: &str = "data, content doesn't matter";
const DATA_2: &str = "other data, content doesn't matter";
const FINGERPRINT_1: &str = "D4AB192964F76A7F8F8A9B357BD18320DEADFA11";
fn open_db() -> (TempDir, Sqlite) {
let tmpdir = TempDir::new().unwrap();
let db = Sqlite::new_file(tmpdir.path()).unwrap();
(tmpdir, db)
}
#[test]
fn new() {
let (_tmp_dir, db) = open_db();
let k1 = CertBuilder::new()
.add_userid("a@invalid.example.org")
.generate()
.unwrap()
.0;
let k2 = CertBuilder::new()
.add_userid("b@invalid.example.org")
.generate()
.unwrap()
.0;
let k3 = CertBuilder::new()
.add_userid("c@invalid.example.org")
.generate()
.unwrap()
.0;
assert!(db.merge(k1).unwrap().into_tpk_status().email_status.len() > 0);
assert!(
db.merge(k2.clone())
.unwrap()
.into_tpk_status()
.email_status
.len()
> 0
);
assert!(!db.merge(k2).unwrap().into_tpk_status().email_status.len() > 0);
assert!(
db.merge(k3.clone())
.unwrap()
.into_tpk_status()
.email_status
.len()
> 0
);
assert!(
!db.merge(k3.clone())
.unwrap()
.into_tpk_status()
.email_status
.len()
> 0
);
assert!(!db.merge(k3).unwrap().into_tpk_status().email_status.len() > 0);
}
#[test]
fn xx_by_fpr_full() -> Result<()> {
let (_tmp_dir, db) = open_db();
let fpr1 = Fingerprint::from_str(FINGERPRINT_1)?;
let lock = db.transaction().unwrap();
lock.move_tmp_to_full(lock.write_to_temp(DATA_1.as_bytes())?, &fpr1)?;
lock.link_fpr(&fpr1, &fpr1)?;
lock.commit().unwrap();
assert_eq!(db.by_fpr_full(&fpr1).expect("must find key"), DATA_1);
Ok(())
}
#[test]
fn xx_by_kid() -> Result<()> {
let (_tmp_dir, db) = open_db();
let fpr1 = Fingerprint::from_str(FINGERPRINT_1)?;
let lock = db.transaction().unwrap();
lock.move_tmp_to_full(lock.write_to_temp(DATA_1.as_bytes())?, &fpr1)?;
lock.move_tmp_to_published(lock.write_to_temp(DATA_2.as_bytes())?, &fpr1)?;
lock.link_fpr(&fpr1, &fpr1)?;
lock.commit().unwrap();
assert_eq!(db.by_kid(&fpr1.into()).expect("must find key"), DATA_2);
Ok(())
}
#[test]
fn xx_by_primary_fpr() -> Result<()> {
let (_tmp_dir, db) = open_db();
let fpr1 = Fingerprint::from_str(FINGERPRINT_1)?;
let lock = db.transaction().unwrap();
lock.move_tmp_to_full(lock.write_to_temp(DATA_1.as_bytes())?, &fpr1)?;
lock.move_tmp_to_published(lock.write_to_temp(DATA_2.as_bytes())?, &fpr1)?;
lock.commit().unwrap();
assert_eq!(db.by_primary_fpr(&fpr1).expect("must find key"), DATA_2);
Ok(())
}
#[test]
fn uid_verification() {
let (_tmp_dir, mut db) = open_db();
test::test_uid_verification(&mut db);
db.check_consistency().expect("inconsistent database");
}
#[test]
fn uid_deletion() {
let (_tmp_dir, mut db) = open_db();
test::test_uid_deletion(&mut db);
db.check_consistency().expect("inconsistent database");
}
#[test]
fn subkey_lookup() {
let (_tmp_dir, mut db) = open_db();
test::test_subkey_lookup(&mut db);
db.check_consistency().expect("inconsistent database");
}
#[test]
fn kid_lookup() {
let (_tmp_dir, mut db) = open_db();
test::test_kid_lookup(&mut db);
db.check_consistency().expect("inconsistent database");
}
#[test]
fn upload_revoked_tpk() {
let (_tmp_dir, mut db) = open_db();
test::test_upload_revoked_tpk(&mut db);
db.check_consistency().expect("inconsistent database");
}
#[test]
fn uid_revocation() {
let (_tmp_dir, mut db) = open_db();
test::test_uid_revocation(&mut db);
db.check_consistency().expect("inconsistent database");
}
#[test]
fn regenerate() {
let (_tmp_dir, mut db) = open_db();
test::test_regenerate(&mut db);
db.check_consistency().expect("inconsistent database");
}
#[test]
fn key_reupload() {
let (_tmp_dir, mut db) = open_db();
test::test_reupload(&mut db);
db.check_consistency().expect("inconsistent database");
}
#[test]
fn uid_replacement() {
let (_tmp_dir, mut db) = open_db();
test::test_uid_replacement(&mut db);
db.check_consistency().expect("inconsistent database");
}
#[test]
fn uid_unlinking() {
let (_tmp_dir, mut db) = open_db();
test::test_unlink_uid(&mut db);
db.check_consistency().expect("inconsistent database");
}
#[test]
fn same_email_1() {
let (_tmp_dir, mut db) = open_db();
test::test_same_email_1(&mut db);
db.check_consistency().expect("inconsistent database");
}
#[test]
fn same_email_2() {
let (_tmp_dir, mut db) = open_db();
test::test_same_email_2(&mut db);
db.check_consistency().expect("inconsistent database");
}
#[test]
fn same_email_3() {
let (_tmp_dir, mut db) = open_db();
test::test_same_email_3(&mut db);
db.check_consistency().expect("inconsistent database");
}
#[test]
fn same_email_4() {
let (_tmp_dir, mut db) = open_db();
test::test_same_email_4(&mut db);
db.check_consistency().expect("inconsistent database");
}
#[test]
fn no_selfsig() {
let (_tmp_dir, mut db) = open_db();
test::test_no_selfsig(&mut db);
db.check_consistency().expect("inconsistent database");
}
#[test]
fn bad_uids() {
let (_tmp_dir, mut db) = open_db();
test::test_bad_uids(&mut db);
db.check_consistency().expect("inconsistent database");
}
#[test]
fn reverse_fingerprint_to_path() {
let tmpdir = TempDir::new().unwrap();
let db = Sqlite::new_file(tmpdir.path()).unwrap();
let _fp: Fingerprint = "CBCD8F030588653EEDD7E2659B7DD433F254904A".parse().unwrap();
// XXX: fixme
//assert_eq!(Sqlite::path_to_fingerprint(&db.link_by_fingerprint(&fp)),
// Some(fp.clone()));
db.check_consistency().expect("inconsistent database");
}
#[test]
fn attested_key_signatures() -> Result<()> {
let (_tmp_dir, mut db) = open_db();
test::attested_key_signatures(&mut db)?;
db.check_consistency()?;
Ok(())
}
#[test]
fn nonexportable_sigs() -> Result<()> {
let (_tmp_dir, mut db) = open_db();
test::nonexportable_sigs(&mut db)?;
db.check_consistency()?;
Ok(())
}
}

View File

@@ -1,6 +1,6 @@
use std::io::{Read,Write};
use std::path::PathBuf;
use std::fs::{create_dir_all, remove_file, File};
use std::io::{Read, Write};
use std::path::PathBuf;
use std::str;

File diff suppressed because it is too large Load Diff

View File

@@ -3,10 +3,16 @@ use std::fmt;
use std::result;
use std::str::FromStr;
use openpgp::packet::UserID;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use anyhow::Error;
use {Result};
use hex::ToHex;
use openpgp::packet::UserID;
use r2d2_sqlite::rusqlite::types::FromSql;
use r2d2_sqlite::rusqlite::types::FromSqlError;
use r2d2_sqlite::rusqlite::types::FromSqlResult;
use r2d2_sqlite::rusqlite::types::ToSql;
use r2d2_sqlite::rusqlite::types::ValueRef;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use Result;
/// Holds a normalized email address.
///
@@ -26,11 +32,27 @@ impl Email {
}
}
impl FromSql for Email {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value
.as_str()
.and_then(|s| Self::from_str(s).map_err(|_| FromSqlError::InvalidType))
}
}
impl ToSql for Email {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
Ok(rusqlite::types::ToSqlOutput::Borrowed(
rusqlite::types::ValueRef::Text(self.0.as_bytes()),
))
}
}
impl TryFrom<&UserID> for Email {
type Error = Error;
fn try_from(uid: &UserID) -> Result<Self> {
if let Some(address) = uid.email()? {
if let Some(address) = uid.email2()? {
let mut iter = address.split('@');
let localpart = iter.next().expect("Invalid email address");
let domain = iter.next().expect("Invalid email address");
@@ -41,7 +63,7 @@ impl TryFrom<&UserID> for Email {
.map_err(|e| anyhow!("punycode conversion failed: {:?}", e))?;
// TODO this is a hotfix for a lettre vulnerability. remove once fixed upstream.
if localpart.starts_with("-") {
if localpart.starts_with('-') {
return Err(anyhow!("malformed email address: '{:?}'", uid.value()));
}
@@ -77,17 +99,32 @@ impl FromStr for Email {
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Fingerprint([u8; 20]);
impl FromSql for Fingerprint {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value
.as_str()
.and_then(|s| Self::from_str(s).map_err(|_| FromSqlError::InvalidType))
}
}
impl ToSql for Fingerprint {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
Ok(rusqlite::types::ToSqlOutput::Owned(
rusqlite::types::Value::Text(self.to_string()),
))
}
}
impl TryFrom<sequoia_openpgp::Fingerprint> for Fingerprint {
type Error = Error;
fn try_from(fpr: sequoia_openpgp::Fingerprint) -> Result<Self> {
match fpr {
sequoia_openpgp::Fingerprint::V4(a) => Ok(Fingerprint(a)),
sequoia_openpgp::Fingerprint::Invalid(_) =>
Err(anyhow!("invalid fingerprint")),
sequoia_openpgp::Fingerprint::Invalid(_) => Err(anyhow!("invalid fingerprint")),
_ => Err(anyhow!("unknown fingerprint type")),
}
}
@@ -95,7 +132,6 @@ impl TryFrom<sequoia_openpgp::Fingerprint> for Fingerprint {
impl fmt::Display for Fingerprint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use ::hex::ToHex;
self.0.write_hex_upper(f)
}
}
@@ -116,8 +152,7 @@ impl<'de> Deserialize<'de> for Fingerprint {
{
use serde::de::Error;
String::deserialize(deserializer).and_then(|string| {
Self::from_str(&string)
.map_err(|err| Error::custom(err.to_string()))
Self::from_str(&string).map_err(|err| Error::custom(err.to_string()))
})
}
}
@@ -128,8 +163,9 @@ impl FromStr for Fingerprint {
fn from_str(s: &str) -> Result<Fingerprint> {
match sequoia_openpgp::Fingerprint::from_hex(s)? {
sequoia_openpgp::Fingerprint::V4(a) => Ok(Fingerprint(a)),
sequoia_openpgp::Fingerprint::Invalid(_) =>
Err(anyhow!("'{}' is not a valid fingerprint", s)),
sequoia_openpgp::Fingerprint::Invalid(_) => {
Err(anyhow!("'{}' is not a valid fingerprint", s))
}
_ => Err(anyhow!("unknown fingerprint type")),
}
}
@@ -138,15 +174,29 @@ impl FromStr for Fingerprint {
#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)]
pub struct KeyID([u8; 8]);
impl FromSql for KeyID {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
value
.as_str()
.and_then(|s| Self::from_str(s).map_err(|_| FromSqlError::InvalidType))
}
}
impl ToSql for KeyID {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
Ok(rusqlite::types::ToSqlOutput::Owned(
rusqlite::types::Value::Text(self.to_string()),
))
}
}
impl TryFrom<sequoia_openpgp::Fingerprint> for KeyID {
type Error = Error;
fn try_from(fpr: sequoia_openpgp::Fingerprint) -> Result<Self> {
match fpr {
sequoia_openpgp::Fingerprint::V4(a) => Ok(Fingerprint(a).into()),
sequoia_openpgp::Fingerprint::Invalid(_) => {
Err(anyhow!("invalid fingerprint"))
},
sequoia_openpgp::Fingerprint::Invalid(_) => Err(anyhow!("invalid fingerprint")),
_ => Err(anyhow!("unknown fingerprint type")),
}
}
@@ -172,7 +222,6 @@ impl From<Fingerprint> for KeyID {
impl fmt::Display for KeyID {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use ::hex::ToHex;
self.0.write_hex_upper(f)
}
}
@@ -183,8 +232,9 @@ impl FromStr for KeyID {
fn from_str(s: &str) -> Result<KeyID> {
match sequoia_openpgp::KeyID::from_hex(s)? {
sequoia_openpgp::KeyID::V4(a) => Ok(KeyID(a)),
sequoia_openpgp::KeyID::Invalid(_) =>
Err(anyhow!("'{}' is not a valid long key ID", s)),
sequoia_openpgp::KeyID::Invalid(_) => {
Err(anyhow!("'{}' is not a valid long key ID", s))
}
_ => Err(anyhow!("unknown keyid type")),
}
}
@@ -203,10 +253,11 @@ mod tests {
assert_eq!(c("Foo Bar <foo@example.org>").as_str(), "foo@example.org");
// FIXME gotta fix this
// assert_eq!(c("foo@example.org <foo@example.org>").as_str(), "foo@example.org");
assert_eq!(c("\"Foo Bar\" <foo@example.org>").as_str(),
"foo@example.org");
assert_eq!(c("foo@👍.example.org").as_str(),
"foo@xn--yp8h.example.org");
assert_eq!(
c("\"Foo Bar\" <foo@example.org>").as_str(),
"foo@example.org"
);
assert_eq!(c("foo@👍.example.org").as_str(), "foo@xn--yp8h.example.org");
assert_eq!(c("Foo@example.org").as_str(), "foo@example.org");
assert_eq!(c("foo@EXAMPLE.ORG").as_str(), "foo@example.org");
}

View File

@@ -1,11 +1,11 @@
use super::Result;
use crate::openpgp::types::HashAlgorithm;
use zbase32;
use super::Result;
// cannibalized from
// https://gitlab.com/sequoia-pgp/sequoia/blob/master/net/src/wkd.rs
pub fn encode_wkd(address: impl AsRef<str>) -> Result<(String,String)> {
pub fn encode_wkd(address: impl AsRef<str>) -> Result<(String, String)> {
let (local_part, domain) = split_address(address)?;
let local_part_encoded = encode_local_part(local_part);
@@ -13,11 +13,11 @@ pub fn encode_wkd(address: impl AsRef<str>) -> Result<(String,String)> {
Ok((local_part_encoded, domain))
}
fn split_address(email_address: impl AsRef<str>) -> Result<(String,String)> {
fn split_address(email_address: impl AsRef<str>) -> Result<(String, String)> {
let email_address = email_address.as_ref();
let v: Vec<&str> = email_address.split('@').collect();
if v.len() != 2 {
Err(anyhow!("Malformed email address".to_owned()))?;
return Err(anyhow!("Malformed email address".to_owned()));
};
// Convert to lowercase without tailoring, i.e. without taking any

36
default.nix Normal file
View File

@@ -0,0 +1,36 @@
{ lib, rustPlatform, sqlite, openssl, gettext, pkg-config }:
rustPlatform.buildRustPackage rec {
pname = "hagrid";
version = "1.0.0";
src = ./.;
cargoLock = {
lockFile = ./Cargo.lock;
outputHashes = {
"rocket_i18n-0.5.0" = "sha256-EbUE8Z3TQBnDnptl9qWK6JvsACCgP7EXTxcA7pouYbc=";
};
};
postInstall = ''
cp -r dist $out
'';
nativeBuildInputs = [
pkg-config
gettext
];
buildInputs = [
sqlite
openssl
];
meta = with lib; {
description = "A verifying keyserver";
homepage = "https://gitlab.com/keys.openpgp.org/hagrid";
license = with licenses; [ gpl3 ];
maintainers = with maintainers; [ valodim ];
platforms = platforms.all;
};
}

11
dist/assets/site.css vendored
View File

@@ -4,6 +4,7 @@ body {
font-family: 'Roboto', sans-serif;
font-weight: 300;
color: #050505;
word-wrap: break-word;
}
/* roboto-300 - latin-ext */
@@ -184,6 +185,13 @@ abbr {
padding: 0 15%;
}
@media screen and (min-width: 450px) {
.search, .upload, .manage {
display: flex;
padding: 0 15%;
}
}
.manageEmail, .searchTerm, .fileUpload {
flex-grow: 1;
border: 3px solid;
@@ -325,11 +333,10 @@ span.email {
bottom: 0;
right: 0;
left: 0;
width: 100%;
text-align: end;
font-size: 12px;
color: #bbb;
margin: 10px;
margin: 10px 20px;
}
.attribution a {

View File

@@ -1,26 +0,0 @@
<!doctype html>
<html lang="{{lang}}">
<head>
<meta charset=utf-8>
<title>Your key upload on {{domain}}</title>
</head>
<body>
<p>
Hi,
<p>
This is an automated message from <a href="{{base_uri}}" style="text-decoration:none; color: #333">{{domain}}</a>.
If you didn't upload your key there, please ignore this message.
<p>
OpenPGP key: <tt>{{primary_fp}}</tt>
<p>
This key was just uploaded to keys.openpgp.org. If you want to allow others to find this key by e-mail address, please follow this link:
<p>
<a rel="nofollow" href="{{uri}}">{{uri}}</a>
<p>
You can find more info at <a href="{{base_uri}}/about">{{domain}}/about</a>.
<p>
<a href="{{base_uri}}">{{base_uri}}</a><br />
distributing OpenPGP keys since 2019
</body>
</html>

View File

@@ -1,18 +0,0 @@
Hi,
This is an automated message from {{domain}}.
If you didn't upload your key, please ignore this message.
OpenPGP key: {{primary_fp}}
This key was just uploaded to keys.openpgp.org. If you want to allow
others to find this key by e-mail address, please follow this link:
{{uri}}
You can find more info at {{base_uri}}/about
--
{{ base_uri }}
distributing OpenPGP keys since 2019

View File

@@ -64,7 +64,7 @@
<p>
This service is run as a community effort.
You can talk to us in
#hagrid on Freenode IRC,
#hagrid on OFTC IRC,
also reachable as #hagrid:stratum0.org on Matrix.
Of course you can also reach us via email,
at <tt>support at keys dot openpgp dot org</tt>.
@@ -75,7 +75,7 @@
<p>
Technically,
<span class="brand">keys.openpgp.org</span> runs on the <a href="https://gitlab.com/hagrid-keyserver/hagrid" target="_blank">Hagrid</a> keyserver software,
<span class="brand">keys.openpgp.org</span> runs on the <a href="https://gitlab.com/keys.openpgp.org/hagrid" target="_blank">Hagrid</a> keyserver software,
which is based on <a href="https://sequoia-pgp.org">Sequoia-PGP</a>.
We are running on <a href="https://eclips.is" target="_blank">eclips.is</a>,
a hosting platform focused on Internet Freedom projects,

View File

@@ -190,13 +190,15 @@
limited:
</p>
<ul>
<li>Requests by fingerprint or key id are limited to a rate of five requests
per second. Excessive requests are delayed to match this rate. Short
bursts are allowed.
<li>
Requests by fingerprint or key id are limited to five requests per second.
Excessive requests will fail with <tt>error 429</tt>.
There is a burst window of 1000.
</li>
<li>Requests by email address are limited to one request per minute.
Excessive requests will fail with <tt>error 429</tt>. Short bursts are
allowed.
<li>
Requests by email address are limited to one request per minute.
Excessive requests will fail with <tt>error 429</tt>.
There is a burst window of 50.
</li>
</ul>
@@ -271,7 +273,7 @@
<li>No support for <code>op=vindex</code>.</li>
<li>Only exact matches by email address, fingerprint or long key id are returned.</li>
<li>All requests return either one or no keys.</li>
<li>The expiration date field in <code>op=index</code> is left blank (discussion <a target="_blank" href="https://gitlab.com/hagrid-keyserver/hagrid/issues/134">here</a>).</li>
<li>The expiration date field in <code>op=index</code> is left blank (discussion <a target="_blank" href="https://gitlab.com/keys.openpgp.org/hagrid/issues/134">here</a>).</li>
<li>All parameters and options other than <code>op</code> and <code>search</code> are ignored.</li>
<li>Output is always machine readable (i.e. <code>options=mr</code> is always assumed).</li>
<li>Uploads are restricted to 1 MiB.</li>

View File

@@ -2,6 +2,55 @@
<div class="about">
<center><h2><a href="/about">About</a> | News | <a href="/about/usage">Usage</a> | <a href="/about/faq">FAQ</a> | <a href="/about/stats">Stats</a> | <a href="/about/privacy">Privacy</a></h2></center>
<h2 id="2023-04-28-governance">
<div style="float: right; font-size: small; line-height: 2em;">2023-04-28 📅</div>
<a style="color: black;" href="/about/news#2023-04-28-governance">keys.openpgp.org governance 📜</a>
</h2>
<p>
It's been quite a while since the last update.
Not a lot happened around <span class="brand">keys.openpgp.org</span> during this time, operationally. 😴
<p>
But no news is good news in this case:
A few bugs were fixed, some software maintenance was perfomed to keep up with the ecosystem.
There were no significant outages, we've had some steady growth of users, things are generally working as expected.
Hurray!
<p>
There is, however, an important bit of news:
<span class="brand">keys.openpgp.org</span> has a governance process now.
In particular, there is now a written constitution for the service,
which you can find <a href="https://gitlab.com/keys.openpgp.org/governance/-/blob/main/constitution.md">here</a>.
<p>
Most importantly, there is now a board, who were elected by a community of contributors to the OpenPGP ecosystem.
This board currently consists of:
<ul>
<li>Daniel Huigens, from Proton</li>
<li>Lukas Pitschl, from GPGTools</li>
<li>Neal Walfield, from Sequoia-PGP</li>
<li>Ola Bini</li>
<li>Vincent Breitmoser</li>
</ul>
<p>
The primary responsibility of the board is to make decisions on the future of <span class="brand">keys.openpgp.org</span>.
Which features should go in, which not?
We are having regular meetings at the moment, and progress is slow but steady.
We'll be sure to let you know (via this news blog) when anything exciting happens!
<p>
You can find more info about governance in the <a href="https://gitlab.com/keys.openpgp.org/governance/">repository</a>.
You can also reach the board via email at <tt>board</tt> <tt>at</tt> <tt>keys.openpgp.org</tt>.
<p>
That's all for now!
<span style="font-size: x-large;">🙇</span>
<hr style="margin-top: 2em; margin-bottom: 2em;" />
<h2 id="2019-11-12-celebrating-100k">
<div style="float: right; font-size: small; line-height: 2em;">2019-11-12 📅</div>
<a style="color: black;" href="/about/news#2019-11-12-celebrating-100k">Celebrating 100.000 verified addresses! 📈</a>
@@ -30,7 +79,7 @@
</li>
<li>
We have been working on
a <strong><a target="_blank" href="https://gitlab.com/hagrid-keyserver/hagrid/issues/131">new mechanism to refresh keys</a></strong>
a <strong><a target="_blank" href="https://gitlab.com/keys.openpgp.org/hagrid/issues/131">new mechanism to refresh keys</a></strong>
that better protects the user's privacy.
</li>
<li>
@@ -133,7 +182,7 @@
<a href="https://blog.torproject.org/whats-new-tor-0298" target="_blank">single-hop</a>
mode on our Tor Onion Service.
You can find a more complete list
<a href="https://gitlab.com/hagrid-keyserver/hagrid/merge_requests?scope=all&utf8=%E2%9C%93&state=merged" target="_blank">here</a>.
<a href="https://gitlab.com/keys.openpgp.org/hagrid/merge_requests?scope=all&utf8=%E2%9C%93&state=merged" target="_blank">here</a>.
<h4>Secure email delivery with MTA-STS</h4>

View File

@@ -2,67 +2,103 @@
<div class="about">
<center><h2><a href="/about">About</a> | <a href="/about/news">News</a> | <a href="/about/usage">Usage</a> | <a href="/about/faq">FAQ</a> | <a href="/about/stats">Stats</a> | Privacy</h2></center>
<p style="text-align: left;">
The public keyserver running on keys.openpgp.org processes, stores and
distributes OpenPGP key data. The specific way in which data is processed
differs by type as follows:
<ul>
<li><b>Email Addresses</b>
<p>Email addresses contained in <abbr title="Packet Tag 13">User
IDs</abbr> are personally identifiable information (PII).
Special care is taken to make sure they are used only with
consent:
<ul>
<li>
Publishing requires <a target="_blank"
href="https://en.wikipedia.org/wiki/Opt-in_email#Confirmed_opt-in_(COI)_/_Double_opt-in_(DOI)">double
opt-in</a> validation, to prove ownership of the
email address in question.
</li>
<li>Addresses are searchable by exact email address,
but not by associated name.</li>
<li>Enumeration of addresses is not possible.</li>
<li>Deletion of addresses is possible via simple proof
of ownership in an automated fashion, similar to
publication. To unlist an address where this isn't
possible, write to support at keys dot openpgp dot
org.
</li>
</ul>
</p>
<p>This data is never handed collectively ("as a dump") to third
parties.
</p>
</li>
<li><b>Public Key Data</b>
<p>The cryptographic content of OpenPGP keys is not considered personally
identifiable information. This includes specifically
<abbr title="Packet Tags 6 and 14">public key material</abbr>,
<abbr title="Packet Tag 2, Signature types 0x10-0x13, 0x18, 0x19, 0x1F">self-signatures</abbr>, and
<abbr title="Packet Tag 2, Signature types 0x20, 0x28, 0x30">revocation signatures</abbr>.
</p>
<p>This data is not usually collectively available ("as
a dump"), but may be handed upon request to third
parties for purposes of development or research.
</p>
</li>
<li><b>Other User ID data</b>
<p>An OpenPGP key may contain personal data other than email
addresses, such as <abbr title="Packet Tag 13">User IDs</abbr>
that do not contain email addresses, or <abbr
title="Packet Tag 17">image attributes</abbr>. This data is stripped
during upload and never stored, processed, or distributed in
any way.
</p>
<p>OpenPGP packet types that were not specifically mentioned above are
stripped during upload and never stored, processed or
distributed in any way.
</p>
</li>
</ul>
<p style="text-align: left">Data is never relayed to third parties outside of
what is available from the <a href="/about/api">public API interfaces</a>,
and what is described in this policy.
</p>
<h3>Name and contact details</h3>
<p>
<span class="brand">keys.openpgp.org</span> is a community effort.
You can find more information about us, and our contact, details <a href="https://keys.openpgp.org/about">here</a>.
</p>
<h3>How we process data</h3>
<p>
The public keyserver running on <span class="brand">keys.openpgp.org</span> processes, stores, and distributes OpenPGP certificate data.
The specific way in which data is processed differs by type as follows:
</p>
<ul>
<li>
<h4>Email Addresses</h4>
<p>
Email addresses of individuals contained in <abbr title="Packet Tag 13">User IDs</abbr> are personal data.
Special care is taken to make sure they are used only with consent, which you can withdraw at any time:
</p>
<ul>
<li>Publishing requires double opt-in validation, to prove ownership of the email address in question.</li>
<li>Addresses are searchable by exact email address, but not by associated name.</li>
<li>Enumeration of addresses is not possible.</li>
<li>Deletion of addresses is possible via simple proof of ownership in an automated fashion, similar to publication, using the <a href="https://keys.openpgp.org/manage">“manage“ tool</a>. To unlist an address where this isn't possible, write to support at keys dot openpgp dot org.</li>
</ul>
<p>
This data is never handed collectively (“as a dump“) to third parties.
</p>
</li>
<li>
<h4>Public Key Data</h4>
<p>
We process the cryptographic content of OpenPGP certificates - such as public key material, self-signatures, and revocation signatures for the legitimate interest of providing the service.
</p>
<p>
This data is not usually collectively available (“as a dump), but may be handed upon request to third parties for purposes of development or research.
</p>
<p>
If you upload your OpenPGP certificates to the service, you are the source of this data.
It is also possible for anyone who has your public OpenPGP certificate to upload them to this service for example, if you have published them somewhere else, or sent them to someone. This does not include publication of Email Addresses, which are only used with explicit consent as described above.
</p>
</li>
<li>
<h4>Other User ID data</h4>
<p>
An OpenPGP certificate may contain personal data other than email addresses, such as User IDs that do not contain email addresses, or image attributes.
This data is stripped during upload and never stored, processed, or distributed in any way.
</p>
<p>
OpenPGP packet types that were not specifically mentioned above are stripped during upload and never stored, processed or distributed in any way.
</p>
</li>
</ul>
<p>
Data is never relayed to third parties outside of what is available from the public API interfaces, and what is described in this policy and on our <a href="https://keys.openpgp.org/about">about page</a>.
</p>
<p>
This service is available on the Internet, so anyone, anywhere in the world, can access it and retrieve data from it.
</p>
<h3>Retention periods</h3>
<p>
We will retain your email address linked with your OpenPGP certificates until you remove it.
We will remove your Public Key Data if you wish, but note that anyone can re-upload it to the service, in keeping with the “public” nature of this key material.
</p>
<p>
All incoming requests are logged for a period of 30 days, and only used as necessary for operation of the service.
IP addresses are anonymized for storage.
</p>
<h3>Your rights</h3>
<p>
You can withdraw consent to the processing of your email address at any time, or erase your email addresses, using the <a href="https://keys.openpgp.org/manage">“manage“ tool</a>.
</p>
<p>
You can obtain access to the personal data we process about you by viewing your OpenPGP certificates, or searching for your certificates using your email addresses, using this service.
</p>
<p>
You can delete your OpenPGP certificates by emailing support at keys dot openpgp dot org, but note that anyone can upload them again. If you object to having your certificate re-uploaded, email support at keys dot openpgp dot org and we will banlist your keys.
</p>
<p>
To exercise the right of portability, you can download your OpenPGP certificate using this service.
</p>
<p>
If you are in the EEA or UK, you also have the right to lodge a complaint with a supervisory authority, such as your local data protection authority.
</p>
</div>
{{/layout}}

View File

@@ -3,7 +3,13 @@
<title>keys.openpgp.org</title>
<link href="{{ base_uri }}/atom.xml" rel="self"/>
<id>urn:uuid:8e783366-73b1-460e-83d3-42f01046646d</id>
<updated>2019-11-12T12:00:00Z</updated>
<updated>2023-04-28T12:00:00Z</updated>
<entry>
<title>k.o.o governance 📜</title>
<link href="{{ base_uri }}/about/news#2023-04-28-governance" />
<updated>2023-04-28T12:00:00Z</updated>
<id>urn:uuid:75dfcd1e-ac6a-4d1b-9d0f-0e1821322f87</id>
</entry>
<entry>
<title>Celebrating 100.000 verified addresses! 📈</title>
<link href="{{ base_uri }}/about/news#2019-11-12-celebrating-100k" />

View File

@@ -25,7 +25,7 @@
<hr />
<p>
<strong>{{ text "News:" }}</strong> {{ text "<a href=\"/about/news#2019-11-12-celebrating-100k\">Celebrating 100.000 verified addresses! 📈</a> (2019-11-12)" }}
<strong>{{ text "News:" }}</strong> {{ text "<a href=\"/about/news#2023-04-28-governance\">keys.openpgp.org governance 📜</a> (2023-04-28)" }}
</p>
{{/with}}
{{/layout}}

View File

@@ -14,9 +14,9 @@
</div>
<div class="attribution">
<p>
<a href="https://gitlab.com/hagrid-keyserver/hagrid/">Hagrid</a>
<a href="https://gitlab.com/keys.openpgp.org/hagrid/">Hagrid</a>
{{ text "v{{ version }} built from" rerender }}
<a href="https://gitlab.com/hagrid-keyserver/hagrid/commit/{{ commit }}">{{ commit }}</a>
<a href="https://gitlab.com/keys.openpgp.org/hagrid/commit/{{ commit }}">{{ commit }}</a>
</p>
<p>{{ text "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>" }}</p>
<p>{{ text "Background image retrieved from <a href=\"https://www.toptal.com/designers/subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0" }}</p>

10
docker-build/Dockerfile Normal file
View File

@@ -0,0 +1,10 @@
FROM rust:bullseye
RUN apt update -qy
RUN apt install -qy libclang-dev build-essential pkg-config clang libssl-dev libsqlite3-dev gettext zsh
RUN useradd -u 1000 -d /home/user user && mkdir /home/user && chown user:user /home/user
USER user
RUN rustup install 1.82.0
WORKDIR /home/user/src

11
docker-build/README.md Normal file
View File

@@ -0,0 +1,11 @@
# Instructions
This docker image can be used to build hagrid for a Debian environment.
```sh
# in the main source directory
docker build -t hagrid-builder:1.0 docker-build/
# bind in volumes to use cache from hosts
docker run --rm -i -t --user $UID --volume $PWD:/home/user/src --volume $HOME/.cargo/registry:/usr/local/cargo/registry --volume $HOME/.cargo/git:/usr/local/cargo/git hagrid-builder:1.0 cargo build --release --frozen
# release artifact will be in target directory
```

61
flake.lock generated Normal file
View File

@@ -0,0 +1,61 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1739357830,
"narHash": "sha256-9xim3nJJUFbVbJCz48UP4fGRStVW5nv4VdbimbKxJ3I=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "0ff09db9d034a04acd4e8908820ba0b410d7a33a",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"utils": "utils"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

16
flake.nix Normal file
View File

@@ -0,0 +1,16 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, utils }:
utils.lib.eachDefaultSystem (system: let
pkgs = nixpkgs.legacyPackages."${system}";
in rec {
packages.hagrid = pkgs.callPackage ./. { };
packages.default = packages.hagrid;
}) // {
overlays.hagrid = (final: prev: { hagrid = self.packages."${final.system}".hagrid; });
overlays.default = self.overlays.hagrid;
};
}

View File

@@ -33,32 +33,33 @@ location /vks/v1/request-verify {
location /vks {
location ~ ^/vks/v1/by-fingerprint/(?:0x)?([^/][^/])([^/][^/])(..*)$ {
limit_req zone=search_fpr_keyid burst=30;
limit_req zone=search_fpr_keyid burst=1000 nodelay;
error_page 404 /errors-static/404-by-fpr.htm;
default_type application/pgp-keys;
add_header Content-Disposition 'attachment; filename="$1$2$3.asc"';
# default_type application/pgp-keys;
# add_header Content-Disposition 'attachment; filename="$1$2$3.asc"';
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Cache-Control' 'no-cache' always;
etag off;
try_files /keys/links/by-fpr/$1/$2/$3 =404;
proxy_pass http://127.0.0.1:8080;
}
location ~ ^/vks/v1/by-keyid/(?:0x)?([^/][^/])([^/][^/])(.*)$ {
limit_req zone=search_fpr_keyid burst=30;
limit_req zone=search_fpr_keyid burst=1000 nodelay;
error_page 429 /errors-static/429-rate-limit-vks-fpr.htm;
error_page 404 /errors-static/404-by-keyid.htm;
default_type application/pgp-keys;
add_header Content-Disposition 'attachment; filename="$1$2$3.asc"';
# default_type application/pgp-keys;
# add_header Content-Disposition 'attachment; filename="$1$2$3.asc"';
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Cache-Control' 'no-cache' always;
etag off;
try_files /keys/links/by-keyid/$1/$2/$3 =404;
proxy_pass http://127.0.0.1:8080;
}
location /vks/v1/by-email/ {
limit_req zone=search_email burst=50 nodelay;
limit_req zone=search_email_loose burst=200 nodelay;
error_page 429 /errors-static/429-rate-limit-vks-email.htm;
set $args "";
@@ -105,15 +106,16 @@ location /pks/lookup {
location /.well-known/openpgpkey {
location ~ "^/.well-known/openpgpkey/([^/]+)/hu/([^/][^/])([^/][^/])(.*)" {
limit_req zone=search_email burst=50 nodelay;
limit_req zone=search_email_loose burst=200 nodelay;
error_page 429 /errors-static/429-rate-limit-vks-email.htm;
error_page 404 /errors-static/404-wkd.htm;
default_type application/pgp-keys;
add_header Content-Disposition 'attachment; filename="$2$3$4.asc"';
# default_type application/octet-stream;
# add_header Content-Disposition 'attachment; filename="$2$3$4.asc"';
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Cache-Control' 'no-cache' always;
etag off;
try_files /keys/links/wkd/$1/$2/$3/$4 =404;
proxy_pass http://127.0.0.1:8080;
}
location ~ "^/.well-known/openpgpkey/([^/]+)/policy$" {
@@ -158,7 +160,7 @@ location /pks/internal {
# index by fingerprint
# gpg --search-keys <FINGEPRINT>
location ~ "^/pks/internal/index/(?:0x)?([a-fA-F0-9]{40})$" {
limit_req zone=search_fpr_keyid burst=30;
limit_req zone=search_fpr_keyid burst=1000 nodelay;
limit_req_status 429;
error_page 429 /errors-static/429-rate-limit-pks-index.htm;
@@ -172,7 +174,7 @@ location /pks/internal {
# index by keyid
# gpg --search-keys <KEYID>
location ~ "^/pks/internal/index/(?:0x)?([a-fA-F0-9]{16})$" {
limit_req zone=search_fpr_keyid burst=30;
limit_req zone=search_fpr_keyid burst=1000 nodelay;
limit_req_status 429;
error_page 429 /errors-static/429-rate-limit-pks-index.htm;
@@ -187,6 +189,7 @@ location /pks/internal {
# gpg --search-keys <QUERY>
location ~ ^/pks/internal/index/(.+(?:%40|@).+)$ {
limit_req zone=search_email burst=50 nodelay;
limit_req zone=search_email_loose burst=200 nodelay;
limit_req_status 429;
error_page 429 /errors-static/429-rate-limit-pks-index.htm;
@@ -219,6 +222,7 @@ location /errors {
location /search {
limit_req zone=search_email burst=50 nodelay;
limit_req zone=search_email_loose burst=200 nodelay;
error_page 429 /errors/429/rate-limit-web;
proxy_pass http://127.0.0.1:8080;
}

View File

@@ -6,22 +6,22 @@ authors = ["Vincent Breitmoser <look@my.amazin.horse>"]
[dependencies]
hagrid-database = { path = "../database" }
anyhow = "1"
sequoia-openpgp = { version = "1", default-features = false, features = ["crypto-nettle"] }
sequoia-openpgp = { version = "1.17.0", default-features = false, features = ["crypto-openssl"] }
multipart = "0"
log = "0"
rand = "0.6"
serde = { version = "1.0", features = ["derive"] }
serde_derive = "1.0"
serde_json = "1.0"
serde_derive = "1"
serde_json = "1"
time = "0.1"
tempfile = "3.0"
url = "1.6"
tempfile = "3"
url = "1"
hex = "0.3"
base64 = "0.10"
pathdiff = "0.1"
idna = "0.1"
fs2 = "0.4"
walkdir = "2.2"
walkdir = "2"
clap = "2"
toml = "0.5.0"
indicatif = "0.11.0"
toml = "0.5"
indicatif = "0.11"

View File

@@ -1,22 +1,23 @@
use std::path::{Path,PathBuf};
use std::cmp::min;
use std::convert::TryInto;
use std::fs::File;
use std::io::Read;
use std::thread;
use std::cmp::min;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::thread;
use anyhow::Result;
extern crate tempfile;
extern crate sequoia_openpgp as openpgp;
use openpgp::Packet;
use openpgp::parse::{PacketParser, PacketParserResult, Parse};
use openpgp::Packet;
extern crate hagrid_database as database;
use database::{Database, KeyDatabase, ImportResult};
use database::{Database, EmailAddressStatus, ImportResult, KeyDatabase};
use indicatif::{MultiProgress,ProgressBar,ProgressStyle};
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
use HagridConfig;
@@ -25,7 +26,8 @@ use HagridConfig;
// returns after the first few threads.
const NUM_THREADS_MAX: usize = 3;
pub fn do_import(config: &HagridConfig, dry_run: bool, input_files: Vec<PathBuf>) -> Result<()> {
#[allow(clippy::needless_collect)]
pub fn do_import(config: &HagridConfig, input_files: Vec<PathBuf>) -> Result<()> {
let num_threads = min(NUM_THREADS_MAX, input_files.len());
let input_file_chunks = setup_chunks(input_files, num_threads);
@@ -38,8 +40,7 @@ pub fn do_import(config: &HagridConfig, dry_run: bool, input_files: Vec<PathBuf>
let config = config.clone();
let multi_progress = multi_progress.clone();
thread::spawn(move || {
import_from_files(
&config, dry_run, input_file_chunk, multi_progress).unwrap();
import_from_files(&config, input_file_chunk, multi_progress).unwrap();
})
})
.collect();
@@ -53,15 +54,12 @@ pub fn do_import(config: &HagridConfig, dry_run: bool, input_files: Vec<PathBuf>
Ok(())
}
fn setup_chunks(
mut input_files: Vec<PathBuf>,
num_threads: usize,
) -> Vec<Vec<PathBuf>> {
fn setup_chunks(mut input_files: Vec<PathBuf>, num_threads: usize) -> Vec<Vec<PathBuf>> {
let chunk_size = (input_files.len() + (num_threads - 1)) / num_threads;
(0..num_threads)
.map(|_| {
let len = input_files.len();
input_files.drain(0..min(chunk_size,len)).collect()
input_files.drain(0..min(chunk_size, len)).collect()
})
.collect()
}
@@ -76,7 +74,7 @@ struct ImportStats<'a> {
count_unchanged: u64,
}
impl <'a> ImportStats<'a> {
impl<'a> ImportStats<'a> {
fn new(progress: &'a ProgressBar, filename: String) -> Self {
ImportStats {
progress,
@@ -106,24 +104,23 @@ impl <'a> ImportStats<'a> {
return;
}
self.progress.set_message(&format!(
"{}, imported {:5} keys, {:5} New {:5} Updated {:5} Unchanged {:5} Errors",
&self.filename, self.count_total, self.count_new, self.count_updated, self.count_unchanged, self.count_err));
"{}, imported {:5} keys, {:5} New {:5} Updated {:5} Unchanged {:5} Errors",
&self.filename,
self.count_total,
self.count_new,
self.count_updated,
self.count_unchanged,
self.count_err
));
}
}
fn import_from_files(
config: &HagridConfig,
dry_run: bool,
input_files: Vec<PathBuf>,
multi_progress: Arc<MultiProgress>,
) -> Result<()> {
let db = KeyDatabase::new_internal(
config.keys_internal_dir.as_ref().unwrap(),
config.keys_external_dir.as_ref().unwrap(),
config.tmp_dir.as_ref().unwrap(),
dry_run,
)?;
let db = KeyDatabase::new_file(config.keys_internal_dir.as_ref().unwrap())?;
for input_file in input_files {
import_from_file(&db, &input_file, &multi_progress)?;
@@ -137,10 +134,11 @@ fn import_from_file(db: &KeyDatabase, input: &Path, multi_progress: &MultiProgre
let bytes_total = input_file.metadata()?.len();
let progress_bar = multi_progress.add(ProgressBar::new(bytes_total));
progress_bar
.set_style(ProgressStyle::default_bar()
progress_bar.set_style(
ProgressStyle::default_bar()
.template("[{elapsed_precise}] {bar:40.cyan/blue} {msg}")
.progress_chars("##-"));
.progress_chars("##-"),
);
progress_bar.set_message("Starting…");
let input_reader = &mut progress_bar.wrap_read(input_file);
@@ -149,16 +147,33 @@ fn import_from_file(db: &KeyDatabase, input: &Path, multi_progress: &MultiProgre
read_file_to_tpks(input_reader, &mut |acc| {
let primary_key = acc[0].clone();
let result = import_key(&db, acc);
let key_fpr = match primary_key {
Packet::PublicKey(key) => key.fingerprint(),
Packet::SecretKey(key) => key.fingerprint(),
_ => return (),
};
let result = import_key(db, acc);
if let Ok(ref result) = result {
let tpk_status = result.as_tpk_status();
if !tpk_status.is_revoked {
for (email, status) in &tpk_status.email_status {
if status == &EmailAddressStatus::NotPublished {
db.set_email_published(&key_fpr.clone().try_into().unwrap(), &email)
.unwrap();
}
}
}
}
if let Err(ref e) = result {
let key_fpr = match primary_key {
Packet::PublicKey(key) => key.fingerprint().to_hex(),
Packet::SecretKey(key) => key.fingerprint().to_hex(),
_ => "Unknown".to_owned(),
};
let error = format!("{}:{:05}:{}: {}\n{}", filename, stats.count_total,
key_fpr, e.to_string(), e.backtrace());
let error = format!(
"{}:{:05}:{}: {}",
filename,
stats.count_total,
key_fpr.to_hex(),
e
);
progress_bar.println(error);
return ();
}
stats.update(result);
})?;
@@ -169,7 +184,7 @@ fn import_from_file(db: &KeyDatabase, input: &Path, multi_progress: &MultiProgre
fn read_file_to_tpks(
reader: impl Read + Send + Sync,
callback: &mut impl FnMut(Vec<Packet>) -> ()
callback: &mut impl FnMut(Vec<Packet>),
) -> Result<()> {
let mut ppr = PacketParser::from_reader(reader)?;
let mut acc = Vec::new();
@@ -183,7 +198,7 @@ fn read_file_to_tpks(
if !acc.is_empty() {
if let Packet::PublicKey(_) | Packet::SecretKey(_) = packet {
callback(acc);
acc = vec!();
acc = vec![];
}
}
@@ -194,51 +209,5 @@ fn read_file_to_tpks(
}
fn import_key(db: &KeyDatabase, packets: Vec<Packet>) -> Result<ImportResult> {
openpgp::Cert::from_packets(packets.into_iter())
.and_then(|tpk| {
db.merge(tpk)
})
openpgp::Cert::from_packets(packets.into_iter()).and_then(|tpk| db.merge(tpk))
}
/*
#[cfg(test)]
mod import_tests {
use std::fs::File;
use tempfile::tempdir;
use openpgp::serialize::Serialize;
use super::*;
#[test]
fn import() {
let root = tempdir().unwrap();
let db = KeyDatabase::new_from_base(root.path().to_path_buf()).unwrap();
// Generate a key and import it.
let (tpk, _) = openpgp::tpk::TPKBuilder::autocrypt(
None, Some("foo@invalid.example.com".into()))
.generate().unwrap();
let import_me = root.path().join("import-me");
tpk.serialize(&mut File::create(&import_me).unwrap()).unwrap();
do_import(root.path().to_path_buf(), vec![import_me]).unwrap();
let check = |query: &str| {
let tpk_ = db.lookup(&query.parse().unwrap()).unwrap().unwrap();
assert_eq!(tpk.fingerprint(), tpk_.fingerprint());
assert_eq!(tpk.subkeys().map(|skb| skb.subkey().fingerprint())
.collect::<Vec<_>>(),
tpk_.subkeys().map(|skb| skb.subkey().fingerprint())
.collect::<Vec<_>>());
assert_eq!(tpk_.userids().count(), 0);
};
check(&format!("{}", tpk.primary().fingerprint()));
check(&format!("{}", tpk.primary().fingerprint().to_keyid()));
check(&format!("{}", tpk.subkeys().nth(0).unwrap().subkey()
.fingerprint()));
check(&format!("{}", tpk.subkeys().nth(0).unwrap().subkey()
.fingerprint().to_keyid()));
}
}
*/

View File

@@ -1,15 +1,12 @@
#![feature(proc_macro_hygiene, plugin, decl_macro)]
#![recursion_limit = "1024"]
extern crate anyhow;
extern crate clap;
extern crate tempfile;
extern crate sequoia_openpgp as openpgp;
extern crate hagrid_database as database;
extern crate sequoia_openpgp as openpgp;
extern crate tempfile;
#[macro_use]
extern crate serde_derive;
extern crate toml;
extern crate indicatif;
extern crate toml;
extern crate walkdir;
use std::fs;
@@ -18,84 +15,81 @@ use std::str::FromStr;
use anyhow::Result;
use clap::{Arg, App, SubCommand};
use clap::{App, Arg, SubCommand};
mod import;
mod regenerate;
#[derive(Deserialize)]
pub struct HagridConfigs {
development: HagridConfig,
debug: HagridConfig,
staging: HagridConfig,
production: HagridConfig,
release: HagridConfig,
}
// this is not an exact match - Rocket config has more complicated semantics
// than a plain toml file.
// see also https://github.com/SergioBenitez/Rocket/issues/228
#[derive(Deserialize,Clone)]
#[derive(Deserialize, Clone)]
pub struct HagridConfig {
_template_dir: Option<PathBuf>,
keys_internal_dir: Option<PathBuf>,
keys_external_dir: Option<PathBuf>,
_keys_external_dir: Option<PathBuf>,
_assets_dir: Option<PathBuf>,
_token_dir: Option<PathBuf>,
tmp_dir: Option<PathBuf>,
_tmp_dir: Option<PathBuf>,
_maintenance_file: Option<PathBuf>,
}
fn main() -> Result<()> {
let matches = App::new("Hagrid Control")
.version("0.1")
.about("Control hagrid database externally")
.arg(Arg::with_name("config")
.short("c")
.long("config")
.value_name("FILE")
.help("Sets a custom config file")
.takes_value(true))
.arg(Arg::with_name("env")
.short("e")
.long("env")
.value_name("ENVIRONMENT")
.takes_value(true)
.default_value("prod")
.possible_values(&["dev","stage","prod"]))
.subcommand(SubCommand::with_name("regenerate")
.about("Regenerate symlink directory"))
.subcommand(SubCommand::with_name("import")
.about("Import keys into Hagrid")
.arg(Arg::with_name("dry run")
.short("n")
.long("dry-run")
.help("don't actually keep imported keys")
)
.arg(Arg::with_name("keyring files")
.required(true)
.multiple(true)))
.get_matches();
.version("0.1")
.about("Control hagrid database externally")
.arg(
Arg::with_name("config")
.short("c")
.long("config")
.value_name("FILE")
.help("Sets a custom config file")
.takes_value(true),
)
.arg(
Arg::with_name("env")
.short("e")
.long("env")
.value_name("ENVIRONMENT")
.takes_value(true)
.default_value("prod")
.possible_values(&["dev", "stage", "prod"]),
)
.subcommand(
SubCommand::with_name("import")
.about("Import keys into Hagrid")
.arg(
Arg::with_name("keyring files")
.required(true)
.multiple(true),
),
)
.get_matches();
let config_file = matches.value_of("config").unwrap_or("Rocket.toml");
let config_data = fs::read_to_string(config_file).unwrap();
let configs: HagridConfigs = toml::from_str(&config_data).unwrap();
let config = match matches.value_of("env").unwrap() {
"dev" => configs.development,
"dev" => configs.debug,
"stage" => configs.staging,
"prod" => configs.production,
_ => configs.development,
"prod" => configs.release,
_ => configs.debug,
};
if let Some(matches) = matches.subcommand_matches("import") {
let dry_run = matches.occurrences_of("dry run") > 0;
let keyrings: Vec<PathBuf> = matches
.values_of_lossy("keyring files")
.unwrap()
.iter()
.map(|arg| PathBuf::from_str(arg).unwrap())
.collect();
import::do_import(&config, dry_run, keyrings)?;
} else if let Some(_matches) = matches.subcommand_matches("regenerate") {
regenerate::do_regenerate(&config)?;
import::do_import(&config, keyrings)?;
} else {
println!("{}", matches.usage());
}

View File

@@ -1,123 +0,0 @@
use anyhow::Result;
use std::path::Path;
use std::time::Instant;
use walkdir::WalkDir;
use indicatif::{ProgressBar,ProgressStyle};
use HagridConfig;
use database::{Database,KeyDatabase,RegenerateResult};
use database::types::Fingerprint;
struct RegenerateStats<'a> {
progress: &'a ProgressBar,
prefix: String,
count_total: u64,
count_err: u64,
count_updated: u64,
count_unchanged: u64,
count_partial: u64,
start_time_partial: Instant,
kps_partial: u64,
}
impl <'a> RegenerateStats<'a> {
fn new(progress: &'a ProgressBar) -> Self {
Self {
progress,
prefix: "".to_owned(),
count_total: 0,
count_err: 0,
count_updated: 0,
count_unchanged: 0,
count_partial: 0,
start_time_partial: Instant::now(),
kps_partial: 0,
}
}
fn update(&mut self, result: Result<RegenerateResult>, fpr: Fingerprint) {
// If a new TPK starts, parse and import.
self.count_total += 1;
self.count_partial += 1;
if (self.count_total % 10) == 0 {
self.prefix = fpr.to_string()[0..4].to_owned();
}
match result {
Err(e) => {
self.progress.println(format!("{}: {}", fpr, e.to_string()));
self.count_err += 1;
},
Ok(RegenerateResult::Updated) => self.count_updated += 1,
Ok(RegenerateResult::Unchanged) => self.count_unchanged += 1,
}
self.progress_update();
}
fn progress_update(&mut self) {
if (self.count_total % 10) != 0 {
return;
}
if self.count_partial >= 1000 {
let runtime = (self.start_time_partial.elapsed().as_millis() + 1) as u64;
self.kps_partial = (self.count_partial * 1000) / runtime;
self.start_time_partial = Instant::now();
self.count_partial = 0;
}
self.progress.set_message(&format!(
"prefix {} regenerated {:5} keys, {:5} Updated {:5} Unchanged {:5} Errors ({:3} keys/s)",
self.prefix, self.count_total, self.count_updated, self.count_unchanged, self.count_err, self.kps_partial));
}
}
pub fn do_regenerate(config: &HagridConfig) -> Result<()> {
let db = KeyDatabase::new_internal(
config.keys_internal_dir.as_ref().unwrap(),
config.keys_external_dir.as_ref().unwrap(),
config.tmp_dir.as_ref().unwrap(),
false,
)?;
let published_dir = config.keys_external_dir.as_ref().unwrap().join("links").join("by-email");
let dirs: Vec<_> = WalkDir::new(published_dir)
.min_depth(1)
.max_depth(1)
.sort_by(|a,b| a.file_name().cmp(b.file_name()))
.into_iter()
.flatten()
.map(|entry| entry.into_path())
.collect();
let progress_bar = ProgressBar::new(dirs.len() as u64);
progress_bar
.set_style(ProgressStyle::default_bar()
.template("[{elapsed_precise}] {bar:40.cyan/blue} {msg}")
.progress_chars("##-"));
let mut stats = RegenerateStats::new(&progress_bar);
for dir in dirs {
progress_bar.inc(1);
regenerate_dir_recursively(&db, &mut stats, &dir)?;
}
progress_bar.finish();
Ok(())
}
fn regenerate_dir_recursively(db: &KeyDatabase, stats: &mut RegenerateStats, dir: &Path) -> Result<()> {
for path in WalkDir::new(dir)
.follow_links(true)
.into_iter()
.flatten()
.filter(|e| e.file_type().is_file())
.map(|entry| entry.into_path()) {
let fpr = KeyDatabase::path_to_primary(&path).unwrap();
let result = db.regenerate_links(&fpr);
stats.update(result, fpr);
}
Ok(())
}

View File

@@ -1,35 +0,0 @@
# allow 6 requests per min -> one each 10s on avg.
limit_req_zone $binary_remote_addr zone=search_email:10m rate=1r/m;
limit_req_zone $binary_remote_addr zone=search_fpr_keyid:10m rate=5r/s;
proxy_cache_path /tmp/nginx_cache use_temp_path=off keys_zone=static_cache:10m;
proxy_cache_valid 200 5m;
server {
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/edge.keys.openpgp.org/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/edge.keys.openpgp.org/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
rewrite_log on;
error_log /home/hagrid/error.log notice;
root /home/hagrid/run/public;
server_name edge.keys.openpgp.org; # managed by Certbot
include hagrid-routes.conf;
}
server {
if ($host = edge.keys.openpgp.org) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80 ;
listen [::]:80 ;
server_name edge.keys.openpgp.org;
return 404; # managed by Certbot
}

View File

@@ -1,10 +1,28 @@
error_log stderr;
pid nginx/nginx.pid;
pid nginx.pid;
daemon off;
http {
# allow 6 requests per min -> one each 10s on avg.
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=6r/m;
geo $allowlist {
default 0;
# CIDR in the list below are using a more lenient limiter
1.2.3.4/32 1;
}
map $allowlist $limit {
0 $binary_remote_addr;
1 "";
}
map $allowlist $limit_loose {
1 $binary_remote_addr;
0 "";
}
# limit zones are used in hagrid-routes.conf
limit_req_zone $limit zone=search_email:10m rate=1r/s;
limit_req_zone $limit_loose zone=search_email_loose:10m rate=1r/m;
limit_req_zone $binary_remote_addr zone=search_fpr_keyid:10m rate=5r/s;
proxy_cache_path /tmp/nginx_cache use_temp_path=off keys_zone=static_cache:10m;
proxy_cache_valid 200 5m;
@@ -12,13 +30,13 @@ http {
server {
listen 0.0.0.0:8090;
access_log nginx/access_log;
access_log stderr;
# To debug the rewrite rules, enable these directives:
#error_log stderr notice;
#rewrite_log on;
# error_log stderr notice;
# rewrite_log on;
include /etc/nginx/mime.types;
# include /etc/nginx/mime.types;
default_type application/octet-stream;
root dist/public;

View File

@@ -17,37 +17,21 @@ msgstr ""
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
"&& n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
#: src/mail.rs:107
msgctxt "Subject for verification email"
msgid "Verify {userid} for your key on {domain}"
msgstr "‫تحققْ من {userid} لأجل مفتاحك في {domain}"
#: src/mail.rs:140
msgctxt "Subject for manage email"
msgid "Manage your key on {domain}"
msgstr "‫أدرْ مفتاحك في {domain}"
#: src/gettext_strings.rs:4
msgid "Error"
msgstr "هناك خطأ"
#: src/gettext_strings.rs:5
msgid "Looks like something went wrong :("
msgstr "‫يبدو أن مشكلة ما حصلت :("
#: src/gettext_strings.rs:6
msgid "Error message: {{ internal_error }}"
msgstr "‫رسالة الخطأ : {{ internal_error }}"
#: src/gettext_strings.rs:7
msgid "There was an error with your request:"
msgstr "‫هناك خطأ في طلبك :"
#: src/gettext_strings.rs:8
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
msgstr "‫لقد عثرنا على مُدخَلة لـ <span class=\"email\">{{ query }}</span> :"
#: src/gettext_strings.rs:9
msgid ""
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
@@ -57,19 +41,15 @@ msgstr ""
"org</span> انطلاقا من برنامج OpenPGP. <br /> عليك بإلقاء نظرة عن ذلك في <a "
"href=\"/about/usage\">دليل الاستخدام</a> للمزيد من التفاصيل."
#: src/gettext_strings.rs:10
msgid "debug info"
msgstr "معلومات التصحيح"
#: src/gettext_strings.rs:11
msgid "Search by Email Address / Key ID / Fingerprint"
msgstr "ابحث بعنوان البريد الإلكتروني أو بمُعرِّف المفتاح أو بالبصمة"
#: src/gettext_strings.rs:12
msgid "Search"
msgstr "ابحث"
#: src/gettext_strings.rs:13
msgid ""
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
"a> your key."
@@ -77,31 +57,25 @@ msgstr ""
"يمكنك أيضا <a href=\"/upload\">رفع</a> أو <a href=\"/manage\">إدارة</a> "
"مفتاحك."
#: src/gettext_strings.rs:14
msgid "Find out more <a href=\"/about\">about this service</a>."
msgstr "اعرف المزيد <a href=\"/about\">عن هذه الخدمة</a>."
#: src/gettext_strings.rs:15
msgid "News:"
msgstr "اﻷخبار :"
#: src/gettext_strings.rs:16
msgid ""
"<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
"launch ✨</a> (2019-09-12)"
"<a href=\"/about/news#2019-11-12-celebrating-100k\">Celebrating 100.000 "
"verified addresses! 📈</a> (2019-11-12)"
msgstr ""
"<a href=\"/about/news#2019-09-12-three-months-later\">بعد مرور ثلاثة أشهر "
"على انطلاقه ✨</a> (2019-09-12) (الصفحة بالإنجليزية)"
"<a href=\"/about/news#2019-11-12-celebrating-100k\">‫احتفلنا بالتحقق من 100 "
"000 عنوان للبريد الإلكتروني ! 📈</a> (2019-11-12)"
#: src/gettext_strings.rs:17
msgid "v{{ version }} built from"
msgstr "‫الإصدار {{ version }} بُنِي انطلاقا من"
#: src/gettext_strings.rs:18
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
msgstr "‫مُشغَّل بواسطة <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
#: src/gettext_strings.rs:19
msgid ""
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
@@ -110,23 +84,18 @@ msgstr ""
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> بموجب اﻹصدار 3.0 من « رخصة "
"المشاع الإبداعي » (النِّسبَة، الترخيص بالمثل)"
#: src/gettext_strings.rs:20
msgid "Maintenance Mode"
msgstr "وضع الصيانة"
#: src/gettext_strings.rs:21
msgid "Manage your key"
msgstr "أدِر مفتاحك"
#: src/gettext_strings.rs:22
msgid "Enter any verified email address for your key"
msgstr "أدخِل عنوان البريد الإلكتروني المُتحقَّق منه لمفتاحك"
#: src/gettext_strings.rs:23
msgid "Send link"
msgstr "أرسِلْ الوصلة"
#: src/gettext_strings.rs:24
msgid ""
"We will send you an email with a link you can use to remove any of your "
"email addresses from search."
@@ -134,7 +103,6 @@ msgstr ""
"سنرسل لك رسالة إلكترونية تحتوي على وصلة تسمح لك بإزالة أحد عناوين بريدك "
"اﻹلكتروني من البحث."
#: src/gettext_strings.rs:25
msgid ""
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -142,15 +110,12 @@ msgstr ""
"‫إدارة المفتاح <span class=\"fingerprint\"><a href=\"{{ key_link }}\" target="
"\"_blank\">{{ key_fpr }}</a></span>."
#: src/gettext_strings.rs:26
msgid "Your key is published with the following identity information:"
msgstr "مفتاحك منشور بموجب معلومات الهوية أسفله :"
#: src/gettext_strings.rs:27
msgid "Delete"
msgstr "احدف"
#: src/gettext_strings.rs:28
msgid ""
"Clicking \"delete\" on any address will remove it from this key. It will no "
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
@@ -160,7 +125,6 @@ msgstr ""
"يظهر بعد ذلك في البحث. <br /> لإضافة عنوان آخر، عليك ب<a href=\"/upload"
"\">رفع</a> المفتاح مرة أخرى."
#: src/gettext_strings.rs:29
msgid ""
"Your key is published as only non-identity information. (<a href=\"/about\" "
"target=\"_blank\">What does this mean?</a>)"
@@ -168,11 +132,9 @@ msgstr ""
"‫لقد نُشِر مفتاحك فقط كمعلومة لا تمكِّن من التعرف على هويتك. (<a href=\"/about\" "
"target=\"_blank\">ماذا يعني ذلك ؟</a>)"
#: src/gettext_strings.rs:30
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
msgstr "ﻹضافة عنوان، عليك ب<a href=\"/upload\">رفع</a> المفتاح مرة أخرى."
#: src/gettext_strings.rs:31
msgid ""
"We have sent an email with further instructions to <span class=\"email"
"\">{{ address }}</span>."
@@ -180,11 +142,9 @@ msgstr ""
"‫لقد أرسلنا رسالة إليك، فيها تعليمات أخرى إلى العنوان اﻹلكتروني <span class="
"\"email\">{{ address }}</span>."
#: src/gettext_strings.rs:32
msgid "This address has already been verified."
msgstr "لقد سبق التحقق من هذا العنوان."
#: src/gettext_strings.rs:33
msgid ""
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
@@ -194,15 +154,12 @@ msgstr ""
"للهوية <a href=\"{{userid_link}}\" target=\"_blank\"><span class=\"email"
"\">{{ userid }}</span></a>."
#: src/gettext_strings.rs:34
msgid "Upload your key"
msgstr "ارفع مفتاحك"
#: src/gettext_strings.rs:35
msgid "Upload"
msgstr "ارفع"
#: src/gettext_strings.rs:36
msgid ""
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
@@ -210,7 +167,6 @@ msgstr ""
"‫أأنت بحاجة إلى معلومات أخرى ؟ يمكنك تفقد <a target=\"_blank\" href=\"/about"
"\">مقدمتنا</a> و<a target=\"_blank\" href=\"/about/usage\">دليلنا</a>."
#: src/gettext_strings.rs:37
msgid ""
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -218,11 +174,9 @@ msgstr ""
"‫لقد رفعت المفتاح <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
#: src/gettext_strings.rs:38
msgid "This key is revoked."
msgstr "لقد أُبطِل هذا المفتاح."
#: src/gettext_strings.rs:39
msgid ""
"It is published without identity information and can't be made available for "
"search by email address (<a href=\"/about\" target=\"_blank\">what does this "
@@ -232,7 +186,6 @@ msgstr ""
"بواسطة عنوان البريد الإلكتروني (<a href=\"/about\" target=\"_blank\">ماذا "
"يعني ذلك ؟</a>)."
#: src/gettext_strings.rs:40
msgid ""
"This key is now published with the following identity information (<a href="
"\"/about\" target=\"_blank\">what does this mean?</a>):"
@@ -240,11 +193,9 @@ msgstr ""
"مفتاحك منشور بموجب معلومات الهوية أسفله (<a href=\"/about\" target=\"_blank"
"\">ماذا يعني ذلك ؟</a>) :"
#: src/gettext_strings.rs:41
msgid "Published"
msgstr "نُشِر"
#: src/gettext_strings.rs:42
msgid ""
"This key is now published with only non-identity information. (<a href=\"/"
"about\" target=\"_blank\">What does this mean?</a>)"
@@ -252,18 +203,15 @@ msgstr ""
"‫لقد نُشِر مفتاحك الآن فقط كمعلومة لا تمكِّن من التعرف على هويتك. (<a href=\"/"
"about\" target=\"_blank\">ماذا يعني ذلك ؟</a>)"
#: src/gettext_strings.rs:43
msgid ""
"To make the key available for search by email address, you can verify it "
"belongs to you:"
msgstr ""
"لتمكين البحث عن المفتاح عبر عنوان البريد اﻹلكتروني، يمكنك تأكيد ملكيتك له :"
#: src/gettext_strings.rs:44
msgid "Verification Pending"
msgstr "في انتظار التحقُّق"
#: src/gettext_strings.rs:45
msgid ""
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
"prevent spam. Please be patient."
@@ -271,11 +219,9 @@ msgstr ""
"<strong>ملاحظة : </strong>قد يؤخر بعض مزودي خدمة البريد اﻹلكتروني وصول "
"الرسائل إلى ما يقارب 15 دقيقة للوقاية من الرسائل المزعجة. يُرجى الصبر قليلا."
#: src/gettext_strings.rs:46
msgid "Send Verification Email"
msgstr "أرسِل رسالة التحقُّق"
#: src/gettext_strings.rs:47
msgid ""
"This key contains one identity that could not be parsed as an email address."
"<br /> This identity can't be published on <span class=\"brand\">keys."
@@ -286,7 +232,6 @@ msgstr ""
"<br /> لذا، سيتعذر نشر هذه الهوية في <span class=\"brand\">keys.openpgp.org</"
"span>. (<a href=\"/about/faq#non-email-uids\" target=\"_blank\">لماذا ؟</a>)"
#: src/gettext_strings.rs:48
msgid ""
"This key contains {{ count_unparsed }} identities that could not be parsed "
"as an email address.<br /> These identities can't be published on <span "
@@ -298,7 +243,6 @@ msgstr ""
"\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target="
"\"_blank\">لماذا ؟</a>)"
#: src/gettext_strings.rs:49
msgid ""
"This key contains one revoked identity, which is not published. (<a href=\"/"
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -306,7 +250,6 @@ msgstr ""
"‫يحتوي هذا المفتاح على هوية واحدة قد أُبطلَت، ولم يتم نشرها. (<a href=\"/about/"
"faq#non-email-uids\" target=\"_blank\">لماذا ؟</a>)"
#: src/gettext_strings.rs:50
msgid ""
"This key contains {{ count_revoked }} revoked identities, which are not "
"published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -314,11 +257,9 @@ msgstr ""
"‫يحتوي هذا المفتاح على {{ count_revoked }} هويات قد أُبطلَت، ولم يتم نشرها. (<a "
"href=\"/about/faq#non-email-uids\" target=\"_blank\">لماذا ؟</a>)"
#: src/gettext_strings.rs:51
msgid "Your keys have been successfully uploaded:"
msgstr "لقد رُفعَت مفاتيحك بنجاح :"
#: src/gettext_strings.rs:52
msgid ""
"<strong>Note:</strong> To make keys searchable by email address, you must "
"upload them individually."
@@ -326,11 +267,9 @@ msgstr ""
"<strong>ملاحظة :</strong> لتمكين البحث عن مفاتيحك بواسطة عنوان البريد "
"الإلكتروني، عليك برفعها فرديا. "
#: src/gettext_strings.rs:53
msgid "Verifying your email address…"
msgstr "يجري التحقق من عنوان بريدك الإلكتروني…‏"
#: src/gettext_strings.rs:54
msgid ""
"If the process doesn't complete after a few seconds, please <input type="
"\"submit\" class=\"textbutton\" value=\"click here\" />."
@@ -338,15 +277,12 @@ msgstr ""
"إذا لم تكتمل العملية بعد بضع ثوان، يُرجى <input type=\"submit\" class="
"\"textbutton\" value=\"cliquer ici\"/>."
#: src/gettext_strings.rs:56
msgid "Manage your key on {{domain}}"
msgstr "أدرْ مفتاحك في {domain}"
#: src/gettext_strings.rs:58
msgid "Hi,"
msgstr "أهلا بك،‏"
#: src/gettext_strings.rs:59
msgid ""
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
@@ -354,22 +290,18 @@ msgstr ""
"‫هذه رسالة آلية من <a href=\"{{base_uri}}\" style=\"text-decoration:none; "
"color: #333\">{{domain}}</a>."
#: src/gettext_strings.rs:60
msgid "If you didn't request this message, please ignore it."
msgstr "إذا لم تطلب التوصل بهذه الرسالة، يُرجى تجاهلها."
#: src/gettext_strings.rs:61
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
msgstr "‫مفتاح OpenPGP : <tt>{{primary_fp}}</tt>"
#: src/gettext_strings.rs:62
msgid ""
"To manage and delete listed addresses on this key, please follow the link "
"below:"
msgstr ""
"لإدارة أو حذف العناوين المُدرَجة مع هذا المفتاح، يُرجى اتباع الوصلة أسفله :"
#: src/gettext_strings.rs:63
msgid ""
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
"a>."
@@ -377,27 +309,21 @@ msgstr ""
"‫يمكنك الاطلاع على المزيد من المعلومات عبر الصفحة <a href=\"{{base_uri}}/about"
"\">{{domain}}/about</a>."
#: src/gettext_strings.rs:64
msgid "distributing OpenPGP keys since 2019"
msgstr "‫يوزِّع مفاتيح OpenPGP منذ 2019"
#: src/gettext_strings.rs:67
msgid "This is an automated message from {{domain}}."
msgstr "‫هذه رسالة آلية من {{domain}}."
#: src/gettext_strings.rs:69
msgid "OpenPGP key: {{primary_fp}}"
msgstr "‫مفتاح OpenPGP : {{primary_fp}}"
#: src/gettext_strings.rs:71
msgid "You can find more info at {{base_uri}}/about"
msgstr "‫يمكنك الاطلاع على المزيد من المعلومات عبر الصفحة {{domain}}/about."
#: src/gettext_strings.rs:74
msgid "Verify {{userid}} for your key on {{domain}}"
msgstr "‫تحققْ من {userid} لأجل مفتاحك في {domain}"
#: src/gettext_strings.rs:80
msgid ""
"To let others find this key from your email address \"<a rel=\"nofollow\" "
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
@@ -407,7 +333,6 @@ msgstr ""
"<a rel=\"nofollow\" href=\"#\" style=\"text-decoration:none; color : "
"#333\">{userid}}</a> »، يُرجى الضغط على الوصلة أسفله :"
#: src/gettext_strings.rs:88
msgid ""
"To let others find this key from your email address \"{{userid}}\",\n"
"please follow the link below:"
@@ -416,42 +341,57 @@ msgstr ""
"« {userid}} »،‬\n"
"‫يُرجى اتباع الوصلة أسفله :"
#: src/web/manage.rs:103
msgid "No key found for fingerprint {}"
msgstr "‫لم يعثر على المفتاح ذي البصمة {}"
msgid "No key found for key id {}"
msgstr "‫لم يعثر على المفتاح ذي المُعرِّف {}"
msgid "No key found for email address {}"
msgstr "‫لم يعثر على المفتاح ذي العنوان الإلكتروني {}"
msgid "Search by Short Key ID is not supported."
msgstr "‫إن البحث بالمُعرِّف المختصر للمفتاح ليس مدعوما."
msgid "Invalid search query."
msgstr "طلب البحث غير صالح."
msgctxt "Subject for verification email, {0} = userid, {1} = keyserver domain"
msgid "Verify {0} for your key on {1}"
msgstr "‫تحققْ من {0} لأجل مفتاحك في {1}"
msgctxt "Subject for manage email, {} = keyserver domain"
msgid "Manage your key on {}"
msgstr "‫أدرْ مفتاحك في {}"
msgid "This link is invalid or expired"
msgstr "هذه الوصلة باطلة أو انتهت صلاحيتها"
#: src/web/manage.rs:129
msgid "Malformed address: {address}"
msgstr "‫العنوان غير صحيح : {address}"
#, fuzzy
msgid "Malformed address: {}"
msgstr "‫العنوان غير صحيح : {}"
#: src/web/manage.rs:136
msgid "No key for address: {address}"
msgstr "‫لا يوجد مفتاح لهذا العنوان : {address}"
#, fuzzy
msgid "No key for address: {}"
msgstr "‫لا يوجد مفتاح لهذا العنوان : {}"
#: src/web/manage.rs:152
msgid "A request has already been sent for this address recently."
msgstr "لقد سبق إرسال الطلب إلى هذا العنوان حديثا."
#: src/web/vks.rs:111
msgid "Parsing of key data failed."
msgstr "فشِل تحليل بيانات المفتاح."
#: src/web/vks.rs:120
msgid "Whoops, please don't upload secret keys!"
msgstr "‫تحذير : يُرجى عدم رفع مفاتيحك السرية !"
#: src/web/vks.rs:133
msgid "No key uploaded."
msgstr "لم يُرفَع أي مفتاح."
#: src/web/vks.rs:177
msgid "Error processing uploaded key."
msgstr "حدث خطأ أثناء معالجة المفتاح المرفوع."
#: src/web/vks.rs:247
msgid "Upload session expired. Please try again."
msgstr "انتهت مهلة جلسة الرفع. يُرجى المحاولة مرة أخرى."
#: src/web/vks.rs:284
msgid "Invalid verification link."
msgstr "وصلة التحقُّق باطلة."

View File

@@ -1,5 +1,7 @@
#
# Translators:
# Vincent Breitmoser <look@my.amazin.horse>, 2019
# Vincent Breitmoser <look@my.amazin.horse>, 2021
# Kevin Kandlbinder <kevin@kevink.dev>, 2021
#
msgid ""
msgstr ""
@@ -7,7 +9,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2019-09-27 18:05+0000\n"
"Last-Translator: Vincent Breitmoser <look@my.amazin.horse>, 2019\n"
"Last-Translator: Kevin Kandlbinder <kevin@kevink.dev>, 2021\n"
"Language-Team: German (https://www.transifex.com/otf/teams/102430/de/)\n"
"Language: de\n"
"MIME-Version: 1.0\n"
@@ -15,37 +17,21 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: src/mail.rs:107
msgctxt "Subject for verification email"
msgid "Verify {userid} for your key on {domain}"
msgstr "Bestätige {userid} für deinen Schlüssel auf {domain}"
#: src/mail.rs:140
msgctxt "Subject for manage email"
msgid "Manage your key on {domain}"
msgstr "Schlüssel-Verwaltung auf {domain}"
#: src/gettext_strings.rs:4
msgid "Error"
msgstr "Fehler"
#: src/gettext_strings.rs:5
msgid "Looks like something went wrong :("
msgstr "Die Operation hat einen internen Fehler versursacht :("
#: src/gettext_strings.rs:6
msgid "Error message: {{ internal_error }}"
msgstr "<strong>Fehlermeldung:</strong> {{ internal_error }}"
#: src/gettext_strings.rs:7
msgid "There was an error with your request:"
msgstr "Die Anfrage verursachte einen Fehler:"
#: src/gettext_strings.rs:8
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
msgstr "Eintrag gefunden für <span class=\"email\">{{ query }}</span>:"
#: src/gettext_strings.rs:9
msgid ""
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
@@ -55,19 +41,15 @@ msgstr ""
"org</span> aus OpenPGP-Software heraus zu verwenden. <br />\n"
"Mehr dazu findest du in den <a href=\"/about/usage\">Nutzungshinweisen</a>."
#: src/gettext_strings.rs:10
msgid "debug info"
msgstr "debug info"
#: src/gettext_strings.rs:11
msgid "Search by Email Address / Key ID / Fingerprint"
msgstr "Suche nach Email-Adresse / Schlüssel-ID / Fingerprint"
#: src/gettext_strings.rs:12
msgid "Search"
msgstr "Suchen"
#: src/gettext_strings.rs:13
msgid ""
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
"a> your key."
@@ -75,29 +57,25 @@ msgstr ""
"Du kannst deinen Schlüssel <a href=\"/upload\">hochladen</a> oder <a href=\"/"
"manage\">verwalten</a>."
#: src/gettext_strings.rs:14
msgid "Find out more <a href=\"/about\">about this service</a>."
msgstr "Erfahre mehr <a href=\"/about\">über diesen Keyserver</a>."
#: src/gettext_strings.rs:15
msgid "News:"
msgstr "News:"
#: src/gettext_strings.rs:16
msgid ""
"<a href=\"/about/news#2019-11-12-celebrating-100k\">Celebrating 100.000 "
"verified addresses! 📈</a> (2019-11-12)"
msgstr ""
"<a href=\"/about/news#2019-11-12-celebrating-100k\">Wir feiern 100.000 "
"überprüfte Adressen! 📈</a> (2019-11-12)"
#: src/gettext_strings.rs:17
msgid "v{{ version }} built from"
msgstr "v{{ version }}, Revision"
#: src/gettext_strings.rs:18
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
msgstr "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
#: src/gettext_strings.rs:19
msgid ""
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
@@ -105,23 +83,18 @@ msgstr ""
"Hintergrund von <a href=\"https://www.toptal.com/designers/subtlepatterns/"
"subtle-grey/\">Subtle Patterns</a> unter CC BY-SA 3.0"
#: src/gettext_strings.rs:20
msgid "Maintenance Mode"
msgstr "Wartungsarbeiten"
#: src/gettext_strings.rs:21
msgid "Manage your key"
msgstr "Schlüssel verwalten"
#: src/gettext_strings.rs:22
msgid "Enter any verified email address for your key"
msgstr "Email-Adresse des zu verwaltenden Schlüssels"
#: src/gettext_strings.rs:23
msgid "Send link"
msgstr "Sende Link"
#: src/gettext_strings.rs:24
msgid ""
"We will send you an email with a link you can use to remove any of your "
"email addresses from search."
@@ -129,7 +102,6 @@ msgstr ""
"Du wirst eine Email mit einem Link erhalten, der es erlaubt, Adressen aus "
"der Suche zu entfernen."
#: src/gettext_strings.rs:25
msgid ""
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -137,15 +109,12 @@ msgstr ""
"Verwaltung des Schlüssels <span class=\"fingerprint\"><a href="
"\"{{ key_link }}\" target=\"_blank\">{{ key_fpr }}</a></span>."
#: src/gettext_strings.rs:26
msgid "Your key is published with the following identity information:"
msgstr "Dieser Schlüssel ist veröffentlicht mit diesen Identitäten:"
#: src/gettext_strings.rs:27
msgid "Delete"
msgstr "Entfernen"
#: src/gettext_strings.rs:28
msgid ""
"Clicking \"delete\" on any address will remove it from this key. It will no "
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
@@ -155,7 +124,6 @@ msgstr ""
"entfernt werden. <br /> Um eine Adresse hinzuzufügen, muss der Schlüssel <a "
"href=\"/upload\">erneut hochgeladen</a> werden."
#: src/gettext_strings.rs:29
msgid ""
"Your key is published as only non-identity information. (<a href=\"/about\" "
"target=\"_blank\">What does this mean?</a>)"
@@ -163,13 +131,11 @@ msgstr ""
"Dieser Schlüssel ist jetzt ohne Identitäts-Informationen veröffentlicht. (<a "
"href=\"/about\" target=\"_blank\">Was heisst das?</a>)"
#: src/gettext_strings.rs:30
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
msgstr ""
"Um eine Identität hinzuzufügen, lade den Schlüssel <a href=\"/upload"
"\">erneut hoch</a>."
#: src/gettext_strings.rs:31
msgid ""
"We have sent an email with further instructions to <span class=\"email"
"\">{{ address }}</span>."
@@ -177,11 +143,9 @@ msgstr ""
"Eine Email mit den nächsten Schritten wurde an <span class=\"email"
"\">{{ address }}</span> gesendet."
#: src/gettext_strings.rs:32
msgid "This address has already been verified."
msgstr "Diese Adresse war bereits bestätigt."
#: src/gettext_strings.rs:33
msgid ""
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
@@ -191,15 +155,12 @@ msgstr ""
"veröffentlicht mit der Identität <a href=\"{{userid_link}}\" target=\"_blank"
"\"><span class=\"email\">{{ userid }}</span></a>."
#: src/gettext_strings.rs:34
msgid "Upload your key"
msgstr "Schlüssel hochladen"
#: src/gettext_strings.rs:35
msgid "Upload"
msgstr "Upload"
#: src/gettext_strings.rs:36
msgid ""
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
@@ -208,7 +169,6 @@ msgstr ""
"\">Übersicht</a> und <a target=\"_blank\" href=\"/about/usage"
"\">Nutzungshinweise</a>."
#: src/gettext_strings.rs:37
msgid ""
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -216,11 +176,9 @@ msgstr ""
"Schlüssel <span class=\"fingerprint\"><a href=\"{{ key_link }}\" target="
"\"_blank\">{{key_fpr}}</a></span> erfolgreich hochgeladen."
#: src/gettext_strings.rs:38
msgid "This key is revoked."
msgstr "Dieser Schlüssel ist widerrufen."
#: src/gettext_strings.rs:39
msgid ""
"It is published without identity information and can't be made available for "
"search by email address (<a href=\"/about\" target=\"_blank\">what does this "
@@ -230,7 +188,6 @@ msgstr ""
"Email-Adresse verfügbar. (<a href=\"/about\" target=\"_blank\">Was heisst "
"das?</a>)"
#: src/gettext_strings.rs:40
msgid ""
"This key is now published with the following identity information (<a href="
"\"/about\" target=\"_blank\">what does this mean?</a>):"
@@ -238,11 +195,9 @@ msgstr ""
"Dieser Schlüssel ist mit folgenden Identitäten veröffentlicht (<a href=\"/"
"about\" target=\"_blank\">was heisst das?</a>):"
#: src/gettext_strings.rs:41
msgid "Published"
msgstr "Veröffentlicht"
#: src/gettext_strings.rs:42
msgid ""
"This key is now published with only non-identity information. (<a href=\"/"
"about\" target=\"_blank\">What does this mean?</a>)"
@@ -250,7 +205,6 @@ msgstr ""
"Dieser Schlüssel ist jetzt ohne Identitäts-Informationen veröffentlicht. (<a "
"href=\"/about\" target=\"_blank\">Was heisst das?</a>)"
#: src/gettext_strings.rs:43
msgid ""
"To make the key available for search by email address, you can verify it "
"belongs to you:"
@@ -258,11 +212,9 @@ msgstr ""
"Um den Schlüssel für eine Suche nach Email-Adresse verfügbar zu machen, muss "
"die entsprechende Adresse erst verifiziert werden:"
#: src/gettext_strings.rs:44
msgid "Verification Pending"
msgstr "Bestätigung wird erwartet"
#: src/gettext_strings.rs:45
msgid ""
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
"prevent spam. Please be patient."
@@ -270,11 +222,9 @@ msgstr ""
"<strong>Hinweis:</strong> Manche Provider verzögern den Empfang von Emails "
"um bis zu 15 Minuten, um Spam zu verhindern. Bitte einen Moment Geduld."
#: src/gettext_strings.rs:46
msgid "Send Verification Email"
msgstr "Bestätigungs-Email senden"
#: src/gettext_strings.rs:47
msgid ""
"This key contains one identity that could not be parsed as an email address."
"<br /> This identity can't be published on <span class=\"brand\">keys."
@@ -286,7 +236,6 @@ msgstr ""
"class=\"brand\">keys.openpgp.org</span> veröffentlich werden. (<a href=\"/"
"about/faq#non-email-uids\" target=\"_blank\">Warum?</a>)"
#: src/gettext_strings.rs:48
msgid ""
"This key contains {{ count_unparsed }} identities that could not be parsed "
"as an email address.<br /> These identities can't be published on <span "
@@ -298,7 +247,6 @@ msgstr ""
"auf <span class=\"brand\">keys.openpgp.org</span> veröffentlicht werden. (<a "
"href=\"/about/faq#non-email-uids\" target=\"_blank\">Warum?</a>)"
#: src/gettext_strings.rs:49
msgid ""
"This key contains one revoked identity, which is not published. (<a href=\"/"
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -307,7 +255,6 @@ msgstr ""
"veröffentlicht. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Warum?"
"</a>)"
#: src/gettext_strings.rs:50
msgid ""
"This key contains {{ count_revoked }} revoked identities, which are not "
"published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -316,11 +263,9 @@ msgstr ""
"werden nicht veröffentlicht. (<a href=\"/about/faq#revoked-uids\" target="
"\"_blank\">Warum?</a>)"
#: src/gettext_strings.rs:51
msgid "Your keys have been successfully uploaded:"
msgstr "Schlüssel erfolgreich hochgeladen:"
#: src/gettext_strings.rs:52
msgid ""
"<strong>Note:</strong> To make keys searchable by email address, you must "
"upload them individually."
@@ -328,11 +273,9 @@ msgstr ""
"<strong>Hinweis:</strong> Um Schlüssel für die Email-Adresssuche zu "
"bestätigen, müssen sie jeweils einzeln hochgeladen werden."
#: src/gettext_strings.rs:53
msgid "Verifying your email address…"
msgstr "Email-Adresse wird bestätigt..."
#: src/gettext_strings.rs:54
msgid ""
"If the process doesn't complete after a few seconds, please <input type="
"\"submit\" class=\"textbutton\" value=\"click here\" />."
@@ -340,15 +283,12 @@ msgstr ""
"Wenn der Vorgang nicht in einigen Sekunden erfolgreich ist, bitte<input type="
"\"submit\" class=\"textbutton\" value=\"hier klicken\"/>."
#: src/gettext_strings.rs:56
msgid "Manage your key on {{domain}}"
msgstr "Schlüssel-Verwaltung auf {{domain}}"
#: src/gettext_strings.rs:58
msgid "Hi,"
msgstr "Hi,"
#: src/gettext_strings.rs:59
msgid ""
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
@@ -356,22 +296,18 @@ msgstr ""
"Dies ist eine automatisierte Nachricht von <a href=\"{{base_uri}}\" style="
"\"text-decoration:none; color: #333\">{{ domain }}</a>."
#: src/gettext_strings.rs:60
msgid "If you didn't request this message, please ignore it."
msgstr "Falls dies unerwartet ist, bitte die Nachricht ignorieren."
#: src/gettext_strings.rs:61
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
msgstr "OpenPGP Schlüssel: <tt>{{primary_fp}}</tt>"
#: src/gettext_strings.rs:62
msgid ""
"To manage and delete listed addresses on this key, please follow the link "
"below:"
msgstr ""
"Du kannst die Identitäten dieses Schlüssels unter folgendem Link verwalten:"
#: src/gettext_strings.rs:63
msgid ""
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
"a>."
@@ -379,27 +315,21 @@ msgstr ""
"Weiter Informationen findest du unter <a href=\"{{base_uri}}/about"
"\">{{domain}}/about</a>."
#: src/gettext_strings.rs:64
msgid "distributing OpenPGP keys since 2019"
msgstr "Verzeichnis für OpenPGP-Schlüssel seit 2019"
#: src/gettext_strings.rs:67
msgid "This is an automated message from {{domain}}."
msgstr "Dies ist eine automatische Nachricht von {{domain}}."
#: src/gettext_strings.rs:69
msgid "OpenPGP key: {{primary_fp}}"
msgstr "OpenPGP Schlüssel: {{primary_fp}}"
#: src/gettext_strings.rs:71
msgid "You can find more info at {{base_uri}}/about"
msgstr "Weiter Informationen findest du unter {{base_uri}}/about"
#: src/gettext_strings.rs:74
msgid "Verify {{userid}} for your key on {{domain}}"
msgstr "Bestätige {{userid}} für deinen Schlüssel auf {{domain}}"
#: src/gettext_strings.rs:80
msgid ""
"To let others find this key from your email address \"<a rel=\"nofollow\" "
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
@@ -409,7 +339,6 @@ msgstr ""
"style=\"text-decoration:none; color: #333\">{{userid}}</a>\" gefunden werden "
"kann, klicke den folgenden Link:"
#: src/gettext_strings.rs:88
msgid ""
"To let others find this key from your email address \"{{userid}}\",\n"
"please follow the link below:"
@@ -418,42 +347,57 @@ msgstr ""
"kann,\n"
"klicke den folgenden Link:"
#: src/web/manage.rs:103
msgid "No key found for fingerprint {}"
msgstr "Kein Schlüssel gefunden für Fingerprint {}"
msgid "No key found for key id {}"
msgstr "Kein Schlüssel gefunden für Schlüssel-Id {}"
msgid "No key found for email address {}"
msgstr "Kein Schlüssel gefunden für Email-Adresse {}"
msgid "Search by Short Key ID is not supported."
msgstr "Suche nach kurzer Schlüssel-ID wird nicht unterstützt."
msgid "Invalid search query."
msgstr "Ungültige Suchanfrage."
msgctxt "Subject for verification email, {0} = userid, {1} = keyserver domain"
msgid "Verify {0} for your key on {1}"
msgstr "Bestätige {0} für deinen Schlüssel auf {1}"
msgctxt "Subject for manage email, {} = keyserver domain"
msgid "Manage your key on {}"
msgstr "Schlüssel-Verwaltung auf {}"
msgid "This link is invalid or expired"
msgstr "Dieser Link ist ungültig, oder bereits abgelaufen."
#: src/web/manage.rs:129
msgid "Malformed address: {address}"
msgstr "Ungültiges Adress-Format: {address}"
#, fuzzy
msgid "Malformed address: {}"
msgstr "Ungültiges Adress-Format: {}"
#: src/web/manage.rs:136
msgid "No key for address: {address}"
msgstr "Kein Schlüssel gefunden für {address}"
#, fuzzy
msgid "No key for address: {}"
msgstr "Kein Schlüssel gefunden für {}"
#: src/web/manage.rs:152
msgid "A request has already been sent for this address recently."
msgstr "Eine E-Mail für diesen Schlüssel wurde erst kürzlich versandt."
#: src/web/vks.rs:111
msgid "Parsing of key data failed."
msgstr "Fehler bei Verarbeitung des Schlüssel-Materials."
#: src/web/vks.rs:120
msgid "Whoops, please don't upload secret keys!"
msgstr "Ups, bitte keine geheimen Schlüssel hochladen!"
#: src/web/vks.rs:133
msgid "No key uploaded."
msgstr "Es wurde kein Schlüssel hochgeladen."
#: src/web/vks.rs:177
msgid "Error processing uploaded key."
msgstr "Fehler bei Verarbeitung des hochgeladenen Schlüssels."
#: src/web/vks.rs:247
msgid "Upload session expired. Please try again."
msgstr "Zeitlimit beim Hochladen abgelaufen. Bitte versuch es erneut."
#: src/web/vks.rs:284
msgid "Invalid verification link."
msgstr "Ungültiger Bestätigungs-Link."

View File

@@ -12,60 +12,38 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: src/mail.rs:107
#, fuzzy
msgctxt "Subject for verification email"
msgid "Verify {userid} for your key on {domain}"
msgstr "Verify {{userid}} for your key on {{domain}}"
#: src/mail.rs:140
#, fuzzy
msgctxt "Subject for manage email"
msgid "Manage your key on {domain}"
msgstr "Manage your key on {{domain}}"
#: src/gettext_strings.rs:4
msgid "Error"
msgstr "Error"
#: src/gettext_strings.rs:5
msgid "Looks like something went wrong :("
msgstr "Looks like something went wrong :("
#: src/gettext_strings.rs:6
#, fuzzy
msgid "Error message: {{ internal_error }}"
msgstr "<strong>Error:</strong> {{ internal_error }}"
#: src/gettext_strings.rs:7
msgid "There was an error with your request:"
msgstr ""
#: src/gettext_strings.rs:8
#, fuzzy
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
msgstr "We found an entry for <span class=\"email\">{{ query }}</span>:"
#: src/gettext_strings.rs:9
msgid ""
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
"our <a href=\"/about/usage\">usage guide</a> for details."
msgstr ""
#: src/gettext_strings.rs:10
msgid "debug info"
msgstr "debug info"
#: src/gettext_strings.rs:11
msgid "Search by Email Address / Key ID / Fingerprint"
msgstr "Search by Email Address / Key ID / Fingerprint"
#: src/gettext_strings.rs:12
msgid "Search"
msgstr "Search"
#: src/gettext_strings.rs:13
msgid ""
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
"a> your key."
@@ -73,30 +51,24 @@ msgstr ""
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
"a> your key."
#: src/gettext_strings.rs:14
msgid "Find out more <a href=\"/about\">about this service</a>."
msgstr "Find out more <a href=\"/about\">about this service</a>."
#: src/gettext_strings.rs:15
msgid "News:"
msgstr "News:"
#: src/gettext_strings.rs:16
msgid ""
"<a href=\"/about/news#2019-11-12-celebrating-100k\">Celebrating 100.000 "
"verified addresses! 📈</a> (2019-11-12)"
msgstr ""
#: src/gettext_strings.rs:17
#, fuzzy
msgid "v{{ version }} built from"
msgstr "v{{ version }} built from"
#: src/gettext_strings.rs:18
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
msgstr "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
#: src/gettext_strings.rs:19
msgid ""
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
@@ -104,24 +76,19 @@ msgstr ""
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
#: src/gettext_strings.rs:20
msgid "Maintenance Mode"
msgstr "Maintenance Mode"
#: src/gettext_strings.rs:21
msgid "Manage your key"
msgstr "Manage your key"
#: src/gettext_strings.rs:22
#, fuzzy
msgid "Enter any verified email address for your key"
msgstr "Enter any verified e-mail address of your key"
#: src/gettext_strings.rs:23
msgid "Send link"
msgstr "Send link"
#: src/gettext_strings.rs:24
#, fuzzy
msgid ""
"We will send you an email with a link you can use to remove any of your "
@@ -130,7 +97,6 @@ msgstr ""
"We will send you an e-mail with a link you can use to remove any of your e-"
"mail addresses from search."
#: src/gettext_strings.rs:25
#, fuzzy
msgid ""
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
@@ -139,15 +105,12 @@ msgstr ""
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
#: src/gettext_strings.rs:26
msgid "Your key is published with the following identity information:"
msgstr "Your key is published with the following identity information:"
#: src/gettext_strings.rs:27
msgid "Delete"
msgstr ""
#: src/gettext_strings.rs:28
msgid ""
"Clicking \"delete\" on any address will remove it from this key. It will no "
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
@@ -157,7 +120,6 @@ msgstr ""
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
"\">upload</a> the key again."
#: src/gettext_strings.rs:29
#, fuzzy
msgid ""
"Your key is published as only non-identity information. (<a href=\"/about\" "
@@ -166,11 +128,9 @@ msgstr ""
"Your key is published as only non-identity information. (<a href=\"/about\" "
"target=\"_blank\">what does this mean?</a>)"
#: src/gettext_strings.rs:30
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
msgstr "To add an address, <a href=\"/upload\">upload</a> the key again."
#: src/gettext_strings.rs:31
#, fuzzy
msgid ""
"We have sent an email with further instructions to <span class=\"email"
@@ -179,12 +139,10 @@ msgstr ""
"We have sent an email with further instructions to <span class=\"email"
"\">{{ address }}</span>"
#: src/gettext_strings.rs:32
#, fuzzy
msgid "This address has already been verified."
msgstr "This address was already verified."
#: src/gettext_strings.rs:33
#, fuzzy
msgid ""
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
@@ -195,16 +153,13 @@ msgstr ""
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
"\"email\">{{ userid }}</span></a>."
#: src/gettext_strings.rs:34
#, fuzzy
msgid "Upload your key"
msgstr "Manage your key"
#: src/gettext_strings.rs:35
msgid "Upload"
msgstr "Upload"
#: src/gettext_strings.rs:36
#, fuzzy
msgid ""
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
@@ -213,7 +168,6 @@ msgstr ""
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>!"
#: src/gettext_strings.rs:37
#, fuzzy
msgid ""
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
@@ -222,11 +176,9 @@ msgstr ""
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
#: src/gettext_strings.rs:38
msgid "This key is revoked."
msgstr "This key is revoked."
#: src/gettext_strings.rs:39
#, fuzzy
msgid ""
"It is published without identity information and can't be made available for "
@@ -236,7 +188,6 @@ msgstr ""
"It is published without identity information and can't be made available for "
"search by e-mail address"
#: src/gettext_strings.rs:40
msgid ""
"This key is now published with the following identity information (<a href="
"\"/about\" target=\"_blank\">what does this mean?</a>):"
@@ -244,11 +195,9 @@ msgstr ""
"This key is now published with the following identity information (<a href="
"\"/about\" target=\"_blank\">what does this mean?</a>):"
#: src/gettext_strings.rs:41
msgid "Published"
msgstr "Published"
#: src/gettext_strings.rs:42
#, fuzzy
msgid ""
"This key is now published with only non-identity information. (<a href=\"/"
@@ -257,7 +206,6 @@ msgstr ""
"This key is now published with only non-identity information (<a href=\"/"
"about\" target=\"_blank\">what does this mean?</a>)"
#: src/gettext_strings.rs:43
#, fuzzy
msgid ""
"To make the key available for search by email address, you can verify it "
@@ -266,22 +214,18 @@ msgstr ""
"To make the key available for search by e-mail address, you can verify it "
"belongs to you:"
#: src/gettext_strings.rs:44
msgid "Verification Pending"
msgstr "Verification Pending"
#: src/gettext_strings.rs:45
msgid ""
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
"prevent spam. Please be patient."
msgstr ""
#: src/gettext_strings.rs:46
#, fuzzy
msgid "Send Verification Email"
msgstr "Send Verification Mail"
#: src/gettext_strings.rs:47
#, fuzzy
msgid ""
"This key contains one identity that could not be parsed as an email address."
@@ -294,7 +238,6 @@ msgstr ""
"openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target=\"_blank"
"\">why?</a>)"
#: src/gettext_strings.rs:48
#, fuzzy
msgid ""
"This key contains {{ count_unparsed }} identities that could not be parsed "
@@ -307,14 +250,12 @@ msgstr ""
"class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-"
"uids\" target=\"_blank\">why?</a>)"
#: src/gettext_strings.rs:49
#, fuzzy
msgid ""
"This key contains one revoked identity, which is not published. (<a href=\"/"
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
msgstr "This key contains one revoked identity, which is not published."
#: src/gettext_strings.rs:50
#, fuzzy
msgid ""
"This key contains {{ count_revoked }} revoked identities, which are not "
@@ -323,11 +264,9 @@ msgstr ""
"This key contains {{ count_revoked }} revoked identities, which are not "
"published."
#: src/gettext_strings.rs:51
msgid "Your keys have been successfully uploaded:"
msgstr "Your keys have been successfully uploaded:"
#: src/gettext_strings.rs:52
#, fuzzy
msgid ""
"<strong>Note:</strong> To make keys searchable by email address, you must "
@@ -336,11 +275,9 @@ msgstr ""
"<strong>Note:</strong> To make keys searchable by address, you must upload "
"them individually."
#: src/gettext_strings.rs:53
msgid "Verifying your email address…"
msgstr "Verifying your email address…"
#: src/gettext_strings.rs:54
#, fuzzy
msgid ""
"If the process doesn't complete after a few seconds, please <input type="
@@ -349,15 +286,12 @@ msgstr ""
"If the process doesn't complete after a few seconds, <input type=\"submit\" "
"class=\"textbutton\" value=\"click here\" />."
#: src/gettext_strings.rs:56
msgid "Manage your key on {{domain}}"
msgstr "Manage your key on {{domain}}"
#: src/gettext_strings.rs:58
msgid "Hi,"
msgstr "Hi,"
#: src/gettext_strings.rs:59
#, fuzzy
msgid ""
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
@@ -367,15 +301,12 @@ msgstr ""
"\"{{ base_uri }}\" style=\"text-decoration:none; color: "
"#333\"><tt>{{ domain }}</tt></a>."
#: src/gettext_strings.rs:60
msgid "If you didn't request this message, please ignore it."
msgstr "If you didn't request this message, please ignore it."
#: src/gettext_strings.rs:61
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
msgstr "OpenPGP key: <tt>{{primary_fp}}</tt>"
#: src/gettext_strings.rs:62
msgid ""
"To manage and delete listed addresses on this key, please follow the link "
"below:"
@@ -383,7 +314,6 @@ msgstr ""
"To manage and delete listed addresses on this key, please follow the link "
"below:"
#: src/gettext_strings.rs:63
msgid ""
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
"a>."
@@ -391,28 +321,22 @@ msgstr ""
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
"a>."
#: src/gettext_strings.rs:64
msgid "distributing OpenPGP keys since 2019"
msgstr ""
#: src/gettext_strings.rs:67
#, fuzzy
msgid "This is an automated message from {{domain}}."
msgstr "this is an automated message from {{domain}}. If you didn't"
#: src/gettext_strings.rs:69
msgid "OpenPGP key: {{primary_fp}}"
msgstr "OpenPGP key: {{primary_fp}}"
#: src/gettext_strings.rs:71
msgid "You can find more info at {{base_uri}}/about"
msgstr "You can find more info at {{base_uri}}/about"
#: src/gettext_strings.rs:74
msgid "Verify {{userid}} for your key on {{domain}}"
msgstr "Verify {{userid}} for your key on {{domain}}"
#: src/gettext_strings.rs:80
msgid ""
"To let others find this key from your email address \"<a rel=\"nofollow\" "
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
@@ -422,55 +346,78 @@ msgstr ""
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
"please click the link below:"
#: src/gettext_strings.rs:88
#, fuzzy
msgid ""
"To let others find this key from your email address \"{{userid}}\",\n"
"please follow the link below:"
msgstr "To let others find this key from your email address \"{{userid}}\","
#: src/web/manage.rs:103
msgid "No key found for fingerprint {}"
msgstr "No key found for fingerprint {}"
msgid "No key found for key id {}"
msgstr "No key found for key id {}"
msgid "No key found for email address {}"
msgstr "No key found for email address {}"
msgid "Search by Short Key ID is not supported."
msgstr ""
msgid "Invalid search query."
msgstr ""
msgctxt "Subject for verification email, {0} = userid, {1} = keyserver domain"
msgid "Verify {0} for your key on {1}"
msgstr "Verify {0} for your key on {1}"
msgctxt "Subject for manage email, {} = keyserver domain"
msgid "Manage your key on {}"
msgstr "Manage your key on {}"
msgid "This link is invalid or expired"
msgstr ""
#: src/web/manage.rs:129
msgid "Malformed address: {address}"
msgid "Malformed address: {}"
msgstr ""
#: src/web/manage.rs:136
msgid "No key for address: {address}"
msgstr ""
#, fuzzy
msgid "No key for address: {}"
msgstr "Verifying your email address…"
#: src/web/manage.rs:152
msgid "A request has already been sent for this address recently."
msgstr ""
#: src/web/vks.rs:111
msgid "Parsing of key data failed."
msgstr ""
#: src/web/vks.rs:120
msgid "Whoops, please don't upload secret keys!"
msgstr ""
#: src/web/vks.rs:133
#, fuzzy
msgid "No key uploaded."
msgstr "Your key upload on {}"
#: src/web/vks.rs:177
msgid "Error processing uploaded key."
msgstr ""
#: src/web/vks.rs:247
msgid "Upload session expired. Please try again."
msgstr ""
#: src/web/vks.rs:284
#, fuzzy
msgid "Invalid verification link."
msgstr "Send Verification Mail"
#, fuzzy
#~ msgctxt "Subject for verification email"
#~ msgid "Verify {userid} for your key on {domain}"
#~ msgstr "Verify {{userid}} for your key on {{domain}}"
#, fuzzy
#~ msgctxt "Subject for manage email"
#~ msgid "Manage your key on {domain}"
#~ msgstr "Manage your key on {{domain}}"
#, fuzzy
#~ msgctxt "Subject for welcome email"
#~ msgid "Your key upload on {domain}"
@@ -598,12 +545,6 @@ msgstr "Send Verification Mail"
#~ msgid "what does this mean?"
#~ msgstr "what does this mean?"
#~ msgid "Verify {} for your key on {}"
#~ msgstr "Verify {} for your key on {}"
#~ msgid "Manage your key on {}"
#~ msgstr "Manage your key on {}"
#~ msgid "(<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
#~ msgstr "(<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"

402
po/hagrid/es.po Normal file
View File

@@ -0,0 +1,402 @@
#
# Translators:
# Luis Figueroa, 2021
#
msgid ""
msgstr ""
"Project-Id-Version: hagrid\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2019-09-27 18:05+0000\n"
"Last-Translator: Luis Figueroa, 2021\n"
"Language-Team: Spanish (https://www.transifex.com/otf/teams/102430/es/)\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Error"
msgstr "Error"
msgid "Looks like something went wrong :("
msgstr "Parece que algo salió mal :("
msgid "Error message: {{ internal_error }}"
msgstr "Mensaje de error: {{ internal_error }}"
msgid "There was an error with your request:"
msgstr "Hubo un error con tu solicitud:"
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
msgstr "Encontramos un registro para <span class=\"email\">{{ query }}</span>:"
msgid ""
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
"our <a href=\"/about/usage\">usage guide</a> for details."
msgstr ""
"<strong>Consejo:</strong> Es más conveniente usar <span class=\"brand\">keys."
"openpgp.org</span> desde tu software con OpenPGP.<br /> Echa un vistazo a "
"nuestra <a href=\"/about/usage\">guía de uso</a> para más detalles."
msgid "debug info"
msgstr "información de depuración"
msgid "Search by Email Address / Key ID / Fingerprint"
msgstr "Busca por Dirección de Correo / ID de Clave / Huella Digital"
msgid "Search"
msgstr "Buscar"
msgid ""
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
"a> your key."
msgstr ""
"También puedes <a href=\"/upload\">subir</a> o <a href=\"/manage"
"\">administrar</a> tu clave."
msgid "Find out more <a href=\"/about\">about this service</a>."
msgstr "Encuentra más <a href=\"/about\">acerca de este servicio</a>."
msgid "News:"
msgstr "Noticias:"
msgid ""
"<a href=\"/about/news#2019-11-12-celebrating-100k\">Celebrating 100.000 "
"verified addresses! 📈</a> (2019-11-12)"
msgstr ""
"<a href=\"/about/news#2019-11-12-celebrating-100k\">¡Celebrando 100.000 "
"direcciones verificadas! 📈</a> (2019-11-12)"
msgid "v{{ version }} built from"
msgstr "v{{ version }} hecho desde"
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
msgstr "impulsado por <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
msgid ""
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
msgstr ""
"Imagen de fondo tomada de <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> bajo licencia CC BY-SA 3.0"
msgid "Maintenance Mode"
msgstr "Modo Mantenimiento"
msgid "Manage your key"
msgstr "Administra tu clave"
msgid "Enter any verified email address for your key"
msgstr "Ingresa cualquier dirección de correo verificada para tu clave"
msgid "Send link"
msgstr "Enviar enlace"
msgid ""
"We will send you an email with a link you can use to remove any of your "
"email addresses from search."
msgstr ""
"Te enviaremos un correo con un enlace que puedes usar para eliminar "
"cualquiera de tus direcciones de correo de la búsqueda."
msgid ""
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
msgstr ""
"Administrando la clave <span class=\"fingerprint\"><a href="
"\"{{ key_link }}\" target=\"_blank\">{{ key_fpr }}</a></span>."
msgid "Your key is published with the following identity information:"
msgstr "Tu clave está publicada con la siguiente información de identidad:"
msgid "Delete"
msgstr "Eliminar"
msgid ""
"Clicking \"delete\" on any address will remove it from this key. It will no "
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
"\">upload</a> the key again."
msgstr ""
"Haciendo clic en \"Eliminar\" en cualquier dirección la removerá de esta "
"llave. No aparecerá más en una búsqueda.<br />Para añadir otra dirección, <a "
"href=\"/upload\">sube</a> la clave de nuevo."
msgid ""
"Your key is published as only non-identity information. (<a href=\"/about\" "
"target=\"_blank\">What does this mean?</a>)"
msgstr ""
"Tu clave está publicada sin información de identidad. (<a href=\"/about\" "
"target=\"_blank\">¿Qué significa esto?</a>)"
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
msgstr ""
"Para añadir una dirección de correo, <a href=\"/upload\">sube</a> la clave "
"de nuevo."
msgid ""
"We have sent an email with further instructions to <span class=\"email"
"\">{{ address }}</span>."
msgstr ""
"Te hemos enviado un correo con más instrucciones a <span class=\"email"
"\">{{ address }}</span>."
msgid "This address has already been verified."
msgstr "Esta dirección ya ha sido verificada."
msgid ""
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
"\"email\">{{ userid }}</span></a>."
msgstr ""
"Tu clave <span class=\"fingerprint\">{{ key_fpr }}</span> ahora está "
"publicada con la identidad <a href=\"{{userid_link}}\" target=\"_blank"
"\"><span class=\"email\">{{ userid }}</span></a>."
msgid "Upload your key"
msgstr "Sube tu clave"
msgid "Upload"
msgstr "Subir"
msgid ""
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
msgstr ""
"¿Necesitas más información? Mira nuestra <a target=\"_blank\" href=\"/about"
"\">introducción</a> y <a target=\"_blank\" href=\"/about/usage\">guía de "
"uso</a>."
msgid ""
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
msgstr ""
"Subiste la clave <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
msgid "This key is revoked."
msgstr "Esta clave está revocada."
msgid ""
"It is published without identity information and can't be made available for "
"search by email address (<a href=\"/about\" target=\"_blank\">what does this "
"mean?</a>)."
msgstr ""
"Está publicada sin información de identidad y no puede hacerse disponible "
"para buscar por dirección de correo electrónico. (<a href=\"/about\" target="
"\"_blank\">¿Qué significa esto?</a>)."
msgid ""
"This key is now published with the following identity information (<a href="
"\"/about\" target=\"_blank\">what does this mean?</a>):"
msgstr ""
"Esta clave ahora está publicada con la siguiente información de identidad "
"(<a href=\"/about\" target=\"_blank\">¿Qué significa esto?</a>):"
msgid "Published"
msgstr "Publicada"
msgid ""
"This key is now published with only non-identity information. (<a href=\"/"
"about\" target=\"_blank\">What does this mean?</a>)"
msgstr ""
"Esta clave ahora está publicada sin información de identidad. (<a href=\"/"
"about\" target=\"_blank\">¿Qué significa esto?</a>)"
msgid ""
"To make the key available for search by email address, you can verify it "
"belongs to you:"
msgstr ""
"Para hacer la clave disponible en la búsqueda con dirección de correo, "
"puedes verificar que te pertenece:"
msgid "Verification Pending"
msgstr "Verificación Pendiente"
msgid ""
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
"prevent spam. Please be patient."
msgstr ""
"<strong>Nota:</strong> Algunos proveedores retrasan los correos hasta por 15 "
"minutos para prevenir correo no deseado. Por favor sé paciente."
msgid "Send Verification Email"
msgstr "Enviar Correo de Verificación"
msgid ""
"This key contains one identity that could not be parsed as an email address."
"<br /> This identity can't be published on <span class=\"brand\">keys."
"openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target=\"_blank"
"\">Why?</a>)"
msgstr ""
"Esta clave contiene una identidad que no pudo ser interpretada como una "
"dirección de correo.<br />Esta identidad no puede ser publicada en <span "
"class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-uids"
"\" target=\"_blank\">¿Por qué?</a>)"
msgid ""
"This key contains {{ count_unparsed }} identities that could not be parsed "
"as an email address.<br /> These identities can't be published on <span "
"class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-"
"uids\" target=\"_blank\">Why?</a>)"
msgstr ""
"Esta clave contiene {{ count_unparsed }} identidades que no pudieron ser "
"interpretadas como una dirección de correo.<br />Estas identidades no pueden "
"ser publicadas en <span class=\"brand\">keys.openpgp.org</span>. (<a href=\"/"
"about/faq#non-email-uids\" target=\"_blank\">¿Por qué?</a>)"
msgid ""
"This key contains one revoked identity, which is not published. (<a href=\"/"
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
msgstr ""
"Esta clave contiene una identidad revocada, la cual no está publicada. (<a "
"href=\"/about/faq#revoked-uids\" target=\"_blank\">¿Por qué?)"
msgid ""
"This key contains {{ count_revoked }} revoked identities, which are not "
"published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
msgstr ""
"Esta clave contiene {{ count_revoked }} identidades revocadas, las cual no "
"están publicadas. (<a href=\"/about/faq#revoked-uids\" target=\"_blank"
"\">¿Por qué?)"
msgid "Your keys have been successfully uploaded:"
msgstr "Tus claves han sido subidas exitosamente:"
msgid ""
"<strong>Note:</strong> To make keys searchable by email address, you must "
"upload them individually."
msgstr ""
"<strong>Nota:</strong> Para hacer que las claves se puedan buscar por "
"dirección de correo, debes subirlas individualmente."
msgid "Verifying your email address…"
msgstr "Verificando tu dirección de correo..."
msgid ""
"If the process doesn't complete after a few seconds, please <input type="
"\"submit\" class=\"textbutton\" value=\"click here\" />."
msgstr ""
"Si el proceso no se completa luego de unos segundos, por favor <input type="
"\"submit\" class=\"textbutton\" value=\"click here\" />."
msgid "Manage your key on {{domain}}"
msgstr "Administra tu clave en {{domain}}"
msgid "Hi,"
msgstr "Hola,"
msgid ""
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
msgstr ""
"Este es un mensaje automatizado de <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
msgid "If you didn't request this message, please ignore it."
msgstr "Si tú no solicitaste este mensaje, por favor ignóralo."
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
msgstr "Clave OpenPGP: <tt>{{primary_fp}}</tt>"
msgid ""
"To manage and delete listed addresses on this key, please follow the link "
"below:"
msgstr ""
"Para administrar y eliminar las direcciones listadas en esta clave, por "
"favor ve al siguiente enlace:"
msgid ""
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
"a>."
msgstr ""
"Puedes encontrar más información en <a href=\"{{base_uri}}/about"
"\">{{domain}}/about</a>."
msgid "distributing OpenPGP keys since 2019"
msgstr "distribuyendo claves OpenPGP desde 2019"
msgid "This is an automated message from {{domain}}."
msgstr "Este es un mensaje automatizado de {{domain}}."
msgid "OpenPGP key: {{primary_fp}}"
msgstr "Clave OpenPGP: {{primary_fp}}"
msgid "You can find more info at {{base_uri}}/about"
msgstr "Puedes encontrar más información en {{base_uri}}/about"
msgid "Verify {{userid}} for your key on {{domain}}"
msgstr "Verifica {{userid}} para tu clave en {{domain}}"
msgid ""
"To let others find this key from your email address \"<a rel=\"nofollow\" "
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
"please click the link below:"
msgstr ""
"Para permitir que otros encuentren esta clave mediante tu dirección de "
"correo \"<a rel=\"nofollow\" href=\"#\" style=\"text-decoration:none; color: "
"#333\">{{userid}}</a>\", por favor haz clic en el siguiente enlace:"
msgid ""
"To let others find this key from your email address \"{{userid}}\",\n"
"please follow the link below:"
msgstr ""
"Para permitir que otros encuentren esta clave mediante tu dirección de "
"correo \"{{userid}}\", \n"
"por favor haz clic en el siguiente enlace:"
msgid "No key found for fingerprint {}"
msgstr "No se encontraron claves para la huella digital {}"
msgid "No key found for key id {}"
msgstr "No se encontraron claves para el ID de clave {}"
msgid "No key found for email address {}"
msgstr "No se encontraron claves para la dirección de correo electrónico {}"
msgid "Search by Short Key ID is not supported."
msgstr "Buscar por ID de Short Key no está soportado."
msgid "Invalid search query."
msgstr "Búsqueda inválida."
msgctxt "Subject for verification email, {0} = userid, {1} = keyserver domain"
msgid "Verify {0} for your key on {1}"
msgstr "Verifica {0} para tu clave en {1}"
msgctxt "Subject for manage email, {} = keyserver domain"
msgid "Manage your key on {}"
msgstr "Administra tu clave en {}"
msgid "This link is invalid or expired"
msgstr "Este enlace es inválido o caducado"
#, fuzzy
msgid "Malformed address: {}"
msgstr "Dirección malformada: {}"
#, fuzzy
msgid "No key for address: {}"
msgstr "No hay claves para la dirección: {}"
msgid "A request has already been sent for this address recently."
msgstr "Una solicitud para esta dirección ya se ha enviado recientemente."
msgid "Parsing of key data failed."
msgstr "Interpretación de datos de la clave fallida."
msgid "Whoops, please don't upload secret keys!"
msgstr "Ups, ¡por favor no subas claves secretas!"
msgid "No key uploaded."
msgstr "No se subió una clave."
msgid "Error processing uploaded key."
msgstr "Error procesando la clave subida."
msgid "Upload session expired. Please try again."
msgstr "Sesión de subida caducada. Por favor intente de nuevo."
msgid "Invalid verification link."
msgstr "Enlace de verificación inválido."

View File

@@ -1,5 +1,7 @@
#
# Translators:
# AO <ao@localizationlab.org>, 2019
# Vincent Breitmoser <look@my.amazin.horse>, 2020
# AO <ao@localizationlab.org>, 2021
#
msgid ""
msgstr ""
@@ -7,7 +9,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2019-09-27 18:05+0000\n"
"Last-Translator: AO <ao@localizationlab.org>, 2019\n"
"Last-Translator: AO <ao@localizationlab.org>, 2021\n"
"Language-Team: French (https://www.transifex.com/otf/teams/102430/fr/)\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
@@ -15,38 +17,22 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: src/mail.rs:107
msgctxt "Subject for verification email"
msgid "Verify {userid} for your key on {domain}"
msgstr "Confirmer {userid} pour votre clé sur {domain}"
#: src/mail.rs:140
msgctxt "Subject for manage email"
msgid "Manage your key on {domain}"
msgstr "Gérer votre clé sur {domain}"
#: src/gettext_strings.rs:4
msgid "Error"
msgstr "Erreur"
#: src/gettext_strings.rs:5
msgid "Looks like something went wrong :("
msgstr "Il semble quun problème est survenu :("
#: src/gettext_strings.rs:6
msgid "Error message: {{ internal_error }}"
msgstr "Message derreur : {{ internal_error }}"
#: src/gettext_strings.rs:7
msgid "There was an error with your request:"
msgstr "Votre demande a généré une erreur :"
#: src/gettext_strings.rs:8
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
msgstr ""
"Nous avons trouvé une entrée pour <span class=\"email\">{{ query }}</span> :"
#: src/gettext_strings.rs:9
msgid ""
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
@@ -57,19 +43,15 @@ msgstr ""
"Vous trouverez plus de précisions dans notre <a href=\"/about/usage\">guide "
"dutilisation</a>."
#: src/gettext_strings.rs:10
msgid "debug info"
msgstr "renseignements de débogage"
#: src/gettext_strings.rs:11
msgid "Search by Email Address / Key ID / Fingerprint"
msgstr "Cherchez par adresse courriel / ID de clé / empreinte"
#: src/gettext_strings.rs:12
msgid "Search"
msgstr "Chercher"
#: src/gettext_strings.rs:13
msgid ""
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
"a> your key."
@@ -77,33 +59,25 @@ msgstr ""
"Vous pouvez aussi <a href=\"/upload\">téléverser</a> ou <a href=\"/manage"
"\">gérer</a> votre clé."
#: src/gettext_strings.rs:14
msgid "Find out more <a href=\"/about\">about this service</a>."
msgstr "En apprendre davantage <a href=\"/about\">sur ce service</a>."
#: src/gettext_strings.rs:15
msgid "News:"
msgstr "Nouvelles :"
#: src/gettext_strings.rs:16
msgid ""
"<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
"launch ✨</a> (2019-09-12)"
"<a href=\"/about/news#2019-11-12-celebrating-100k\">Celebrating 100.000 "
"verified addresses! 📈</a> (2019-11-12)"
msgstr ""
"<a href=\"/about/news#2019-09-12-three-months-later\">Trois mois après le "
"lancement ✨</a> (12-09-2019) (page en anglais)"
"<a href=\"/about/news#2019-11-12-celebrating-100k\">Nous célébrons 100"
"000 adresses confirmées! 📈</a> (12-11-2019)"
#: src/gettext_strings.rs:17
msgid "v{{ version }} built from"
msgstr "v{{ version }} compilée à partir de"
#: src/gettext_strings.rs:18
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
msgstr ""
"Propulsé par <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a> (site en "
"anglais)"
msgstr "Propulsé par <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
#: src/gettext_strings.rs:19
msgid ""
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
@@ -112,23 +86,18 @@ msgstr ""
"designers/subtlepatterns/subtle-grey/\">Subtle Patterns</a> sous licence CC "
"BY-SA 3.0"
#: src/gettext_strings.rs:20
msgid "Maintenance Mode"
msgstr "Mode de maintenance"
#: src/gettext_strings.rs:21
msgid "Manage your key"
msgstr "Gérer votre clé"
#: src/gettext_strings.rs:22
msgid "Enter any verified email address for your key"
msgstr "Saisissez une adresse courriel confirmée pour votre clé"
#: src/gettext_strings.rs:23
msgid "Send link"
msgstr "Envoyer le lien"
#: src/gettext_strings.rs:24
msgid ""
"We will send you an email with a link you can use to remove any of your "
"email addresses from search."
@@ -136,7 +105,6 @@ msgstr ""
"Nous vous enverrons un courriel avec un lien que vous pourrez utiliser pour "
"supprimer de la recherche lune de vos adresses courriel."
#: src/gettext_strings.rs:25
msgid ""
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -144,15 +112,12 @@ msgstr ""
"Gestion de la clé <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
#: src/gettext_strings.rs:26
msgid "Your key is published with the following identity information:"
msgstr "Votre clé est publiée avec les renseignements didentité suivants :"
#: src/gettext_strings.rs:27
msgid "Delete"
msgstr "Supprimer"
#: src/gettext_strings.rs:28
msgid ""
"Clicking \"delete\" on any address will remove it from this key. It will no "
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
@@ -162,7 +127,6 @@ msgstr ""
"Elle napparaîtra plus lors dune recherche.<br /> Pour ajouter une autre "
"adresse, <a href=\"/upload\">téléversez</a> la clé de nouveau."
#: src/gettext_strings.rs:29
msgid ""
"Your key is published as only non-identity information. (<a href=\"/about\" "
"target=\"_blank\">What does this mean?</a>)"
@@ -171,13 +135,11 @@ msgstr ""
"vous identifier. (<a href=\"/about\" target=\"_blank\">Quest-ce que cela "
"signifie?</a>)"
#: src/gettext_strings.rs:30
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
msgstr ""
"Pour ajouter une adresse, <a href=\"/upload\">téléversez</a> la clé de "
"nouveau."
#: src/gettext_strings.rs:31
msgid ""
"We have sent an email with further instructions to <span class=\"email"
"\">{{ address }}</span>."
@@ -185,11 +147,9 @@ msgstr ""
"Un courriel avec de plus amples instructions a été envoyé à <span class="
"\"email\">{{ address }}</span>."
#: src/gettext_strings.rs:32
msgid "This address has already been verified."
msgstr "Cette adresse a déjà été confirmée."
#: src/gettext_strings.rs:33
msgid ""
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
@@ -199,15 +159,12 @@ msgstr ""
"publiée pour lidentité <a href=\"{{userid_link}}\" target=\"_blank\"><span "
"class=\"email\">{{ userid }}</span></a>. "
#: src/gettext_strings.rs:34
msgid "Upload your key"
msgstr "Téléverser votre clé"
#: src/gettext_strings.rs:35
msgid "Upload"
msgstr "Téléverser"
#: src/gettext_strings.rs:36
msgid ""
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
@@ -216,7 +173,6 @@ msgstr ""
"about\">présentation</a> et notre <a target=\"_blank\" href=\"/about/usage"
"\">guide dutilisation</a>."
#: src/gettext_strings.rs:37
msgid ""
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -224,11 +180,9 @@ msgstr ""
"Vous avez téléversé la clé <span class=\"fingerprint\"><a href="
"\"{{ key_link }}\" target=\"_blank\">{{ key_fpr }}</a></span>."
#: src/gettext_strings.rs:38
msgid "This key is revoked."
msgstr "Cette clé est révoquée."
#: src/gettext_strings.rs:39
msgid ""
"It is published without identity information and can't be made available for "
"search by email address (<a href=\"/about\" target=\"_blank\">what does this "
@@ -238,7 +192,6 @@ msgstr ""
"courriel ne la trouvera pas (<a href=\"/about\" target=\"_blank\">quest-ce "
"que cela signifie?</a>)."
#: src/gettext_strings.rs:40
msgid ""
"This key is now published with the following identity information (<a href="
"\"/about\" target=\"_blank\">what does this mean?</a>):"
@@ -246,11 +199,9 @@ msgstr ""
"Cette clé est maintenant publiée avec les renseignements didentité suivants "
"(<a href=\"/about\" target=\"_blank\">quest-ce que cela signifie?</a>) :"
#: src/gettext_strings.rs:41
msgid "Published"
msgstr "Publiée"
#: src/gettext_strings.rs:42
msgid ""
"This key is now published with only non-identity information. (<a href=\"/"
"about\" target=\"_blank\">What does this mean?</a>)"
@@ -259,7 +210,6 @@ msgstr ""
"permettent pas de vous identifier. (<a href=\"/about\" target=\"_blank"
"\">Quest-ce que cela signifie?</a>)"
#: src/gettext_strings.rs:43
msgid ""
"To make the key available for search by email address, you can verify it "
"belongs to you:"
@@ -267,11 +217,9 @@ msgstr ""
"Afin quune recherche par adresse courriel trouve cette clé, vous pouvez "
"confirmer quelle vous appartient :"
#: src/gettext_strings.rs:44
msgid "Verification Pending"
msgstr "La confirmation est en attente"
#: src/gettext_strings.rs:45
msgid ""
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
"prevent spam. Please be patient."
@@ -280,11 +228,9 @@ msgstr ""
"jusquà 15 minutes afin de prévenir les courriels indésirables (pourriels). "
"Veuillez faire preuve de patience."
#: src/gettext_strings.rs:46
msgid "Send Verification Email"
msgstr "Envoyer un courriel de confirmation"
#: src/gettext_strings.rs:47
msgid ""
"This key contains one identity that could not be parsed as an email address."
"<br /> This identity can't be published on <span class=\"brand\">keys."
@@ -296,7 +242,6 @@ msgstr ""
"class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-uids"
"\" target=\"_blank\">Pourquoi?</a>) "
#: src/gettext_strings.rs:48
msgid ""
"This key contains {{ count_unparsed }} identities that could not be parsed "
"as an email address.<br /> These identities can't be published on <span "
@@ -308,7 +253,6 @@ msgstr ""
"être publiées sur <span class=\"brand\">keys.openpgp.org</span>. (<a href=\"/"
"about/faq#non-email-uids\" target=\"_blank\">Pourquoi?</a>) "
#: src/gettext_strings.rs:49
msgid ""
"This key contains one revoked identity, which is not published. (<a href=\"/"
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -316,7 +260,6 @@ msgstr ""
"Cette clé comprend une identité révoquée qui nest pas publiée. (<a href=\"/"
"about/faq#revoked-uids\" target=\"_blank\">Pourquoi?</a>)"
#: src/gettext_strings.rs:50
msgid ""
"This key contains {{ count_revoked }} revoked identities, which are not "
"published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -325,11 +268,9 @@ msgstr ""
"publiées. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Pourquoi?</"
"a>)"
#: src/gettext_strings.rs:51
msgid "Your keys have been successfully uploaded:"
msgstr "Vos clés ont été téléversées avec succès :"
#: src/gettext_strings.rs:52
msgid ""
"<strong>Note:</strong> To make keys searchable by email address, you must "
"upload them individually."
@@ -337,11 +278,9 @@ msgstr ""
"<strong>Note :</strong> Afin quune recherche par adresse courriel trouve "
"des clés, vous devez les téléverser individuellement."
#: src/gettext_strings.rs:53
msgid "Verifying your email address…"
msgstr "Confirmation de votre adresse courriel…"
#: src/gettext_strings.rs:54
msgid ""
"If the process doesn't complete after a few seconds, please <input type="
"\"submit\" class=\"textbutton\" value=\"click here\" />."
@@ -349,15 +288,12 @@ msgstr ""
"Si le processus nest pas terminé après quelques secondes, veuillez <input "
"type=\"submit\" class=\"textbutton\" value=\"cliquer ici\"/>."
#: src/gettext_strings.rs:56
msgid "Manage your key on {{domain}}"
msgstr "Gérer votre clé sur {{domain}}"
#: src/gettext_strings.rs:58
msgid "Hi,"
msgstr "Bonjour,"
#: src/gettext_strings.rs:59
msgid ""
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
@@ -365,15 +301,12 @@ msgstr ""
"Ceci est un courriel automatisé de <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
#: src/gettext_strings.rs:60
msgid "If you didn't request this message, please ignore it."
msgstr "Si vous navez pas demandé ce courriel, veuillez lignorer."
#: src/gettext_strings.rs:61
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
msgstr "Clé OpenPGP : <tt>{{primary_fp}}</tt>"
#: src/gettext_strings.rs:62
msgid ""
"To manage and delete listed addresses on this key, please follow the link "
"below:"
@@ -381,7 +314,6 @@ msgstr ""
"Pour gérer et supprimer les adresses répertoriées de cette clé, veuillez "
"suivre le lien ci-dessous :"
#: src/gettext_strings.rs:63
msgid ""
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
"a>."
@@ -389,27 +321,21 @@ msgstr ""
"Pour de plus amples renseignements, consultez <a href=\"{{base_uri}}/about"
"\">{{domain}}/about</a>."
#: src/gettext_strings.rs:64
msgid "distributing OpenPGP keys since 2019"
msgstr "distribue des clés OpenPGP depuis 2019"
#: src/gettext_strings.rs:67
msgid "This is an automated message from {{domain}}."
msgstr "Ceci est un courriel automatisé de {{domain}}."
#: src/gettext_strings.rs:69
msgid "OpenPGP key: {{primary_fp}}"
msgstr "Clé OpenPGP : {{primary_fp}}"
#: src/gettext_strings.rs:71
msgid "You can find more info at {{base_uri}}/about"
msgstr "Pour de plus amples renseignements, consultez {{base_uri}}/about"
#: src/gettext_strings.rs:74
msgid "Verify {{userid}} for your key on {{domain}}"
msgstr "Confirmer {{userid}} pour votre clé sur {{domain}}"
#: src/gettext_strings.rs:80
msgid ""
"To let others find this key from your email address \"<a rel=\"nofollow\" "
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
@@ -419,7 +345,6 @@ msgstr ""
"courriel « <a rel=\"nofollow\" href=\"#\" style=\"text-decoration:none; "
"color : #333\">{{userid}}</a> », veuillez cliquer sur le lien ci-dessous :"
#: src/gettext_strings.rs:88
msgid ""
"To let others find this key from your email address \"{{userid}}\",\n"
"please follow the link below:"
@@ -427,42 +352,57 @@ msgstr ""
"Afin de permettre à dautres de trouver cette clé à partir de votre\n"
"adresse courriel « {{userid}} », veuillez suivre le lien ci-dessous :"
#: src/web/manage.rs:103
msgid "No key found for fingerprint {}"
msgstr "Aucune clé na été trouvée pour lempreinte {}"
msgid "No key found for key id {}"
msgstr "Aucune clé na été trouvée pour lID de clé {}"
msgid "No key found for email address {}"
msgstr "Aucune clé na été trouvée pour ladresse courriel {}"
msgid "Search by Short Key ID is not supported."
msgstr "La recherche par ID de clé courte nest pas prise en charge."
msgid "Invalid search query."
msgstr "La requête dinterrogation est invalide."
msgctxt "Subject for verification email, {0} = userid, {1} = keyserver domain"
msgid "Verify {0} for your key on {1}"
msgstr "Confirmer {0} pour votre clé sur {1}"
msgctxt "Subject for manage email, {} = keyserver domain"
msgid "Manage your key on {}"
msgstr "Gérer votre clé sur {}"
msgid "This link is invalid or expired"
msgstr "Ce lien est invalide ou expiré"
#: src/web/manage.rs:129
msgid "Malformed address: {address}"
#, fuzzy
msgid "Malformed address: {}"
msgstr "Cette adresse est malformée :"
#: src/web/manage.rs:136
msgid "No key for address: {address}"
#, fuzzy
msgid "No key for address: {}"
msgstr "Il ny a pas de clé pour cette adresse : {address}"
#: src/web/manage.rs:152
msgid "A request has already been sent for this address recently."
msgstr "Une demande a déjà été envoyée récemment pour cette adresse."
#: src/web/vks.rs:111
msgid "Parsing of key data failed."
msgstr "Échec danalyse des données de la clé."
#: src/web/vks.rs:120
msgid "Whoops, please don't upload secret keys!"
msgstr "Attention : Veuillez ne pas téléverser de clés secrètes!"
#: src/web/vks.rs:133
msgid "No key uploaded."
msgstr "Aucune clé na été téléversée."
#: src/web/vks.rs:177
msgid "Error processing uploaded key."
msgstr "Erreur de traitement de la clé téléversée."
#: src/web/vks.rs:247
msgid "Upload session expired. Please try again."
msgstr "La session de téléversement est expirée. Veuillez ressayer."
#: src/web/vks.rs:284
msgid "Invalid verification link."
msgstr "Le lien de confirmation est invalide."

View File

@@ -12,332 +12,250 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
#: src/mail.rs:107
msgctxt "Subject for verification email"
msgid "Verify {userid} for your key on {domain}"
msgstr ""
#: src/mail.rs:140
msgctxt "Subject for manage email"
msgid "Manage your key on {domain}"
msgstr ""
#: src/i18n_helpers.rs:8
msgid "No key found for fingerprint {fingerprint}"
msgstr ""
#: src/i18n_helpers.rs:10
msgid "No key found for key id {key_id}"
msgstr ""
#: src/i18n_helpers.rs:12
msgid "No key found for email address {email}"
msgstr ""
#: src/i18n_helpers.rs:13
msgid "Search by Short Key ID is not supported."
msgstr ""
#: src/i18n_helpers.rs:14
msgid "Invalid search query."
msgstr ""
#: src/gettext_strings.rs:4
msgid "Error"
msgstr ""
#: src/gettext_strings.rs:5
msgid "Looks like something went wrong :("
msgstr ""
#: src/gettext_strings.rs:6
msgid "Error message: {{ internal_error }}"
msgstr ""
#: src/gettext_strings.rs:7
msgid "There was an error with your request:"
msgstr ""
#: src/gettext_strings.rs:8
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
msgstr ""
#: src/gettext_strings.rs:9
msgid "<strong>Hint:</strong> It's more convenient to use <span class=\"brand\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at our <a href=\"/about/usage\">usage guide</a> for details."
msgstr ""
#: src/gettext_strings.rs:10
msgid "debug info"
msgstr ""
#: src/gettext_strings.rs:11
msgid "Search by Email Address / Key ID / Fingerprint"
msgstr ""
#: src/gettext_strings.rs:12
msgid "Search"
msgstr ""
#: src/gettext_strings.rs:13
msgid "You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</a> your key."
msgstr ""
#: src/gettext_strings.rs:14
msgid "Find out more <a href=\"/about\">about this service</a>."
msgstr ""
#: src/gettext_strings.rs:15
msgid "News:"
msgstr ""
#: src/gettext_strings.rs:16
msgid "<a href=\"/about/news#2019-11-12-celebrating-100k\">Celebrating 100.000 verified addresses! 📈</a> (2019-11-12)"
msgstr ""
#: src/gettext_strings.rs:17
msgid "v{{ version }} built from"
msgstr ""
#: src/gettext_strings.rs:18
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
msgstr ""
#: src/gettext_strings.rs:19
msgid "Background image retrieved from <a href=\"https://www.toptal.com/designers/subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
msgstr ""
#: src/gettext_strings.rs:20
msgid "Maintenance Mode"
msgstr ""
#: src/gettext_strings.rs:21
msgid "Manage your key"
msgstr ""
#: src/gettext_strings.rs:22
msgid "Enter any verified email address for your key"
msgstr ""
#: src/gettext_strings.rs:23
msgid "Send link"
msgstr ""
#: src/gettext_strings.rs:24
msgid "We will send you an email with a link you can use to remove any of your email addresses from search."
msgstr ""
#: src/gettext_strings.rs:25
msgid "Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" target=\"_blank\">{{ key_fpr }}</a></span>."
msgstr ""
#: src/gettext_strings.rs:26
msgid "Your key is published with the following identity information:"
msgstr ""
#: src/gettext_strings.rs:27
msgid "Delete"
msgstr ""
#: src/gettext_strings.rs:28
msgid "Clicking \"delete\" on any address will remove it from this key. It will no longer appear in a search.<br /> To add another address, <a href=\"/upload\">upload</a> the key again."
msgstr ""
#: src/gettext_strings.rs:29
msgid "Your key is published as only non-identity information. (<a href=\"/about\" target=\"_blank\">What does this mean?</a>)"
msgstr ""
#: src/gettext_strings.rs:30
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
msgstr ""
#: src/gettext_strings.rs:31
msgid "We have sent an email with further instructions to <span class=\"email\">{{ address }}</span>."
msgstr ""
#: src/gettext_strings.rs:32
msgid "This address has already been verified."
msgstr ""
#: src/gettext_strings.rs:33
msgid "Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class=\"email\">{{ userid }}</span></a>."
msgstr ""
#: src/gettext_strings.rs:34
msgid "Upload your key"
msgstr ""
#: src/gettext_strings.rs:35
msgid "Upload"
msgstr ""
#: src/gettext_strings.rs:36
msgid "Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and <a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
msgstr ""
#: src/gettext_strings.rs:37
msgid "You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" target=\"_blank\">{{ key_fpr }}</a></span>."
msgstr ""
#: src/gettext_strings.rs:38
msgid "This key is revoked."
msgstr ""
#: src/gettext_strings.rs:39
msgid "It is published without identity information and can't be made available for search by email address (<a href=\"/about\" target=\"_blank\">what does this mean?</a>)."
msgstr ""
#: src/gettext_strings.rs:40
msgid "This key is now published with the following identity information (<a href=\"/about\" target=\"_blank\">what does this mean?</a>):"
msgstr ""
#: src/gettext_strings.rs:41
msgid "Published"
msgstr ""
#: src/gettext_strings.rs:42
msgid "This key is now published with only non-identity information. (<a href=\"/about\" target=\"_blank\">What does this mean?</a>)"
msgstr ""
#: src/gettext_strings.rs:43
msgid "To make the key available for search by email address, you can verify it belongs to you:"
msgstr ""
#: src/gettext_strings.rs:44
msgid "Verification Pending"
msgstr ""
#: src/gettext_strings.rs:45
msgid "<strong>Note:</strong> Some providers delay emails for up to 15 minutes to prevent spam. Please be patient."
msgstr ""
#: src/gettext_strings.rs:46
msgid "Send Verification Email"
msgstr ""
#: src/gettext_strings.rs:47
msgid "This key contains one identity that could not be parsed as an email address.<br /> This identity can't be published on <span class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target=\"_blank\">Why?</a>)"
msgstr ""
#: src/gettext_strings.rs:48
msgid "This key contains {{ count_unparsed }} identities that could not be parsed as an email address.<br /> These identities can't be published on <span class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target=\"_blank\">Why?</a>)"
msgstr ""
#: src/gettext_strings.rs:49
msgid "This key contains one revoked identity, which is not published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
msgstr ""
#: src/gettext_strings.rs:50
msgid "This key contains {{ count_revoked }} revoked identities, which are not published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
msgstr ""
#: src/gettext_strings.rs:51
msgid "Your keys have been successfully uploaded:"
msgstr ""
#: src/gettext_strings.rs:52
msgid "<strong>Note:</strong> To make keys searchable by email address, you must upload them individually."
msgstr ""
#: src/gettext_strings.rs:53
msgid "Verifying your email address…"
msgstr ""
#: src/gettext_strings.rs:54
msgid "If the process doesn't complete after a few seconds, please <input type=\"submit\" class=\"textbutton\" value=\"click here\" />."
msgstr ""
#: src/gettext_strings.rs:56
msgid "Manage your key on {{domain}}"
msgstr ""
#: src/gettext_strings.rs:58
msgid "Hi,"
msgstr ""
#: src/gettext_strings.rs:59
msgid "This is an automated message from <a href=\"{{base_uri}}\" style=\"text-decoration:none; color: #333\">{{domain}}</a>."
msgstr ""
#: src/gettext_strings.rs:60
msgid "If you didn't request this message, please ignore it."
msgstr ""
#: src/gettext_strings.rs:61
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
msgstr ""
#: src/gettext_strings.rs:62
msgid "To manage and delete listed addresses on this key, please follow the link below:"
msgstr ""
#: src/gettext_strings.rs:63
msgid "You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</a>."
msgstr ""
#: src/gettext_strings.rs:64
msgid "distributing OpenPGP keys since 2019"
msgstr ""
#: src/gettext_strings.rs:67
msgid "This is an automated message from {{domain}}."
msgstr ""
#: src/gettext_strings.rs:69
msgid "OpenPGP key: {{primary_fp}}"
msgstr ""
#: src/gettext_strings.rs:71
msgid "You can find more info at {{base_uri}}/about"
msgstr ""
#: src/gettext_strings.rs:74
msgid "Verify {{userid}} for your key on {{domain}}"
msgstr ""
#: src/gettext_strings.rs:80
msgid "To let others find this key from your email address \"<a rel=\"nofollow\" href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", please click the link below:"
msgstr ""
#: src/gettext_strings.rs:88
msgid "To let others find this key from your email address \"{{userid}}\",\nplease follow the link below:"
msgstr ""
#: src/web/manage.rs:103
msgid "No key found for fingerprint {}"
msgstr ""
msgid "No key found for key id {}"
msgstr ""
msgid "No key found for email address {}"
msgstr ""
msgid "Search by Short Key ID is not supported."
msgstr ""
msgid "Invalid search query."
msgstr ""
msgctxt "Subject for verification email, {0} = userid, {1} = keyserver domain"
msgid "Verify {0} for your key on {1}"
msgstr ""
msgctxt "Subject for manage email, {} = keyserver domain"
msgid "Manage your key on {}"
msgstr ""
msgid "This link is invalid or expired"
msgstr ""
#: src/web/manage.rs:129
msgid "Malformed address: {address}"
msgid "Malformed address: {}"
msgstr ""
#: src/web/manage.rs:136
msgid "No key for address: {address}"
msgid "No key for address: {}"
msgstr ""
#: src/web/manage.rs:152
msgid "A request has already been sent for this address recently."
msgstr ""
#: src/web/vks.rs:111
msgid "Parsing of key data failed."
msgstr ""
#: src/web/vks.rs:120
msgid "Whoops, please don't upload secret keys!"
msgstr ""
#: src/web/vks.rs:133
msgid "No key uploaded."
msgstr ""
#: src/web/vks.rs:177
msgid "Error processing uploaded key."
msgstr ""
#: src/web/vks.rs:247
msgid "Upload session expired. Please try again."
msgstr ""
#: src/web/vks.rs:284
msgid "Invalid verification link."
msgstr ""

View File

@@ -15,37 +15,21 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: src/mail.rs:107
msgctxt "Subject for verification email"
msgid "Verify {userid} for your key on {domain}"
msgstr "Verifica {userid} per la tua chiave su {domain}"
#: src/mail.rs:140
msgctxt "Subject for manage email"
msgid "Manage your key on {domain}"
msgstr "Gestisci la tua chiave su {domain}"
#: src/gettext_strings.rs:4
msgid "Error"
msgstr "Errore"
#: src/gettext_strings.rs:5
msgid "Looks like something went wrong :("
msgstr "Sembra che qualcosa sia andato storto :("
#: src/gettext_strings.rs:6
msgid "Error message: {{ internal_error }}"
msgstr "Messaggio di errore: {{ internal_error }}"
#: src/gettext_strings.rs:7
msgid "There was an error with your request:"
msgstr "Si è verificato un errore con la tua richiesta:"
#: src/gettext_strings.rs:8
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
msgstr "Abbiamo trovato una voce per <span class=\"email\">{{ query }}</span>:"
#: src/gettext_strings.rs:9
msgid ""
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
@@ -55,19 +39,15 @@ msgstr ""
"\">keys.opepgp.org</span>dalla tua installazione di OpenPGP. <br />Consulta "
"la nostra <a href=\"/about/usage\">guida all'uso</a> per i dettagli."
#: src/gettext_strings.rs:10
msgid "debug info"
msgstr "informazioni di debug"
#: src/gettext_strings.rs:11
msgid "Search by Email Address / Key ID / Fingerprint"
msgstr "Cerca per indirizzo email / ID Chiave / Fingerprint"
#: src/gettext_strings.rs:12
msgid "Search"
msgstr "Cerca"
#: src/gettext_strings.rs:13
msgid ""
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
"a> your key."
@@ -75,31 +55,23 @@ msgstr ""
"Puoi anche <a href=\"/upload\">caricare</a> o <a href=\"/manage\">gestire</"
"a> una tua chiave."
#: src/gettext_strings.rs:14
msgid "Find out more <a href=\"/about\">about this service</a>."
msgstr "Scopri di più <a href=\"/about\">su questo servizio</a>."
#: src/gettext_strings.rs:15
msgid "News:"
msgstr "Novità:"
#: src/gettext_strings.rs:16
msgid ""
"<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
"launch ✨</a> (2019-09-12)"
"<a href=\"/about/news#2019-11-12-celebrating-100k\">Celebrating 100.000 "
"verified addresses! 📈</a> (2019-11-12)"
msgstr ""
"<a href=\"/about/news#2019-09-12-three-months-later\">Tre mesi dal lancio ✨</"
"a> (12/09/2019)"
#: src/gettext_strings.rs:17
msgid "v{{ version }} built from"
msgstr "v{{ version }} compilata da "
#: src/gettext_strings.rs:18
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
msgstr "Fatto con <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
#: src/gettext_strings.rs:19
msgid ""
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
@@ -107,23 +79,18 @@ msgstr ""
"Immagine di sfondo ottenuta da <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> secondo CC BY-SA 3.0"
#: src/gettext_strings.rs:20
msgid "Maintenance Mode"
msgstr "Modalità Manutenzione"
#: src/gettext_strings.rs:21
msgid "Manage your key"
msgstr "Gestisci la tua chiave"
#: src/gettext_strings.rs:22
msgid "Enter any verified email address for your key"
msgstr "Inserisci un qualsiasi indirizzo email verificato per la tua chiave"
#: src/gettext_strings.rs:23
msgid "Send link"
msgstr "Invia link"
#: src/gettext_strings.rs:24
msgid ""
"We will send you an email with a link you can use to remove any of your "
"email addresses from search."
@@ -131,7 +98,6 @@ msgstr ""
"Ti manderemo una email con un link, che potrai usare per rimuovere uno dei "
"tuoi indirizzi email dai risultati della ricerca"
#: src/gettext_strings.rs:25
msgid ""
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -139,16 +105,13 @@ msgstr ""
"Stai gestendo la chiave <span class=\"fingerprint\"><a href="
"\"{{ key_link }}\" target=\"_blank\">{{ key_fpr }}</a></span>."
#: src/gettext_strings.rs:26
msgid "Your key is published with the following identity information:"
msgstr ""
"La tua chiave è stata pubblicata con le seguenti informazioni identificative:"
#: src/gettext_strings.rs:27
msgid "Delete"
msgstr "Rimuovi"
#: src/gettext_strings.rs:28
msgid ""
"Clicking \"delete\" on any address will remove it from this key. It will no "
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
@@ -158,7 +121,6 @@ msgstr ""
"chiave. Non apparirà più nelle ricerche.<br />Per aggiungere un altro "
"indirizzo, <a href=\"/upload\">carica</a> la chiave nuovamente."
#: src/gettext_strings.rs:29
msgid ""
"Your key is published as only non-identity information. (<a href=\"/about\" "
"target=\"_blank\">What does this mean?</a>)"
@@ -166,13 +128,11 @@ msgstr ""
"La tua chiave è pubblicata solo con informazioni non identificative. (<a "
"href=\"/about\" target=\"_blank\">Cosa vuol dire?</a>)"
#: src/gettext_strings.rs:30
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
msgstr ""
"Per aggiungere un indirizzo, <a href=\"/upload\">carica</a> la chiave "
"nuovamente."
#: src/gettext_strings.rs:31
msgid ""
"We have sent an email with further instructions to <span class=\"email"
"\">{{ address }}</span>."
@@ -180,11 +140,9 @@ msgstr ""
"Abbiamo inviato una email con ulteriori istruzioni a <span class=\"email"
"\">{{ address }}</span>."
#: src/gettext_strings.rs:32
msgid "This address has already been verified."
msgstr "L'indirizzo è già stato verificato."
#: src/gettext_strings.rs:33
msgid ""
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
@@ -194,15 +152,12 @@ msgstr ""
"pubblicata per l'indirizzo <a href=\"{{userid_link}}\" target=\"_blank"
"\"><span class=\"email\">{{ userid }}</span></a>."
#: src/gettext_strings.rs:34
msgid "Upload your key"
msgstr "Carica la tua chiave"
#: src/gettext_strings.rs:35
msgid "Upload"
msgstr "Carica"
#: src/gettext_strings.rs:36
msgid ""
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
@@ -211,7 +166,6 @@ msgstr ""
"about\">introduzione</a> e <a target=\"_blank\" href=\"/about/usage\">guida "
"all'uso</a>."
#: src/gettext_strings.rs:37
msgid ""
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -219,11 +173,9 @@ msgstr ""
"Hai caricato la chiave <span class=\"fingerprint\"><a href="
"\"{{ key_link }}\" target=\"_blank\">{{ key_fpr }}</a></span>."
#: src/gettext_strings.rs:38
msgid "This key is revoked."
msgstr "La chiave è stata revocata."
#: src/gettext_strings.rs:39
msgid ""
"It is published without identity information and can't be made available for "
"search by email address (<a href=\"/about\" target=\"_blank\">what does this "
@@ -233,7 +185,6 @@ msgstr ""
"disponibile per la ricerca per indirizzo email (<a href=\"/about\" target="
"\"_blank\">Che cosa vuol dire?</a>)."
#: src/gettext_strings.rs:40
msgid ""
"This key is now published with the following identity information (<a href="
"\"/about\" target=\"_blank\">what does this mean?</a>):"
@@ -241,11 +192,9 @@ msgstr ""
"La chiave è ora pubblicata con le seguenti informazioni identificative (<a "
"href=\"/about\" target=\"_blank\">Cosa vuol dire?</a>):"
#: src/gettext_strings.rs:41
msgid "Published"
msgstr "Pubblicato"
#: src/gettext_strings.rs:42
msgid ""
"This key is now published with only non-identity information. (<a href=\"/"
"about\" target=\"_blank\">What does this mean?</a>)"
@@ -253,7 +202,6 @@ msgstr ""
"La tua chiave è pubblicata solo con informazioni non identificative. (<a "
"href=\"/about\" target=\"_blank\">Cosa vuol dire?</a>)"
#: src/gettext_strings.rs:43
msgid ""
"To make the key available for search by email address, you can verify it "
"belongs to you:"
@@ -261,11 +209,9 @@ msgstr ""
"Per rendere la chiave disponibile nelle richerche per indirizzo email, puoi "
"verificarne il possesso:"
#: src/gettext_strings.rs:44
msgid "Verification Pending"
msgstr "In attesa della verifica"
#: src/gettext_strings.rs:45
msgid ""
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
"prevent spam. Please be patient."
@@ -273,11 +219,9 @@ msgstr ""
"<strong>Nota:</strong> Alcuni provider ritardano le email fino a 15 minuti "
"per prevenire lo spam. Si prega di attendere."
#: src/gettext_strings.rs:46
msgid "Send Verification Email"
msgstr "Invia email di verifica"
#: src/gettext_strings.rs:47
msgid ""
"This key contains one identity that could not be parsed as an email address."
"<br /> This identity can't be published on <span class=\"brand\">keys."
@@ -289,7 +233,6 @@ msgstr ""
"class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-uids"
"\" target=\"_blank\">Perché?</a>)"
#: src/gettext_strings.rs:48
msgid ""
"This key contains {{ count_unparsed }} identities that could not be parsed "
"as an email address.<br /> These identities can't be published on <span "
@@ -301,7 +244,6 @@ msgstr ""
"pubblicate su <span class=\"brand\">keys.openpgp.org</span>. (<a href=\"/"
"about/faq#non-email-uids\" target=\"_blank\">Perché?</a>)"
#: src/gettext_strings.rs:49
msgid ""
"This key contains one revoked identity, which is not published. (<a href=\"/"
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -309,7 +251,6 @@ msgstr ""
"Questa chiave contiene una identità revocata, che non è pubblicata. (<a href="
"\"/about/faq#revoked-uids\" target=\"_blank\">Perché?</a>)"
#: src/gettext_strings.rs:50
msgid ""
"This key contains {{ count_revoked }} revoked identities, which are not "
"published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -318,11 +259,9 @@ msgstr ""
"pubblicate. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Perché?</"
"a>)"
#: src/gettext_strings.rs:51
msgid "Your keys have been successfully uploaded:"
msgstr "Le tue chiavi sono state caricate con successo:"
#: src/gettext_strings.rs:52
msgid ""
"<strong>Note:</strong> To make keys searchable by email address, you must "
"upload them individually."
@@ -330,11 +269,9 @@ msgstr ""
"<strong>Nota:</strong> Per rendere le chiavi disponibili alla ricerca per "
"indirizzo email, devi caricarle singolarmente."
#: src/gettext_strings.rs:53
msgid "Verifying your email address…"
msgstr "Stiamo verificando il tuo indirizzo email..."
#: src/gettext_strings.rs:54
msgid ""
"If the process doesn't complete after a few seconds, please <input type="
"\"submit\" class=\"textbutton\" value=\"click here\" />."
@@ -342,15 +279,12 @@ msgstr ""
"Se il processo non termina dopo qualche secondo, siete pregati di <input "
"type=\"submit\" class=\"textbutton\" value=\"cliccare qui\"/>"
#: src/gettext_strings.rs:56
msgid "Manage your key on {{domain}}"
msgstr "Gestisci la tua chiave su {{domain}}"
#: src/gettext_strings.rs:58
msgid "Hi,"
msgstr "Ciao,"
#: src/gettext_strings.rs:59
msgid ""
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
@@ -358,15 +292,12 @@ msgstr ""
"Questo è un messaggio automatico da <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
#: src/gettext_strings.rs:60
msgid "If you didn't request this message, please ignore it."
msgstr "Se non hai richiesto questo messaggio, sei pregato di ignorarlo."
#: src/gettext_strings.rs:61
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
msgstr "Chiave OpenPGP: {{primary_fp}}"
#: src/gettext_strings.rs:62
msgid ""
"To manage and delete listed addresses on this key, please follow the link "
"below:"
@@ -374,7 +305,6 @@ msgstr ""
"Per gestire e cancellare gli indirizzi associati a questa chiave, usa il "
"link sottostante:"
#: src/gettext_strings.rs:63
msgid ""
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
"a>."
@@ -382,27 +312,21 @@ msgstr ""
"Puoi trovare più informazioni all'indirizzo <a href=\"{{base_uri}}/about"
"\">{{domain}}/about</a>."
#: src/gettext_strings.rs:64
msgid "distributing OpenPGP keys since 2019"
msgstr "Distribuiamo chiavi OpenPGP dal 2019"
#: src/gettext_strings.rs:67
msgid "This is an automated message from {{domain}}."
msgstr "Questo è un messaggio inviato automaticamente da {{domain}}."
#: src/gettext_strings.rs:69
msgid "OpenPGP key: {{primary_fp}}"
msgstr "Chiave OpenPGP: {{primary_fp}}"
#: src/gettext_strings.rs:71
msgid "You can find more info at {{base_uri}}/about"
msgstr "Puoi trovare più informazioni all'indirizzo {{base_uri}}/about"
#: src/gettext_strings.rs:74
msgid "Verify {{userid}} for your key on {{domain}}"
msgstr "Verifica {{userid}} per la tua chiave su {{domain}}"
#: src/gettext_strings.rs:80
msgid ""
"To let others find this key from your email address \"<a rel=\"nofollow\" "
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
@@ -412,7 +336,6 @@ msgstr ""
"\"<a rel=\"nofollow\" href=\"#\" style=\"text-decoration:none; color: "
"#333\">{{userid}}</a>\", vai al link sottostante:"
#: src/gettext_strings.rs:88
msgid ""
"To let others find this key from your email address \"{{userid}}\",\n"
"please follow the link below:"
@@ -420,42 +343,64 @@ msgstr ""
"Per consentire agli altri di trovare questa chiave dal tuo indirizzo email "
"\"{{userid}}\", vai al link sottostante:"
#: src/web/manage.rs:103
msgid "No key found for fingerprint {}"
msgstr "Nessuna chiave per l'indirizzo: {}"
msgid "No key found for key id {}"
msgstr "Nessuna chiave per l'indirizzo: {}"
msgid "No key found for email address {}"
msgstr "Nessuna chiave per l'indirizzo: {}"
msgid "Search by Short Key ID is not supported."
msgstr ""
msgid "Invalid search query."
msgstr ""
msgctxt "Subject for verification email, {0} = userid, {1} = keyserver domain"
msgid "Verify {0} for your key on {1}"
msgstr "Verifica {0} per la tua chiave su {1}"
msgctxt "Subject for manage email, {} = keyserver domain"
msgid "Manage your key on {}"
msgstr "Gestisci la tua chiave su {}"
msgid "This link is invalid or expired"
msgstr "Questo link non è valido o è scaduto"
#: src/web/manage.rs:129
msgid "Malformed address: {address}"
msgstr "Indirizzo non valido: {address}"
#, fuzzy
msgid "Malformed address: {}"
msgstr "Indirizzo non valido: {}"
#: src/web/manage.rs:136
msgid "No key for address: {address}"
msgstr "Nessuna chiave per l'indirizzo: {address}"
#, fuzzy
msgid "No key for address: {}"
msgstr "Nessuna chiave per l'indirizzo: {}"
#: src/web/manage.rs:152
msgid "A request has already been sent for this address recently."
msgstr "Una richiesta per questo indirizzo è già stata inviata recentemente."
#: src/web/vks.rs:111
msgid "Parsing of key data failed."
msgstr "Impossibile elaborare i dati della chiave."
#: src/web/vks.rs:120
msgid "Whoops, please don't upload secret keys!"
msgstr "Ooops, non caricare chiavi segrete!"
#: src/web/vks.rs:133
msgid "No key uploaded."
msgstr "Nessuna chaive caricata."
#: src/web/vks.rs:177
msgid "Error processing uploaded key."
msgstr "Errore nell'elaborazione della chiave caricata."
#: src/web/vks.rs:247
msgid "Upload session expired. Please try again."
msgstr "Sessione di caricamento scaduta. Si prega di riprovare."
#: src/web/vks.rs:284
msgid "Invalid verification link."
msgstr "Link di verifica non valido."
#~ msgid ""
#~ "<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
#~ "launch ✨</a> (2019-09-12)"
#~ msgstr ""
#~ "<a href=\"/about/news#2019-09-12-three-months-later\">Tre mesi dal lancio "
#~ "✨</a> (12/09/2019)"

View File

@@ -15,38 +15,22 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: src/mail.rs:107
msgctxt "Subject for verification email"
msgid "Verify {userid} for your key on {domain}"
msgstr " {domain}のあなたの鍵のために{userid}を検証する"
#: src/mail.rs:140
msgctxt "Subject for manage email"
msgid "Manage your key on {domain}"
msgstr "{domain}の鍵を管理する"
#: src/gettext_strings.rs:4
msgid "Error"
msgstr "エラー"
#: src/gettext_strings.rs:5
msgid "Looks like something went wrong :("
msgstr "なにかがうまくいかないようです :("
#: src/gettext_strings.rs:6
msgid "Error message: {{ internal_error }}"
msgstr "エラーメッセージ: {{ internal_error }}"
#: src/gettext_strings.rs:7
msgid "There was an error with your request:"
msgstr "あなたのリクエストにエラーがありました。"
#: src/gettext_strings.rs:8
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
msgstr ""
"<span class=\"email\">{{ query }}</span>に対して一つのエントリをみつけました:"
#: src/gettext_strings.rs:9
msgid ""
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
@@ -56,19 +40,15 @@ msgstr ""
"のOpenPGPソフトウェアから使うのがより便利です。<br /> 詳しくは、わたしたちの"
"<a href=\"/about/usage\">使い方のガイド</a>をご覧ください。"
#: src/gettext_strings.rs:10
msgid "debug info"
msgstr "デバッグ情報"
#: src/gettext_strings.rs:11
msgid "Search by Email Address / Key ID / Fingerprint"
msgstr "メールアドレス / 鍵ID / フィンガープリントで検索する。"
#: src/gettext_strings.rs:12
msgid "Search"
msgstr "検索"
#: src/gettext_strings.rs:13
msgid ""
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
"a> your key."
@@ -76,29 +56,23 @@ msgstr ""
"あなたの鍵を<a href=\"/upload\">アップロード</a>または<a href=\"/manage\">管"
"理</a>もできます。"
#: src/gettext_strings.rs:14
msgid "Find out more <a href=\"/about\">about this service</a>."
msgstr "<a href=\"/about\">このサービスについて</a>さらにくわしく。"
#: src/gettext_strings.rs:15
msgid "News:"
msgstr "ニュース:"
#: src/gettext_strings.rs:16
msgid ""
"<a href=\"/about/news#2019-11-12-celebrating-100k\">Celebrating 100.000 "
"verified addresses! 📈</a> (2019-11-12)"
msgstr ""
#: src/gettext_strings.rs:17
msgid "v{{ version }} built from"
msgstr "v{{ version }} 、以下でビルトされました"
#: src/gettext_strings.rs:18
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
msgstr "<a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>で動いています"
#: src/gettext_strings.rs:19
msgid ""
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
@@ -106,31 +80,25 @@ msgstr ""
"背景の画像は<a href=\"https://www.toptal.com/designers/subtlepatterns/subtle-"
"grey/\">Subtle Patterns</a> からCC BY-SA 3.0のもとで取得されました。"
#: src/gettext_strings.rs:20
msgid "Maintenance Mode"
msgstr "メンテナンスモード"
#: src/gettext_strings.rs:21
msgid "Manage your key"
msgstr "あなたの鍵を管理する"
#: src/gettext_strings.rs:22
msgid "Enter any verified email address for your key"
msgstr ""
"あなたの鍵に対して検証されたメールアドレスをどれでもよいので入力してください"
#: src/gettext_strings.rs:23
msgid "Send link"
msgstr "リンクを送る"
#: src/gettext_strings.rs:24
msgid ""
"We will send you an email with a link you can use to remove any of your "
"email addresses from search."
msgstr ""
"検索からあなたのメールアドレスを削除するためのリンクのメールを送ります。"
#: src/gettext_strings.rs:25
msgid ""
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -138,15 +106,12 @@ msgstr ""
"鍵<span class=\"fingerprint\"><a href=\"{{ key_link }}\" target=\"_blank"
"\">{{ key_fpr }}</a></span>を管理する。"
#: src/gettext_strings.rs:26
msgid "Your key is published with the following identity information:"
msgstr "以下のアイデンティティ情報であなたの鍵は公開されました。"
#: src/gettext_strings.rs:27
msgid "Delete"
msgstr "削除する"
#: src/gettext_strings.rs:28
msgid ""
"Clicking \"delete\" on any address will remove it from this key. It will no "
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
@@ -156,7 +121,6 @@ msgstr ""
"索に現れなくなります。<br />アドレスを加えるにはその鍵を再度<a href=\"/upload"
"\">アップロード</a>してください。"
#: src/gettext_strings.rs:29
msgid ""
"Your key is published as only non-identity information. (<a href=\"/about\" "
"target=\"_blank\">What does this mean?</a>)"
@@ -164,24 +128,20 @@ msgstr ""
"アイデンティティなしのみの情報であなたの鍵は公開されました。. (<a href=\"/"
"about\" target=\"_blank\">これはどういう意味?</a>)"
#: src/gettext_strings.rs:30
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
msgstr ""
"アドレスを加えるにはその鍵を再度<a href=\"/upload\">アップロード</a>してくだ"
"さい。 "
#: src/gettext_strings.rs:31
msgid ""
"We have sent an email with further instructions to <span class=\"email"
"\">{{ address }}</span>."
msgstr ""
"くわしい手順を<span class=\"email\">{{ address }}</span>にメールしました。"
#: src/gettext_strings.rs:32
msgid "This address has already been verified."
msgstr "このアドレスはすでに検証されています。"
#: src/gettext_strings.rs:33
msgid ""
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
@@ -191,15 +151,12 @@ msgstr ""
"ティティ<a href=\"{{userid_link}}\" target=\"_blank\"><span class=\"email"
"\">{{ userid }}</span></a>に対して公開されました。"
#: src/gettext_strings.rs:34
msgid "Upload your key"
msgstr "あなたの鍵をアップロードする"
#: src/gettext_strings.rs:35
msgid "Upload"
msgstr "アップロード"
#: src/gettext_strings.rs:36
msgid ""
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
@@ -208,7 +165,6 @@ msgstr ""
"トロ</a> と<a target=\"_blank\" href=\"/about/usage\">ユーザーガイド</a>をご"
"覧ください。"
#: src/gettext_strings.rs:37
msgid ""
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -216,11 +172,9 @@ msgstr ""
"この鍵<span class=\"fingerprint\"><a href=\"{{ key_link }}\" target=\"_blank"
"\">{{ key_fpr }}</a></span>をあなたはアップロードしました。"
#: src/gettext_strings.rs:38
msgid "This key is revoked."
msgstr "この鍵はリボークされました。"
#: src/gettext_strings.rs:39
msgid ""
"It is published without identity information and can't be made available for "
"search by email address (<a href=\"/about\" target=\"_blank\">what does this "
@@ -229,7 +183,6 @@ msgstr ""
"アイデンティティの情報なしで公開され、メールアドレスによる検索は利用可能では"
"ありません(<a href=\"/about\" target=\"_blank\">どういう意味か?</a>)。"
#: src/gettext_strings.rs:40
msgid ""
"This key is now published with the following identity information (<a href="
"\"/about\" target=\"_blank\">what does this mean?</a>):"
@@ -237,11 +190,9 @@ msgstr ""
"この鍵は下記のアイデンティティ情報とともに公開されました (<a href=\"/about\" "
"target=\"_blank\">どういう意味?</a>):"
#: src/gettext_strings.rs:41
msgid "Published"
msgstr "公開されました"
#: src/gettext_strings.rs:42
msgid ""
"This key is now published with only non-identity information. (<a href=\"/"
"about\" target=\"_blank\">What does this mean?</a>)"
@@ -249,7 +200,6 @@ msgstr ""
"アイデンティティではない情報だけでこの鍵は公開されました。(<a href=\"/about"
"\" target=\"_blank\">どういう意味か?</a>)"
#: src/gettext_strings.rs:43
msgid ""
"To make the key available for search by email address, you can verify it "
"belongs to you:"
@@ -257,11 +207,9 @@ msgstr ""
"メールアドレスでの検索で鍵が利用できるようにするために、そのメールアドレスが"
"あなたのものであることを検証することができます。"
#: src/gettext_strings.rs:44
msgid "Verification Pending"
msgstr "検証を出願中です。"
#: src/gettext_strings.rs:45
msgid ""
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
"prevent spam. Please be patient."
@@ -269,11 +217,9 @@ msgstr ""
"<strong>注意:</strong> 迷惑メール防止のために、プロバイダは15分ほどメールを遅"
"らせることがあります。しばらくお待ちください。"
#: src/gettext_strings.rs:46
msgid "Send Verification Email"
msgstr "検証のメールを送る"
#: src/gettext_strings.rs:47
msgid ""
"This key contains one identity that could not be parsed as an email address."
"<br /> This identity can't be published on <span class=\"brand\">keys."
@@ -285,7 +231,6 @@ msgstr ""
"は公開できません。 (<a href=\"/about/faq#non-email-uids\" target=\"_blank\">"
"なぜか?</a>)"
#: src/gettext_strings.rs:48
msgid ""
"This key contains {{ count_unparsed }} identities that could not be parsed "
"as an email address.<br /> These identities can't be published on <span "
@@ -297,7 +242,6 @@ msgstr ""
"のアイデンティティは公開できません。 (<a href=\"/about/faq#non-email-uids\" "
"target=\"_blank\">なぜか?</a>)"
#: src/gettext_strings.rs:49
msgid ""
"This key contains one revoked identity, which is not published. (<a href=\"/"
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -305,7 +249,6 @@ msgstr ""
"この鍵はリボークされたアイデンティティが含まれており、公開されませんでした。 "
"(<a href=\"/about/faq#revoked-uids\" target=\"_blank\">なぜか?</a>)"
#: src/gettext_strings.rs:50
msgid ""
"This key contains {{ count_revoked }} revoked identities, which are not "
"published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -314,11 +257,9 @@ msgstr ""
"が、公開されません。 (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">"
"なぜか?</a>)"
#: src/gettext_strings.rs:51
msgid "Your keys have been successfully uploaded:"
msgstr "あなたの鍵はアップロードに成功しました:"
#: src/gettext_strings.rs:52
msgid ""
"<strong>Note:</strong> To make keys searchable by email address, you must "
"upload them individually."
@@ -326,11 +267,9 @@ msgstr ""
"<strong>注意:</strong> メールアドレスで鍵を検索可能とするために、ひとつひとつ"
"独立にアップロードする必要があります。"
#: src/gettext_strings.rs:53
msgid "Verifying your email address…"
msgstr "メールアドレスを確認しています..."
#: src/gettext_strings.rs:54
msgid ""
"If the process doesn't complete after a few seconds, please <input type="
"\"submit\" class=\"textbutton\" value=\"click here\" />."
@@ -338,15 +277,12 @@ msgstr ""
"数秒後にこのプロセスが完了しない場合、どうぞこちらに<input type=\"submit\" "
"class=\"textbutton\" value=\"click here\" />お願いします。"
#: src/gettext_strings.rs:56
msgid "Manage your key on {{domain}}"
msgstr "{{domain}}のあなたの鍵を管理する"
#: src/gettext_strings.rs:58
msgid "Hi,"
msgstr "どうも、"
#: src/gettext_strings.rs:59
msgid ""
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
@@ -354,15 +290,12 @@ msgstr ""
"<a href=\"{{base_uri}}\" style=\"text-decoration:none; color: "
"#333\">{{domain}}</a>からの自動メッセージです。"
#: src/gettext_strings.rs:60
msgid "If you didn't request this message, please ignore it."
msgstr "このメッセージを要求していない場合、無視してください。"
#: src/gettext_strings.rs:61
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
msgstr "OpenPGPの鍵: <tt>{{primary_fp}}</tt>"
#: src/gettext_strings.rs:62
msgid ""
"To manage and delete listed addresses on this key, please follow the link "
"below:"
@@ -370,7 +303,6 @@ msgstr ""
"この鍵の掲示されたアドレスを管理し削除するには、以下のリンクをたどってくださ"
"い:"
#: src/gettext_strings.rs:63
msgid ""
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
"a>."
@@ -378,27 +310,21 @@ msgstr ""
"よりくわしい情報はこちら <a href=\"{{base_uri}}/about\">{{domain}}/about</a>"
"にあります。"
#: src/gettext_strings.rs:64
msgid "distributing OpenPGP keys since 2019"
msgstr "OpenPGPの鍵を2019年から配布しています"
#: src/gettext_strings.rs:67
msgid "This is an automated message from {{domain}}."
msgstr "{{domain}}からの自動メッセージです。"
#: src/gettext_strings.rs:69
msgid "OpenPGP key: {{primary_fp}}"
msgstr "OpenPGPの鍵: {{primary_fp}}"
#: src/gettext_strings.rs:71
msgid "You can find more info at {{base_uri}}/about"
msgstr "詳しい情報はこちらにあります: {{base_uri}}/about"
#: src/gettext_strings.rs:74
msgid "Verify {{userid}} for your key on {{domain}}"
msgstr " {{domain}}に対するあなたの鍵の {{userid}}を検証する"
#: src/gettext_strings.rs:80
msgid ""
"To let others find this key from your email address \"<a rel=\"nofollow\" "
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
@@ -408,7 +334,6 @@ msgstr ""
"decoration:none; color: #333\">{{userid}}</a>\"からこの鍵を見つけられるために"
"は、以下のリンクをクリックしてください:"
#: src/gettext_strings.rs:88
msgid ""
"To let others find this key from your email address \"{{userid}}\",\n"
"please follow the link below:"
@@ -417,42 +342,57 @@ msgstr ""
"は、\n"
"以下のリンクをたどってください:"
#: src/web/manage.rs:103
msgid "No key found for fingerprint {}"
msgstr "このアドレスに対する鍵がありません: {}"
msgid "No key found for key id {}"
msgstr "このアドレスに対する鍵がありません: {}"
msgid "No key found for email address {}"
msgstr "このアドレスに対する鍵がありません: {}"
msgid "Search by Short Key ID is not supported."
msgstr "Search by Short Key ID is not supported."
msgid "Invalid search query."
msgstr "Invalid seach query."
msgctxt "Subject for verification email, {0} = userid, {1} = keyserver domain"
msgid "Verify {0} for your key on {1}"
msgstr " {1}のあなたの鍵のために{1}を検証する"
msgctxt "Subject for manage email, {} = keyserver domain"
msgid "Manage your key on {}"
msgstr "{}の鍵を管理する"
msgid "This link is invalid or expired"
msgstr "このリンクは無効であるか失効しています"
#: src/web/manage.rs:129
msgid "Malformed address: {address}"
msgstr "形のこわれたアドレスです: {address}"
#, fuzzy
msgid "Malformed address: {}"
msgstr "形のこわれたアドレスです: {}"
#: src/web/manage.rs:136
msgid "No key for address: {address}"
msgstr "このアドレスに対する鍵がありません: {address}"
#, fuzzy
msgid "No key for address: {}"
msgstr "このアドレスに対する鍵がありません: {}"
#: src/web/manage.rs:152
msgid "A request has already been sent for this address recently."
msgstr "最近、このアドレスへリクエストがすでに送信されています。"
#: src/web/vks.rs:111
msgid "Parsing of key data failed."
msgstr "鍵データのパーズが失敗しました。"
#: src/web/vks.rs:120
msgid "Whoops, please don't upload secret keys!"
msgstr "あらら、秘密鍵をアップロードしないでください!"
#: src/web/vks.rs:133
msgid "No key uploaded."
msgstr "鍵はアップロードされませんでした。"
#: src/web/vks.rs:177
msgid "Error processing uploaded key."
msgstr "アップロードされた鍵の処理に失敗しました。"
#: src/web/vks.rs:247
msgid "Upload session expired. Please try again."
msgstr "アップロードのセッションが時間切れです。もう一度試してください。"
#: src/web/vks.rs:284
msgid "Invalid verification link."
msgstr "無効な検証のリンクです。"

View File

@@ -1,6 +1,7 @@
#
# Translators:
# ylsun <y15un@y15un.dog>, 2020
# Vincent Breitmoser <look@my.amazin.horse>, 2020
# ylsun <y15un@y15un.dog>, 2021
#
msgid ""
msgstr ""
@@ -8,7 +9,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2019-09-27 18:05+0000\n"
"Last-Translator: ylsun <y15un@y15un.dog>, 2020\n"
"Last-Translator: ylsun <y15un@y15un.dog>, 2021\n"
"Language-Team: Korean (https://www.transifex.com/otf/teams/102430/ko/)\n"
"Language: ko\n"
"MIME-Version: 1.0\n"
@@ -16,37 +17,21 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: src/mail.rs:107
msgctxt "Subject for verification email"
msgid "Verify {userid} for your key on {domain}"
msgstr "{domain}에 {userid} 명의로 올린 키 인증"
#: src/mail.rs:140
msgctxt "Subject for manage email"
msgid "Manage your key on {domain}"
msgstr "{domain}에 올린 내 키 관리"
#: src/gettext_strings.rs:4
msgid "Error"
msgstr "오류"
#: src/gettext_strings.rs:5
msgid "Looks like something went wrong :("
msgstr "뭔가 단단히 잘못된 모양입니다 :("
#: src/gettext_strings.rs:6
msgid "Error message: {{ internal_error }}"
msgstr "오류 내용: {{ internal_error }}"
#: src/gettext_strings.rs:7
msgid "There was an error with your request:"
msgstr "내 요청과 관련해 아래와 같은 문제가 발생했습니다:"
#: src/gettext_strings.rs:8
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
msgstr "<span class=\"email\">{{ query }}</span>에 해당하는 키를 찾았습니다:"
#: src/gettext_strings.rs:9
msgid ""
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
@@ -56,19 +41,15 @@ msgstr ""
"span> 이용은 내 OpenPGP 소프트웨어를 통해서 하는 게 더 편하답니다.<br /> 자세"
"한 사항은 <a href=\"/about/usage\">사용 안내서</a>에서 알아보세요."
#: src/gettext_strings.rs:10
msgid "debug info"
msgstr "디버그 정보"
#: src/gettext_strings.rs:11
msgid "Search by Email Address / Key ID / Fingerprint"
msgstr "전자 메일 주소 / 키 ID / 지문으로 키를 찾아보세요"
#: src/gettext_strings.rs:12
msgid "Search"
msgstr "찾기"
#: src/gettext_strings.rs:13
msgid ""
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
"a> your key."
@@ -76,31 +57,25 @@ msgstr ""
"내 키를 <a href=\"/upload\">올리</a>거나 <a href=\"/manage\">관리</a>할 수도 "
"있습니다."
#: src/gettext_strings.rs:14
msgid "Find out more <a href=\"/about\">about this service</a>."
msgstr "이 서비스에 대해 <a href=\"/about\">더 알아볼래요</a>."
#: src/gettext_strings.rs:15
msgid "News:"
msgstr "새 소식:"
#: src/gettext_strings.rs:16
msgid ""
"<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
"launch ✨</a> (2019-09-12)"
"<a href=\"/about/news#2019-11-12-celebrating-100k\">Celebrating 100.000 "
"verified addresses! 📈</a> (2019-11-12)"
msgstr ""
"<a href=\"/about/news#2019-09-12-three-months-later\">서비스 개시 후 3개월이 "
"지났습니다</a> (2019-09-12)"
"<a href=\"/about/news#2019-11-12-celebrating-100k\">주소 인증 100,000건을 달"
"성했습니다! 📈</a> (2019-11-12)"
#: src/gettext_strings.rs:17
msgid "v{{ version }} built from"
msgstr "버전 {{ version }} / 빌드 커밋"
#: src/gettext_strings.rs:18
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
msgstr "<a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a> 기반"
#: src/gettext_strings.rs:19
msgid ""
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
@@ -108,23 +83,18 @@ msgstr ""
"배경 그림은 <a href=\"https://www.toptal.com/designers/subtlepatterns/subtle-"
"grey/\">Subtle Patterns</a>에서 가져왔고 CC BY-SA 3.0 허가서를 따릅니다"
#: src/gettext_strings.rs:20
msgid "Maintenance Mode"
msgstr "유지보수 모드"
#: src/gettext_strings.rs:21
msgid "Manage your key"
msgstr "내 키 관리"
#: src/gettext_strings.rs:22
msgid "Enter any verified email address for your key"
msgstr "내 키에 대해 인증된 전자 메일 주소를 입력하세요"
#: src/gettext_strings.rs:23
msgid "Send link"
msgstr "링크 보내기"
#: src/gettext_strings.rs:24
msgid ""
"We will send you an email with a link you can use to remove any of your "
"email addresses from search."
@@ -132,7 +102,6 @@ msgstr ""
"내 전자 메일 주소를 검색에서 지울 수 있는 링크가 담긴 전자 메일 한 통이 갈 거"
"예요."
#: src/gettext_strings.rs:25
msgid ""
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -140,15 +109,12 @@ msgstr ""
"<span class=\"fingerprint\"><a href=\"{{ key_link }}\" target=\"_blank"
"\">{{ key_fpr }}</a></span> 키를 관리합니다."
#: src/gettext_strings.rs:26
msgid "Your key is published with the following identity information:"
msgstr "내 키에 대해 현재 아래 명의가 함께 공개돼 있습니다:"
#: src/gettext_strings.rs:27
msgid "Delete"
msgstr "지우기"
#: src/gettext_strings.rs:28
msgid ""
"Clicking \"delete\" on any address will remove it from this key. It will no "
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
@@ -157,7 +123,6 @@ msgstr ""
"\"지우기\"를 누르면 해당 주소가 내 키에서 지워지고 검색에 나타나지 않습니다."
"<br />다른 주소를 추가하려면 이 키를 다시 <a href=\"/upload\">올리세요</a>."
#: src/gettext_strings.rs:29
msgid ""
"Your key is published as only non-identity information. (<a href=\"/about\" "
"target=\"_blank\">What does this mean?</a>)"
@@ -165,11 +130,9 @@ msgstr ""
"내 키의 비-명의 정보만 공개된 상태입니다. (<a href=\"/about\" target=\"_blank"
"\">이게 뭘 뜻하나요?</a>)"
#: src/gettext_strings.rs:30
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
msgstr "새 주소를 추가하려면 이 키를 다시 <a href=\"/upload\">올리세요</a>."
#: src/gettext_strings.rs:31
msgid ""
"We have sent an email with further instructions to <span class=\"email"
"\">{{ address }}</span>."
@@ -177,11 +140,9 @@ msgstr ""
"상세 사용 설명이 담긴 전자 메일을 <span class=\"email\">{{ address }}</span> "
"주소로 보냈습니다."
#: src/gettext_strings.rs:32
msgid "This address has already been verified."
msgstr "이 주소는 이미 인증됐습니다."
#: src/gettext_strings.rs:33
msgid ""
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
@@ -191,15 +152,12 @@ msgstr ""
"\">{{ userid }}</span></a> 명의가 내 <span class=\"fingerprint"
"\">{{ key_fpr }}</span> 키의 일부로 공개됩니다."
#: src/gettext_strings.rs:34
msgid "Upload your key"
msgstr "내 키 올리기"
#: src/gettext_strings.rs:35
msgid "Upload"
msgstr "올리기"
#: src/gettext_strings.rs:36
msgid ""
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
@@ -207,7 +165,6 @@ msgstr ""
"도움이 더 필요한가요? <a target=\"_blank\" href=\"/about\">서비스 소개</a>와 "
"<a target=\"_blank\" href=\"/about/usage\">사용 안내서</a>를 읽어보세요."
#: src/gettext_strings.rs:37
msgid ""
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -215,11 +172,9 @@ msgstr ""
"<span class=\"fingerprint\"><a href=\"{{ key_link }}\" target=\"_blank"
"\">{{ key_fpr }}</a></span> 키를 올렸습니다."
#: src/gettext_strings.rs:38
msgid "This key is revoked."
msgstr "이 키는 폐기됐습니다."
#: src/gettext_strings.rs:39
msgid ""
"It is published without identity information and can't be made available for "
"search by email address (<a href=\"/about\" target=\"_blank\">what does this "
@@ -228,7 +183,6 @@ msgstr ""
"명의 정보가 없는 상태로 공개됐기 때문에 이 키는 전자 메일 주소로 찾을 수 없습"
"니다. (<a href=\"/about\" target=\"_blank\">이게 뭘 뜻하나요?</a>)"
#: src/gettext_strings.rs:40
msgid ""
"This key is now published with the following identity information (<a href="
"\"/about\" target=\"_blank\">what does this mean?</a>):"
@@ -236,11 +190,9 @@ msgstr ""
"이제 다음 명의가 이 키의 일부로 함께 공개됩니다: (<a href=\"/about\" target="
"\"_blank\">이게 뭘 뜻하나요?</a>)"
#: src/gettext_strings.rs:41
msgid "Published"
msgstr "공개됨"
#: src/gettext_strings.rs:42
msgid ""
"This key is now published with only non-identity information. (<a href=\"/"
"about\" target=\"_blank\">What does this mean?</a>)"
@@ -248,7 +200,6 @@ msgstr ""
"이 키는 이제 비-명의 정보만 공개됩니다. (<a href=\"/about\" target=\"_blank"
"\">이게 뭘 뜻하나요?</a>)"
#: src/gettext_strings.rs:43
msgid ""
"To make the key available for search by email address, you can verify it "
"belongs to you:"
@@ -256,11 +207,9 @@ msgstr ""
"이 키를 전자 메일 주소로 찾을 수 있게 하려면 내가 진짜 이 주소를 가지고 있음"
"을 인증하세요:"
#: src/gettext_strings.rs:44
msgid "Verification Pending"
msgstr "인증 대기 중"
#: src/gettext_strings.rs:45
msgid ""
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
"prevent spam. Please be patient."
@@ -268,11 +217,9 @@ msgstr ""
"<strong>알아두기:</strong> 몇몇 서비스 제공자는 스팸 방지를 위해 최대 15분까"
"지 전자 메일 발송을 미루기도 합니다. 조금만 여유로이 기다려볼까요?"
#: src/gettext_strings.rs:46
msgid "Send Verification Email"
msgstr "인증 전자 메일 보내기"
#: src/gettext_strings.rs:47
msgid ""
"This key contains one identity that could not be parsed as an email address."
"<br /> This identity can't be published on <span class=\"brand\">keys."
@@ -283,7 +230,6 @@ msgstr ""
">이런 명의는 <span class=\"brand\">keys.openpgp.org</span>에 공개할 수 없습니"
"다. (<a href=\"/about/faq#non-email-uids\" target=\"_blank\">왜죠?</a>)"
#: src/gettext_strings.rs:48
msgid ""
"This key contains {{ count_unparsed }} identities that could not be parsed "
"as an email address.<br /> These identities can't be published on <span "
@@ -295,7 +241,6 @@ msgstr ""
"에 공개할 수 없습니다. (<a href=\"/about/faq#non-email-uids\" target=\"_blank"
"\">왜죠?</a>)"
#: src/gettext_strings.rs:49
msgid ""
"This key contains one revoked identity, which is not published. (<a href=\"/"
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -303,7 +248,6 @@ msgstr ""
"이 키에는 폐기된 명의 하나가 포함돼 있네요. 폐기된 명의는 공개하지 않습니다. "
"(<a href=\"/about/faq#revoked-uids\" target=\"_blank\">왜죠?</a>)"
#: src/gettext_strings.rs:50
msgid ""
"This key contains {{ count_revoked }} revoked identities, which are not "
"published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -312,11 +256,9 @@ msgstr ""
"공개하지 않습니다. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">왜"
"죠?</a>)"
#: src/gettext_strings.rs:51
msgid "Your keys have been successfully uploaded:"
msgstr "내 키를 성공적으로 올렸습니다:"
#: src/gettext_strings.rs:52
msgid ""
"<strong>Note:</strong> To make keys searchable by email address, you must "
"upload them individually."
@@ -324,11 +266,9 @@ msgstr ""
"<strong>알아두기:</strong> 개별 키를 전자 메일 주소로 찾을 수 있게 하려면 각"
"각 따로 올리세요."
#: src/gettext_strings.rs:53
msgid "Verifying your email address…"
msgstr "전자 메일 주소를 인증하는 중..."
#: src/gettext_strings.rs:54
msgid ""
"If the process doesn't complete after a few seconds, please <input type="
"\"submit\" class=\"textbutton\" value=\"click here\" />."
@@ -336,15 +276,12 @@ msgstr ""
"몇 초가 지나도 이 절차가 끝나지 않는다면 <input type=\"submit\" class="
"\"textbutton\" value=\"여기를 누르세요\" />."
#: src/gettext_strings.rs:56
msgid "Manage your key on {{domain}}"
msgstr "{{domain}}에 올린 내 키 관리"
#: src/gettext_strings.rs:58
msgid "Hi,"
msgstr "안녕하세요."
#: src/gettext_strings.rs:59
msgid ""
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
@@ -352,21 +289,17 @@ msgstr ""
"이 메시지는 <a href=\"{{base_uri}}\" style=\"text-decoration:none; color: "
"#333\">{{domain}}</a>에서 자동으로 발송됐습니다."
#: src/gettext_strings.rs:60
msgid "If you didn't request this message, please ignore it."
msgstr "만약 직접 요청하신 게 아니라면 그냥 무시하세요."
#: src/gettext_strings.rs:61
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
msgstr "OpenPGP 키: <tt>{{primary_fp}}</tt>"
#: src/gettext_strings.rs:62
msgid ""
"To manage and delete listed addresses on this key, please follow the link "
"below:"
msgstr "위 키에 엮인 주소를 숨기거나 하는 키 관리는 아래 링크에서 가능합니다:"
msgstr "위 키에 엮인 주소를 숨기는 등의 키 관리는 아래 링크에서 할 수 있어요:"
#: src/gettext_strings.rs:63
msgid ""
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
"a>."
@@ -374,27 +307,21 @@ msgstr ""
"더 자세한 사항은 <a href=\"{{base_uri}}/about\">{{domain}}/about</a> 페이지"
"를 참고하세요."
#: src/gettext_strings.rs:64
msgid "distributing OpenPGP keys since 2019"
msgstr "2019년 이래 OpenPGP 키 배포에 이바지하는 중"
#: src/gettext_strings.rs:67
msgid "This is an automated message from {{domain}}."
msgstr "이 메시지는 {{domain}}에서 자동으로 발송됐습니다."
#: src/gettext_strings.rs:69
msgid "OpenPGP key: {{primary_fp}}"
msgstr "OpenPGP 키: {{primary_fp}}"
#: src/gettext_strings.rs:71
msgid "You can find more info at {{base_uri}}/about"
msgstr "더 자세한 사항은 {{base_uri}}/about 페이지를 참고하세요"
msgstr "더 자세한 사항은 {{base_uri}}/about 페이지를 참고하세요."
#: src/gettext_strings.rs:74
msgid "Verify {{userid}} for your key on {{domain}}"
msgstr "{{domain}}에 {{userid}} 명의로 올린 키 인증"
#: src/gettext_strings.rs:80
msgid ""
"To let others find this key from your email address \"<a rel=\"nofollow\" "
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
@@ -404,7 +331,6 @@ msgstr ""
"decoration:none; color: #333\">{{userid}}</a>)로 위 키를 찾을 수 있게 하려면 "
"아래 링크를 따라가세요:"
#: src/gettext_strings.rs:88
msgid ""
"To let others find this key from your email address \"{{userid}}\",\n"
"please follow the link below:"
@@ -412,42 +338,57 @@ msgstr ""
"다른 사람들이 내 전자 메일 주소({{userid}})로 위 키를 찾을 수 있게 하려면\n"
"아래 링크를 따라가세요:"
#: src/web/manage.rs:103
msgid "No key found for fingerprint {}"
msgstr "지문 {}에 해당하는 키를 찾지 못했습니다."
msgid "No key found for key id {}"
msgstr "키 ID {}에 해당하는 키를 찾지 못했습니다."
msgid "No key found for email address {}"
msgstr "전자 메일 주소 {}에 해당하는 키를 찾지 못했습니다."
msgid "Search by Short Key ID is not supported."
msgstr "'짧은 키 ID' 형식은 지원하지 않습니다."
msgid "Invalid search query."
msgstr "올바르지 않은 검색어입니다."
msgctxt "Subject for verification email, {0} = userid, {1} = keyserver domain"
msgid "Verify {0} for your key on {1}"
msgstr "{1}에 {0} 명의로 올린 키 인증"
msgctxt "Subject for manage email, {} = keyserver domain"
msgid "Manage your key on {}"
msgstr "{}에 올린 내 키 관리"
msgid "This link is invalid or expired"
msgstr "링크가 올바르지 않거나 이미 만료됐습니다"
#: src/web/manage.rs:129
msgid "Malformed address: {address}"
msgstr "잘못된 형식의 주소: {address}"
#, fuzzy
msgid "Malformed address: {}"
msgstr "잘못된 형식의 주소: {}"
#: src/web/manage.rs:136
msgid "No key for address: {address}"
msgstr "다음 주소와 엮인 키 없음: {address}"
#, fuzzy
msgid "No key for address: {}"
msgstr "다음 주소와 엮인 키 없음: {}"
#: src/web/manage.rs:152
msgid "A request has already been sent for this address recently."
msgstr "최근에 이 주소로 이미 요청이 접수됐습니다."
#: src/web/vks.rs:111
msgid "Parsing of key data failed."
msgstr "키 데이터를 해석하지 못했습니다."
#: src/web/vks.rs:120
msgid "Whoops, please don't upload secret keys!"
msgstr "아이고 맙소사! 비밀키는 올리면 안 돼요!"
#: src/web/vks.rs:133
msgid "No key uploaded."
msgstr "아무 키도 올리지 않았습니다."
#: src/web/vks.rs:177
msgid "Error processing uploaded key."
msgstr "올린 키를 처리하지 못했습니다."
#: src/web/vks.rs:247
msgid "Upload session expired. Please try again."
msgstr "올리기 세션이 만료됐습니다. 다시 해보세요."
#: src/web/vks.rs:284
msgid "Invalid verification link."
msgstr "올바르지 않은 인증 링크"

View File

@@ -1,5 +1,7 @@
#
# Translators:
# Kristoffer Håvik, 2020
# Vincent Breitmoser <look@my.amazin.horse>, 2020
# Kristoffer Håvik, 2021
#
msgid ""
msgstr ""
@@ -7,7 +9,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2019-09-27 18:05+0000\n"
"Last-Translator: Kristoffer Håvik, 2020\n"
"Last-Translator: Kristoffer Håvik, 2021\n"
"Language-Team: Norwegian Bokmål (https://www.transifex.com/otf/teams/102430/"
"nb/)\n"
"Language: nb\n"
@@ -16,37 +18,21 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: src/mail.rs:107
msgctxt "Subject for verification email"
msgid "Verify {userid} for your key on {domain}"
msgstr "Bekreft {userid} for nøkkelen din på {domain}"
#: src/mail.rs:140
msgctxt "Subject for manage email"
msgid "Manage your key on {domain}"
msgstr "Administrer nøkkelen din på {domain}"
#: src/gettext_strings.rs:4
msgid "Error"
msgstr "Feil"
#: src/gettext_strings.rs:5
msgid "Looks like something went wrong :("
msgstr "Det ser ut til at noe gikk galt. :("
#: src/gettext_strings.rs:6
msgid "Error message: {{ internal_error }}"
msgstr "Feilmelding: {{ internal_error }}"
#: src/gettext_strings.rs:7
msgid "There was an error with your request:"
msgstr "Det skjedde en feil ved behandling av forespørselen din:"
#: src/gettext_strings.rs:8
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
msgstr "Vi fant en oppføring for <span class=\"email\">{{ query }}</span>:"
#: src/gettext_strings.rs:9
msgid ""
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
@@ -56,19 +42,15 @@ msgstr ""
"\">keys.openpgp.org</span> via OpenPGP-programvaren.<br /> Les gjerne <a "
"href=\"/about/usage\">brukerveiledningen</a> for detaljer."
#: src/gettext_strings.rs:10
msgid "debug info"
msgstr "feilsøkingsinfo"
#: src/gettext_strings.rs:11
msgid "Search by Email Address / Key ID / Fingerprint"
msgstr "Søk etter e-postadresse / nøkkel-ID / fingeravtrykk"
#: src/gettext_strings.rs:12
msgid "Search"
msgstr "Søk"
#: src/gettext_strings.rs:13
msgid ""
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
"a> your key."
@@ -76,31 +58,25 @@ msgstr ""
"Du kan også <a href=\"/upload\">laste opp</a> eller <a href=\"/manage"
"\">administrere</a> nøkkelen din."
#: src/gettext_strings.rs:14
msgid "Find out more <a href=\"/about\">about this service</a>."
msgstr "Finn ut mer <a href=\"/about\">om denne tjenesten</a>."
#: src/gettext_strings.rs:15
msgid "News:"
msgstr "Nyheter:"
#: src/gettext_strings.rs:16
msgid ""
"<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
"launch ✨</a> (2019-09-12)"
"<a href=\"/about/news#2019-11-12-celebrating-100k\">Celebrating 100.000 "
"verified addresses! 📈</a> (2019-11-12)"
msgstr ""
"<a href=\"/about/news#2019-09-12-three-months-later\">Tre måneder etter "
"lansering ✨</a> (12. september 2019)"
"<a href=\"/about/news#2019-11-12-celebrating-100k\">Vi feirer 100 000 "
"bekreftede adresser! 📈</a> (2019-11-12)"
#: src/gettext_strings.rs:17
msgid "v{{ version }} built from"
msgstr "v{{ version }} bygd fra"
#: src/gettext_strings.rs:18
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
msgstr "Drevet av <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
#: src/gettext_strings.rs:19
msgid ""
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
@@ -108,23 +84,18 @@ msgstr ""
"Bakgrunnsbildet er hentet fra <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> på lisens CC BY-SA 3.0"
#: src/gettext_strings.rs:20
msgid "Maintenance Mode"
msgstr "Vedlikeholdsmodus"
#: src/gettext_strings.rs:21
msgid "Manage your key"
msgstr "Administrer nøkkelen din"
#: src/gettext_strings.rs:22
msgid "Enter any verified email address for your key"
msgstr "Skriv inn en bekreftet e-postadresse som tilhører nøkkelen din"
#: src/gettext_strings.rs:23
msgid "Send link"
msgstr "Send lenke"
#: src/gettext_strings.rs:24
msgid ""
"We will send you an email with a link you can use to remove any of your "
"email addresses from search."
@@ -132,7 +103,6 @@ msgstr ""
"Vi sender deg en e-post med en lenke som du kan bruke for å ta bort e-"
"postadressene dine fra søket."
#: src/gettext_strings.rs:25
msgid ""
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -140,15 +110,12 @@ msgstr ""
"Administrerer nøkkelen <span class=\"fingerprint\"><a href="
"\"{{ key_link }}\" target=\"_blank\">{{ key_fpr }}</a></span>."
#: src/gettext_strings.rs:26
msgid "Your key is published with the following identity information:"
msgstr "Nøkkelen din er offentliggjort med følgende identitetsinformasjon:"
#: src/gettext_strings.rs:27
msgid "Delete"
msgstr "Slett"
#: src/gettext_strings.rs:28
msgid ""
"Clicking \"delete\" on any address will remove it from this key. It will no "
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
@@ -158,7 +125,6 @@ msgstr ""
"nøkkelen og vil ikke lenger dukke opp i søk.<br /> For å legge til en annen "
"adresse, <a href=\"/upload\">last opp</a> nøkkelen på nytt."
#: src/gettext_strings.rs:29
msgid ""
"Your key is published as only non-identity information. (<a href=\"/about\" "
"target=\"_blank\">What does this mean?</a>)"
@@ -166,13 +132,11 @@ msgstr ""
"Nøkkelen din er offentliggjort uten identitetsinformasjon. (<a href=\"/about"
"\" target=\"_blank\">Hva betyr dette?</a>)"
#: src/gettext_strings.rs:30
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
msgstr ""
"For å legge til en adresse, <a href=\"/upload\">last opp</a> nøkkelen på "
"nytt."
#: src/gettext_strings.rs:31
msgid ""
"We have sent an email with further instructions to <span class=\"email"
"\">{{ address }}</span>."
@@ -180,11 +144,9 @@ msgstr ""
"Vi har sendt en e-post med videre instruksjoner til <span class=\"email"
"\">{{ address }}</span>."
#: src/gettext_strings.rs:32
msgid "This address has already been verified."
msgstr "Denne adressen er allerede bekreftet."
#: src/gettext_strings.rs:33
msgid ""
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
@@ -194,15 +156,12 @@ msgstr ""
"offentliggjort for identiteten <a href=\"{{userid_link}}\" target=\"_blank"
"\"><span class=\"email\">{{ userid }}</span></a>."
#: src/gettext_strings.rs:34
msgid "Upload your key"
msgstr "Last opp nøkkelen din"
#: src/gettext_strings.rs:35
msgid "Upload"
msgstr "Last opp"
#: src/gettext_strings.rs:36
msgid ""
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
@@ -210,7 +169,6 @@ msgstr ""
"Vil du vite mer? Les <a target=\"_blank\" href=\"/about\">innføringen</a> og "
"<a target=\"_blank\" href=\"/about/usage\">bruksanvisningen</a>."
#: src/gettext_strings.rs:37
msgid ""
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -218,11 +176,9 @@ msgstr ""
"Du har lastet opp nøkkelen <span class=\"fingerprint\"><a href="
"\"{{ key_link }}\" target=\"_blank\">{{ key_fpr}}</a></span>."
#: src/gettext_strings.rs:38
msgid "This key is revoked."
msgstr "Denne nøkkelen har blitt trukket tilbake."
#: src/gettext_strings.rs:39
msgid ""
"It is published without identity information and can't be made available for "
"search by email address (<a href=\"/about\" target=\"_blank\">what does this "
@@ -232,7 +188,6 @@ msgstr ""
"tilgjengelig i e-postadressesøk (<a href=\"/about\" target=\"_blank\">hva "
"betyr dette?</a>)."
#: src/gettext_strings.rs:40
msgid ""
"This key is now published with the following identity information (<a href="
"\"/about\" target=\"_blank\">what does this mean?</a>):"
@@ -240,11 +195,9 @@ msgstr ""
"Denne nøkkelen er nå offentliggjort med følgende identitetsinformasjon (<a "
"href=\"/about\" target=\"_blank\">hva betyr dette?</a>):"
#: src/gettext_strings.rs:41
msgid "Published"
msgstr "Offentliggjort"
#: src/gettext_strings.rs:42
msgid ""
"This key is now published with only non-identity information. (<a href=\"/"
"about\" target=\"_blank\">What does this mean?</a>)"
@@ -252,7 +205,6 @@ msgstr ""
"Denne nøkkelen er nå offentliggjort uten identitetsinformasjon. (<a href=\"/"
"about\" target=\"_blank\">Hva betyr dette?</a>)"
#: src/gettext_strings.rs:43
msgid ""
"To make the key available for search by email address, you can verify it "
"belongs to you:"
@@ -260,11 +212,9 @@ msgstr ""
"For at nøkkelen skal kunne finnes ved å søke etter e-postadresse, kan du "
"bekrefte at den tilhører deg:"
#: src/gettext_strings.rs:44
msgid "Verification Pending"
msgstr "Bekreftelse pågår"
#: src/gettext_strings.rs:45
msgid ""
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
"prevent spam. Please be patient."
@@ -272,11 +222,9 @@ msgstr ""
"<strong>Merk:</strong> Noen leverandører forsinker e-poster med inntil 15 "
"min. for å forhindre søppelpost. Vennligst vent."
#: src/gettext_strings.rs:46
msgid "Send Verification Email"
msgstr "Send bekreftelses-epost"
#: src/gettext_strings.rs:47
msgid ""
"This key contains one identity that could not be parsed as an email address."
"<br /> This identity can't be published on <span class=\"brand\">keys."
@@ -288,7 +236,6 @@ msgstr ""
"\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" "
"target=\"_blank\">Hvorfor?</a>)"
#: src/gettext_strings.rs:48
msgid ""
"This key contains {{ count_unparsed }} identities that could not be parsed "
"as an email address.<br /> These identities can't be published on <span "
@@ -300,7 +247,6 @@ msgstr ""
"på <span class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-"
"email-uids\" target=\"_blank\">Hvorfor?</a>)"
#: src/gettext_strings.rs:49
msgid ""
"This key contains one revoked identity, which is not published. (<a href=\"/"
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -309,7 +255,6 @@ msgstr ""
"ikke er offentliggjort. (<a href=\"/about/faq#revoked-uids\" target=\"_blank"
"\">Hvorfor?</a>)"
#: src/gettext_strings.rs:50
msgid ""
"This key contains {{ count_revoked }} revoked identities, which are not "
"published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -318,11 +263,9 @@ msgstr ""
"trukket tilbake og som ikke er offentliggjorte. (<a href=\"/about/"
"faq#revoked-uids\" target=\"_blank\">Hvorfor?</a>)"
#: src/gettext_strings.rs:51
msgid "Your keys have been successfully uploaded:"
msgstr "Nøklene dine er lastet opp:"
#: src/gettext_strings.rs:52
msgid ""
"<strong>Note:</strong> To make keys searchable by email address, you must "
"upload them individually."
@@ -330,11 +273,9 @@ msgstr ""
"<strong>Merk:</strong> For at nøkler skal kunne finnes ved å søke etter e-"
"postadresse, må de lastes opp én og én."
#: src/gettext_strings.rs:53
msgid "Verifying your email address…"
msgstr "Bekrefter e-postadressen din…"
#: src/gettext_strings.rs:54
msgid ""
"If the process doesn't complete after a few seconds, please <input type="
"\"submit\" class=\"textbutton\" value=\"click here\" />."
@@ -342,15 +283,12 @@ msgstr ""
"Dersom det tar mer enn noen få sekunder, <input type=\"submit\" class="
"\"textbutton\" value=\"klikk her\"/>."
#: src/gettext_strings.rs:56
msgid "Manage your key on {{domain}}"
msgstr "Administrer nøkkelen din på {{domain}}"
#: src/gettext_strings.rs:58
msgid "Hi,"
msgstr "Hei,"
#: src/gettext_strings.rs:59
msgid ""
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
@@ -358,16 +296,13 @@ msgstr ""
"Dette er en automatisk beskjed fra <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
#: src/gettext_strings.rs:60
msgid "If you didn't request this message, please ignore it."
msgstr ""
"Hvis ikke det var du som ba om denne beskjeden, kan du se bort fra den."
#: src/gettext_strings.rs:61
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
msgstr "OpenPGP-nøkkel: <tt>{{primary_fp}}</tt>"
#: src/gettext_strings.rs:62
msgid ""
"To manage and delete listed addresses on this key, please follow the link "
"below:"
@@ -375,7 +310,6 @@ msgstr ""
"For å administrere og slette adressene som hører til denne nøkkelen, "
"vennligst følg lenken nedenfor:"
#: src/gettext_strings.rs:63
msgid ""
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
"a>."
@@ -383,27 +317,21 @@ msgstr ""
"Du finner mer informasjon på <a href=\"{{base_uri}}/about\">{{domain}}/"
"about</a>."
#: src/gettext_strings.rs:64
msgid "distributing OpenPGP keys since 2019"
msgstr "har distribuert OpenPGP-nøkler siden 2019"
#: src/gettext_strings.rs:67
msgid "This is an automated message from {{domain}}."
msgstr "Dette er en automatisk melding fra {{domain}}."
#: src/gettext_strings.rs:69
msgid "OpenPGP key: {{primary_fp}}"
msgstr "OpenPGP-nøkkel: {{primary_fp}}"
#: src/gettext_strings.rs:71
msgid "You can find more info at {{base_uri}}/about"
msgstr "Du finner mer informasjon på {{base_uri}}/about"
#: src/gettext_strings.rs:74
msgid "Verify {{userid}} for your key on {{domain}}"
msgstr "Bekreft {{userid}} for nøkkelen din på {{domain}}"
#: src/gettext_strings.rs:80
msgid ""
"To let others find this key from your email address \"<a rel=\"nofollow\" "
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
@@ -414,7 +342,6 @@ msgstr ""
"#333\">{{userid}}</a>\",\n"
"vennligst klikk på lenken nedenfor:"
#: src/gettext_strings.rs:88
msgid ""
"To let others find this key from your email address \"{{userid}}\",\n"
"please follow the link below:"
@@ -423,42 +350,57 @@ msgstr ""
"\"{{userid}}\",\n"
"vennligst følg lenken nedenfor:"
#: src/web/manage.rs:103
msgid "No key found for fingerprint {}"
msgstr "Fant ingen nøkkel for fingeravtrykk {}"
msgid "No key found for key id {}"
msgstr "Fant ingen nøkkel for nøkkel-ID {}"
msgid "No key found for email address {}"
msgstr "Fant ingen nøkkel for e-postadresse {}"
msgid "Search by Short Key ID is not supported."
msgstr "Tjenesten støtter ikke søk etter forkortet nøkkel-ID."
msgid "Invalid search query."
msgstr "Ugyldig forespørsel."
msgctxt "Subject for verification email, {0} = userid, {1} = keyserver domain"
msgid "Verify {0} for your key on {1}"
msgstr "Bekreft {0} for nøkkelen din på {1}"
msgctxt "Subject for manage email, {} = keyserver domain"
msgid "Manage your key on {}"
msgstr "Administrer nøkkelen din på {}"
msgid "This link is invalid or expired"
msgstr "Denne lenken er ugyldig eller utløpt"
#: src/web/manage.rs:129
msgid "Malformed address: {address}"
msgstr "Deformert adresse: {address}"
#, fuzzy
msgid "Malformed address: {}"
msgstr "Deformert adresse: {}"
#: src/web/manage.rs:136
msgid "No key for address: {address}"
msgstr "Ingen nøkler for adressen {address}"
#, fuzzy
msgid "No key for address: {}"
msgstr "Ingen nøkler for adressen {}"
#: src/web/manage.rs:152
msgid "A request has already been sent for this address recently."
msgstr "En forespørsel har allerede blitt sendt for denne adressen nylig."
#: src/web/vks.rs:111
msgid "Parsing of key data failed."
msgstr "Kunne ikke analysere innholdet i nøkkelen."
#: src/web/vks.rs:120
msgid "Whoops, please don't upload secret keys!"
msgstr "Hopp sann! Vennligst ikke last opp hemmelige nøkler!"
#: src/web/vks.rs:133
msgid "No key uploaded."
msgstr "Ingen nøkkel er lastet opp."
#: src/web/vks.rs:177
msgid "Error processing uploaded key."
msgstr "Kunne ikke behandle den opplastede nøkkelen."
#: src/web/vks.rs:247
msgid "Upload session expired. Please try again."
msgstr "Opplastingsøkten er utgått. Vennligst prøv på nytt."
#: src/web/vks.rs:284
msgid "Invalid verification link."
msgstr "Bekreftelseslenken er ugyldig."

View File

@@ -16,37 +16,21 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: src/mail.rs:107
msgctxt "Subject for verification email"
msgid "Verify {userid} for your key on {domain}"
msgstr "Verifieer {userid} voor uw sleutel op {domein}"
#: src/mail.rs:140
msgctxt "Subject for manage email"
msgid "Manage your key on {domain}"
msgstr "Beheer uw sleutel op {domein}"
#: src/gettext_strings.rs:4
msgid "Error"
msgstr "Fout"
#: src/gettext_strings.rs:5
msgid "Looks like something went wrong :("
msgstr "Het lijkt erop dat er iets mis is gegaan :("
#: src/gettext_strings.rs:6
msgid "Error message: {{ internal_error }}"
msgstr "Foutmelding: {{internal_error}}"
#: src/gettext_strings.rs:7
msgid "There was an error with your request:"
msgstr "Er is een fout opgetreden bij uw verzoek:"
#: src/gettext_strings.rs:8
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
msgstr "We hebben een vermelding gevonden voor {{query}}:"
#: src/gettext_strings.rs:9
msgid ""
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
@@ -56,19 +40,15 @@ msgstr ""
"org</span> te gebruiken vanuit uw OpenPGP-software.<br /> Bekijk onze <a "
"href=\"/about/usage\">gebruikershandleiding</a> voor meer informatie."
#: src/gettext_strings.rs:10
msgid "debug info"
msgstr "debug info"
#: src/gettext_strings.rs:11
msgid "Search by Email Address / Key ID / Fingerprint"
msgstr "Zoeken op e-mailadres / sleutel-ID / vingerafdruk"
#: src/gettext_strings.rs:12
msgid "Search"
msgstr "Zoek"
#: src/gettext_strings.rs:13
msgid ""
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
"a> your key."
@@ -76,31 +56,23 @@ msgstr ""
"U kunt uw sleutel ook <a href=\"/upload\">uploaden</a> of <a href=\"/manage"
"\">beheren</a>."
#: src/gettext_strings.rs:14
msgid "Find out more <a href=\"/about\">about this service</a>."
msgstr "Lees meer over <a href=\"/about\">deze dienst</a>."
#: src/gettext_strings.rs:15
msgid "News:"
msgstr "Nieuws:"
#: src/gettext_strings.rs:16
msgid ""
"<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
"launch ✨</a> (2019-09-12)"
"<a href=\"/about/news#2019-11-12-celebrating-100k\">Celebrating 100.000 "
"verified addresses! 📈</a> (2019-11-12)"
msgstr ""
"<a href=\"/about/news#2019-09-12-three-months-later\">Drie maanden na "
"lancering ✨</a> (12-09-2019)"
#: src/gettext_strings.rs:17
msgid "v{{ version }} built from"
msgstr "v{{version}} gebouwd van"
#: src/gettext_strings.rs:18
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
msgstr "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
#: src/gettext_strings.rs:19
msgid ""
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
@@ -109,23 +81,18 @@ msgstr ""
"designers/subtlepatterns/subtle-grey/\">Subtle Patterns</a> onder CC BY-SA "
"3.0"
#: src/gettext_strings.rs:20
msgid "Maintenance Mode"
msgstr "Onderhoud"
#: src/gettext_strings.rs:21
msgid "Manage your key"
msgstr "Beheer uw sleutel"
#: src/gettext_strings.rs:22
msgid "Enter any verified email address for your key"
msgstr "Voer één van de geverifieerd e-mailadressen van uw sleutel in"
#: src/gettext_strings.rs:23
msgid "Send link"
msgstr "Stuur link"
#: src/gettext_strings.rs:24
msgid ""
"We will send you an email with a link you can use to remove any of your "
"email addresses from search."
@@ -133,7 +100,6 @@ msgstr ""
"We sturen u een e-mail met een link die u kunt gebruiken om al uw e-"
"mailadressen uit de zoekresultaten te verwijderen."
#: src/gettext_strings.rs:25
msgid ""
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -141,15 +107,12 @@ msgstr ""
"Beheren van de sleutel <span class=\"fingerprint\"><a href="
"\"{{ key_link }}\" target=\"_blank\"> {{key_fpr}}</a></span>."
#: src/gettext_strings.rs:26
msgid "Your key is published with the following identity information:"
msgstr "Uw sleutel wordt gepubliceerd met de volgende identiteitsgegevens:"
#: src/gettext_strings.rs:27
msgid "Delete"
msgstr "Verwijderen"
#: src/gettext_strings.rs:28
msgid ""
"Clicking \"delete\" on any address will remove it from this key. It will no "
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
@@ -160,7 +123,6 @@ msgstr ""
"<br /> <a href=\"/upload\">Upload</a> de sleutel opnieuw om een ander adres "
"toe te voegen."
#: src/gettext_strings.rs:29
msgid ""
"Your key is published as only non-identity information. (<a href=\"/about\" "
"target=\"_blank\">What does this mean?</a>)"
@@ -168,12 +130,10 @@ msgstr ""
"Uw sleutel wordt gepubliceerd als niet-identiteitsinformatie. (<a href=\"/"
"about\" target=\"_blank\">Wat betekent dit?</a>)"
#: src/gettext_strings.rs:30
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
msgstr ""
"<a href=\"/upload\">Upload</a> de sleutel opnieuw om een adres toe te voegen."
#: src/gettext_strings.rs:31
msgid ""
"We have sent an email with further instructions to <span class=\"email"
"\">{{ address }}</span>."
@@ -181,11 +141,9 @@ msgstr ""
"We hebben een e-mail gestuurd met verdere instructies naar <span class="
"\"email\">{{address}}</span>."
#: src/gettext_strings.rs:32
msgid "This address has already been verified."
msgstr "Dit adres is al geverifieerd."
#: src/gettext_strings.rs:33
msgid ""
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
@@ -195,15 +153,12 @@ msgstr ""
"voor de identiteit <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
"\"email\">{{userid}}</span></a>."
#: src/gettext_strings.rs:34
msgid "Upload your key"
msgstr "Upload uw sleutel"
#: src/gettext_strings.rs:35
msgid "Upload"
msgstr "Upload"
#: src/gettext_strings.rs:36
msgid ""
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
@@ -212,7 +167,6 @@ msgstr ""
"\">intro</a> en <a target=\"_blank\" href=\"/about/usage"
"\">gebruikshandleiding</a>."
#: src/gettext_strings.rs:37
msgid ""
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -220,11 +174,9 @@ msgstr ""
"U heeft de sleutel <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{key_fpr}}</a></span> geüpload."
#: src/gettext_strings.rs:38
msgid "This key is revoked."
msgstr "Deze sleutel is ingetrokken."
#: src/gettext_strings.rs:39
msgid ""
"It is published without identity information and can't be made available for "
"search by email address (<a href=\"/about\" target=\"_blank\">what does this "
@@ -234,7 +186,6 @@ msgstr ""
"doorzocht op e-mailadres (<a href=\"/about\" target=\"_blank\">wat betekent "
"dit?</a>)."
#: src/gettext_strings.rs:40
msgid ""
"This key is now published with the following identity information (<a href="
"\"/about\" target=\"_blank\">what does this mean?</a>):"
@@ -242,11 +193,9 @@ msgstr ""
"Deze sleutel wordt nu gepubliceerd met de volgende identiteitsgegevens (<a "
"href=\"/about\" target=\"_blank\">wat betekent dit?</a>):"
#: src/gettext_strings.rs:41
msgid "Published"
msgstr "Gepubliceerd"
#: src/gettext_strings.rs:42
msgid ""
"This key is now published with only non-identity information. (<a href=\"/"
"about\" target=\"_blank\">What does this mean?</a>)"
@@ -254,7 +203,6 @@ msgstr ""
"Deze sleutel wordt nu gepubliceerd zonder identiteitsinformatie. (<a href=\"/"
"about\" target=\"_blank\">Wat betekent dit?</a>)"
#: src/gettext_strings.rs:43
msgid ""
"To make the key available for search by email address, you can verify it "
"belongs to you:"
@@ -262,11 +210,9 @@ msgstr ""
"Om de sleutel beschikbaar te maken voor zoeken op e-mailadres, kunt u "
"valideren dat deze van u is:"
#: src/gettext_strings.rs:44
msgid "Verification Pending"
msgstr "Verificatie in behandeling"
#: src/gettext_strings.rs:45
msgid ""
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
"prevent spam. Please be patient."
@@ -274,11 +220,9 @@ msgstr ""
"<strong>Opmerking:</strong> sommige providers vertragen e-mails maximaal 15 "
"minuten om spam te voorkomen. Wees alstublieft geduldig."
#: src/gettext_strings.rs:46
msgid "Send Verification Email"
msgstr "Verzend verificatie-e-mail"
#: src/gettext_strings.rs:47
msgid ""
"This key contains one identity that could not be parsed as an email address."
"<br /> This identity can't be published on <span class=\"brand\">keys."
@@ -290,7 +234,6 @@ msgstr ""
"\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" "
"target=\"_blank\">Waarom?</a>)"
#: src/gettext_strings.rs:48
msgid ""
"This key contains {{ count_unparsed }} identities that could not be parsed "
"as an email address.<br /> These identities can't be published on <span "
@@ -302,7 +245,6 @@ msgstr ""
"gepubliceerd op <span class=\"brand\">keys.openpgp.org</span>. (<a href=\"/"
"about/faq#non-email-uids\" target=\"_blank\">Waarom?</a>)"
#: src/gettext_strings.rs:49
msgid ""
"This key contains one revoked identity, which is not published. (<a href=\"/"
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -310,7 +252,6 @@ msgstr ""
"Deze sleutel bevat één ingetrokken identiteit, die niet is gepubliceerd. (<a "
"href=\"/about/faq#revoked-uids\" target=\"_blank\">Waarom?</a>)"
#: src/gettext_strings.rs:50
msgid ""
"This key contains {{ count_revoked }} revoked identities, which are not "
"published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -319,11 +260,9 @@ msgstr ""
"gepubliceerd. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Waarom?"
"</a>)"
#: src/gettext_strings.rs:51
msgid "Your keys have been successfully uploaded:"
msgstr "Uw sleutels zijn succesvol geüpload:"
#: src/gettext_strings.rs:52
msgid ""
"<strong>Note:</strong> To make keys searchable by email address, you must "
"upload them individually."
@@ -331,11 +270,9 @@ msgstr ""
"<strong>Opmerking:</strong> Om sleutels doorzoekbaar te maken op e-"
"mailadres, moet u deze afzonderlijk uploaden."
#: src/gettext_strings.rs:53
msgid "Verifying your email address…"
msgstr "Uw e-mailadres verifiëren..."
#: src/gettext_strings.rs:54
msgid ""
"If the process doesn't complete after a few seconds, please <input type="
"\"submit\" class=\"textbutton\" value=\"click here\" />."
@@ -343,15 +280,12 @@ msgstr ""
"Als het proces na enkele seconden niet is voltooid, <input type=\"submit\" "
"class=\"textbutton\" value=\"click here\" /> alstublieft."
#: src/gettext_strings.rs:56
msgid "Manage your key on {{domain}}"
msgstr "Beheer uw sleutel op {{domain}}"
#: src/gettext_strings.rs:58
msgid "Hi,"
msgstr "Hallo,"
#: src/gettext_strings.rs:59
msgid ""
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
@@ -359,15 +293,12 @@ msgstr ""
"Dit is een automatisch bericht van <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
#: src/gettext_strings.rs:60
msgid "If you didn't request this message, please ignore it."
msgstr "Als je dit bericht niet hebt opgevraagd, negeer het dan."
#: src/gettext_strings.rs:61
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
msgstr "OpenPGP sleutel: <tt>{{primary_fp}}</tt>"
#: src/gettext_strings.rs:62
msgid ""
"To manage and delete listed addresses on this key, please follow the link "
"below:"
@@ -375,7 +306,6 @@ msgstr ""
"Om de vermelde adressen op deze sleutel te beheren en/of te verwijderen, "
"volg de onderstaande link:"
#: src/gettext_strings.rs:63
msgid ""
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
"a>."
@@ -383,27 +313,21 @@ msgstr ""
"Je kunt meer informatie vinden op<a href=\"{{base_uri}}/about\"> {{domain}}/"
"over</a>."
#: src/gettext_strings.rs:64
msgid "distributing OpenPGP keys since 2019"
msgstr "distributie van OpenPGP-sleutels sinds 2019"
#: src/gettext_strings.rs:67
msgid "This is an automated message from {{domain}}."
msgstr "Dit is een automatisch bericht van {{domain}}."
#: src/gettext_strings.rs:69
msgid "OpenPGP key: {{primary_fp}}"
msgstr "OpenPGP sleutel: {{primary_fp}}"
#: src/gettext_strings.rs:71
msgid "You can find more info at {{base_uri}}/about"
msgstr "Je kunt meer informatie vinden op {{base_uri}}/over"
#: src/gettext_strings.rs:74
msgid "Verify {{userid}} for your key on {{domain}}"
msgstr "Verifieer {{userid}} voor uw sleutel op {{domein}}"
msgstr "Verifieer {{userid}} voor uw sleutel op {{domain}}"
#: src/gettext_strings.rs:80
msgid ""
"To let others find this key from your email address \"<a rel=\"nofollow\" "
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
@@ -413,7 +337,6 @@ msgstr ""
"e-mailadres \"<a rel=\"nofollow\" href=\"#\" style=\"text-decoration:none; "
"color: #333\">{{userid}}</a>\":"
#: src/gettext_strings.rs:88
msgid ""
"To let others find this key from your email address \"{{userid}}\",\n"
"please follow the link below:"
@@ -421,42 +344,64 @@ msgstr ""
"Om anderen deze sleutel te laten vinden van uw e-mailadres \"{{userid}}\",\n"
"klik op de onderstaande link:"
#: src/web/manage.rs:103
msgid "No key found for fingerprint {}"
msgstr "Geen sleutel voor adres: {}"
msgid "No key found for key id {}"
msgstr "Geen sleutel voor adres: {}"
msgid "No key found for email address {}"
msgstr "Geen sleutel voor adres: {}"
msgid "Search by Short Key ID is not supported."
msgstr ""
msgid "Invalid search query."
msgstr ""
msgctxt "Subject for verification email, {0} = userid, {1} = keyserver domain"
msgid "Verify {0} for your key on {1}"
msgstr "Verifieer {0} voor uw sleutel op {1}"
msgctxt "Subject for manage email, {} = keyserver domain"
msgid "Manage your key on {}"
msgstr "Beheer uw sleutel op {}"
msgid "This link is invalid or expired"
msgstr "Deze link is ongeldig of verlopen"
#: src/web/manage.rs:129
msgid "Malformed address: {address}"
msgstr "Ongeldig adres: {adres}"
#, fuzzy
msgid "Malformed address: {}"
msgstr "Ongeldig adres: {}"
#: src/web/manage.rs:136
msgid "No key for address: {address}"
msgstr "Geen sleutel voor adres: {address}"
#, fuzzy
msgid "No key for address: {}"
msgstr "Geen sleutel voor adres: {}"
#: src/web/manage.rs:152
msgid "A request has already been sent for this address recently."
msgstr "Er is onlangs al een verzoek voor dit adres verzonden."
#: src/web/vks.rs:111
msgid "Parsing of key data failed."
msgstr "Ontleden van sleutel gegevens is mislukt."
#: src/web/vks.rs:120
msgid "Whoops, please don't upload secret keys!"
msgstr "Oeps, upload geen geheime sleutels!"
#: src/web/vks.rs:133
msgid "No key uploaded."
msgstr "Geen sleutel geüpload."
#: src/web/vks.rs:177
msgid "Error processing uploaded key."
msgstr "Fout bij het verwerken van geüploade sleutel."
#: src/web/vks.rs:247
msgid "Upload session expired. Please try again."
msgstr "Uploadsessie is verlopen. Probeer het alstublieft opnieuw."
#: src/web/vks.rs:284
msgid "Invalid verification link."
msgstr "Ongeldige verificatielink."
#~ msgid ""
#~ "<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
#~ "launch ✨</a> (2019-09-12)"
#~ msgstr ""
#~ "<a href=\"/about/news#2019-09-12-three-months-later\">Drie maanden na "
#~ "lancering ✨</a> (12-09-2019)"

View File

@@ -17,37 +17,21 @@ msgstr ""
"%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n"
"%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
#: src/mail.rs:107
msgctxt "Subject for verification email"
msgid "Verify {userid} for your key on {domain}"
msgstr "Zweryfikuj {userid} dla twojego klucza na {domain}"
#: src/mail.rs:140
msgctxt "Subject for manage email"
msgid "Manage your key on {domain}"
msgstr "Zarządzaj swoim kluczem na {domain}"
#: src/gettext_strings.rs:4
msgid "Error"
msgstr "Błąd"
#: src/gettext_strings.rs:5
msgid "Looks like something went wrong :("
msgstr "Wygląda na to, że coś poszło nie tak :("
#: src/gettext_strings.rs:6
msgid "Error message: {{ internal_error }}"
msgstr "Komunikat błędu: {{ internal_error }}"
#: src/gettext_strings.rs:7
msgid "There was an error with your request:"
msgstr "Podczas przetwarzania twojego zapytania wystąpił błąd:"
#: src/gettext_strings.rs:8
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
msgstr "Znaleźliśmy wpis dla <span class=\"email\">{{ query }}</span>:"
#: src/gettext_strings.rs:9
msgid ""
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
@@ -57,19 +41,15 @@ msgstr ""
"openpgp.org</span> z twojego oprogramowania OpenPGP.<br /> Sprawdź nasz <a "
"href=\"/about/usage\">poradnik</a> aby dowiedzieć się więcej."
#: src/gettext_strings.rs:10
msgid "debug info"
msgstr "informacje diagnostyczne"
#: src/gettext_strings.rs:11
msgid "Search by Email Address / Key ID / Fingerprint"
msgstr "Wyszukaj po adresie e-mail / ID klucza / odcisku"
#: src/gettext_strings.rs:12
msgid "Search"
msgstr "Wyszukaj"
#: src/gettext_strings.rs:13
msgid ""
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
"a> your key."
@@ -77,31 +57,23 @@ msgstr ""
"Możesz także <a href=\"/upload\">wgrać</a> lub <a href=\"/manage"
"\">zarządzać</a> swoim kluczem."
#: src/gettext_strings.rs:14
msgid "Find out more <a href=\"/about\">about this service</a>."
msgstr "Dowiedz się więcej <a href=\"/about\">o tej usłudze</a>."
#: src/gettext_strings.rs:15
msgid "News:"
msgstr "Nowości:"
#: src/gettext_strings.rs:16
msgid ""
"<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
"launch ✨</a> (2019-09-12)"
"<a href=\"/about/news#2019-11-12-celebrating-100k\">Celebrating 100.000 "
"verified addresses! 📈</a> (2019-11-12)"
msgstr ""
"<a href=\"/about/news#2019-09-12-three-months-later\">Trzy miesiące od "
"startu ✨</a> (2019-09-12)"
#: src/gettext_strings.rs:17
msgid "v{{ version }} built from"
msgstr "v{{ version }} zbudowana z "
#: src/gettext_strings.rs:18
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
msgstr "Używa <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
#: src/gettext_strings.rs:19
msgid ""
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
@@ -109,23 +81,18 @@ msgstr ""
"Tło pobrane z <a href=\"https://www.toptal.com/designers/subtlepatterns/"
"subtle-grey/\">Subtle Patterns</a> na licencji CC BY-SA 3.0"
#: src/gettext_strings.rs:20
msgid "Maintenance Mode"
msgstr "Tryb Serwisowania"
#: src/gettext_strings.rs:21
msgid "Manage your key"
msgstr "Zarządzaj kluczem"
#: src/gettext_strings.rs:22
msgid "Enter any verified email address for your key"
msgstr "Wpisz dowolny zweryfikowany adres swojego klucza"
#: src/gettext_strings.rs:23
msgid "Send link"
msgstr "Wyślij łącze"
#: src/gettext_strings.rs:24
msgid ""
"We will send you an email with a link you can use to remove any of your "
"email addresses from search."
@@ -133,7 +100,6 @@ msgstr ""
"Wyślemy ci e-mail z łączem którego możesz użyć do usunięcia dowolnego adresu "
"z wyszukiwania."
#: src/gettext_strings.rs:25
msgid ""
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -141,16 +107,13 @@ msgstr ""
"Zarządzanie kluczem <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>"
#: src/gettext_strings.rs:26
msgid "Your key is published with the following identity information:"
msgstr ""
"Twój klucz został opublikowany z następującymi informacjami tożsamości:"
#: src/gettext_strings.rs:27
msgid "Delete"
msgstr "Usuń"
#: src/gettext_strings.rs:28
msgid ""
"Clicking \"delete\" on any address will remove it from this key. It will no "
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
@@ -160,7 +123,6 @@ msgstr ""
"on wyświetlany podczas wyszukiwania.<br /> Aby dodać inny adres <a href=\"/"
"upload\">wyślij</a> klucz ponownie."
#: src/gettext_strings.rs:29
msgid ""
"Your key is published as only non-identity information. (<a href=\"/about\" "
"target=\"_blank\">What does this mean?</a>)"
@@ -168,11 +130,9 @@ msgstr ""
"Twój klucz jest opublikowany bez informacji o tożsamości. (<a href=\"/about"
"\" target=\"_blank\">Co to oznacza?</a>)"
#: src/gettext_strings.rs:30
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
msgstr "Aby dodać adres, <a href=\"/upload\">wyślij</a> klucz ponownie."
#: src/gettext_strings.rs:31
msgid ""
"We have sent an email with further instructions to <span class=\"email"
"\">{{ address }}</span>."
@@ -180,11 +140,9 @@ msgstr ""
"Wysłaliśmy e-mail z dalszymi instrukcjami pod <span class=\"email"
"\">{{ address }}</span>."
#: src/gettext_strings.rs:32
msgid "This address has already been verified."
msgstr "Adres został już zweryfikowany."
#: src/gettext_strings.rs:33
msgid ""
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
@@ -194,15 +152,12 @@ msgstr ""
"opublikowany dla tożsamości <a href=\"{{userid_link}}\" target=\"_blank"
"\"><span class=\"email\">{{ userid }}</span></a>."
#: src/gettext_strings.rs:34
msgid "Upload your key"
msgstr "Wyślij swój klucz"
#: src/gettext_strings.rs:35
msgid "Upload"
msgstr "Wyślij"
#: src/gettext_strings.rs:36
msgid ""
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
@@ -211,7 +166,6 @@ msgstr ""
"about\">wprowadzenie</a> oraz <a target=\"_blank\" href=\"/about/usage"
"\">instrukcję użytkowania</a>."
#: src/gettext_strings.rs:37
msgid ""
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -219,11 +173,9 @@ msgstr ""
"Wysłałeś klucz <span class=\"fingerprint\"><a href=\"{{ key_link }}\" target="
"\"_blank\">{{ key_fpr }}</a></span>."
#: src/gettext_strings.rs:38
msgid "This key is revoked."
msgstr "Ten klucz jest unieważniony."
#: src/gettext_strings.rs:39
msgid ""
"It is published without identity information and can't be made available for "
"search by email address (<a href=\"/about\" target=\"_blank\">what does this "
@@ -233,7 +185,6 @@ msgstr ""
"pomocą adresu e-mail (<a href=\"/about\" target=\"_blank\">co to znaczy?</"
"a>)."
#: src/gettext_strings.rs:40
msgid ""
"This key is now published with the following identity information (<a href="
"\"/about\" target=\"_blank\">what does this mean?</a>):"
@@ -241,11 +192,9 @@ msgstr ""
"Ten klucz jest teraz opublikowany z następującymi informacjami tożsamości "
"(<a href=\"/about\" target=\"_blank\">co to znaczy?</a>);"
#: src/gettext_strings.rs:41
msgid "Published"
msgstr "Opublikowany"
#: src/gettext_strings.rs:42
msgid ""
"This key is now published with only non-identity information. (<a href=\"/"
"about\" target=\"_blank\">What does this mean?</a>)"
@@ -253,7 +202,6 @@ msgstr ""
"Ten klucz jest opublikowany bez informacji o tożsamości (<a href=\"/about\" "
"target=\"_blank\">Co to oznacza?</a>)"
#: src/gettext_strings.rs:43
msgid ""
"To make the key available for search by email address, you can verify it "
"belongs to you:"
@@ -261,11 +209,9 @@ msgstr ""
"Aby można było wyszukać ten klucz używając adresu e-mail, zweryfikuj, że "
"należy do ciebie:"
#: src/gettext_strings.rs:44
msgid "Verification Pending"
msgstr "Czekam na weryfikację"
#: src/gettext_strings.rs:45
msgid ""
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
"prevent spam. Please be patient."
@@ -273,11 +219,9 @@ msgstr ""
"<strong>Informacja:</strong> Niektórzy dostawcy poczty opóźniają e-maile do "
"15 minut aby zapobiegać spamowi. Proszę być cierpliwym."
#: src/gettext_strings.rs:46
msgid "Send Verification Email"
msgstr "Wyślij E-mail Weryfikacyjny"
#: src/gettext_strings.rs:47
msgid ""
"This key contains one identity that could not be parsed as an email address."
"<br /> This identity can't be published on <span class=\"brand\">keys."
@@ -289,7 +233,6 @@ msgstr ""
"\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target="
"\"_blank\">Dlaczego?</a>)"
#: src/gettext_strings.rs:48
msgid ""
"This key contains {{ count_unparsed }} identities that could not be parsed "
"as an email address.<br /> These identities can't be published on <span "
@@ -301,7 +244,6 @@ msgstr ""
"<span class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-"
"email-uids\" target=\"_blank\">Dlaczego?</a>)"
#: src/gettext_strings.rs:49
msgid ""
"This key contains one revoked identity, which is not published. (<a href=\"/"
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -310,7 +252,6 @@ msgstr ""
"opublikowana. (<a href=\"/about/faq#revoked-uids\" target=\"_blank"
"\">Dlaczego?</a>)"
#: src/gettext_strings.rs:50
msgid ""
"This key contains {{ count_revoked }} revoked identities, which are not "
"published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -319,11 +260,9 @@ msgstr ""
"zostały opublikowane. (<a href=\"/about/faq#revoked-uids\" target=\"_blank"
"\">Dlaczego?</a>)"
#: src/gettext_strings.rs:51
msgid "Your keys have been successfully uploaded:"
msgstr "Twoje klucze zostały pomyślnie wysłane:"
#: src/gettext_strings.rs:52
msgid ""
"<strong>Note:</strong> To make keys searchable by email address, you must "
"upload them individually."
@@ -331,11 +270,9 @@ msgstr ""
"<strong>Informacja:</strong> Aby można było wyszukać kluczy za pomocą adresu "
"e-mail, wyślij je pojedynczo."
#: src/gettext_strings.rs:53
msgid "Verifying your email address…"
msgstr "Weryfikowanie twojego adresu e-mail..."
#: src/gettext_strings.rs:54
msgid ""
"If the process doesn't complete after a few seconds, please <input type="
"\"submit\" class=\"textbutton\" value=\"click here\" />."
@@ -343,15 +280,12 @@ msgstr ""
"Jeśli proces nie zakończy się po kilku sekundach proszę <input type=\"submit"
"\" class=\"textbutton\" value=\"kliknij tutaj\"/>."
#: src/gettext_strings.rs:56
msgid "Manage your key on {{domain}}"
msgstr "Zarządzaj swoim kluczem na {{domain}}"
#: src/gettext_strings.rs:58
msgid "Hi,"
msgstr "Cześć,"
#: src/gettext_strings.rs:59
msgid ""
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
@@ -359,21 +293,17 @@ msgstr ""
"To jest automatyczna wiadomość od <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
#: src/gettext_strings.rs:60
msgid "If you didn't request this message, please ignore it."
msgstr "Jeśli nie prosiłeś o tą wiadomość, zignorują ją."
#: src/gettext_strings.rs:61
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
msgstr "Klucz OpenPGP: <tt>{{primary_fp}}</tt>"
#: src/gettext_strings.rs:62
msgid ""
"To manage and delete listed addresses on this key, please follow the link "
"below:"
msgstr "Aby zarządzać adresami na kluczu lub je usunąć, użyj tego łącza:"
#: src/gettext_strings.rs:63
msgid ""
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
"a>."
@@ -381,27 +311,21 @@ msgstr ""
"Więcej informacji znajdziesz na <a href=\"{{base_uri}}/about\">{{domain}}/"
"about</a>."
#: src/gettext_strings.rs:64
msgid "distributing OpenPGP keys since 2019"
msgstr "dostarczanie kluczy OpenPGP od 2019"
#: src/gettext_strings.rs:67
msgid "This is an automated message from {{domain}}."
msgstr "To automatyczna wiadomość z {{domain}}."
#: src/gettext_strings.rs:69
msgid "OpenPGP key: {{primary_fp}}"
msgstr "Klucz OpenPGP: {{primary_fp}}"
#: src/gettext_strings.rs:71
msgid "You can find more info at {{base_uri}}/about"
msgstr "Znajdziesz więcej informacji na {{base_uri}}/about"
#: src/gettext_strings.rs:74
msgid "Verify {{userid}} for your key on {{domain}}"
msgstr "Zweryfikuj {{userid}} dla klucza na {{domain}}"
#: src/gettext_strings.rs:80
msgid ""
"To let others find this key from your email address \"<a rel=\"nofollow\" "
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
@@ -411,7 +335,6 @@ msgstr ""
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
"proszę kliknąć w łącze poniżej:"
#: src/gettext_strings.rs:88
msgid ""
"To let others find this key from your email address \"{{userid}}\",\n"
"please follow the link below:"
@@ -419,42 +342,64 @@ msgstr ""
"Aby inni mogli wyszukać twój klucz przez adres e-mail \"{{userid}}\", proszę "
"kliknąć w łącze poniżej:"
#: src/web/manage.rs:103
msgid "No key found for fingerprint {}"
msgstr "Brak klucza dla adresu: {}"
msgid "No key found for key id {}"
msgstr "Brak klucza dla adresu: {}"
msgid "No key found for email address {}"
msgstr "Brak klucza dla adresu: {}"
msgid "Search by Short Key ID is not supported."
msgstr ""
msgid "Invalid search query."
msgstr ""
msgctxt "Subject for verification email, {0} = userid, {1} = keyserver domain"
msgid "Verify {0} for your key on {1}"
msgstr "Zweryfikuj {0} dla twojego klucza na {1}"
msgctxt "Subject for manage email, {} = keyserver domain"
msgid "Manage your key on {}"
msgstr "Zarządzaj swoim kluczem na {}"
msgid "This link is invalid or expired"
msgstr "Łącze jest nieważne lub wygasło"
#: src/web/manage.rs:129
msgid "Malformed address: {address}"
msgstr "Nieprawidłowy adres: {address}"
#, fuzzy
msgid "Malformed address: {}"
msgstr "Nieprawidłowy adres: {}"
#: src/web/manage.rs:136
msgid "No key for address: {address}"
msgstr "Brak klucza dla adresu: {address}"
#, fuzzy
msgid "No key for address: {}"
msgstr "Brak klucza dla adresu: {}"
#: src/web/manage.rs:152
msgid "A request has already been sent for this address recently."
msgstr "Zapytanie o ten adres zostało niedawno wysłane."
#: src/web/vks.rs:111
msgid "Parsing of key data failed."
msgstr "Przetwarzanie danych klucza się nie powiodło."
#: src/web/vks.rs:120
msgid "Whoops, please don't upload secret keys!"
msgstr "Ups, nie wysyłaj kluczy prywatnych!"
#: src/web/vks.rs:133
msgid "No key uploaded."
msgstr "Klucz nie został wysłany."
#: src/web/vks.rs:177
msgid "Error processing uploaded key."
msgstr "Błąd przetwarzania wysłanego klucza."
#: src/web/vks.rs:247
msgid "Upload session expired. Please try again."
msgstr "Sesja wysyłania wygasła. Spróbuj ponownie."
#: src/web/vks.rs:284
msgid "Invalid verification link."
msgstr "Nieprawidłowy link weryfikacyjny."
#~ msgid ""
#~ "<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
#~ "launch ✨</a> (2019-09-12)"
#~ msgstr ""
#~ "<a href=\"/about/news#2019-09-12-three-months-later\">Trzy miesiące od "
#~ "startu ✨</a> (2019-09-12)"

403
po/hagrid/ro.po Normal file
View File

@@ -0,0 +1,403 @@
#
# Translators:
# Simona Iacob <s@zp1.net>, 2021
#
msgid ""
msgstr ""
"Project-Id-Version: hagrid\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2019-09-27 18:05+0000\n"
"Last-Translator: Simona Iacob <s@zp1.net>, 2021\n"
"Language-Team: Romanian (https://www.transifex.com/otf/teams/102430/ro/)\n"
"Language: ro\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?"
"2:1));\n"
msgid "Error"
msgstr "Eroare"
msgid "Looks like something went wrong :("
msgstr "Se pare că ceva a mers prost :("
msgid "Error message: {{ internal_error }}"
msgstr "Mesaj de eroare: {{ internal_error }}"
msgid "There was an error with your request:"
msgstr "A existat o eroare în cererea dumneavoastră:"
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
msgstr "Am găsit o intrare pentru <span class=\"email\">{{ query }}</span>:"
msgid ""
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
"our <a href=\"/about/usage\">usage guide</a> for details."
msgstr ""
"<strong>Indicație:</strong> Este mai convenabil să utilizați <span class="
"\"brand\">keys.openpgp.org</span> din software-ul OpenPGP.<br /> Consultați "
"ghidul nostru <a href=\"/about/usage\">de utilizare</a> pentru detalii."
msgid "debug info"
msgstr "informații de depanare"
msgid "Search by Email Address / Key ID / Fingerprint"
msgstr "Căutare după adresa de e-mail / ID cheie / amprentă digitală"
msgid "Search"
msgstr "Căutare"
msgid ""
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
"a> your key."
msgstr ""
"De asemenea, puteți <a href=\"/upload\">să vă încărcați</a> ori <a href=\"/"
"manage\">să vă gestionați</a> cheia."
msgid "Find out more <a href=\"/about\">about this service</a>."
msgstr "Aflați mai multe <a href=\"/about\">despre acest serviciu</a>."
msgid "News:"
msgstr "Știri:"
msgid ""
"<a href=\"/about/news#2019-11-12-celebrating-100k\">Celebrating 100.000 "
"verified addresses! 📈</a> (2019-11-12)"
msgstr ""
"<a href=\"/about/news#2019-11-12-celebrating-100k\">Sărbătorim 100.000 de "
"adrese verificate! 📈</a> (2019-11-12)"
msgid "v{{ version }} built from"
msgstr "v{{ version }} construit din"
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
msgstr "Alimentat de <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
msgid ""
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
msgstr ""
"Imagine de fundal preluată de la <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> sub CC BY-SA 3.0"
msgid "Maintenance Mode"
msgstr "Modul de întreținere"
msgid "Manage your key"
msgstr "Gestionați cheia dumneavoastră"
msgid "Enter any verified email address for your key"
msgstr ""
"Introduceți orice adresă de e-mail verificată pentru cheia dumneavoastră"
msgid "Send link"
msgstr "Trimite link"
msgid ""
"We will send you an email with a link you can use to remove any of your "
"email addresses from search."
msgstr ""
"Vă vom trimite un e-mail cu un link pe care îl puteți utiliza pentru a "
"elimina oricare dintre adresele dvs. de e-mail din căutare."
msgid ""
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
msgstr ""
"Gestionarea cheii <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
msgid "Your key is published with the following identity information:"
msgstr "Cheia dvs. este publicată cu următoarele informații de identitate:"
msgid "Delete"
msgstr "Ștergeți"
msgid ""
"Clicking \"delete\" on any address will remove it from this key. It will no "
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
"\">upload</a> the key again."
msgstr ""
"Dacă faceți clic pe \"delete\" pe orice adresă, aceasta va fi eliminată din "
"această cheie. Aceasta nu va mai apărea într-o căutare.<br /> Pentru a "
"adăuga o altă adresă, <a href=\"/upload\">încărcați</a> din nou cheia."
msgid ""
"Your key is published as only non-identity information. (<a href=\"/about\" "
"target=\"_blank\">What does this mean?</a>)"
msgstr ""
"Cheia dvs. este publicată doar ca informație neidentitară. (<a href=\"/about"
"\" target=\"_blank\">Ce înseamnă acest lucru?</a>)"
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
msgstr ""
"Pentru a adăuga o adresă, <a href=\"/upload\">încărcați</a> din nou cheia."
msgid ""
"We have sent an email with further instructions to <span class=\"email"
"\">{{ address }}</span>."
msgstr ""
"Am trimis un e-mail cu instrucțiuni suplimentare la <span class=\"email"
"\">{{ address }}</span>."
msgid "This address has already been verified."
msgstr "Această adresă a fost deja verificată."
msgid ""
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
"\"email\">{{ userid }}</span></a>."
msgstr ""
"Cheia dvs. <span class=\"fingerprint\">{{ key_fpr }}</span> este acum "
"publicată pentru identitate <a href=\"{{userid_link}}\" target=\"_blank"
"\"><span class=\"email\">{{ userid }}</span></a>."
msgid "Upload your key"
msgstr "Încărcați cheia dumneavoastră"
msgid "Upload"
msgstr "Încărcați"
msgid ""
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
msgstr ""
"Aveți nevoie de mai multe informații? Consultați ghidul nostru <a target="
"\"_blank\" href=\"/about\">introductiv</a> și <a target=\"_blank\" href=\"/"
"about/usage\">de utilizare</a>."
msgid ""
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
msgstr ""
"Ați încărcat cheia <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
msgid "This key is revoked."
msgstr "Această cheie este revocată."
msgid ""
"It is published without identity information and can't be made available for "
"search by email address (<a href=\"/about\" target=\"_blank\">what does this "
"mean?</a>)."
msgstr ""
"Acesta este publicat fără informații privind identitatea și nu poate fi "
"disponibil pentru căutare după adresa de e-mail (<a href=\"/about\" target="
"\"_blank\">ce înseamnă acest lucru?</a>)."
msgid ""
"This key is now published with the following identity information (<a href="
"\"/about\" target=\"_blank\">what does this mean?</a>):"
msgstr ""
"Această cheie este acum publicată cu următoarele informații de identitate "
"(<a href=\"/about\" target=\"_blank\">ce înseamnă acest lucru?</a>):"
msgid "Published"
msgstr "Publicat"
msgid ""
"This key is now published with only non-identity information. (<a href=\"/"
"about\" target=\"_blank\">What does this mean?</a>)"
msgstr ""
"Această cheie este acum publicată doar cu informații care nu au legătură cu "
"identitatea. (<a href=\"/about\" target=\"_blank\">Ce înseamnă acest lucru?</"
"a>)"
msgid ""
"To make the key available for search by email address, you can verify it "
"belongs to you:"
msgstr ""
"Pentru ca cheia să fie disponibilă pentru căutare după adresa de e-mail, "
"puteți verifica dacă vă aparține:"
msgid "Verification Pending"
msgstr "Verificare în așteptare"
msgid ""
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
"prevent spam. Please be patient."
msgstr ""
"<strong>Notă:</strong> Unii furnizori întârzie e-mailurile cu până la 15 "
"minute pentru a preveni spam-ul. Vă rugăm să aveți răbdare."
msgid "Send Verification Email"
msgstr "Trimiteți e-mail de verificare"
msgid ""
"This key contains one identity that could not be parsed as an email address."
"<br /> This identity can't be published on <span class=\"brand\">keys."
"openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target=\"_blank"
"\">Why?</a>)"
msgstr ""
"Această cheie conține o identitate care nu a putut fi interpretată ca o "
"adresă de e-mail.<br /> Această identitate nu poate fi publicată pe <span "
"class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-"
"uids\" target=\"_blank\">De ce?</a>)"
msgid ""
"This key contains {{ count_unparsed }} identities that could not be parsed "
"as an email address.<br /> These identities can't be published on <span "
"class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-"
"uids\" target=\"_blank\">Why?</a>)"
msgstr ""
"Această cheie conține {{ count_unparsed }} identități care nu au putut fi "
"analizate ca adresă de e-mail.<br /> Aceste identități nu pot fi publicate "
"pe <span class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-"
"email-uids\" target=\"_blank\">De ce?</a>)"
msgid ""
"This key contains one revoked identity, which is not published. (<a href=\"/"
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
msgstr ""
"Această cheie conține o identitate revocată, care nu este publicată. (<a "
"href=\"/about/faq#revoked-uids\" target=\"_blank\">De ce?</a>)"
msgid ""
"This key contains {{ count_revoked }} revoked identities, which are not "
"published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
msgstr ""
"Această cheie conține {{ count_revoked }} identități revocate, care nu sunt "
"publicate. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">De ce?</a>)"
msgid "Your keys have been successfully uploaded:"
msgstr "Cheile dvs. au fost încărcate cu succes:"
msgid ""
"<strong>Note:</strong> To make keys searchable by email address, you must "
"upload them individually."
msgstr ""
"<strong>Notă:</strong> Pentru ca cheile să poată fi căutate în funcție de "
"adresa de e-mail, trebuie să le încărcați individual."
msgid "Verifying your email address…"
msgstr "Verificarea adresei dvs. de e-mail..."
msgid ""
"If the process doesn't complete after a few seconds, please <input type="
"\"submit\" class=\"textbutton\" value=\"click here\" />."
msgstr ""
"Dacă procesul nu se finalizează după câteva secunde, vă rugăm să <input type="
"\"submit\" class=\"textbutton\" value=\"click here\" />."
msgid "Manage your key on {{domain}}"
msgstr "Gestionați cheia dvs. pe {{domain}}"
msgid "Hi,"
msgstr "Salut,"
msgid ""
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
msgstr ""
"Acesta este un mesaj automat de la <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
msgid "If you didn't request this message, please ignore it."
msgstr "Dacă nu ați cerut acest mesaj, vă rugăm să îl ignorați."
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
msgstr "OpenPGP key: <tt>{{primary_fp}}</tt>"
msgid ""
"To manage and delete listed addresses on this key, please follow the link "
"below:"
msgstr ""
"Pentru a gestiona și șterge adresele listate pe această cheie, vă rugăm să "
"urmați linkul de mai jos:"
msgid ""
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
"a>."
msgstr ""
"Puteți găsi mai multe informații la <a href=\"{{base_uri}}/about"
"\">{{domain}}/about</a>."
msgid "distributing OpenPGP keys since 2019"
msgstr "distribuirea cheilor OpenPGP din 2019"
msgid "This is an automated message from {{domain}}."
msgstr "Acesta este un mesaj automat de la {{domain}}."
msgid "OpenPGP key: {{primary_fp}}"
msgstr "OpenPGP cheie: {{primary_fp}}"
msgid "You can find more info at {{base_uri}}/about"
msgstr "Puteți găsi mai multe informații la {{base_uri}}/about"
msgid "Verify {{userid}} for your key on {{domain}}"
msgstr "Verificați {{userid}} pentru cheia dvs. pe {{domain}}"
msgid ""
"To let others find this key from your email address \"<a rel=\"nofollow\" "
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
"please click the link below:"
msgstr ""
"Pentru a permite altora să găsească această cheie de la adresa dvs. de e-"
"mail \"<a rel=\"nofollow\" href=\"#\" style=\"text-decoration:none; color: "
"#333\">{{userid}}</a>\", vă rugăm să faceți clic pe link-ul de mai jos:"
msgid ""
"To let others find this key from your email address \"{{userid}}\",\n"
"please follow the link below:"
msgstr ""
"Pentru a permite altora să găsească această cheie de la adresa dvs. de e-"
"mail \"{{userid}}\",\n"
"vă rugăm să urmați linkul de mai jos:"
msgid "No key found for fingerprint {}"
msgstr "Nu s-a găsit nicio cheie pentru amprenta digitală {}"
msgid "No key found for key id {}"
msgstr "Nu s-a găsit nicio cheie pentru cheia id {}"
msgid "No key found for email address {}"
msgstr "Nu s-a găsit nicio cheie pentru adresa de e-mail {}"
msgid "Search by Short Key ID is not supported."
msgstr "Căutarea după ID-ul cheii scurte nu este acceptată."
msgid "Invalid search query."
msgstr "Interogare de căutare invalidă."
msgctxt "Subject for verification email, {0} = userid, {1} = keyserver domain"
msgid "Verify {0} for your key on {1}"
msgstr "Verificați {0} pentru cheia dvs. pe {1}"
msgctxt "Subject for manage email, {} = keyserver domain"
msgid "Manage your key on {}"
msgstr "Gestionați cheia dvs. pe {}"
msgid "This link is invalid or expired"
msgstr "Acest link nu este valabil sau a expirat"
#, fuzzy
msgid "Malformed address: {}"
msgstr "Adresa malformată: {}"
#, fuzzy
msgid "No key for address: {}"
msgstr "Nu există cheie pentru adresă: {}"
msgid "A request has already been sent for this address recently."
msgstr "O cerere a fost deja trimisă recent pentru această adresă."
msgid "Parsing of key data failed."
msgstr "Analiza datelor cheie a eșuat."
msgid "Whoops, please don't upload secret keys!"
msgstr "Whoops, vă rugăm să nu încărcați chei secrete!"
msgid "No key uploaded."
msgstr "Nu a fost încărcată nicio cheie."
msgid "Error processing uploaded key."
msgstr "Eroare de procesare a cheii încărcate."
msgid "Upload session expired. Please try again."
msgstr "Sesiunea de încărcare a expirat. Vă rugăm să încercați din nou."
msgid "Invalid verification link."
msgstr "Link de verificare invalid."

View File

@@ -18,37 +18,21 @@ msgstr ""
"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
"%100>=11 && n%100<=14)? 2 : 3);\n"
#: src/mail.rs:107
msgctxt "Subject for verification email"
msgid "Verify {userid} for your key on {domain}"
msgstr "Подтверди {userid} для твоего ключа на {domain}"
#: src/mail.rs:140
msgctxt "Subject for manage email"
msgid "Manage your key on {domain}"
msgstr "Натрой свой ключ на {domain}"
#: src/gettext_strings.rs:4
msgid "Error"
msgstr "Ошибка"
#: src/gettext_strings.rs:5
msgid "Looks like something went wrong :("
msgstr "Похоже, что-то пошло не так :("
#: src/gettext_strings.rs:6
msgid "Error message: {{ internal_error }}"
msgstr "Сообщение об ошибке: {{ internal_error }}"
#: src/gettext_strings.rs:7
msgid "There was an error with your request:"
msgstr "Запрос привёл к ошибке:"
#: src/gettext_strings.rs:8
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
msgstr "Найдена запись для <span class=\"email\">{{ query }}</span>:"
#: src/gettext_strings.rs:9
msgid ""
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
@@ -59,19 +43,15 @@ msgstr ""
"подробной информации смотри <a href=\"/about/usage\">руководство "
"пользования</a>."
#: src/gettext_strings.rs:10
msgid "debug info"
msgstr "отладочная информация"
#: src/gettext_strings.rs:11
msgid "Search by Email Address / Key ID / Fingerprint"
msgstr "Поиск по адресу электронной почты / ID ключа / отпечаткам пальцев"
#: src/gettext_strings.rs:12
msgid "Search"
msgstr "Искать"
#: src/gettext_strings.rs:13
msgid ""
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
"a> your key."
@@ -79,31 +59,23 @@ msgstr ""
"Вы также можете <a href=\"/upload\">загрузить</a> или <a href=\"/manage"
"\">настроить</a> свой ключ."
#: src/gettext_strings.rs:14
msgid "Find out more <a href=\"/about\">about this service</a>."
msgstr "Узнайте больше <a href=\"/about\">об этом сервисе</a>."
#: src/gettext_strings.rs:15
msgid "News:"
msgstr "Новости:"
#: src/gettext_strings.rs:16
msgid ""
"<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
"launch ✨</a> (2019-09-12)"
"<a href=\"/about/news#2019-11-12-celebrating-100k\">Celebrating 100.000 "
"verified addresses! 📈</a> (2019-11-12)"
msgstr ""
"<a href=\"/about/news#2019-09-12-three-months-later\">Спустя три месяца "
"после запуска✨</a> (2019-09-12)"
#: src/gettext_strings.rs:17
msgid "v{{ version }} built from"
msgstr "версия {{ version }} собранная из"
#: src/gettext_strings.rs:18
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
msgstr "работающая на <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
#: src/gettext_strings.rs:19
msgid ""
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
@@ -111,23 +83,18 @@ msgstr ""
"Фоновое изображение, полученное из <a href=\"https://www.toptal.com/"
"designers/subtlepatterns/subtle-grey/\">Subtle Patterns</a> под CC BY-SA 3.0"
#: src/gettext_strings.rs:20
msgid "Maintenance Mode"
msgstr "Режим технического обслуживания"
#: src/gettext_strings.rs:21
msgid "Manage your key"
msgstr "Натрой свой ключ"
#: src/gettext_strings.rs:22
msgid "Enter any verified email address for your key"
msgstr "Введи любой проверенный адрес электронной почты для твоего ключа"
#: src/gettext_strings.rs:23
msgid "Send link"
msgstr "Отправь ссылку"
#: src/gettext_strings.rs:24
msgid ""
"We will send you an email with a link you can use to remove any of your "
"email addresses from search."
@@ -135,7 +102,6 @@ msgstr ""
"Мы вышлем вам письмо со ссылкой, которую вы можете использовать для удаления "
"любого из ваших адресов электронной почты из поиска."
#: src/gettext_strings.rs:25
msgid ""
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -143,15 +109,12 @@ msgstr ""
"Управление ключом <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
#: src/gettext_strings.rs:26
msgid "Your key is published with the following identity information:"
msgstr "Ваш ключ опубликован со следующей идентификационной информацией:"
#: src/gettext_strings.rs:27
msgid "Delete"
msgstr "Удалить"
#: src/gettext_strings.rs:28
msgid ""
"Clicking \"delete\" on any address will remove it from this key. It will no "
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
@@ -161,7 +124,6 @@ msgstr ""
"не будет отображаться в поиске.<br />Чтобы добавить еще один адрес, <a href="
"\"/upload\">загрузи</a>ключ снова."
#: src/gettext_strings.rs:29
msgid ""
"Your key is published as only non-identity information. (<a href=\"/about\" "
"target=\"_blank\">What does this mean?</a>)"
@@ -169,11 +131,9 @@ msgstr ""
"Ключ опубликован как не имеющий идентификационной информации. (<a href=\"/"
"about\" target=\"_blank\">Что это значит?</a>)"
#: src/gettext_strings.rs:30
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
msgstr "Чтобы добавить адрес, <a href=\"/upload\">загрузи</a> ключ снова."
#: src/gettext_strings.rs:31
msgid ""
"We have sent an email with further instructions to <span class=\"email"
"\">{{ address }}</span>."
@@ -181,11 +141,9 @@ msgstr ""
"Мы отправили электронное письмо с дальнейшими инструкциями на <span class="
"\"email\">{{ address }}</span>."
#: src/gettext_strings.rs:32
msgid "This address has already been verified."
msgstr "Этот адрес был уже проверен."
#: src/gettext_strings.rs:33
msgid ""
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
@@ -195,15 +153,12 @@ msgstr ""
"с идентификацией <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
"\"email\">{{ userid }}</span></a>."
#: src/gettext_strings.rs:34
msgid "Upload your key"
msgstr "Загрузка ключа"
#: src/gettext_strings.rs:35
msgid "Upload"
msgstr "Загрузи"
#: src/gettext_strings.rs:36
msgid ""
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
@@ -212,7 +167,6 @@ msgstr ""
"\">введение</a> и <a target=\"_blank\" href=\"/about/usage\">руководство "
"пользования</a>."
#: src/gettext_strings.rs:37
msgid ""
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -220,11 +174,9 @@ msgstr ""
"Загружен ключ <span class=\"fingerprint\"><a href=\"{{ key_link }}\" target="
"\"_blank\">{{ key_fpr }}</a></span>."
#: src/gettext_strings.rs:38
msgid "This key is revoked."
msgstr "Этот ключ отозван."
#: src/gettext_strings.rs:39
msgid ""
"It is published without identity information and can't be made available for "
"search by email address (<a href=\"/about\" target=\"_blank\">what does this "
@@ -234,7 +186,6 @@ msgstr ""
"электронному адресу (<a href=\"/about\" target=\"_blank\">что это значит?</"
"a>)."
#: src/gettext_strings.rs:40
msgid ""
"This key is now published with the following identity information (<a href="
"\"/about\" target=\"_blank\">what does this mean?</a>):"
@@ -242,11 +193,9 @@ msgstr ""
"Этот ключ опубликован со следующей идентификационной информацией (<a href=\"/"
"about\" target=\"_blank\">что это значит?</a>):"
#: src/gettext_strings.rs:41
msgid "Published"
msgstr "Опубликован"
#: src/gettext_strings.rs:42
msgid ""
"This key is now published with only non-identity information. (<a href=\"/"
"about\" target=\"_blank\">What does this mean?</a>)"
@@ -254,7 +203,6 @@ msgstr ""
"Этот ключ опубликован без идентификационной информации. (<a href=\"/about\" "
"target=\"_blank\">Что это значит</a>)"
#: src/gettext_strings.rs:43
msgid ""
"To make the key available for search by email address, you can verify it "
"belongs to you:"
@@ -262,11 +210,9 @@ msgstr ""
"Чтобы сделать ключ доступным для поиска по адресу электронной почты, ты "
"можешь подтвердить, что он принадлежит тебе:"
#: src/gettext_strings.rs:44
msgid "Verification Pending"
msgstr "Ожидает подтверждения"
#: src/gettext_strings.rs:45
msgid ""
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
"prevent spam. Please be patient."
@@ -274,11 +220,9 @@ msgstr ""
"<strong>Примечание:</strong> Некоторые провайдеры задерживают рассылку писем "
"до 15 минут для предотвращения спама. Пожалуйста, наберитесь терпения."
#: src/gettext_strings.rs:46
msgid "Send Verification Email"
msgstr "Отправить проверочное письмо"
#: src/gettext_strings.rs:47
msgid ""
"This key contains one identity that could not be parsed as an email address."
"<br /> This identity can't be published on <span class=\"brand\">keys."
@@ -290,7 +234,6 @@ msgstr ""
"<span class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-"
"email-uids\" target=\"_blank\">Почему?</a>)"
#: src/gettext_strings.rs:48
msgid ""
"This key contains {{ count_unparsed }} identities that could not be parsed "
"as an email address.<br /> These identities can't be published on <span "
@@ -302,7 +245,6 @@ msgstr ""
"быть опубликованы на <span class=\"brand\">keys.openpgp.org</span>. (<a href="
"\"/about/faq#non-email-uids\" target=\"_blank\">Почему?</a>)"
#: src/gettext_strings.rs:49
msgid ""
"This key contains one revoked identity, which is not published. (<a href=\"/"
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -310,7 +252,6 @@ msgstr ""
"Этот ключ содержит отозванную идентификацию, которая не опубликована. (<a "
"href=\"/about/faq#revoked-uids\" target=\"_blank\">Почему?</a>)"
#: src/gettext_strings.rs:50
msgid ""
"This key contains {{ count_revoked }} revoked identities, which are not "
"published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -319,11 +260,9 @@ msgstr ""
"опубликованны. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Почему?"
"</a>)"
#: src/gettext_strings.rs:51
msgid "Your keys have been successfully uploaded:"
msgstr "Твои ключи были успешно загружены:"
#: src/gettext_strings.rs:52
msgid ""
"<strong>Note:</strong> To make keys searchable by email address, you must "
"upload them individually."
@@ -331,11 +270,9 @@ msgstr ""
"<strong>Примечание:</strong> Чтобы сделать ключи доступными для поиска по "
"адресу электронной почты, надо загрузить их по отдельности."
#: src/gettext_strings.rs:53
msgid "Verifying your email address…"
msgstr "Проверка адреса электронной почты..."
#: src/gettext_strings.rs:54
msgid ""
"If the process doesn't complete after a few seconds, please <input type="
"\"submit\" class=\"textbutton\" value=\"click here\" />."
@@ -343,15 +280,12 @@ msgstr ""
"Если процесс не завершится через несколько секунд, пожалуйста <input type="
"\"submit\" class=\"textbutton\" value=\"нажми сюда\"/>."
#: src/gettext_strings.rs:56
msgid "Manage your key on {{domain}}"
msgstr "Натрой свой ключ на {{domain}}"
#: src/gettext_strings.rs:58
msgid "Hi,"
msgstr "Привет,"
#: src/gettext_strings.rs:59
msgid ""
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
@@ -359,15 +293,12 @@ msgstr ""
"Это автоматическое сообщение сообщение от <a href=\"{{base_uri}}\" style="
"\"text-decoration:none; color: #333\">{{domain}}</a>."
#: src/gettext_strings.rs:60
msgid "If you didn't request this message, please ignore it."
msgstr "Если вы не запрашивали это сообщение, проигнорируйте его пожалуйста."
#: src/gettext_strings.rs:61
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
msgstr "OpenPGP ключ: <tt>{{primary_fp}}</tt>"
#: src/gettext_strings.rs:62
msgid ""
"To manage and delete listed addresses on this key, please follow the link "
"below:"
@@ -375,7 +306,6 @@ msgstr ""
"Для настройки и удаления перечисленных на этом ключе адресов перейди по "
"ссылке ниже:"
#: src/gettext_strings.rs:63
msgid ""
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
"a>."
@@ -383,27 +313,21 @@ msgstr ""
"Дополнительную информацию можно найти по адресу <a href=\"{{base_uri}}/about"
"\">{{domain}}/about</a>."
#: src/gettext_strings.rs:64
msgid "distributing OpenPGP keys since 2019"
msgstr "распространение OpenPGP ключей с 2019 года"
#: src/gettext_strings.rs:67
msgid "This is an automated message from {{domain}}."
msgstr "Это автоматическое сообщение сообщение от {{domain}}."
#: src/gettext_strings.rs:69
msgid "OpenPGP key: {{primary_fp}}"
msgstr "OpenPGP ключ: {{primary_fp}}"
#: src/gettext_strings.rs:71
msgid "You can find more info at {{base_uri}}/about"
msgstr "Дополнительную информацию можно найти по адресу {{base_uri}}/about"
#: src/gettext_strings.rs:74
msgid "Verify {{userid}} for your key on {{domain}}"
msgstr "Подтверди {{userid}} для твоего ключа на {{domain}}"
#: src/gettext_strings.rs:80
msgid ""
"To let others find this key from your email address \"<a rel=\"nofollow\" "
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
@@ -413,7 +337,6 @@ msgstr ""
"почты \"<a rel=\"nofollow\" href=\"#\" style=\"text-decoration:none; color: "
"#333\">{{userid}}</a>\", перейдите по ссылке ниже пожалуйста:"
#: src/gettext_strings.rs:88
msgid ""
"To let others find this key from your email address \"{{userid}}\",\n"
"please follow the link below:"
@@ -422,42 +345,64 @@ msgstr ""
"почты \"{{userid}}\",\n"
"перейдите по ссылке ниже пожалуйста:"
#: src/web/manage.rs:103
msgid "No key found for fingerprint {}"
msgstr "Нет ключа для адреса: {}"
msgid "No key found for key id {}"
msgstr "Нет ключа для адреса: {}"
msgid "No key found for email address {}"
msgstr "Нет ключа для адреса: {}"
msgid "Search by Short Key ID is not supported."
msgstr ""
msgid "Invalid search query."
msgstr ""
msgctxt "Subject for verification email, {0} = userid, {1} = keyserver domain"
msgid "Verify {0} for your key on {1}"
msgstr "Подтверди {0} для твоего ключа на {1}"
msgctxt "Subject for manage email, {} = keyserver domain"
msgid "Manage your key on {}"
msgstr "Натрой свой ключ на {}"
msgid "This link is invalid or expired"
msgstr "Эта ссылка неверна или просрочена"
#: src/web/manage.rs:129
msgid "Malformed address: {address}"
msgstr "Некорректный адрес: {address}"
#, fuzzy
msgid "Malformed address: {}"
msgstr "Некорректный адрес: {}"
#: src/web/manage.rs:136
msgid "No key for address: {address}"
msgstr "Нет ключа для адреса: {address}"
#, fuzzy
msgid "No key for address: {}"
msgstr "Нет ключа для адреса: {}"
#: src/web/manage.rs:152
msgid "A request has already been sent for this address recently."
msgstr "На этот адрес уже был недавно отправлен запрос."
#: src/web/vks.rs:111
msgid "Parsing of key data failed."
msgstr "Не удалось распознать данные ключа."
#: src/web/vks.rs:120
msgid "Whoops, please don't upload secret keys!"
msgstr "Оп-ля, пожалуйста не загружай секретные ключи!"
#: src/web/vks.rs:133
msgid "No key uploaded."
msgstr "Ключ не загружен."
#: src/web/vks.rs:177
msgid "Error processing uploaded key."
msgstr "Ошибка обработки загруженного ключа."
#: src/web/vks.rs:247
msgid "Upload session expired. Please try again."
msgstr "Сессия загрузки просрочена. Попробуй снова пожалуйста."
#: src/web/vks.rs:284
msgid "Invalid verification link."
msgstr "Неверная проверочная ссылка."
#~ msgid ""
#~ "<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
#~ "launch ✨</a> (2019-09-12)"
#~ msgstr ""
#~ "<a href=\"/about/news#2019-09-12-three-months-later\">Спустя три месяца "
#~ "после запуска✨</a> (2019-09-12)"

401
po/hagrid/sv.po Normal file
View File

@@ -0,0 +1,401 @@
#
# Translators:
# Felicia Jongleur, 2021
#
msgid ""
msgstr ""
"Project-Id-Version: hagrid\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2019-09-27 18:05+0000\n"
"Last-Translator: Felicia Jongleur, 2021\n"
"Language-Team: Swedish (https://www.transifex.com/otf/teams/102430/sv/)\n"
"Language: sv\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Error"
msgstr "Fel"
msgid "Looks like something went wrong :("
msgstr "Det verkar som att något gick snett :("
msgid "Error message: {{ internal_error }}"
msgstr "Felmeddelande: {{ internal_error }}"
msgid "There was an error with your request:"
msgstr "Det uppstod ett fel med din begäran:"
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
msgstr "Vi hittade en post för <span class=\"email\">{{ query }}</span>:"
msgid ""
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
"our <a href=\"/about/usage\">usage guide</a> for details."
msgstr ""
"<strong>Tips:</strong> Det är enklare att använda <span class=\"brand\">keys."
"openpgp.org</span> från din OpenPGP-mjukvara.<br /> Kolla in vår <a href=\"/"
"about/usage\">användarguide</a> för mer detaljer."
msgid "debug info"
msgstr "felsökningsinfo"
msgid "Search by Email Address / Key ID / Fingerprint"
msgstr "Sök efter e-postadress / nyckel-ID / fingeravtryck"
msgid "Search"
msgstr "Sök"
msgid ""
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
"a> your key."
msgstr ""
"Du kan också <a href=\"/upload\">ladda upp</a> eller <a href=\"/manage"
"\">hantera</a> din nyckel."
msgid "Find out more <a href=\"/about\">about this service</a>."
msgstr "Läs mer <a href=\"/about\">om den här tjänsten</a>."
msgid "News:"
msgstr "Nyheter:"
msgid ""
"<a href=\"/about/news#2019-11-12-celebrating-100k\">Celebrating 100.000 "
"verified addresses! 📈</a> (2019-11-12)"
msgstr ""
"<a href=\"/about/news#2019-11-12-celebrating-100k\">Vi firar 100 000 "
"verifierade adresser! 📈</a> (2019-11-12)"
msgid "v{{ version }} built from"
msgstr "v{{ version }} byggd från"
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
msgstr "Drivs av <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
msgid ""
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
msgstr ""
"Bakgrundsbild hämtad från <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
msgid "Maintenance Mode"
msgstr "Underhållsläge"
msgid "Manage your key"
msgstr "Hantera din nyckel"
msgid "Enter any verified email address for your key"
msgstr "Ange någon verifierad e-postadress för din nyckel"
msgid "Send link"
msgstr "Skicka länk"
msgid ""
"We will send you an email with a link you can use to remove any of your "
"email addresses from search."
msgstr ""
"Vi kommer att skicka ett e-postmeddelande till dig med en länk som du kan "
"använda för att ta bort någon av dina e-postadresser från sökresultaten."
msgid ""
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
msgstr ""
"Hanterar nyckeln <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
msgid "Your key is published with the following identity information:"
msgstr "Din nyckel är publicerad med följande identitetsinformation:"
msgid "Delete"
msgstr "Radera"
msgid ""
"Clicking \"delete\" on any address will remove it from this key. It will no "
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
"\">upload</a> the key again."
msgstr ""
"Att klicka \"radera\" på en adress kommer att ta bort den från den här "
"nyckeln. Den kommer inte längre att visas i en sökning.<br /> För att lägga "
"till en annan adress, <a href=\"/upload\">ladda upp</a>nyckeln igen. "
msgid ""
"Your key is published as only non-identity information. (<a href=\"/about\" "
"target=\"_blank\">What does this mean?</a>)"
msgstr ""
"Din nyckel är nu publicerad som endast icke-identifierbar information. (<a "
"href=\"/about\" target=\"_blank\">Vad betyder detta?</a>)"
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
msgstr ""
"För att lägga till en adress, <a href=\"/upload\">ladda upp</a> nyckeln igen."
msgid ""
"We have sent an email with further instructions to <span class=\"email"
"\">{{ address }}</span>."
msgstr ""
"Vi har skickat ett e-postmeddelande med vidare instruktioner till <span "
"class=\"email\">{{ address }}</span>."
msgid "This address has already been verified."
msgstr "Den här adressen har redan verifierats."
msgid ""
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
"\"email\">{{ userid }}</span></a>."
msgstr ""
"Din nyckel <span class=\"fingerprint\">{{ key_fpr }}</span> är nu publicerad "
"för identiteten <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
"\"email\">{{ userid }}</span></a>."
msgid "Upload your key"
msgstr "Ladda upp din nyckel"
msgid "Upload"
msgstr "Ladda upp"
msgid ""
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
msgstr ""
"Behöver du mer information? Kolla in vår <a target=\"_blank\" href=\"/about"
"\">introduktion</a> och <a target=\"_blank\" href=\"/about/usage"
"\">användarguide</a>."
msgid ""
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
msgstr ""
"Du laddade upp nyckeln <span class=\"fingerprint\"><a href="
"\"{{ key_link }}\" target=\"_blank\">{{ key_fpr }}</a></span>."
msgid "This key is revoked."
msgstr "Den här nyckeln är återkallad."
msgid ""
"It is published without identity information and can't be made available for "
"search by email address (<a href=\"/about\" target=\"_blank\">what does this "
"mean?</a>)."
msgstr ""
"Den är nu publicerad utan identitetsinformation och kan inte göras "
"tillgänglig för sökning via e-postadress (<a href=\"/about\" target=\"_blank"
"\">vad betyder detta?</a>)."
msgid ""
"This key is now published with the following identity information (<a href="
"\"/about\" target=\"_blank\">what does this mean?</a>):"
msgstr ""
"Den här nyckeln är nu publicerad med följande identitetsinformation (<a href="
"\"/about\" target=\"_blank\">vad betyder detta?</a>):"
msgid "Published"
msgstr "Publicerad"
msgid ""
"This key is now published with only non-identity information. (<a href=\"/"
"about\" target=\"_blank\">What does this mean?</a>)"
msgstr ""
"Den här nyckeln är nu publicerad med endast icke-identifierbar information. "
"(<a href=\"/about\" target=\"_blank\">Vad betyder detta?</a>)"
msgid ""
"To make the key available for search by email address, you can verify it "
"belongs to you:"
msgstr ""
"Du kan verifiera att nyckeln tillhör dig för att göra den sökbar via e-"
"postadress:"
msgid "Verification Pending"
msgstr "Avvaktar verifiering"
msgid ""
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
"prevent spam. Please be patient."
msgstr ""
"<strong>Obs:</strong> Vissa leverantörer fördröjer e-postmeddelanden i upp "
"till 15 minuter för att förhindra spam. Ha tålamod."
msgid "Send Verification Email"
msgstr "Skicka verifieringsmeddelande"
msgid ""
"This key contains one identity that could not be parsed as an email address."
"<br /> This identity can't be published on <span class=\"brand\">keys."
"openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" target=\"_blank"
"\">Why?</a>)"
msgstr ""
"Den här nyckeln innehåller en identitet som inte kunde behandlas som en e-"
"postadress.<br /> Den här identiteten kan inte publiceras på <span class="
"\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-uids\" "
"target=\"_blank\">Varför?</a>)"
msgid ""
"This key contains {{ count_unparsed }} identities that could not be parsed "
"as an email address.<br /> These identities can't be published on <span "
"class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-email-"
"uids\" target=\"_blank\">Why?</a>)"
msgstr ""
"Den här nyckeln innehåller {{ count_unparsed }} identiteter som inte kunde "
"behandlas som en e-postadress.<br /> Dessa identiteter kan inte publiceras "
"på <span class=\"brand\">keys.openpgp.org</span>. (<a href=\"/about/faq#non-"
"email-uids\" target=\"_blank\">Varför?</a>)"
msgid ""
"This key contains one revoked identity, which is not published. (<a href=\"/"
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
msgstr ""
"Den här nyckeln innehåller en återkallad identitet, som inte är publicerad. "
"(<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Varför?</a>)"
msgid ""
"This key contains {{ count_revoked }} revoked identities, which are not "
"published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
msgstr ""
"Den här nyckeln innehåller {{ count_revoked }} återkallade identiteter, som "
"inte är publicerade. (<a href=\"/about/faq#revoked-uids\" target=\"_blank"
"\">Varför?</a>)"
msgid "Your keys have been successfully uploaded:"
msgstr "Din nycklar har laddats upp:"
msgid ""
"<strong>Note:</strong> To make keys searchable by email address, you must "
"upload them individually."
msgstr ""
"<strong>Obs:</strong> För att göra nycklar sökbara via e-postadress behöver "
"du ladda upp dem en och en."
msgid "Verifying your email address…"
msgstr "Verifierar din e-postadress…"
msgid ""
"If the process doesn't complete after a few seconds, please <input type="
"\"submit\" class=\"textbutton\" value=\"click here\" />."
msgstr ""
"Om processen inte slutförs efter några sekunder, vänligen <input type="
"\"submit\" class=\"textbutton\" value=\"click here\" />."
msgid "Manage your key on {{domain}}"
msgstr "Hantera din nyckel på {{domain}}"
msgid "Hi,"
msgstr "Hej,"
msgid ""
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
msgstr ""
"Detta är ett automatiskt meddelande från <a href=\"{{base_uri}}\" style="
"\"text-decoration:none; color: #333\">{{domain}}</a>."
msgid "If you didn't request this message, please ignore it."
msgstr "Om du inte bad om det här meddelandet, vänligen ignorera det."
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
msgstr "OpenPGP-nyckel: <tt>{{primary_fp}}</tt>"
msgid ""
"To manage and delete listed addresses on this key, please follow the link "
"below:"
msgstr ""
"För att hantera och radera listade adresser på den här nyckeln, följ länken "
"nedan:"
msgid ""
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
"a>."
msgstr ""
"Du kan hitta mer information på <a href=\"{{base_uri}}/about\">{{domain}}/"
"about</a>."
msgid "distributing OpenPGP keys since 2019"
msgstr "distribuerar OpenPGP-nycklar sedan 2019"
msgid "This is an automated message from {{domain}}."
msgstr "Detta är ett automatiskt meddelande från {{domain}}."
msgid "OpenPGP key: {{primary_fp}}"
msgstr "OpenPGP-nyckel: {{primary_fp}}"
msgid "You can find more info at {{base_uri}}/about"
msgstr "Du kan hitta mer information på {{base_uri}}/about"
msgid "Verify {{userid}} for your key on {{domain}}"
msgstr "Verifiera {{userid}} för din nyckel på {{domain}}"
msgid ""
"To let others find this key from your email address \"<a rel=\"nofollow\" "
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
"please click the link below:"
msgstr ""
"För att låta andra hitta den här nyckeln från din e-postadress \"<a rel="
"\"nofollow\" href=\"#\" style=\"text-decoration:none; color: "
"#333\">{{userid}}</a>\", klicka på länken nedan:"
msgid ""
"To let others find this key from your email address \"{{userid}}\",\n"
"please follow the link below:"
msgstr ""
"För att låta andra hitta den här nyckeln från din e-postadress "
"\"{{userid}}\",\n"
"klicka på länken nedan:"
msgid "No key found for fingerprint {}"
msgstr "Ingen nyckel hittades för fingeravtrycket {}"
msgid "No key found for key id {}"
msgstr "Ingen nyckel hittades för nyckel-ID {}"
msgid "No key found for email address {}"
msgstr "Ingen nyckel hittades för e-postadressen {}"
msgid "Search by Short Key ID is not supported."
msgstr "Att söka efter kort nyckel-ID stöds ej."
msgid "Invalid search query."
msgstr "Ogiltig sökterm."
msgctxt "Subject for verification email, {0} = userid, {1} = keyserver domain"
msgid "Verify {0} for your key on {1}"
msgstr "Verifiera {0} för din nyckel på {1}"
msgctxt "Subject for manage email, {} = keyserver domain"
msgid "Manage your key on {}"
msgstr "Hantera din nyckel på {}"
msgid "This link is invalid or expired"
msgstr "Den här länken är ogiltig eller har upphört att gälla"
#, fuzzy
msgid "Malformed address: {}"
msgstr "Felformaterad adress: {}"
#, fuzzy
msgid "No key for address: {}"
msgstr "Ingen nyckel för adress: {}"
msgid "A request has already been sent for this address recently."
msgstr "En begäran har redan skickats för den här adressen nyligen."
msgid "Parsing of key data failed."
msgstr "Behandling av nyckelinformation misslyckades."
msgid "Whoops, please don't upload secret keys!"
msgstr "Hoppsan, ladda inte upp privata nycklar tack!"
msgid "No key uploaded."
msgstr "Ingen nyckel laddades upp."
msgid "Error processing uploaded key."
msgstr "Fel när uppladdad nyckel bearbetades."
msgid "Upload session expired. Please try again."
msgstr "Uppladdningssession gick ut. Vänligen försök igen."
msgid "Invalid verification link."
msgstr "Ogiltig verifieringslänk."

View File

@@ -1,5 +1,7 @@
#
# Translators:
# T. E. Kalayci <tekrei@gmail.com>, 2019
# Vincent Breitmoser <look@my.amazin.horse>, 2020
# T. E. Kalayci <tekrei@gmail.com>, 2021
#
msgid ""
msgstr ""
@@ -7,7 +9,7 @@ msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2018-06-15 16:33-0700\n"
"PO-Revision-Date: 2019-09-27 18:05+0000\n"
"Last-Translator: T. E. Kalayci <tekrei@gmail.com>, 2019\n"
"Last-Translator: T. E. Kalayci <tekrei@gmail.com>, 2021\n"
"Language-Team: Turkish (https://www.transifex.com/otf/teams/102430/tr/)\n"
"Language: tr\n"
"MIME-Version: 1.0\n"
@@ -15,37 +17,21 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: src/mail.rs:107
msgctxt "Subject for verification email"
msgid "Verify {userid} for your key on {domain}"
msgstr "{domain} üzerindeki anahtarınız için {userid} doğrulaması"
#: src/mail.rs:140
msgctxt "Subject for manage email"
msgid "Manage your key on {domain}"
msgstr "{domain} sunucusundaki anahtarınızı yönetin"
#: src/gettext_strings.rs:4
msgid "Error"
msgstr "Hata"
#: src/gettext_strings.rs:5
msgid "Looks like something went wrong :("
msgstr "Bir şeyler ters gitti gibi gözüküyor :("
#: src/gettext_strings.rs:6
msgid "Error message: {{ internal_error }}"
msgstr "Hata mesajı: {{ internal_error }}"
#: src/gettext_strings.rs:7
msgid "There was an error with your request:"
msgstr "İsteğinizle ilgili bir hata oluştu:"
#: src/gettext_strings.rs:8
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
msgstr "<span class=\"email\">{{ query }}</span> için bir girdi bulundu:"
#: src/gettext_strings.rs:9
msgid ""
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
@@ -55,19 +41,15 @@ msgstr ""
"hizmetini OpenPGP yazılımından kullanmak daha elverişlidir.<br />Ayrıntılar "
"için <a href=\"/about/usage\">kullanma kılavuzumuza</a> bakabilirsiniz."
#: src/gettext_strings.rs:10
msgid "debug info"
msgstr "hata ayıklama bilgileri"
#: src/gettext_strings.rs:11
msgid "Search by Email Address / Key ID / Fingerprint"
msgstr "E-posta adresi / Anahtar Kimliği / Parmak İzi ile arama"
#: src/gettext_strings.rs:12
msgid "Search"
msgstr "Ara"
#: src/gettext_strings.rs:13
msgid ""
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
"a> your key."
@@ -75,31 +57,25 @@ msgstr ""
"Ayrıca anahtarınızı <a href=\"/upload\">yükleyebilir</a> veya <a href=\"/"
"manage\">yönetebilirsiniz</a>."
#: src/gettext_strings.rs:14
msgid "Find out more <a href=\"/about\">about this service</a>."
msgstr "<a href=\"/about\">Bu hizmet hakkında</a> daha fazlasını öğrenin."
#: src/gettext_strings.rs:15
msgid "News:"
msgstr "Haberler:"
#: src/gettext_strings.rs:16
msgid ""
"<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
"launch ✨</a> (2019-09-12)"
"<a href=\"/about/news#2019-11-12-celebrating-100k\">Celebrating 100.000 "
"verified addresses! 📈</a> (2019-11-12)"
msgstr ""
"<a href=\"/about/news#2019-09-12-three-months-later\">Başlangıçtan itibaren "
"üç ay✨</a> (12 Eylül 2019)"
"<a href=\"/about/news#2019-11-12-celebrating-100k\"> 100.000 doğrulanmış "
"adresi kutluyoruz 📈</a> (2019-11-12)"
#: src/gettext_strings.rs:17
msgid "v{{ version }} built from"
msgstr "v{{ version }} dayandığı sürüm"
#: src/gettext_strings.rs:18
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
msgstr "<a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>desteklidir"
#: src/gettext_strings.rs:19
msgid ""
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
@@ -108,23 +84,18 @@ msgstr ""
"subtle-grey/\">Subtle Patterns</a>tarafından CC BY-SA 3.0 lisansıyla "
"sağlanmıştır."
#: src/gettext_strings.rs:20
msgid "Maintenance Mode"
msgstr "Bakım Modu"
#: src/gettext_strings.rs:21
msgid "Manage your key"
msgstr "Anahtarınızı yönetin"
#: src/gettext_strings.rs:22
msgid "Enter any verified email address for your key"
msgstr "Anahtarınız için doğrulanmış bir e-posta adresi girin"
#: src/gettext_strings.rs:23
msgid "Send link"
msgstr "Bağlantıyı gönder"
#: src/gettext_strings.rs:24
msgid ""
"We will send you an email with a link you can use to remove any of your "
"email addresses from search."
@@ -132,7 +103,6 @@ msgstr ""
"E-posta adreslerinizi aramadan kaldırabilmek için kullanabileceğiniz bir "
"bağlantıyı size e-posta olarak göndereceğiz."
#: src/gettext_strings.rs:25
msgid ""
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -140,15 +110,12 @@ msgstr ""
"<span class=\"fingerprint\"><a href=\"{{ key_link }}\" target=\"_blank"
"\">{{ key_fpr }}</a></span> anahtarı yönetiliyor."
#: src/gettext_strings.rs:26
msgid "Your key is published with the following identity information:"
msgstr "Anahtarınız aşağıdaki kimlik bilgileriyle yayınlanıyor:"
#: src/gettext_strings.rs:27
msgid "Delete"
msgstr "Sil"
#: src/gettext_strings.rs:28
msgid ""
"Clicking \"delete\" on any address will remove it from this key. It will no "
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
@@ -158,7 +125,6 @@ msgstr ""
"kaldıracaktır. Aramada artık gözükmeyecektir. <br />Başka bir adres eklemek "
"için anahtarı tekrar <a href=\"/upload\">yükleyin</a> ."
#: src/gettext_strings.rs:29
msgid ""
"Your key is published as only non-identity information. (<a href=\"/about\" "
"target=\"_blank\">What does this mean?</a>)"
@@ -166,12 +132,10 @@ msgstr ""
"Anahtarınız kimlik bilgisi içermeden yayınlanıyor. (<a href=\"/about\" "
"target=\"_blank\">Bunun anlamı nedir</a>)"
#: src/gettext_strings.rs:30
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
msgstr ""
"Bir adres eklemek için anahtarı tekrar <a href=\"/upload\">yükleyin</a>."
#: src/gettext_strings.rs:31
msgid ""
"We have sent an email with further instructions to <span class=\"email"
"\">{{ address }}</span>."
@@ -179,11 +143,9 @@ msgstr ""
"Ek yönergeleri içeren bir e-postayı <span class=\"email\">{{ address }}</"
"span> adresine gönderdik."
#: src/gettext_strings.rs:32
msgid "This address has already been verified."
msgstr "Bu adres zaten doğrulanmış."
#: src/gettext_strings.rs:33
msgid ""
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
@@ -193,15 +155,12 @@ msgstr ""
"\"{{userid_link}}\" target=\"_blank\"><span class=\"email\">{{ userid }}</"
"span></a> kimliği için yayınlandı."
#: src/gettext_strings.rs:34
msgid "Upload your key"
msgstr "Anahtarınızı yükleyin"
#: src/gettext_strings.rs:35
msgid "Upload"
msgstr "Yükle"
#: src/gettext_strings.rs:36
msgid ""
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
@@ -210,7 +169,6 @@ msgstr ""
"<a target=\"_blank\" href=\"/about/usage\">kullanma kılavuzuna</a> "
"bakabilirsiniz."
#: src/gettext_strings.rs:37
msgid ""
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -218,11 +176,9 @@ msgstr ""
"<span class=\"fingerprint\"><a href=\"{{ key_link }}\" target=\"_blank"
"\">{{ key_fpr }}</a></span> anahtarını yüklediniz."
#: src/gettext_strings.rs:38
msgid "This key is revoked."
msgstr "Bu anahtar feshedildi."
#: src/gettext_strings.rs:39
msgid ""
"It is published without identity information and can't be made available for "
"search by email address (<a href=\"/about\" target=\"_blank\">what does this "
@@ -231,7 +187,6 @@ msgstr ""
"Kimlik bilgisi olmayan yayınlandı ve e-posta aramasıyla bulunamaz (<a href="
"\"/about\" target=\"_blank\">bunun anlamı nedir?</a>)"
#: src/gettext_strings.rs:40
msgid ""
"This key is now published with the following identity information (<a href="
"\"/about\" target=\"_blank\">what does this mean?</a>):"
@@ -239,11 +194,9 @@ msgstr ""
"Bu anahtar şimdi aşağıdaki kimlik bilgisiyle yayınlandı (<a href=\"/about\" "
"target=\"_blank\">bunun anlamı nedir?</a>)"
#: src/gettext_strings.rs:41
msgid "Published"
msgstr "Yayınlandı"
#: src/gettext_strings.rs:42
msgid ""
"This key is now published with only non-identity information. (<a href=\"/"
"about\" target=\"_blank\">What does this mean?</a>)"
@@ -251,7 +204,6 @@ msgstr ""
"Bu anahtar şimdi sadece kimlik olmayan bilgiyle yayınlandı (<a href=\"/about"
"\" target=\"_blank\">bunun anlamı nedir?</a>)"
#: src/gettext_strings.rs:43
msgid ""
"To make the key available for search by email address, you can verify it "
"belongs to you:"
@@ -259,11 +211,9 @@ msgstr ""
"Anahtarı e-posta adresiyle bulunabilir yapmak için, size ait olduğunu "
"doğrulamanız gerekiyor:"
#: src/gettext_strings.rs:44
msgid "Verification Pending"
msgstr "Doğrulama Bekleniyor"
#: src/gettext_strings.rs:45
msgid ""
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
"prevent spam. Please be patient."
@@ -272,11 +222,9 @@ msgstr ""
"amacıyla e-postaları 15 dakikaya kadar geciktirebiliyor. Lütfen sabırla "
"bekleyin."
#: src/gettext_strings.rs:46
msgid "Send Verification Email"
msgstr "Doğrulama E-postasını Gönder"
#: src/gettext_strings.rs:47
msgid ""
"This key contains one identity that could not be parsed as an email address."
"<br /> This identity can't be published on <span class=\"brand\">keys."
@@ -288,7 +236,6 @@ msgstr ""
"yayınlanamaz. (<a href=\"/about/faq#non-email-uids\" target=\"_blank\">Neden?"
"</a>)"
#: src/gettext_strings.rs:48
msgid ""
"This key contains {{ count_unparsed }} identities that could not be parsed "
"as an email address.<br /> These identities can't be published on <span "
@@ -300,7 +247,6 @@ msgstr ""
"openpgp.org</span> hizmtinde yayınlanamaz. (<a href=\"/about/faq#non-email-"
"uids\" target=\"_blank\">Neden?</a>)"
#: src/gettext_strings.rs:49
msgid ""
"This key contains one revoked identity, which is not published. (<a href=\"/"
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -308,7 +254,6 @@ msgstr ""
"Bu anahtar feshedilmiş, yayınlanmayan bir kimlik içeriyor. (<a href=\"/about/"
"faq#revoked-uids\" target=\"_blank\">Neden?</a>)"
#: src/gettext_strings.rs:50
msgid ""
"This key contains {{ count_revoked }} revoked identities, which are not "
"published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -316,11 +261,9 @@ msgstr ""
"Bu anahtar feshedilmiş, yayınlanmayan {{ count_revoked }} kimlik bilgisi "
"içeriyor. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Neden?</a>)"
#: src/gettext_strings.rs:51
msgid "Your keys have been successfully uploaded:"
msgstr "Anahtarlarınız başarılı bir şekilde yüklendi:"
#: src/gettext_strings.rs:52
msgid ""
"<strong>Note:</strong> To make keys searchable by email address, you must "
"upload them individually."
@@ -328,11 +271,9 @@ msgstr ""
"<strong>Not:</strong> Anahtarlarınızı e-posta adresiyle bulunabilir yapmak "
"için, her birini ayrı ayrı yüklemeniz gerekiyor."
#: src/gettext_strings.rs:53
msgid "Verifying your email address…"
msgstr "E-posta adresiniz doğrulanıyor..."
#: src/gettext_strings.rs:54
msgid ""
"If the process doesn't complete after a few seconds, please <input type="
"\"submit\" class=\"textbutton\" value=\"click here\" />."
@@ -340,15 +281,12 @@ msgstr ""
"Eğer işlem bir kaç saniye içerisinde tamamlanmazsa, lütfen <input type="
"\"submit\" class=\"textbutton\" value=\"click here\" />."
#: src/gettext_strings.rs:56
msgid "Manage your key on {{domain}}"
msgstr "{{ domain }} üzerindeki anahtarınızı yönetin"
#: src/gettext_strings.rs:58
msgid "Hi,"
msgstr "Merhaba,"
#: src/gettext_strings.rs:59
msgid ""
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
@@ -356,15 +294,12 @@ msgstr ""
"Bu <a href=\"{{base_uri}}\" style=\"text-decoration:none; color: "
"#333\">{{domain}}</a> tarafından gönderilen otomatik bir mesajdır. "
#: src/gettext_strings.rs:60
msgid "If you didn't request this message, please ignore it."
msgstr "Eğer bu mesajı siz talep etmediyseniz, lütfen görmezden gelin."
#: src/gettext_strings.rs:61
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
msgstr "OpenPGP anahtarı: <tt>{{primary_fp}}</tt>"
#: src/gettext_strings.rs:62
msgid ""
"To manage and delete listed addresses on this key, please follow the link "
"below:"
@@ -372,7 +307,6 @@ msgstr ""
"Bu anahtarla listelenmiş adresleri yönetmek ve silmek için, lütfen aşağıdaki "
"bağlantıya tıklayın:"
#: src/gettext_strings.rs:63
msgid ""
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
"a>."
@@ -380,27 +314,21 @@ msgstr ""
"Daha fazla bilgiyi <a href=\"{{base_uri}}/about\">{{domain}}/about</a> "
"sayfasında bulabilirsiniz."
#: src/gettext_strings.rs:64
msgid "distributing OpenPGP keys since 2019"
msgstr "OpenPGP anahtarları 2019 yılından beri dağıtılıyor"
#: src/gettext_strings.rs:67
msgid "This is an automated message from {{domain}}."
msgstr "Bu {{domain}} tarafından gönderilen otomatik bir mesajdır. "
#: src/gettext_strings.rs:69
msgid "OpenPGP key: {{primary_fp}}"
msgstr "OpenPGP anahtarı: {{primary_fp}}"
#: src/gettext_strings.rs:71
msgid "You can find more info at {{base_uri}}/about"
msgstr "Daha fazla bilgiyi {{base_uri}}/about sayfasında bulabilirsiniz."
#: src/gettext_strings.rs:74
msgid "Verify {{userid}} for your key on {{domain}}"
msgstr "{{userid}} kimliğini {{domain}} üzerindeki anahtarınız için doğrulayın"
#: src/gettext_strings.rs:80
msgid ""
"To let others find this key from your email address \"<a rel=\"nofollow\" "
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
@@ -410,7 +338,6 @@ msgstr ""
"decoration:none; color: #333\">{{userid}}</a>\" e-posta adresinizi "
"kullanarak bulmasını istiyorsanız lütfen aşağıdaki bağlantıya tıklayın:"
#: src/gettext_strings.rs:88
msgid ""
"To let others find this key from your email address \"{{userid}}\",\n"
"please follow the link below:"
@@ -418,42 +345,57 @@ msgstr ""
"Başkalarının bu anahtarı, \"{{userid}}\" e-posta adresinizi kullanarak "
"bulmasını istiyorsanız lütfen aşağıdaki bağlantıya gidin:"
#: src/web/manage.rs:103
msgid "No key found for fingerprint {}"
msgstr "Parmak izi {} için anahtar bulunamadı"
msgid "No key found for key id {}"
msgstr "Anahtar kimliği {} için anahtar bulunamadı"
msgid "No key found for email address {}"
msgstr "E-posta adresi {} için anahtar bulunamadı"
msgid "Search by Short Key ID is not supported."
msgstr "Kısa Anahtar Kimliği ile arama desteklenmiyor."
msgid "Invalid search query."
msgstr "Geçersiz arama sorgusu."
msgctxt "Subject for verification email, {0} = userid, {1} = keyserver domain"
msgid "Verify {0} for your key on {1}"
msgstr "{1} üzerindeki anahtarınız için {0} doğrulaması"
msgctxt "Subject for manage email, {} = keyserver domain"
msgid "Manage your key on {}"
msgstr "{} sunucusundaki anahtarınızı yönetin"
msgid "This link is invalid or expired"
msgstr "Bu bağlantı geçersiz veya süresi geçmiş."
#: src/web/manage.rs:129
msgid "Malformed address: {address}"
msgstr "Hatalı adres: {address}"
#, fuzzy
msgid "Malformed address: {}"
msgstr "Hatalı adres: {}"
#: src/web/manage.rs:136
msgid "No key for address: {address}"
msgstr "Anahtarsız adres: {address}"
#, fuzzy
msgid "No key for address: {}"
msgstr "Anahtarsız adres: {}"
#: src/web/manage.rs:152
msgid "A request has already been sent for this address recently."
msgstr "Bu adres için bir istek kısa bir süre önce zaten gönderilmişti."
#: src/web/vks.rs:111
msgid "Parsing of key data failed."
msgstr "Anahtar verisi çözümlemesi başarısız oldu."
#: src/web/vks.rs:120
msgid "Whoops, please don't upload secret keys!"
msgstr "Eyvah! Lütfen gizli anahtarınızı yüklemeyin!"
#: src/web/vks.rs:133
msgid "No key uploaded."
msgstr "Anahtar yüklenmedi."
#: src/web/vks.rs:177
msgid "Error processing uploaded key."
msgstr "Yüklenen anahtar işlenirken bir hata oluştu."
#: src/web/vks.rs:247
msgid "Upload session expired. Please try again."
msgstr "Yükleme oturumunun süresi doldu. Lütfen tekrar deneyin."
#: src/web/vks.rs:284
msgid "Invalid verification link."
msgstr "Geçersiz doğrulama bağlantısı."

View File

@@ -16,37 +16,21 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: src/mail.rs:107
msgctxt "Subject for verification email"
msgid "Verify {userid} for your key on {domain}"
msgstr "在 {domain} 上验证你的密钥 {userid}"
#: src/mail.rs:140
msgctxt "Subject for manage email"
msgid "Manage your key on {domain}"
msgstr "在 {domain} 上管理你的密钥"
#: src/gettext_strings.rs:4
msgid "Error"
msgstr "错误"
#: src/gettext_strings.rs:5
msgid "Looks like something went wrong :("
msgstr "看上去有些东西出错了 :("
#: src/gettext_strings.rs:6
msgid "Error message: {{ internal_error }}"
msgstr "错误信息:{{ internal_error }}"
#: src/gettext_strings.rs:7
msgid "There was an error with your request:"
msgstr "你的请求有一个错误:"
#: src/gettext_strings.rs:8
msgid "We found an entry for <span class=\"email\">{{ query }}</span>:"
msgstr "我们找到了询问的地址<span class=\"email\">{{ query }}</span>"
#: src/gettext_strings.rs:9
msgid ""
"<strong>Hint:</strong> It's more convenient to use <span class=\"brand"
"\">keys.openpgp.org</span> from your OpenPGP software.<br /> Take a look at "
@@ -56,50 +40,38 @@ msgstr ""
"openpgp.org</span>会更方便。<br />查阅我们的<a href=\"/about/usage\">用户指南"
"</a>获取细节。"
#: src/gettext_strings.rs:10
msgid "debug info"
msgstr "调试信息"
#: src/gettext_strings.rs:11
msgid "Search by Email Address / Key ID / Fingerprint"
msgstr "通过电子邮件地址 / 密钥ID / 指纹搜索"
#: src/gettext_strings.rs:12
msgid "Search"
msgstr "搜索"
#: src/gettext_strings.rs:13
msgid ""
"You can also <a href=\"/upload\">upload</a> or <a href=\"/manage\">manage</"
"a> your key."
msgstr ""
"你也可以<a href=\"/upload\">上传</a>或<a href=\"/manage\">管理</a>你的密钥。"
#: src/gettext_strings.rs:14
msgid "Find out more <a href=\"/about\">about this service</a>."
msgstr "找到<a href=\"/about\">关于这项服务</a>的更多信息。"
#: src/gettext_strings.rs:15
msgid "News:"
msgstr "新闻:"
#: src/gettext_strings.rs:16
msgid ""
"<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
"launch ✨</a> (2019-09-12)"
"<a href=\"/about/news#2019-11-12-celebrating-100k\">Celebrating 100.000 "
"verified addresses! 📈</a> (2019-11-12)"
msgstr ""
"<a href=\"/about/news#2019-09-12-three-months-later\">已成功运行三个月✨</"
"a>(2019-09-12)"
#: src/gettext_strings.rs:17
msgid "v{{ version }} built from"
msgstr "v{{ version }} built from"
#: src/gettext_strings.rs:18
msgid "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>"
msgstr "由<a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a>支持"
#: src/gettext_strings.rs:19
msgid ""
"Background image retrieved from <a href=\"https://www.toptal.com/designers/"
"subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0"
@@ -107,23 +79,18 @@ msgstr ""
"背景图从<a href=\"https://www.toptal.com/designers/subtlepatterns/subtle-"
"grey/\">Subtle Patterns</a>依据CC BY-SA 3.0协议获得"
#: src/gettext_strings.rs:20
msgid "Maintenance Mode"
msgstr "修复模式"
#: src/gettext_strings.rs:21
msgid "Manage your key"
msgstr "管理你的密钥"
#: src/gettext_strings.rs:22
msgid "Enter any verified email address for your key"
msgstr "输入任意关联到你的密钥的电子邮件地址"
#: src/gettext_strings.rs:23
msgid "Send link"
msgstr "发送链接"
#: src/gettext_strings.rs:24
msgid ""
"We will send you an email with a link you can use to remove any of your "
"email addresses from search."
@@ -131,7 +98,6 @@ msgstr ""
"我们将把链接用电子邮件的形式发给你,那条链接可以让你在搜索结果中移除你的任意"
"邮箱地址"
#: src/gettext_strings.rs:25
msgid ""
"Managing the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -139,15 +105,12 @@ msgstr ""
"管理密钥<span class=\"fingerprint\"><a href=\"{{ key_link }}\" target="
"\"_blank\">{{ key_fpr }}</a></span>"
#: src/gettext_strings.rs:26
msgid "Your key is published with the following identity information:"
msgstr "你的密钥已经用以下身份发布:"
#: src/gettext_strings.rs:27
msgid "Delete"
msgstr "删除"
#: src/gettext_strings.rs:28
msgid ""
"Clicking \"delete\" on any address will remove it from this key. It will no "
"longer appear in a search.<br /> To add another address, <a href=\"/upload"
@@ -156,7 +119,6 @@ msgstr ""
"点击“删除”将会在密钥中删除该地址的关联。之后该地址将不会在搜索结果中出现。"
"<br />如果需要添加另外的地址,请<a href=\"/upload\">重新上传此密钥</a>"
#: src/gettext_strings.rs:29
msgid ""
"Your key is published as only non-identity information. (<a href=\"/about\" "
"target=\"_blank\">What does this mean?</a>)"
@@ -164,11 +126,9 @@ msgstr ""
"你的密钥以无身份的形式发布。(<a href=\"/about\" target=\"_blank\">这意味着什"
"么?</a>"
#: src/gettext_strings.rs:30
msgid "To add an address, <a href=\"/upload\">upload</a> the key again."
msgstr "如果要添加地址,<a href=\"/upload\">重新上传此密钥</a>"
#: src/gettext_strings.rs:31
msgid ""
"We have sent an email with further instructions to <span class=\"email"
"\">{{ address }}</span>."
@@ -176,11 +136,9 @@ msgstr ""
"我们已经向<span class=\"email\">{{ address }}</span>发送了包含后续说明的电子"
"邮件。"
#: src/gettext_strings.rs:32
msgid "This address has already been verified."
msgstr "该地址已经被验证过了。"
#: src/gettext_strings.rs:33
msgid ""
"Your key <span class=\"fingerprint\">{{ key_fpr }}</span> is now published "
"for the identity <a href=\"{{userid_link}}\" target=\"_blank\"><span class="
@@ -190,15 +148,12 @@ msgstr ""
"\"{{userid_link}}\" target=\"_blank\"><span class=\"email\">{{ userid }}</"
"span></a>的身份发布"
#: src/gettext_strings.rs:34
msgid "Upload your key"
msgstr "上传你的密钥"
#: src/gettext_strings.rs:35
msgid "Upload"
msgstr "上传"
#: src/gettext_strings.rs:36
msgid ""
"Need more info? Check our <a target=\"_blank\" href=\"/about\">intro</a> and "
"<a target=\"_blank\" href=\"/about/usage\">usage guide</a>."
@@ -206,7 +161,6 @@ msgstr ""
"想了解更多信息?查阅我们的<a target=\"_blank\" href=\"/about\">介绍</a>和<a "
"target=\"_blank\" href=\"/about/usage\">用户指南</a>。"
#: src/gettext_strings.rs:37
msgid ""
"You uploaded the key <span class=\"fingerprint\"><a href=\"{{ key_link }}\" "
"target=\"_blank\">{{ key_fpr }}</a></span>."
@@ -214,11 +168,9 @@ msgstr ""
"你上传了密钥<span class=\"fingerprint\"><a href=\"{{ key_link }}\" target="
"\"_blank\">{{ key_fpr }}</a></span>。"
#: src/gettext_strings.rs:38
msgid "This key is revoked."
msgstr "该密钥已经被吊销。"
#: src/gettext_strings.rs:39
msgid ""
"It is published without identity information and can't be made available for "
"search by email address (<a href=\"/about\" target=\"_blank\">what does this "
@@ -227,7 +179,6 @@ msgstr ""
"该密钥以无身份信息的形式发布,不能使用电子邮件地址搜索到(<a href=\"/about\" "
"target=\"_blank\">这意味着什么?</a>"
#: src/gettext_strings.rs:40
msgid ""
"This key is now published with the following identity information (<a href="
"\"/about\" target=\"_blank\">what does this mean?</a>):"
@@ -235,11 +186,9 @@ msgstr ""
"该密钥已经用以下身份信息发布(<a href=\"/about\" target=\"_blank\">这意味着什"
"么?</a>"
#: src/gettext_strings.rs:41
msgid "Published"
msgstr "已发布"
#: src/gettext_strings.rs:42
msgid ""
"This key is now published with only non-identity information. (<a href=\"/"
"about\" target=\"_blank\">What does this mean?</a>)"
@@ -247,17 +196,14 @@ msgstr ""
"你的密钥现在以无身份的形式发布。(<a href=\"/about\" target=\"_blank\">这意味"
"着什么?</a>"
#: src/gettext_strings.rs:43
msgid ""
"To make the key available for search by email address, you can verify it "
"belongs to you:"
msgstr "要让密钥能通过电子邮件地址搜索到,你可以验证该地址属于你:"
#: src/gettext_strings.rs:44
msgid "Verification Pending"
msgstr "等待验证"
#: src/gettext_strings.rs:45
msgid ""
"<strong>Note:</strong> Some providers delay emails for up to 15 minutes to "
"prevent spam. Please be patient."
@@ -265,11 +211,9 @@ msgstr ""
"<strong>提醒:</strong>部分服务商会将邮件拖延最多15分钟来阻止垃圾邮件。请保持"
"耐心。"
#: src/gettext_strings.rs:46
msgid "Send Verification Email"
msgstr "发送验证邮件"
#: src/gettext_strings.rs:47
msgid ""
"This key contains one identity that could not be parsed as an email address."
"<br /> This identity can't be published on <span class=\"brand\">keys."
@@ -280,7 +224,6 @@ msgstr ""
"\"brand\">keys.openpgp.org</span>上发布。 <a href=\"/about/faq#non-email-"
"uids\" target=\"_blank\">为什么?</a>"
#: src/gettext_strings.rs:48
msgid ""
"This key contains {{ count_unparsed }} identities that could not be parsed "
"as an email address.<br /> These identities can't be published on <span "
@@ -291,7 +234,6 @@ msgstr ""
"无法在<span class=\"brand\">keys.openpgp.org</span>上发布。 <a href=\"/"
"about/faq#non-email-uids\" target=\"_blank\">为什么?</a>"
#: src/gettext_strings.rs:49
msgid ""
"This key contains one revoked identity, which is not published. (<a href=\"/"
"about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -299,7 +241,6 @@ msgstr ""
"该密钥包含一个被吊销的身份,所以没有发布(<a href=\"/about/faq#revoked-uids"
"\" target=\"_blank\">为什么?</a>"
#: src/gettext_strings.rs:50
msgid ""
"This key contains {{ count_revoked }} revoked identities, which are not "
"published. (<a href=\"/about/faq#revoked-uids\" target=\"_blank\">Why?</a>)"
@@ -307,11 +248,9 @@ msgstr ""
"该密钥包含 {{ count_revoked }}个被吊销的身份,所以没有发布(<a href=\"/about/"
"faq#revoked-uids\" target=\"_blank\">为什么?</a>"
#: src/gettext_strings.rs:51
msgid "Your keys have been successfully uploaded:"
msgstr "你的密钥已成功上传"
#: src/gettext_strings.rs:52
msgid ""
"<strong>Note:</strong> To make keys searchable by email address, you must "
"upload them individually."
@@ -319,11 +258,9 @@ msgstr ""
"<strong>提示:</strong>如果想让一些密钥可以通过邮箱地址搜索到,你必须把这些密"
"钥逐个上传。"
#: src/gettext_strings.rs:53
msgid "Verifying your email address…"
msgstr "正在验证你的邮箱地址……"
#: src/gettext_strings.rs:54
msgid ""
"If the process doesn't complete after a few seconds, please <input type="
"\"submit\" class=\"textbutton\" value=\"click here\" />."
@@ -331,15 +268,12 @@ msgstr ""
"如果这项操作在若干秒后仍没有完成,<input type=\"submit\" class=\"textbutton"
"\" value=\"click here\" />。"
#: src/gettext_strings.rs:56
msgid "Manage your key on {{domain}}"
msgstr "在{{domain}}上管理你的密钥"
#: src/gettext_strings.rs:58
msgid "Hi,"
msgstr "你好,"
#: src/gettext_strings.rs:59
msgid ""
"This is an automated message from <a href=\"{{base_uri}}\" style=\"text-"
"decoration:none; color: #333\">{{domain}}</a>."
@@ -347,21 +281,17 @@ msgstr ""
"这是来自<a href=\"{{base_uri}}\" style=\"text-decoration:none; color: "
"#333\">{{domain}}</a>的一条自动信息"
#: src/gettext_strings.rs:60
msgid "If you didn't request this message, please ignore it."
msgstr "如果你没有申请过这条信息,请忽略。"
#: src/gettext_strings.rs:61
msgid "OpenPGP key: <tt>{{primary_fp}}</tt>"
msgstr "OpenPGP密钥<tt>{{primary_fp}}</tt>"
#: src/gettext_strings.rs:62
msgid ""
"To manage and delete listed addresses on this key, please follow the link "
"below:"
msgstr "要管理和删除此密钥上列出的地址,请点击以下链接:"
#: src/gettext_strings.rs:63
msgid ""
"You can find more info at <a href=\"{{base_uri}}/about\">{{domain}}/about</"
"a>."
@@ -369,27 +299,21 @@ msgstr ""
"你可以在以下链接找到更多信息 <a href=\"{{base_uri}}/about\">{{domain}}/"
"about</a>。"
#: src/gettext_strings.rs:64
msgid "distributing OpenPGP keys since 2019"
msgstr "自2019年起传播 OpenPGP 密钥"
#: src/gettext_strings.rs:67
msgid "This is an automated message from {{domain}}."
msgstr "这是一条来自 {{domain}} 的自动信息。"
#: src/gettext_strings.rs:69
msgid "OpenPGP key: {{primary_fp}}"
msgstr "OpenPGP密钥{{primary_fp}}"
#: src/gettext_strings.rs:71
msgid "You can find more info at {{base_uri}}/about"
msgstr "你可以在以下链接找到更多信息 {{base_uri}}/about"
#: src/gettext_strings.rs:74
msgid "Verify {{userid}} for your key on {{domain}}"
msgstr "在 {{domain}} 上验证你的密钥 {{userid}}"
#: src/gettext_strings.rs:80
msgid ""
"To let others find this key from your email address \"<a rel=\"nofollow\" "
"href=\"#\" style=\"text-decoration:none; color: #333\">{{userid}}</a>\", "
@@ -398,7 +322,6 @@ msgstr ""
"要让其他人能通过你的邮件地址找到此密钥\"<a rel=\"nofollow\" href=\"#\" style="
"\"text-decoration:none; color: #333\">{{userid}}</a>\",请点击下方的链接:"
#: src/gettext_strings.rs:88
msgid ""
"To let others find this key from your email address \"{{userid}}\",\n"
"please follow the link below:"
@@ -406,42 +329,64 @@ msgstr ""
"要让其他人能通过你的邮件地址找到此密钥\"{{userid}}\"\n"
"请点击下方的链接:"
#: src/web/manage.rs:103
msgid "No key found for fingerprint {}"
msgstr "此地址下没有密钥:{}"
msgid "No key found for key id {}"
msgstr "此地址下没有密钥:{}"
msgid "No key found for email address {}"
msgstr "此地址下没有密钥:{}"
msgid "Search by Short Key ID is not supported."
msgstr ""
msgid "Invalid search query."
msgstr ""
msgctxt "Subject for verification email, {0} = userid, {1} = keyserver domain"
msgid "Verify {0} for your key on {1}"
msgstr "在 {0} 上验证你的密钥 {1}"
msgctxt "Subject for manage email, {} = keyserver domain"
msgid "Manage your key on {}"
msgstr "在 {} 上管理你的密钥"
msgid "This link is invalid or expired"
msgstr "此链接无效或者已经过期"
#: src/web/manage.rs:129
msgid "Malformed address: {address}"
msgstr "地址格式错误:{address}"
#, fuzzy
msgid "Malformed address: {}"
msgstr "地址格式错误:{}"
#: src/web/manage.rs:136
msgid "No key for address: {address}"
msgstr "此地址下没有密钥:{address}"
#, fuzzy
msgid "No key for address: {}"
msgstr "此地址下没有密钥:{}"
#: src/web/manage.rs:152
msgid "A request has already been sent for this address recently."
msgstr "对此地址的申请已经发送"
#: src/web/vks.rs:111
msgid "Parsing of key data failed."
msgstr "密钥数据解析失败。"
#: src/web/vks.rs:120
msgid "Whoops, please don't upload secret keys!"
msgstr "我的天!请不要上传私钥!"
#: src/web/vks.rs:133
msgid "No key uploaded."
msgstr "没有密钥被上传"
#: src/web/vks.rs:177
msgid "Error processing uploaded key."
msgstr "处理已上传的密钥时出现问题"
#: src/web/vks.rs:247
msgid "Upload session expired. Please try again."
msgstr "上传会话过期,请重试。"
#: src/web/vks.rs:284
msgid "Invalid verification link."
msgstr "无效的验证链接"
#~ msgid ""
#~ "<a href=\"/about/news#2019-09-12-three-months-later\">Three months after "
#~ "launch ✨</a> (2019-09-12)"
#~ msgstr ""
#~ "<a href=\"/about/news#2019-09-12-three-months-later\">已成功运行三个月✨</"
#~ "a>(2019-09-12)"

View File

@@ -1 +1 @@
nightly-2020-06-01
1.82.0

View File

@@ -1,6 +0,0 @@
overflow_delimited_expr = true
unstable_features = true
use_small_heuristics = "Max"
fn_args_density = "Compressed"
max_width = 80
force_multiline_blocks = true

View File

@@ -1,25 +1,27 @@
with import <nixpkgs> {};
let
src = fetchFromGitHub {
owner = "mozilla";
repo = "nixpkgs-mozilla";
# commit from: 2019-07-15
rev = "8c007b60731c07dd7a052cce508de3bb1ae849b4";
sha256 = "sha256-RsNPnEKd7BcogwkqhaV5kI/HuNC4flH/OQCC/4W5y/8=";
};
rustOverlay = import "${src.out}/rust-overlay.nix" pkgs pkgs;
rustChannel = (rustOverlay.rustChannelOf { rustToolchain = ./rust-toolchain; });
oxalica_overlay = import (builtins.fetchTarball
"https://github.com/oxalica/rust-overlay/archive/master.tar.gz");
pkgs = import <nixpkgs> { overlays = [ oxalica_overlay ]; };
rust_channel = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain;
#rust_channel = pkgs.rust-bin.stable.latest.default;
in
stdenv.mkDerivation {
name = "rust-env";
buildInputs = [
rustChannel.rust
# latest.rustChannels.nightly.rust
# latest.rustChannels.stable.rust
pkgs.mkShell {
nativeBuildInputs = [
(rust_channel.override {
extensions = [ "rust-src" "rust-std" "clippy" ];
targets = [
"x86_64-unknown-linux-gnu"
];
})
];
buildInputs = with pkgs; [
sqlite
openssl
clang
nettle
pkgconfig
pkg-config
gettext
transifex-client
@@ -30,6 +32,7 @@ stdenv.mkDerivation {
# compilation of -sys packages requires manually setting this :(
shellHook = ''
export LIBCLANG_PATH="${pkgs.llvmPackages.libclang}/lib";
export LIBCLANG_PATH="${pkgs.llvmPackages.libclang.lib}/lib";
'';
}

View File

@@ -55,17 +55,18 @@ lazy_static! {
}
pub fn anonymize_address(email: &Email) -> Option<String> {
email.as_str()
.rsplit('@')
.next()
.map(|domain| domain.to_lowercase())
.and_then(|domain| {
if POPULAR_DOMAINS.contains(&domain.as_str()) {
Some(domain)
} else {
domain.rsplit('.').next().map(|tld| tld.to_owned())
}
})
email
.as_str()
.rsplit('@')
.next()
.map(|domain| domain.to_lowercase())
.and_then(|domain| {
if POPULAR_DOMAINS.contains(&domain.as_str()) {
Some(domain)
} else {
domain.rsplit('.').next().map(|tld| tld.to_owned())
}
})
}
pub fn anonymize_address_fallback(email: &Email) -> String {

View File

@@ -8,14 +8,21 @@ use crate::database::types::Email;
lazy_static! {
static ref KEY_UPLOAD: LabelCounter =
LabelCounter::new("hagrid_key_upload", "Uploaded keys", &["result"]);
static ref MAIL_SENT: LabelCounter =
LabelCounter::new("hagrid_mail_sent", "Sent verification mails", &["type", "domain"]);
static ref KEY_ADDRESS_PUBLISHED: LabelCounter =
LabelCounter::new("hagrid_key_address_published", "Verified email addresses", &["domain"]);
static ref KEY_ADDRESS_UNPUBLISHED: LabelCounter =
LabelCounter::new("hagrid_key_address_unpublished", "Unpublished email addresses", &["domain"]);
static ref MAIL_SENT: LabelCounter = LabelCounter::new(
"hagrid_mail_sent",
"Sent verification mails",
&["type", "domain"]
);
static ref KEY_ADDRESS_PUBLISHED: LabelCounter = LabelCounter::new(
"hagrid_key_address_published",
"Verified email addresses",
&["domain"]
);
static ref KEY_ADDRESS_UNPUBLISHED: LabelCounter = LabelCounter::new(
"hagrid_key_address_unpublished",
"Unpublished email addresses",
&["domain"]
);
}
pub fn register_counters(registry: &prometheus::Registry) {
@@ -58,7 +65,9 @@ impl LabelCounter {
}
fn register(&self, registry: &prometheus::Registry) {
registry.register(Box::new(self.prometheus_counter.clone())).unwrap();
registry
.register(Box::new(self.prometheus_counter.clone()))
.unwrap();
}
fn inc(&self, values: &[&str]) {

View File

@@ -4,13 +4,13 @@ use std::convert::TryInto;
use std::path::PathBuf;
extern crate anyhow;
use anyhow::Result as Result;
use anyhow::Result;
extern crate structopt;
use structopt::StructOpt;
extern crate hagrid_database as database;
use crate::database::{Query, Database, KeyDatabase};
use crate::database::{Database, KeyDatabase, Query};
#[derive(Debug, StructOpt)]
#[structopt(
@@ -50,40 +50,36 @@ fn main() {
fn real_main() -> Result<()> {
let opt = Opt::from_args();
let db = KeyDatabase::new_from_base(opt.base.canonicalize()?)?;
let db = KeyDatabase::new_file(opt.base.canonicalize()?)?;
delete(&db, &opt.query.parse()?, opt.all_bindings, opt.all)
}
fn delete(db: &KeyDatabase, query: &Query, all_bindings: bool, mut all: bool)
-> Result<()> {
fn delete(db: &KeyDatabase, query: &Query, all_bindings: bool, mut all: bool) -> Result<()> {
match query {
Query::ByFingerprint(_) | Query::ByKeyID(_) => {
eprintln!("Fingerprint or KeyID given, deleting key and all \
bindings.");
eprintln!(
"Fingerprint or KeyID given, deleting key and all \
bindings."
);
all = true;
},
}
_ => (),
}
let tpk = db.lookup(&query)?.ok_or_else(
|| anyhow::format_err!("No TPK matching {:?}", query))?;
let tpk = db
.lookup(query)?
.ok_or_else(|| anyhow::format_err!("No TPK matching {:?}", query))?;
let fp: database::types::Fingerprint = tpk.fingerprint().try_into()?;
let mut results = Vec::new();
// First, delete the bindings.
if all_bindings || all {
results.push(
("all bindings".into(),
db.set_email_unpublished_all(&fp)));
results.push(("all bindings".into(), db.set_email_unpublished_all(&fp)));
} else if let Query::ByEmail(ref email) = query {
results.push((email.to_string(), db.set_email_unpublished(&fp, email)));
} else {
if let Query::ByEmail(ref email) = query {
results.push(
(email.to_string(),
db.set_email_unpublished(&fp, email)));
} else {
unreachable!()
}
unreachable!()
}
// Now delete the key(s) itself.
@@ -112,12 +108,15 @@ fn delete(db: &KeyDatabase, query: &Query, all_bindings: bool, mut all: bool)
let mut err = Ok(());
for (slug, result) in results {
eprintln!("{}: {}", slug,
if let Err(ref e) = result {
e.to_string()
} else {
"Deleted".into()
});
eprintln!(
"{}: {}",
slug,
if let Err(ref e) = result {
e.to_string()
} else {
"Deleted".into()
}
);
if err.is_ok() {
if let Err(e) = result {
err = Err(e);

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,9 @@
use handlebars::{
Context, Handlebars, Helper, HelperDef, HelperResult, Output, RenderContext, RenderError
use rocket_dyn_templates::handlebars::{
Context, Handlebars, Helper, HelperDef, HelperResult, Output, RenderContext, RenderError,
};
use std::io;
pub struct I18NHelper {
catalogs: Vec<(&'static str, gettext::Catalog)>,
}
@@ -14,11 +13,9 @@ impl I18NHelper {
Self { catalogs }
}
pub fn get_catalog(
&self,
lang: &str,
) -> &gettext::Catalog {
let (_, ref catalog) = self.catalogs
pub fn get_catalog(&self, lang: &str) -> &gettext::Catalog {
let (_, ref catalog) = self
.catalogs
.iter()
.find(|(candidate, _)| *candidate == lang)
.unwrap_or_else(|| self.catalogs.get(0).unwrap());
@@ -56,7 +53,7 @@ impl HelperDef for I18NHelper {
h: &Helper<'reg, 'rc>,
reg: &'reg Handlebars,
context: &'rc Context,
rcx: &mut RenderContext<'reg>,
rcx: &mut RenderContext<'reg, '_>,
out: &mut dyn Output,
) -> HelperResult {
let id = if let Some(id) = h.param(0) {
@@ -75,10 +72,8 @@ impl HelperDef for I18NHelper {
let rerender = h
.param(1)
.and_then(|p| p
.path()
.map(|v| v == "rerender")
).unwrap_or(false);
.and_then(|p| p.relative_path().map(|v| v == "rerender"))
.unwrap_or(false);
let lang = context
.data()
@@ -87,14 +82,21 @@ impl HelperDef for I18NHelper {
.as_str()
.expect("Language must be string");
let response = self.lookup(lang, &id);
fn render_error_with<E>(e: E) -> RenderError
where
E: std::error::Error + Send + Sync + 'static,
{
RenderError::from_error("Failed to render", e)
}
let response = self.lookup(lang, id);
if rerender {
let data = rcx.evaluate(context, ".", false).unwrap();
let response = reg.render_template(&response, data)
.map_err(RenderError::with)?;
out.write(&response).map_err(RenderError::with)?;
let data = rcx.evaluate(context, "this").unwrap();
let response = reg
.render_template(response, data.as_json())
.map_err(render_error_with)?;
out.write(&response).map_err(render_error_with)?;
} else {
out.write(&response).map_err(RenderError::with)?;
out.write(response).map_err(render_error_with)?;
}
Ok(())
}

View File

@@ -1,16 +1,21 @@
use rocket_i18n::I18n;
use crate::database::Query;
use gettext_macros::i18n;
use rocket_i18n::I18n;
pub fn describe_query_error(i18n: &I18n, q: &Query) -> String {
match q {
Query::ByFingerprint(fpr) =>
i18n!(i18n.catalog, "No key found for fingerprint {fingerprint}"; fingerprint = fpr),
Query::ByKeyID(key_id) =>
i18n!(i18n.catalog, "No key found for key id {key_id}"; key_id = key_id),
Query::ByEmail(email) =>
i18n!(i18n.catalog, "No key found for email address {email}"; email = email),
Query::InvalidShort() => i18n!(i18n.catalog, "Search by Short Key ID is not supported."),
Query::ByFingerprint(fpr) => {
i18n!(i18n.catalog, "No key found for fingerprint {}"; fpr)
}
Query::ByKeyID(key_id) => {
i18n!(i18n.catalog, "No key found for key id {}"; key_id)
}
Query::ByEmail(email) => {
i18n!(i18n.catalog, "No key found for email address {}"; email)
}
Query::InvalidShort() => {
i18n!(i18n.catalog, "Search by Short Key ID is not supported.")
}
Query::Invalid() => i18n!(i18n.catalog, "Invalid search query."),
}
}

View File

@@ -1,18 +1,14 @@
use std::path::{PathBuf, Path};
use std::path::{Path, PathBuf};
use anyhow;
use handlebars::Handlebars;
use lettre::{Transport as LettreTransport, SendmailTransport, file::FileTransport};
use lettre::builder::{EmailBuilder, PartBuilder, Mailbox, MimeMultipartType};
use url;
use crate::counters;
use lettre::message::{header, Mailbox, MultiPart, SinglePart};
use lettre::{FileTransport, SendmailTransport, SmtpTransport, Transport as LettreTransport};
use rocket_dyn_templates::handlebars::Handlebars;
use serde::Serialize;
use uuid::Uuid;
use crate::counters;
use rocket_i18n::I18n;
use gettext_macros::i18n;
use rfc2047::rfc2047_encode;
use rocket_i18n::I18n;
use crate::template_helpers;
@@ -52,11 +48,12 @@ mod context {
pub struct Service {
from: Mailbox,
domain: String,
templates: Handlebars,
templates: Handlebars<'static>,
transport: Transport,
}
enum Transport {
LocalSmtp,
Sendmail,
Filemail(PathBuf),
}
@@ -67,19 +64,36 @@ impl Service {
Self::new(from, base_uri, template_dir, Transport::Sendmail)
}
/// Sends mail by storing it in the given directory.
pub fn filemail(from: &str, base_uri: &str, template_dir: &Path, path: &Path) -> Result<Self> {
Self::new(from, base_uri, template_dir, Transport::Filemail(path.to_owned()))
/// Sends mail via local smtp server.
pub fn localsmtp(from: &str, base_uri: &str, template_dir: &Path) -> Result<Self> {
Self::new(from, base_uri, template_dir, Transport::LocalSmtp)
}
fn new(from: &str, base_uri: &str, template_dir: &Path, transport: Transport)
-> Result<Self> {
/// Sends mail by storing it in the given directory.
pub fn filemail(from: &str, base_uri: &str, template_dir: &Path, path: &Path) -> Result<Self> {
Self::new(
from,
base_uri,
template_dir,
Transport::Filemail(path.to_owned()),
)
}
fn new(from: &str, base_uri: &str, template_dir: &Path, transport: Transport) -> Result<Self> {
let templates = template_helpers::load_handlebars(template_dir)?;
let domain =
url::Url::parse(base_uri)
?.host_str().ok_or_else(|| anyhow!("No host in base-URI"))
?.to_string();
Ok(Self { from: from.into(), domain, templates, transport })
let domain = url::Url::parse(base_uri)?
.host_str()
.ok_or_else(|| anyhow!("No host in base-URI"))?
.to_string();
let from = from
.parse()
.map_err(|_| anyhow!("From must be valid email address"))?;
Ok(Self {
from,
domain,
templates,
transport,
})
}
pub fn send_verification(
@@ -88,7 +102,7 @@ impl Service {
base_uri: &str,
tpk_name: String,
userid: &Email,
token: &str
token: &str,
) -> Result<()> {
let ctx = context::Verification {
lang: i18n.lang.to_string(),
@@ -102,13 +116,13 @@ impl Service {
counters::inc_mail_sent("verify", userid);
self.send(
&vec![userid],
&[userid],
&i18n!(
i18n.catalog,
context = "Subject for verification email",
"Verify {userid} for your key on {domain}";
userid = userid,
domain = self.domain
context = "Subject for verification email, {0} = userid, {1} = keyserver domain",
"Verify {0} for your key on {1}";
userid,
self.domain.as_str(),
),
"verify",
i18n.lang,
@@ -138,9 +152,9 @@ impl Service {
&[recipient],
&i18n!(
i18n.catalog,
context = "Subject for manage email",
"Manage your key on {domain}";
domain = self.domain
context = "Subject for manage email, {} = keyserver domain",
"Manage your key on {}";
self.domain.as_str()
),
"manage",
i18n.lang,
@@ -148,38 +162,12 @@ impl Service {
)
}
pub fn send_upload(
&self,
base_uri: &str,
tpk_name: String,
userid: &Email,
token: &str
) -> Result<()> {
let ctx = context::Welcome {
lang: "en".to_owned(),
primary_fp: tpk_name,
uri: format!("{}/upload/{}", base_uri, token),
base_uri: base_uri.to_owned(),
domain: self.domain.clone(),
};
counters::inc_mail_sent("upload", userid);
self.send(
&vec![userid],
&format!("Your key upload on {domain}", domain = self.domain),
"upload",
"en",
ctx,
)
}
pub fn send_welcome(
&self,
base_uri: &str,
tpk_name: String,
userid: &Email,
token: &str
token: &str,
) -> Result<()> {
let ctx = context::Welcome {
lang: "en".to_owned(),
@@ -192,7 +180,7 @@ impl Service {
counters::inc_mail_sent("welcome", userid);
self.send(
&vec![userid],
&[userid],
&format!("Your key upload on {domain}", domain = self.domain),
"welcome",
"en",
@@ -204,12 +192,16 @@ impl Service {
&self,
template: &str,
locale: &str,
ctx: impl Serialize
ctx: impl Serialize,
) -> Result<(String, String)> {
let html = self.templates.render(&format!("{}/{}.htm", locale, template), &ctx)
let html = self
.templates
.render(&format!("{}/{}.htm", locale, template), &ctx)
.or_else(|_| self.templates.render(&format!("{}.htm", template), &ctx))
.map_err(|_| anyhow!("Email template failed to render"))?;
let txt = self.templates.render(&format!("{}/{}.txt", locale, template), &ctx)
let txt = self
.templates
.render(&format!("{}/{}.txt", locale, template), &ctx)
.or_else(|_| self.templates.render(&format!("{}.txt", template), &ctx))
.map_err(|_| anyhow!("Email template failed to render"))?;
@@ -218,88 +210,77 @@ impl Service {
fn send(
&self,
to: &[&Email],
tos: &[&Email],
subject: &str,
template: &str,
locale: &str,
ctx: impl Serialize
ctx: impl Serialize,
) -> Result<()> {
let (html, txt) = self.render_template(template, locale, ctx)?;
if cfg!(debug_assertions) {
for recipient in to.iter() {
println!("To: {}", recipient.to_string());
for recipient in tos.iter() {
println!("To: {}", recipient);
}
println!("{}", &txt);
}
// build this ourselves, as a temporary workaround for https://github.com/lettre/lettre/issues/400
let text = PartBuilder::new()
.body(txt)
.header(("Content-Type", "text/plain; charset=utf-8"))
.header(("Content-Transfer-Encoding", "8bit"))
.build();
let html = PartBuilder::new()
.body(html)
.header(("Content-Type", "text/html; charset=utf-8"))
.header(("Content-Transfer-Encoding", "8bit"))
.build();
let email = EmailBuilder::new()
let mut email = lettre::Message::builder()
.from(self.from.clone())
.subject(rfc2047_encode(subject))
.message_id(format!("<{}@{}>", Uuid::new_v4(), self.domain))
.message_type(MimeMultipartType::Alternative)
.header(("Content-Transfer-Encoding", "8bit"))
.child(text)
.child(html);
.subject(subject)
.message_id(Some(format!("<{}@{}>", Uuid::new_v4(), self.domain)))
.header(header::ContentTransferEncoding::EightBit);
let email = to.iter().fold(email, |email, to| email.to(to.to_string()));
for to in tos.iter() {
email = email.to(to.as_str().parse().unwrap());
}
let email = email.build()?;
let email = email.multipart(
MultiPart::alternative()
.singlepart(
SinglePart::builder()
.header(header::ContentTransferEncoding::EightBit)
.header(header::ContentType::TEXT_PLAIN)
.body(txt),
)
.singlepart(
SinglePart::builder()
.header(header::ContentTransferEncoding::EightBit)
.header(header::ContentType::TEXT_HTML)
.body(html),
),
)?;
match self.transport {
Transport::LocalSmtp => {
let transport = SmtpTransport::unencrypted_localhost();
transport.send(&email)?;
}
Transport::Sendmail => {
let mut transport = SendmailTransport::new();
transport.send(email)?;
},
let transport = SendmailTransport::new();
transport.send(&email)?;
}
Transport::Filemail(ref path) => {
let mut transport = FileTransport::new(path);
transport.send(email)?;
},
let transport = FileTransport::new(path);
transport.send(&email)?;
}
}
Ok(())
}
}
// for some reason, this is no longer public in lettre itself
// FIXME replace with builtin struct on lettre update
// see https://github.com/lettre/lettre/blob/master/lettre/src/file/mod.rs#L41
#[cfg(test)]
#[derive(Deserialize)]
struct SerializableEmail {
#[serde(alias = "envelope")]
_envelope: lettre::Envelope,
#[serde(alias = "message_id")]
_message_id: String,
message: Vec<u8>,
}
/// Returns and removes the first mail it finds from the given
/// directory.
#[cfg(test)]
pub fn pop_mail(dir: &Path) -> Result<Option<String>> {
use std::fs;
use std::{fs, fs::read_to_string};
for entry in fs::read_dir(dir)? {
let entry = entry?;
if entry.file_type()?.is_file() {
let fh = fs::File::open(entry.path())?;
let body = read_to_string(entry.path())?.replace("\r\n", "\n");
fs::remove_file(entry.path())?;
let mail: SerializableEmail = ::serde_json::from_reader(fh)?;
let body = String::from_utf8_lossy(&mail.message).to_string();
println!("{}", body);
return Ok(Some(body));
}
}
@@ -308,23 +289,34 @@ pub fn pop_mail(dir: &Path) -> Result<Option<String>> {
#[cfg(test)]
mod test {
use crate::web::get_i18n;
use super::*;
use tempfile::{tempdir, TempDir};
use gettext_macros::{include_i18n};
use std::str::FromStr;
use tempfile::{tempdir, TempDir};
const BASEDIR: &str = "http://localhost/";
const FROM: &str = "test@localhost";
const TO: &str = "recipient@example.org";
fn configure_i18n(lang: &'static str) -> I18n {
let langs = include_i18n!();
let catalog = langs.clone().into_iter().find(|(l, _)| *l == lang).unwrap().1;
let langs = get_i18n();
let catalog = langs
.clone()
.into_iter()
.find(|(l, _)| *l == lang)
.unwrap()
.1;
rocket_i18n::I18n { catalog, lang }
}
fn configure_mail() -> (Service, TempDir) {
let template_dir: PathBuf = ::std::env::current_dir().unwrap().join("dist/email-templates").to_str().unwrap().into();
let template_dir: PathBuf = ::std::env::current_dir()
.unwrap()
.join("dist/email-templates")
.to_str()
.unwrap()
.into();
let tempdir = tempdir().unwrap();
let service = Service::filemail(FROM, BASEDIR, &template_dir, tempdir.path()).unwrap();
(service, tempdir)
@@ -334,7 +326,7 @@ mod test {
if let Some((_, v)) = headers.iter().find(|(h, _)| *h == name) {
assert!(pred(v));
} else {
panic!(format!("Missing header: {}", name));
panic!("Missing header: {}", name);
}
}
@@ -351,13 +343,14 @@ mod test {
(h, v)
})
.collect();
assert!(headers.contains(&("Content-Transfer-Encoding", "8bit")));
assert!(headers.contains(&("Content-Type", "text/plain; charset=utf-8")));
assert!(headers.contains(&("Content-Type", "text/html; charset=utf-8")));
assert!(headers.contains(&("From", "<test@localhost>")));
assert!(headers.contains(&("To", "<recipient@example.org>")));
assert_header(&headers, "Content-Type", |v| v.starts_with("multipart/alternative"));
assert_header(&headers, "Date", |v| v.contains("+0000"));
assert!(headers.contains(&("From", "test@localhost")));
assert!(headers.contains(&("To", "recipient@example.org")));
assert_header(&headers, "Content-Type", |v| {
v.starts_with("multipart/alternative")
});
assert_header(&headers, "Date", |v| v.contains("-0000"));
assert_header(&headers, "Message-ID", |v| v.contains("@localhost>"));
}
@@ -373,7 +366,14 @@ mod test {
let i18n = configure_i18n("en");
let recipient = Email::from_str(TO).unwrap();
mail.send_verification(&i18n, "test", "fingerprintoo".to_owned(), &recipient, "token").unwrap();
mail.send_verification(
&i18n,
"test",
"fingerprintoo".to_owned(),
&recipient,
"token",
)
.unwrap();
let mail_content = pop_mail(tempdir.path()).unwrap().unwrap();
check_headers(&mail_content);
@@ -391,7 +391,14 @@ mod test {
let i18n = configure_i18n("ja");
let recipient = Email::from_str(TO).unwrap();
mail.send_verification(&i18n, "test", "fingerprintoo".to_owned(), &recipient, "token").unwrap();
mail.send_verification(
&i18n,
"test",
"fingerprintoo".to_owned(),
&recipient,
"token",
)
.unwrap();
let mail_content = pop_mail(tempdir.path()).unwrap().unwrap();
check_headers(&mail_content);
@@ -401,8 +408,9 @@ mod test {
assert!(mail_content.contains("test/verify/token"));
assert!(mail_content.contains("test/about"));
assert!(mail_content.contains("あなたのメールアド"));
assert!(mail_content.contains("Subject: =?utf-8?q?localhost=E3=81=AE=E3=81=82=E3=81=AA=E3=81=9F=E3=81=AE?="));
assert!(mail_content.contains(
"Subject: =?utf-8?b?bG9jYWxob3N044Gu44GC44Gq44Gf44Gu6Y2144Gu44Gf44KB44GrbG9jYWxob3N044KS5qSc6Ki844GZ44KL?="
));
}
#[test]
@@ -411,7 +419,14 @@ mod test {
let i18n = configure_i18n("en");
let recipient = Email::from_str(TO).unwrap();
mail.send_manage_token(&i18n, "test", "fingerprintoo".to_owned(), &recipient, "token").unwrap();
mail.send_manage_token(
&i18n,
"test",
"fingerprintoo".to_owned(),
&recipient,
"token",
)
.unwrap();
let mail_content = pop_mail(tempdir.path()).unwrap().unwrap();
check_headers(&mail_content);
@@ -429,7 +444,14 @@ mod test {
let i18n = configure_i18n("ja");
let recipient = Email::from_str(TO).unwrap();
mail.send_manage_token(&i18n, "test", "fingerprintoo".to_owned(), &recipient, "token").unwrap();
mail.send_manage_token(
&i18n,
"test",
"fingerprintoo".to_owned(),
&recipient,
"token",
)
.unwrap();
let mail_content = pop_mail(tempdir.path()).unwrap().unwrap();
check_headers(&mail_content);
@@ -440,7 +462,9 @@ mod test {
assert!(mail_content.contains("testtoken"));
assert!(mail_content.contains("test/about"));
assert!(mail_content.contains("この鍵の掲示されたア"));
assert!(mail_content.contains("Subject: =?utf-8?q?localhost=E3=81=AE=E9=8D=B5=E3=82=92=E7=AE=A1=E7=90=86?="));
assert!(
mail_content.contains("Subject: =?utf-8?b?bG9jYWxob3N044Gu6Y2144KS566h55CG44GZ44KL?=")
);
}
#[test]
@@ -448,7 +472,8 @@ mod test {
let (mail, tempdir) = configure_mail();
let recipient = Email::from_str(TO).unwrap();
mail.send_welcome("test", "fingerprintoo".to_owned(), &recipient, "token").unwrap();
mail.send_welcome("test", "fingerprintoo".to_owned(), &recipient, "token")
.unwrap();
let mail_content = pop_mail(tempdir.path()).unwrap().unwrap();
check_headers(&mail_content);
@@ -460,4 +485,3 @@ mod test {
assert!(mail_content.contains("first time"));
}
}

View File

@@ -1,17 +1,14 @@
#![feature(proc_macro_hygiene, plugin, decl_macro)]
#![recursion_limit = "1024"]
#[macro_use]
extern crate anyhow;
use anyhow::Result as Result;
use anyhow::Result;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate rocket;
#[macro_use]
extern crate rocket_contrib;
#[cfg(test)]
extern crate regex;
@@ -24,31 +21,22 @@ use gettext_macros::init_i18n;
init_i18n!("hagrid", en, de, ja);
#[cfg(not(debug_assertions))]
init_i18n!("hagrid", en, de, fr, it, ja, nb, pl, tr, zh_Hans, ko, nl, ru, ar);
init_i18n!("hagrid", en, de, fr, it, ja, nb, pl, tr, zh_Hans, ko, nl, ru, ar, sv, es, ro);
mod mail;
mod anonymize_utils;
mod tokens;
mod sealed_state;
mod rate_limiter;
mod dump;
mod counters;
mod dump;
mod gettext_strings;
mod i18n;
mod i18n_helpers;
mod gettext_strings;
mod web;
mod mail;
mod rate_limiter;
mod sealed_state;
mod template_helpers;
mod tokens;
mod web;
fn main() {
if let Err(e) = web::serve() {
eprint!("{}", e);
let mut cause = e.source();
while let Some(c) = cause {
eprint!(":\n {}", c);
cause = c.source();
}
eprintln!();
::std::process::exit(2);
}
#[launch]
fn rocket() -> _ {
web::serve().expect("Rocket config must succeed")
}

View File

@@ -1,6 +1,6 @@
use std::sync::Mutex;
use std::collections::HashMap;
use std::time::{Instant,Duration};
use std::sync::Mutex;
use std::time::{Duration, Instant};
pub struct RateLimiter {
locked_map: Mutex<HashMap<String, Instant>>,
@@ -23,11 +23,12 @@ impl RateLimiter {
self.maybe_cleanup();
let mut locked_map = self.locked_map.lock().unwrap();
let action_ok = locked_map.get(&identifier)
let action_ok = locked_map
.get(&identifier)
.map(|instant| instant.elapsed())
.map(|duration| duration >= self.timeout)
.unwrap_or(true);
if action_ok {
if action_ok {
locked_map.insert(identifier, Instant::now());
}
action_ok
@@ -35,7 +36,8 @@ impl RateLimiter {
pub fn action_check(&self, identifier: String) -> bool {
let locked_map = self.locked_map.lock().unwrap();
locked_map.get(&identifier)
locked_map
.get(&identifier)
.map(|instant| instant.elapsed())
.map(|duration| duration >= self.timeout)
.unwrap_or(true)
@@ -65,7 +67,7 @@ mod tests {
assert!(rate_limiter.action_perform("action".to_owned()));
assert_eq!(false, rate_limiter.action_perform("action".to_owned()));
assert!(!rate_limiter.action_perform("action".to_owned()));
}
#[test]

View File

@@ -1,55 +1,50 @@
use ring::aead::{seal_in_place, open_in_place, Algorithm, AES_256_GCM};
use ring::aead::{OpeningKey, SealingKey};
use ring::rand::{SecureRandom, SystemRandom};
use ring::hmac;
use ring::digest;
use aes_gcm::{
aead::{Aead, OsRng},
AeadCore, Aes256Gcm, Key, KeyInit, Nonce,
};
use sha2::{Digest, Sha256};
// Keep these in sync, and keep the key len synced with the `private` docs as
// well as the `KEYS_INFO` const in secure::Key.
static ALGO: &'static Algorithm = &AES_256_GCM;
const NONCE_LEN: usize = 12;
pub struct SealedState {
sealing_key: SealingKey,
opening_key: OpeningKey,
cipher: Aes256Gcm,
}
impl SealedState {
impl SealedState {
pub fn new(secret: &str) -> Self {
let salt = hmac::SigningKey::new(&digest::SHA256, b"hagrid");
let mut key = vec![0; 32];
ring::hkdf::extract_and_expand(&salt, secret.as_bytes(), b"", &mut key);
let mut hash = Sha256::new();
hash.update(b"hagrid");
hash.update(secret);
let hashed_secret = hash.finalize();
let key = Key::<Aes256Gcm>::from_slice(&hashed_secret);
let cipher = Aes256Gcm::new(&key);
let sealing_key = SealingKey::new(ALGO, key.as_ref()).expect("sealing key creation");
let opening_key = OpeningKey::new(ALGO, key.as_ref()).expect("sealing key creation");
SealedState { sealing_key, opening_key }
SealedState { cipher }
}
pub fn unseal(&self, mut data: Vec<u8>) -> Result<String, &'static str> {
let (nonce, sealed) = data.split_at_mut(NONCE_LEN);
let unsealed = open_in_place(&self.opening_key, nonce, &[], 0, sealed)
pub fn unseal(&self, data: &[u8]) -> Result<String, &'static str> {
if data.len() < NONCE_LEN {
return Err("invalid sealed value: too short");
}
let (sealed, nonce) = data.split_at(data.len() - NONCE_LEN);
let unsealed = self
.cipher
.decrypt(Nonce::from_slice(nonce), sealed)
.map_err(|_| "invalid key/nonce/value: bad seal")?;
::std::str::from_utf8(unsealed)
core::str::from_utf8(&unsealed)
.map(|s| s.to_string())
.map_err(|_| "bad unsealed utf8")
}
pub fn seal(&self, input: &str) -> Vec<u8> {
let mut data;
let output_len = {
let overhead = ALGO.tag_len();
data = vec![0; NONCE_LEN + input.len() + overhead];
let (nonce, in_out) = data.split_at_mut(NONCE_LEN);
SystemRandom::new().fill(nonce).expect("couldn't random fill nonce");
in_out[..input.len()].copy_from_slice(input.as_bytes());
seal_in_place(&self.sealing_key, nonce, &[], in_out, overhead).expect("in-place seal")
};
data[..(NONCE_LEN + output_len)].to_vec()
let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
let mut sealed = self
.cipher
.encrypt(&nonce, input.as_bytes())
.expect("sealing works");
sealed.extend(nonce);
sealed
}
}
@@ -60,10 +55,23 @@ mod tests {
#[test]
fn test_encrypt_decrypt() {
let sv = SealedState::new("swag");
let sealed = sv.seal("test");
let unsealed = sv.unseal(sealed).unwrap();
// use a different instance to make sure no internal state remains
let sv = SealedState::new("swag");
let unsealed = sv.unseal(sealed.as_slice()).unwrap();
assert_eq!("test", unsealed);
}
#[test]
fn too_short() {
let sv = SealedState::new("swag");
let sealed = sv.seal("test");
let sealed_short = &sealed[0..8];
let unsealed_error = sv.unseal(sealed_short);
assert_eq!(Err("invalid sealed value: too short"), unsealed_error);
}
}

View File

@@ -1,12 +1,11 @@
use std::path::{Path, PathBuf};
use std::collections::HashSet;
use std::path::{Path, PathBuf};
use handlebars::Handlebars;
use rocket_dyn_templates::handlebars::Handlebars;
use gettext_macros::include_i18n;
use crate::Result;
use crate::i18n::I18NHelper;
use crate::web::get_i18n;
use crate::Result;
#[derive(Debug)]
pub struct TemplateOverrides(String, HashSet<String>);
@@ -17,10 +16,9 @@ impl TemplateOverrides {
.map(|vec| Self(localized_dir.to_owned(), vec))
}
pub fn get_template_override(&self, lang: &str, tmpl: &str) -> Option<String> {
pub fn get_template_override(&self, lang: &str, tmpl: &str) -> Option<String> {
let template_name = format!("{}/{}/{}", self.0, lang, tmpl);
if self.1.contains(&template_name) {
println!("{}", &template_name);
Some(template_name)
} else {
None
@@ -28,7 +26,10 @@ impl TemplateOverrides {
}
}
fn load_localized_template_names(template_path: &Path, localized_dir: &str) -> Result<HashSet<String>> {
fn load_localized_template_names(
template_path: &Path,
localized_dir: &str,
) -> Result<HashSet<String>> {
let language_glob = template_path.join(localized_dir).join("*");
glob::glob(language_glob.to_str().expect("valid glob path string"))
.unwrap()
@@ -41,17 +42,18 @@ fn load_localized_template_names(template_path: &Path, localized_dir: &str) -> R
.flatten()
.map(move |path| {
// TODO this is a hack
let template_name = remove_extension(remove_extension(path.strip_prefix(&template_path)?));
let template_name =
remove_extension(remove_extension(path.strip_prefix(&template_path)?));
Ok(template_name.to_string_lossy().into_owned())
})
})
.collect()
})
.collect()
}
pub fn load_handlebars(template_dir: &Path) -> Result<Handlebars> {
pub fn load_handlebars(template_dir: &Path) -> Result<Handlebars<'static>> {
let mut handlebars = Handlebars::new();
let i18ns = include_i18n!();
let i18ns = get_i18n();
let i18n_helper = I18NHelper::new(i18ns);
handlebars.register_helper("text", Box::new(i18n_helper));
@@ -71,12 +73,11 @@ fn remove_extension<P: AsRef<Path>>(path: P) -> PathBuf {
let path = path.as_ref();
let stem = match path.file_stem() {
Some(stem) => stem,
None => return path.to_path_buf()
None => return path.to_path_buf(),
};
match path.parent() {
Some(parent) => parent.join(stem),
None => PathBuf::from(stem)
None => PathBuf::from(stem),
}
}

View File

@@ -1,18 +1,16 @@
use crate::sealed_state::SealedState;
use serde_json;
use serde::{Serialize,de::DeserializeOwned};
use crate::Result;
use serde::{de::DeserializeOwned, Serialize};
pub trait StatelessSerializable : Serialize + DeserializeOwned {
}
pub trait StatelessSerializable: Serialize + DeserializeOwned {}
pub struct Service {
sealed_state: SealedState,
validity: u64,
}
#[derive(Serialize,Deserialize)]
#[derive(Serialize, Deserialize)]
struct Token {
#[serde(rename = "c")]
creation: u64,
@@ -23,7 +21,10 @@ struct Token {
impl Service {
pub fn init(secret: &str, validity: u64) -> Self {
let sealed_state = SealedState::new(secret);
Service { sealed_state, validity }
Service {
sealed_state,
validity,
}
}
pub fn create(&self, payload_content: &impl StatelessSerializable) -> String {
@@ -38,17 +39,21 @@ impl Service {
}
pub fn check<T>(&self, token_encoded: &str) -> Result<T>
where T: StatelessSerializable {
where
T: StatelessSerializable,
{
let token_sealed = base64::decode_config(&token_encoded, base64::URL_SAFE_NO_PAD)
.map_err(|_| anyhow!("invalid b64"))?;
let token_str = self.sealed_state.unseal(token_sealed)
.map_err(|_| anyhow!("failed to validate"))?;
let token: Token = serde_json::from_str(&token_str)
.map_err(|_| anyhow!("failed to deserialize"))?;
.map_err(|_| anyhow!("Invalid base64. Did you follow a correct link?"))?;
let token_str = self
.sealed_state
.unseal(token_sealed.as_slice())
.map_err(|_| anyhow!("Failed to validate. Did you follow a correct link?"))?;
let token: Token =
serde_json::from_str(&token_str).map_err(|_| anyhow!("failed to deserialize"))?;
let elapsed = current_time() - token.creation;
if elapsed > self.validity {
Err(anyhow!("Token has expired!"))?;
return Err(anyhow!("Token has expired!"));
}
let payload: T = serde_json::from_str(&token.payload)
@@ -56,13 +61,15 @@ impl Service {
Ok(payload)
}
}
#[cfg(not(test))]
fn current_time() -> u64 {
use std::time::SystemTime;
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs()
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs()
}
#[cfg(test)]
@@ -74,23 +81,23 @@ fn current_time() -> u64 {
mod tests {
use super::*;
#[derive(Debug,Serialize,Deserialize,Clone,PartialEq)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
struct TestStruct1 {
payload: String,
}
impl StatelessSerializable for TestStruct1 {
}
impl StatelessSerializable for TestStruct1 {}
#[derive(Debug,Serialize,Deserialize,Clone,PartialEq)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
struct TestStruct2 {
something: String,
}
impl StatelessSerializable for TestStruct2 {
}
impl StatelessSerializable for TestStruct2 {}
#[test]
fn test_create_check() {
let payload = TestStruct1 { payload: "hello".to_owned() };
let payload = TestStruct1 {
payload: "hello".to_owned(),
};
let mt = Service::init("secret", 60);
let token = mt.create(&payload);
// println!("{}", &token);
@@ -103,8 +110,10 @@ mod tests {
#[test]
fn test_ok() {
let payload = TestStruct1 { payload: "hello".to_owned() };
let token = "rwM_S9gZaRQaf6DLvmWtZSipQhH_G5ronSIJv2FrMdwGBPSYYQ-1jaP58dTHU5WuC14vb8jxmz2Xf_b3pqzpCGTEJj9drm4t";
let payload = TestStruct1 {
payload: "hello".to_owned(),
};
let token = "C6fCPAGv93nZqDQXodl-bsDgzkxqbjDtbeR6Be4v_UHJfL2UJxG2imzmUlK1PfLT4QzNIRWsdFDYWrx_aCgLZ4MgVQWYyazn";
let mt = Service::init("secret", 60);
let check_result = mt.check(token);
@@ -114,7 +123,9 @@ mod tests {
#[test]
fn test_bad_type() {
let payload = TestStruct1 { payload: "hello".to_owned() };
let payload = TestStruct1 {
payload: "hello".to_owned(),
};
let mt = Service::init("secret", 60);
let token = mt.create(&payload);

View File

@@ -3,17 +3,13 @@ use std::io;
use rocket_i18n::I18n;
use crate::dump::{self, Kind};
use crate::web::MyResponse;
use crate::i18n_helpers::describe_query_error;
use crate::web::MyResponse;
use crate::database::{Database, KeyDatabase, Query};
#[get("/debug?<q>")]
pub fn debug_info(
db: rocket::State<KeyDatabase>,
i18n: I18n,
q: String,
) -> MyResponse {
pub fn debug_info(db: &rocket::State<KeyDatabase>, i18n: I18n, q: String) -> MyResponse {
let query = match q.parse::<Query>() {
Ok(query) => query,
Err(_) => return MyResponse::bad_request_plain("bad request"),
@@ -35,16 +31,12 @@ pub fn debug_info(
false,
false,
None,
|_| { None },
|_| { None },
32 * 4 + 80,
);
match dump_result {
Ok((Kind::Cert, _)) => {
match String::from_utf8(result) {
Ok(dump_text) => MyResponse::plain(dump_text),
Err(e) => MyResponse::ise(e.into()),
}
Ok(Kind::Cert) => match String::from_utf8(result) {
Ok(dump_text) => MyResponse::plain(dump_text),
Err(e) => MyResponse::ise(e.into()),
},
Ok(_) => MyResponse::ise(anyhow!("Internal parsing error!")),
Err(e) => MyResponse::ise(e),

View File

@@ -1,154 +1,127 @@
use std::fmt;
use std::time::SystemTime;
use std::collections::HashMap;
use std::str::FromStr;
use std::time::SystemTime;
use rocket::http::ContentType;
use rocket::Data;
use rocket::Outcome;
use rocket::http::{ContentType, Status};
use rocket::request::{self, Request, FromRequest};
use rocket::http::uri::Uri;
use rocket_i18n::I18n;
use url::percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
use crate::database::{Database, Query, KeyDatabase};
use crate::database::types::{Email, Fingerprint, KeyID};
use crate::database::{Database, KeyDatabase, Query};
use crate::rate_limiter::RateLimiter;
use crate::i18n_helpers::describe_query_error;
use crate::rate_limiter::RateLimiter;
use crate::tokens;
use crate::web;
use crate::mail;
use crate::web::{HagridState, RequestOrigin, MyResponse, vks_web};
use crate::web::vks::response::UploadResponse;
use crate::web;
use crate::web::vks::response::EmailStatus;
use crate::web::vks::response::UploadResponse;
use crate::web::{vks_web, MyResponse, RequestOrigin};
#[derive(Debug)]
pub enum Hkp {
Fingerprint { fpr: Fingerprint, index: bool },
KeyID { keyid: KeyID, index: bool },
ShortKeyID { query: String, index: bool },
Email { email: Email, index: bool },
Invalid { query: String, },
Fingerprint { fpr: Fingerprint },
KeyID { keyid: KeyID },
ShortKeyID { query: String },
Email { email: Email },
}
impl fmt::Display for Hkp {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Hkp::Fingerprint{ ref fpr,.. } => write!(f, "{}", fpr),
Hkp::KeyID{ ref keyid,.. } => write!(f, "{}", keyid),
Hkp::Email{ ref email,.. } => write!(f, "{}", email),
Hkp::ShortKeyID{ ref query,.. } => write!(f, "{}", query),
Hkp::Invalid{ ref query } => write!(f, "{}", query),
Hkp::Fingerprint { ref fpr, .. } => write!(f, "{}", fpr),
Hkp::KeyID { ref keyid, .. } => write!(f, "{}", keyid),
Hkp::Email { ref email, .. } => write!(f, "{}", email),
Hkp::ShortKeyID { ref query, .. } => write!(f, "{}", query),
}
}
}
impl<'a, 'r> FromRequest<'a, 'r> for Hkp {
type Error = ();
impl std::str::FromStr for Hkp {
type Err = anyhow::Error;
fn from_str(search: &str) -> Result<Self, Self::Err> {
let maybe_fpr = Fingerprint::from_str(search);
let maybe_keyid = KeyID::from_str(search);
fn from_request(request: &'a Request<'r>) -> request::Outcome<Hkp, ()> {
use std::str::FromStr;
use rocket::request::FormItems;
let query = request.uri().query().unwrap_or("");
let fields = FormItems::from(query)
.map(|item| {
let (k, v) = item.key_value();
let key = k.url_decode().unwrap_or_default();
let value = v.url_decode().unwrap_or_default();
(key, value)
})
.collect::<HashMap<_, _>>();
if fields.contains_key("search")
&& fields
.get("op")
.map(|x| x == "get" || x == "index")
.unwrap_or(false)
{
let index = fields.get("op").map(|x| x == "index").unwrap_or(false);
let search = fields.get("search").cloned().unwrap_or_default();
let maybe_fpr = Fingerprint::from_str(&search);
let maybe_keyid = KeyID::from_str(&search);
let looks_like_short_key_id = !search.contains('@') &&
(search.starts_with("0x") && search.len() < 16 || search.len() == 8);
if looks_like_short_key_id {
Outcome::Success(Hkp::ShortKeyID {
query: search,
index: index,
})
} else if let Ok(fpr) = maybe_fpr {
Outcome::Success(Hkp::Fingerprint {
fpr: fpr,
index: index,
})
} else if let Ok(keyid) = maybe_keyid {
Outcome::Success(Hkp::KeyID {
keyid: keyid,
index: index,
})
} else {
match Email::from_str(&search) {
Ok(email) => {
Outcome::Success(Hkp::Email {
email: email,
index: index,
})
}
Err(_) => {
Outcome::Success(Hkp::Invalid{
query: search
})
}
}
let looks_like_short_key_id = !search.contains('@')
&& (search.starts_with("0x") && search.len() < 16 || search.len() == 8);
let hkp = if looks_like_short_key_id {
Hkp::ShortKeyID {
query: search.to_string(),
}
} else if fields.get("op").map(|x| x == "vindex"
|| x.starts_with("x-"))
.unwrap_or(false)
{
Outcome::Failure((Status::NotImplemented, ()))
} else if let Ok(fpr) = maybe_fpr {
Hkp::Fingerprint { fpr }
} else if let Ok(keyid) = maybe_keyid {
Hkp::KeyID { keyid }
} else {
Outcome::Failure((Status::BadRequest, ()))
}
match Email::from_str(search) {
Ok(email) => Hkp::Email { email },
Err(_) => return Err(anyhow::anyhow!("Invalid search query!")),
}
};
Ok(hkp)
}
}
#[post("/pks/add", format = "multipart/form-data", data = "<data>")]
pub fn pks_add_form_data(
db: rocket::State<KeyDatabase>,
tokens_stateless: rocket::State<tokens::Service>,
rate_limiter: rocket::State<RateLimiter>,
pub async fn pks_add_form_data(
db: &rocket::State<KeyDatabase>,
tokens_stateless: &rocket::State<tokens::Service>,
rate_limiter: &rocket::State<RateLimiter>,
i18n: I18n,
cont_type: &ContentType,
data: Data,
data: Data<'_>,
) -> MyResponse {
match vks_web::process_post_form_data(db, tokens_stateless, rate_limiter, i18n, cont_type, data) {
match vks_web::process_post_form_data(db, tokens_stateless, rate_limiter, i18n, cont_type, data)
.await
{
Ok(_) => MyResponse::plain("Ok".into()),
Err(err) => MyResponse::ise(err),
}
}
#[post("/pks/add", format = "application/x-www-form-urlencoded", data = "<data>")]
pub fn pks_add_form(
request_origin: RequestOrigin,
db: rocket::State<KeyDatabase>,
tokens_stateless: rocket::State<tokens::Service>,
rate_limiter: rocket::State<RateLimiter>,
mail_service: rocket::State<mail::Service>,
#[post(
"/pks/add",
format = "application/x-www-form-urlencoded",
data = "<data>"
)]
pub async fn pks_add_form(
origin: RequestOrigin,
db: &rocket::State<KeyDatabase>,
tokens_stateless: &rocket::State<tokens::Service>,
rate_limiter: &rocket::State<RateLimiter>,
mail_service: &rocket::State<mail::Service>,
i18n: I18n,
data: Data,
data: Data<'_>,
) -> MyResponse {
match vks_web::process_post_form(&db, &tokens_stateless, &rate_limiter, &i18n, data) {
Ok(UploadResponse::Ok { is_new_key, key_fpr, primary_uid, token, status, .. }) => {
let msg = pks_add_ok(&request_origin, &mail_service, &rate_limiter, token, status, is_new_key, key_fpr, primary_uid);
match vks_web::process_post_form(db, tokens_stateless, rate_limiter, &i18n, data).await {
Ok(UploadResponse::Ok {
is_new_key,
key_fpr,
primary_uid,
token,
status,
..
}) => {
let msg = pks_add_ok(
&origin,
mail_service,
rate_limiter,
token,
status,
is_new_key,
key_fpr,
primary_uid,
);
MyResponse::plain(msg)
}
Ok(_) => {
let msg = format!("Upload successful. Please note that identity information will only be published after verification. See {baseuri}/about/usage#gnupg-upload", baseuri = request_origin.get_base_uri());
let msg = format!("Upload successful. Please note that identity information will only be published after verification. See {baseuri}/about/usage#gnupg-upload", baseuri = origin.get_base_uri());
MyResponse::plain(msg)
}
Err(err) => MyResponse::ise(err),
@@ -156,7 +129,7 @@ pub fn pks_add_form(
}
fn pks_add_ok(
request_origin: &RequestOrigin,
origin: &RequestOrigin,
mail_service: &mail::Service,
rate_limiter: &RateLimiter,
token: String,
@@ -166,137 +139,134 @@ fn pks_add_ok(
primary_uid: Option<Email>,
) -> String {
if primary_uid.is_none() {
return format!("Upload successful. Please note that identity information will only be published after verification. See {baseuri}/about/usage#gnupg-upload", baseuri = request_origin.get_base_uri())
return format!("Upload successful. Please note that identity information will only be published after verification. See {baseuri}/about/usage#gnupg-upload", baseuri = origin.get_base_uri());
}
let primary_uid = primary_uid.unwrap();
if is_new_key {
if send_welcome_mail(&request_origin, &mail_service, key_fpr, &primary_uid, token) {
if send_welcome_mail(origin, mail_service, key_fpr, &primary_uid, token) {
rate_limiter.action_perform(format!("hkp-sent-{}", &primary_uid));
return format!("Upload successful. This is a new key, a welcome email has been sent.");
return "Upload successful. This is a new key, a welcome email has been sent."
.to_string();
}
return format!("Upload successful. Please note that identity information will only be published after verification. See {baseuri}/about/usage#gnupg-upload", baseuri = request_origin.get_base_uri())
return format!("Upload successful. Please note that identity information will only be published after verification. See {baseuri}/about/usage#gnupg-upload", baseuri = origin.get_base_uri());
}
let has_unverified = status.iter().any(|(_, v)| *v == EmailStatus::Unpublished);
if !has_unverified {
return format!("Upload successful.");
return "Upload successful.".to_string();
}
// We send this out on the *second* time the key is uploaded (within one ratelimit period).
let uploaded_repeatedly = !rate_limiter.action_perform(format!("hkp-upload-{}", &key_fpr));
if uploaded_repeatedly && rate_limiter.action_perform(format!("hkp-sent-{}", &primary_uid)) {
if send_upload_mail(&request_origin, &mail_service, key_fpr, &primary_uid, token) {
return format!("Upload successful. An upload information email has been sent.");
}
}
return format!("Upload successful. Please note that identity information will only be published after verification. See {baseuri}/about/usage#gnupg-upload", baseuri = request_origin.get_base_uri())
}
fn send_upload_mail(
request_origin: &RequestOrigin,
mail_service: &mail::Service,
fpr: String,
primary_uid: &Email,
token: String,
) -> bool {
mail_service.send_upload(request_origin.get_base_uri(), fpr, primary_uid, &token).is_ok()
return format!("Upload successful. Please note that identity information will only be published after verification. See {baseuri}/about/usage#gnupg-upload", baseuri = origin.get_base_uri());
}
fn send_welcome_mail(
request_origin: &RequestOrigin,
origin: &RequestOrigin,
mail_service: &mail::Service,
fpr: String,
primary_uid: &Email,
token: String,
) -> bool {
mail_service.send_welcome(request_origin.get_base_uri(), fpr, primary_uid, &token).is_ok()
mail_service
.send_welcome(origin.get_base_uri(), fpr, primary_uid, &token)
.is_ok()
}
#[get("/pks/lookup")]
#[get("/pks/lookup?<op>&<search>")]
pub fn pks_lookup(
state: rocket::State<HagridState>,
db: rocket::State<KeyDatabase>,
db: &rocket::State<KeyDatabase>,
i18n: I18n,
key: Hkp
op: Option<String>,
search: Option<String>,
) -> MyResponse {
let (query, index) = match key {
Hkp::Fingerprint { fpr, index } =>
(Query::ByFingerprint(fpr), index),
Hkp::KeyID { keyid, index } =>
(Query::ByKeyID(keyid), index),
Hkp::Email { email, index } => {
(Query::ByEmail(email), index)
}
let search = search.unwrap_or_default();
let key = match Hkp::from_str(&search) {
Ok(key) => key,
Err(_) => return MyResponse::bad_request_plain("Invalid search query!"),
};
let query = match key {
Hkp::Fingerprint { fpr } => Query::ByFingerprint(fpr),
Hkp::KeyID { keyid } => Query::ByKeyID(keyid),
Hkp::Email { email } => Query::ByEmail(email),
Hkp::ShortKeyID { query: _, .. } => {
return MyResponse::bad_request_plain("Search by short key ids is not supported, sorry!");
}
Hkp::Invalid { query: _ } => {
return MyResponse::bad_request_plain("Invalid search query!");
return MyResponse::bad_request_plain(
"Search by short key ids is not supported, sorry!",
);
}
};
if index {
key_to_hkp_index(db, i18n, query)
if let Some(op) = op {
match op.as_str() {
"index" => key_to_hkp_index(db, i18n, query),
"get" => web::key_to_response_plain(db, i18n, query),
"vindex" => MyResponse::not_implemented_plain("vindex not implemented"),
s if s.starts_with("x-") => {
MyResponse::not_implemented_plain("x-* operations not implemented")
}
&_ => MyResponse::bad_request_plain("Invalid op parameter!"),
}
} else {
web::key_to_response_plain(state, db, i18n, query)
MyResponse::bad_request_plain("op parameter required!")
}
}
#[get("/pks/internal/index/<query_string>")]
pub fn pks_internal_index(
db: rocket::State<KeyDatabase>,
db: &rocket::State<KeyDatabase>,
i18n: I18n,
query_string: String,
) -> MyResponse {
match query_string.parse() {
Ok(query) => key_to_hkp_index(db, i18n, query),
Err(_) => MyResponse::bad_request_plain("Invalid search query!")
Err(_) => MyResponse::bad_request_plain("Invalid search query!"),
}
}
fn key_to_hkp_index(
db: rocket::State<KeyDatabase>,
i18n: I18n,
query: Query,
) -> MyResponse {
use sequoia_openpgp::types::RevocationStatus;
fn key_to_hkp_index(db: &rocket::State<KeyDatabase>, i18n: I18n, query: Query) -> MyResponse {
use sequoia_openpgp::policy::StandardPolicy;
use sequoia_openpgp::types::RevocationStatus;
let tpk = match db.lookup(&query) {
Ok(Some(tpk)) => tpk,
Ok(None) => return MyResponse::not_found_plain(describe_query_error(&i18n, &query)),
Err(err) => { return MyResponse::ise(err); }
Err(err) => {
return MyResponse::ise(err);
}
};
let mut out = String::default();
let p = tpk.primary_key();
let ref policy = StandardPolicy::new();
let policy = &StandardPolicy::new();
let ctime = format!("{}", p.creation_time().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs());
let is_rev =
if tpk.revocation_status(policy, None) != RevocationStatus::NotAsFarAsWeKnow {
"r"
} else {
""
};
let ctime = format!(
"{}",
p.creation_time()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs()
);
let is_rev = if tpk.revocation_status(policy, None) != RevocationStatus::NotAsFarAsWeKnow {
"r"
} else {
""
};
let algo: u8 = p.pk_algo().into();
out.push_str("info:1:1\r\n");
out.push_str(&format!(
"pub:{}:{}:{}:{}:{}:{}{}\r\n",
p.fingerprint().to_string().replace(" ", ""),
algo,
p.mpis().bits().unwrap_or(0),
ctime,
"",
"",
is_rev
"pub:{}:{}:{}:{}:{}:{}{}\r\n",
p.fingerprint().to_string().replace(" ", ""),
algo,
p.mpis().bits().unwrap_or(0),
ctime,
"",
"",
is_rev
));
for uid in tpk.userids() {
let uidstr = uid.userid().to_string();
let u = Uri::percent_encode(&uidstr);
let u = utf8_percent_encode(&uidstr, DEFAULT_ENCODE_SET).to_string();
let ctime = uid
.binding_signature(policy, None)
.ok()
@@ -304,18 +274,13 @@ fn key_to_hkp_index(
.and_then(|time| time.duration_since(SystemTime::UNIX_EPOCH).ok())
.map(|x| format!("{}", x.as_secs()))
.unwrap_or_default();
let is_rev = if uid.revocation_status(policy, None)
!= RevocationStatus::NotAsFarAsWeKnow
{
"r"
} else {
""
};
let is_rev = if uid.revocation_status(policy, None) != RevocationStatus::NotAsFarAsWeKnow {
"r"
} else {
""
};
out.push_str(&format!(
"uid:{}:{}:{}:{}{}\r\n",
u, ctime, "", "", is_rev
));
out.push_str(&format!("uid:{}:{}:{}:{}{}\r\n", u, ctime, "", "", is_rev));
}
MyResponse::plain(out)
@@ -323,13 +288,13 @@ fn key_to_hkp_index(
#[cfg(test)]
mod tests {
use rocket::http::Status;
use rocket::http::ContentType;
use rocket::http::Status;
use sequoia_openpgp::serialize::Serialize;
use crate::web::tests::*;
use crate::mail::pop_mail;
use crate::web::tests::*;
#[test]
fn hkp() {
@@ -345,9 +310,8 @@ mod tests {
// Prepare to /pks/add
let mut armored = Vec::new();
{
use sequoia_openpgp::armor::{Writer, Kind};
let mut w = Writer::new(&mut armored, Kind::PublicKey)
.unwrap();
use sequoia_openpgp::armor::{Kind, Writer};
let mut w = Writer::new(&mut armored, Kind::PublicKey).unwrap();
tpk.serialize(&mut w).unwrap();
w.finalize().unwrap();
}
@@ -357,12 +321,13 @@ mod tests {
}
// Add!
let mut response = client.post("/pks/add")
let response = client
.post("/pks/add")
.body(post_data.as_bytes())
.header(ContentType::Form)
.dispatch();
assert_eq!(response.status(), Status::Ok);
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
eprintln!("response: {}", body);
// Check that we get a welcome mail
@@ -370,12 +335,13 @@ mod tests {
assert!(welcome_mail.is_some());
// Add!
let mut response = client.post("/pks/add")
let response = client
.post("/pks/add")
.body(post_data.as_bytes())
.header(ContentType::Form)
.dispatch();
assert_eq!(response.status(), Status::Ok);
let body = response.body_string().unwrap();
let body = response.into_string().unwrap();
eprintln!("response: {}", body);
// No second email right after the welcome one!
@@ -393,7 +359,8 @@ mod tests {
check_hr_responses_by_fingerprint(&client, &tpk, 0);
// Upload the same key again, make sure the welcome mail is not sent again
let response = client.post("/pks/add")
let response = client
.post("/pks/add")
.body(post_data.as_bytes())
.header(ContentType::Form)
.dispatch();
@@ -418,14 +385,14 @@ mod tests {
let mut armored_first = Vec::new();
let mut armored_both = Vec::new();
{
use sequoia_openpgp::armor::{Writer, Kind};
use sequoia_openpgp::armor::{Kind, Writer};
let mut w = Writer::new(&mut armored_both, Kind::PublicKey).unwrap();
tpk_0.serialize(&mut w).unwrap();
tpk_1.serialize(&mut w).unwrap();
w.finalize().unwrap();
}
{
use sequoia_openpgp::armor::{Writer, Kind};
use sequoia_openpgp::armor::{Kind, Writer};
let mut w = Writer::new(&mut armored_first, Kind::PublicKey).unwrap();
tpk_0.serialize(&mut w).unwrap();
w.finalize().unwrap();
@@ -440,7 +407,8 @@ mod tests {
}
// Add!
let response = client.post("/pks/add")
let response = client
.post("/pks/add")
.body(post_data_both.as_bytes())
.header(ContentType::Form)
.dispatch();
@@ -451,7 +419,8 @@ mod tests {
assert!(welcome_mail.is_none());
// Add the first again
let response = client.post("/pks/add")
let response = client
.post("/pks/add")
.body(post_data_first.as_bytes())
.header(ContentType::Form)
.dispatch();
@@ -460,16 +429,6 @@ mod tests {
let upload_mail_1 = pop_mail(filemail_into.as_path()).unwrap();
assert!(upload_mail_1.is_none());
// Add the first again a second time - we should get an upload mail
let response = client.post("/pks/add")
.body(post_data_first.as_bytes())
.header(ContentType::Form)
.dispatch();
assert_eq!(response.status(), Status::Ok);
let upload_mail_2 = pop_mail(filemail_into.as_path()).unwrap();
assert!(upload_mail_2.is_some());
check_mr_responses_by_fingerprint(&client, &tpk_0, 0);
check_mr_responses_by_fingerprint(&client, &tpk_1, 0);
check_hr_responses_by_fingerprint(&client, &tpk_0, 0);

View File

@@ -1,8 +1,9 @@
use rocket::{Request, Data};
use rocket::fairing::{Fairing, Info, Kind};
use rocket::http::Method;
use rocket_contrib::templates::Template;
use rocket::{Data, Request};
use rocket_dyn_templates::Template;
use rocket_i18n::I18n;
use serde_json::json;
use std::fs;
use std::path::PathBuf;
@@ -23,29 +24,30 @@ mod templates {
}
}
#[async_trait]
impl Fairing for MaintenanceMode {
fn info(&self) -> Info {
Info {
name: "Maintenance Mode",
kind: Kind::Request
kind: Kind::Request,
}
}
fn on_request(&self, request: &mut Request, _: &Data) {
async fn on_request(&self, request: &mut Request<'_>, _: &mut Data<'_>) {
let message = match self.get_maintenance_message() {
Some(message) => message,
None => return,
};
let path = request.uri().path();
let path = request.uri().path().as_str();
if self.is_request_json(path) {
request.set_uri(uri!(maintenance_error_json: message));
request.set_uri(uri!(maintenance_error_json(message)));
request.set_method(Method::Get);
} else if self.is_request_plain(path, request.method()) {
request.set_uri(uri!(maintenance_error_plain: message));
request.set_uri(uri!(maintenance_error_plain(message)));
request.set_method(Method::Get);
} else if self.is_request_web(path) {
request.set_uri(uri!(maintenance_error_web: message));
request.set_uri(uri!(maintenance_error_web(message)));
request.set_method(Method::Get);
}
}
@@ -57,8 +59,7 @@ impl MaintenanceMode {
}
fn is_request_json(&self, path: &str) -> bool {
path.starts_with("/vks/v1/upload") ||
path.starts_with("/vks/v1/request-verify")
path.starts_with("/vks/v1/upload") || path.starts_with("/vks/v1/request-verify")
}
fn is_request_plain(&self, path: &str, method: Method) -> bool {
@@ -66,9 +67,7 @@ impl MaintenanceMode {
}
fn is_request_web(&self, path: &str) -> bool {
path.starts_with("/upload") ||
path.starts_with("/manage") ||
path.starts_with("/verify")
path.starts_with("/upload") || path.starts_with("/manage") || path.starts_with("/verify")
}
fn get_maintenance_message(&self) -> Option<String> {
@@ -91,15 +90,12 @@ struct JsonErrorMessage {
#[get("/maintenance/json/<message>")]
pub fn maintenance_error_json(message: String) -> MyResponse {
MyResponse::MaintenanceJson(json!(JsonErrorMessage{ message }))
MyResponse::MaintenanceJson(json!(JsonErrorMessage { message }))
}
#[get("/maintenance/web/<message>")]
pub fn maintenance_error_web(
message: String,
i18n: I18n,
) -> MyResponse {
let ctx = templates::MaintenanceMode{
pub fn maintenance_error_web(message: String, i18n: I18n) -> MyResponse {
let ctx = templates::MaintenanceMode {
message,
version: env!("VERGEN_SEMVER").to_string(),
commit: env!("VERGEN_SHA_SHORT").to_string(),

View File

@@ -1,26 +1,23 @@
use rocket;
use rocket::State;
use rocket::request::Form;
use rocket::form::Form;
use rocket_i18n::I18n;
use crate::Result;
use gettext_macros::i18n;
use crate::web::{RequestOrigin, MyResponse};
use crate::web::vks_web;
use crate::database::{Database, KeyDatabase, types::Email, types::Fingerprint};
use crate::mail;
use crate::counters;
use crate::database::{types::Email, types::Fingerprint, Database, KeyDatabase};
use crate::mail;
use crate::rate_limiter::RateLimiter;
use crate::tokens::{self, StatelessSerializable};
use crate::web::vks_web;
use crate::web::{MyResponse, RequestOrigin};
#[derive(Debug,Serialize,Deserialize)]
#[derive(Debug, Serialize, Deserialize)]
struct StatelessVerifyToken {
fpr: Fingerprint,
}
impl StatelessSerializable for StatelessVerifyToken {
fpr: Fingerprint,
}
impl StatelessSerializable for StatelessVerifyToken {}
mod templates {
#[derive(Serialize)]
@@ -39,8 +36,8 @@ mod templates {
#[derive(Serialize)]
pub struct ManageKeyUidStatus {
pub address: String,
pub published: bool,
pub address: String,
pub published: bool,
}
}
@@ -58,17 +55,17 @@ pub mod forms {
}
#[get("/manage")]
pub fn vks_manage() -> Result<MyResponse> {
Ok(MyResponse::ok_bare("manage/manage"))
pub fn vks_manage(origin: RequestOrigin, i18n: I18n) -> MyResponse {
MyResponse::ok_bare("manage/manage", i18n, origin)
}
#[get("/manage/<token>")]
pub fn vks_manage_key(
request_origin: RequestOrigin,
db: State<KeyDatabase>,
i18n: I18n,
token: String,
token_service: rocket::State<tokens::Service>,
origin: RequestOrigin,
db: &rocket::State<KeyDatabase>,
i18n: I18n,
token: String,
token_service: &rocket::State<tokens::Service>,
) -> MyResponse {
use crate::database::types::Fingerprint;
use std::convert::TryFrom;
@@ -76,88 +73,112 @@ pub fn vks_manage_key(
match db.lookup(&database::Query::ByFingerprint(fpr)) {
Ok(Some(tpk)) => {
let fp = Fingerprint::try_from(tpk.fingerprint()).unwrap();
let mut emails: Vec<Email> = tpk.userids()
let mut emails: Vec<Email> = tpk
.userids()
.map(|u| u.userid().to_string().parse::<Email>())
.flatten()
.collect();
emails.sort_unstable();
emails.dedup();
let uid_status = emails.into_iter().map(|email|
templates::ManageKeyUidStatus {
let uid_status = emails
.into_iter()
.map(|email| templates::ManageKeyUidStatus {
address: email.to_string(),
published: true,
}
).collect();
let key_link = uri!(vks_web::search: fp.to_string()).to_string();
})
.collect();
let key_link = uri!(vks_web::search(q = fp.to_string())).to_string();
let context = templates::ManageKey {
key_fpr: fp.to_string(),
key_link,
uid_status,
token,
base_uri: request_origin.get_base_uri().to_owned(),
base_uri: origin.get_base_uri().to_owned(),
};
MyResponse::ok("manage/manage_key", context)
},
MyResponse::ok("manage/manage_key", context, i18n, origin)
}
Ok(None) => MyResponse::not_found(
Some("manage/manage"),
Some(i18n!(i18n.catalog, "This link is invalid or expired"))),
Some(i18n!(i18n.catalog, "This link is invalid or expired")),
i18n,
origin,
),
Err(e) => MyResponse::ise(e),
}
} else {
MyResponse::not_found(
Some("manage/manage"),
Some(i18n!(i18n.catalog, "This link is invalid or expired")))
Some(i18n!(i18n.catalog, "This link is invalid or expired")),
i18n,
origin,
)
}
}
#[post("/manage", data="<request>")]
#[post("/manage", data = "<request>")]
pub fn vks_manage_post(
db: State<KeyDatabase>,
request_origin: RequestOrigin,
mail_service: rocket::State<mail::Service>,
rate_limiter: rocket::State<RateLimiter>,
db: &rocket::State<KeyDatabase>,
origin: RequestOrigin,
mail_service: &rocket::State<mail::Service>,
rate_limiter: &rocket::State<RateLimiter>,
i18n: I18n,
request: Form<forms::ManageRequest>,
token_service: rocket::State<tokens::Service>,
token_service: &rocket::State<tokens::Service>,
) -> MyResponse {
use std::convert::TryInto;
let email = match request.search_term.parse::<Email>() {
Ok(email) => email,
Err(_) => return MyResponse::not_found(
Some("manage/manage"),
Some(i18n!(i18n.catalog, "Malformed address: {address}"; address = request.search_term)))
Err(_) => {
return MyResponse::not_found(
Some("manage/manage"),
Some(i18n!(i18n.catalog, "Malformed address: {}"; request.search_term.as_str())),
i18n,
origin,
)
}
};
let tpk = match db.lookup(&database::Query::ByEmail(email.clone())) {
Ok(Some(tpk)) => tpk,
Ok(None) => return MyResponse::not_found(
Some("manage/manage"),
Some(i18n!(i18n.catalog, "No key for address: {address}"; address = request.search_term))),
Ok(None) => {
return MyResponse::not_found(
Some("manage/manage"),
Some(i18n!(i18n.catalog, "No key for address: {}"; request.search_term.as_str())),
i18n,
origin,
)
}
Err(e) => return MyResponse::ise(e),
};
let email_exists = tpk.userids()
let email_exists = tpk
.userids()
.flat_map(|binding| binding.userid().to_string().parse::<Email>())
.any(|candidate| candidate == email);
if !email_exists {
return MyResponse::ise(
anyhow!("Internal error: address check failed!"));
return MyResponse::ise(anyhow!("Internal error: address check failed!"));
}
if !rate_limiter.action_perform(format!("manage-{}", &email)) {
return MyResponse::not_found(
Some("manage/manage"),
Some(i18n!(i18n.catalog, "A request has already been sent for this address recently.")));
Some(i18n!(
i18n.catalog,
"A request has already been sent for this address recently."
)),
i18n,
origin,
);
}
let fpr: Fingerprint = tpk.fingerprint().try_into().unwrap();
let fpr_text = fpr.to_string();
let token = token_service.create(&StatelessVerifyToken { fpr });
let link_path = uri!(vks_manage_key: token).to_string();
let link_path = uri!(vks_manage_key(token)).to_string();
let base_uri = request_origin.get_base_uri();
let base_uri = origin.get_base_uri();
if let Err(e) = mail_service.send_manage_token(&i18n, base_uri, fpr_text, &email, &link_path) {
return MyResponse::ise(e);
}
@@ -165,27 +186,27 @@ pub fn vks_manage_post(
let ctx = templates::ManageLinkSent {
address: email.to_string(),
};
MyResponse::ok("manage/manage_link_sent", ctx)
MyResponse::ok("manage/manage_link_sent", ctx, i18n, origin)
}
#[post("/manage/unpublish", data="<request>")]
#[post("/manage/unpublish", data = "<request>")]
pub fn vks_manage_unpublish(
request_origin: RequestOrigin,
db: rocket::State<KeyDatabase>,
origin: RequestOrigin,
db: &rocket::State<KeyDatabase>,
i18n: I18n,
token_service: rocket::State<tokens::Service>,
token_service: &rocket::State<tokens::Service>,
request: Form<forms::ManageDelete>,
) -> MyResponse {
match vks_manage_unpublish_or_fail(request_origin, db, token_service, i18n, request) {
match vks_manage_unpublish_or_fail(origin, db, token_service, i18n, request) {
Ok(response) => response,
Err(e) => MyResponse::ise(e),
}
}
pub fn vks_manage_unpublish_or_fail(
request_origin: RequestOrigin,
db: rocket::State<KeyDatabase>,
token_service: rocket::State<tokens::Service>,
origin: RequestOrigin,
db: &rocket::State<KeyDatabase>,
token_service: &rocket::State<tokens::Service>,
i18n: I18n,
request: Form<forms::ManageDelete>,
) -> Result<MyResponse> {
@@ -195,5 +216,11 @@ pub fn vks_manage_unpublish_or_fail(
db.set_email_unpublished(&verify_token.fpr, &email)?;
counters::inc_address_unpublished(&email);
Ok(vks_manage_key(request_origin, db, i18n, request.token.to_owned(), token_service))
Ok(vks_manage_key(
origin,
db,
i18n,
request.token.to_owned(),
token_service,
))
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +1,26 @@
use crate::Result;
use crate::database::{Database, KeyDatabase, StatefulTokens, EmailAddressStatus, TpkStatus, ImportResult};
use crate::database::types::{Fingerprint,Email};
use crate::mail;
use crate::counters;
use crate::tokens::{self, StatelessSerializable};
use crate::database::types::{Email, Fingerprint};
use crate::database::{
Database, EmailAddressStatus, ImportResult, KeyDatabase, StatefulTokens, TpkStatus,
};
use crate::mail;
use crate::rate_limiter::RateLimiter;
use crate::tokens::{self, StatelessSerializable};
use crate::web::RequestOrigin;
use rocket_i18n::I18n;
use gettext_macros::i18n;
use rocket_i18n::I18n;
use sequoia_openpgp::Cert;
use sequoia_openpgp::parse::{Parse, PacketParserBuilder, Dearmor};
use sequoia_openpgp::cert::CertParser;
use sequoia_openpgp::armor::ReaderMode;
use sequoia_openpgp::cert::CertParser;
use sequoia_openpgp::parse::{Dearmor, PacketParserBuilder, Parse};
use sequoia_openpgp::Cert;
use std::io::Read;
use std::convert::TryFrom;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::io::Read;
use self::response::*;
@@ -38,7 +40,7 @@ pub mod request {
pub mod response {
use crate::database::types::Email;
#[derive(Debug,Serialize,Deserialize,PartialEq,Eq)]
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum EmailStatus {
#[serde(rename = "unpublished")]
Unpublished,
@@ -57,12 +59,14 @@ pub mod response {
token: String,
key_fpr: String,
is_revoked: bool,
status: HashMap<String,EmailStatus>,
status: HashMap<String, EmailStatus>,
count_unparsed: usize,
is_new_key: bool,
primary_uid: Option<Email>,
},
OkMulti { key_fprs: Vec<String> },
OkMulti {
key_fprs: Vec<String>,
},
Error(String),
}
@@ -84,15 +88,14 @@ pub mod response {
}
}
#[derive(Serialize,Deserialize)]
#[derive(Serialize, Deserialize)]
struct VerifyTpkState {
fpr: Fingerprint,
addresses: Vec<Email>,
requested: Vec<Email>,
}
impl StatelessSerializable for VerifyTpkState {
}
impl StatelessSerializable for VerifyTpkState {}
pub fn process_key(
db: &KeyDatabase,
@@ -103,9 +106,7 @@ pub fn process_key(
) -> response::UploadResponse {
// First, parse all Certs and error out if one fails.
let parser = match PacketParserBuilder::from_reader(reader)
.and_then(|ppb| {
ppb.dearmor(Dearmor::Auto(ReaderMode::VeryTolerant)).build()
})
.and_then(|ppb| ppb.dearmor(Dearmor::Auto(ReaderMode::VeryTolerant)).build())
{
Ok(ppr) => CertParser::from(ppr),
Err(_) => return UploadResponse::err(i18n!(i18n.catalog, "Parsing of key data failed.")),
@@ -122,7 +123,7 @@ pub fn process_key(
));
}
t
},
}
Err(_) => {
return UploadResponse::err(i18n!(i18n.catalog, "Parsing of key data failed."));
}
@@ -131,7 +132,13 @@ pub fn process_key(
match tpks.len() {
0 => UploadResponse::err(i18n!(i18n.catalog, "No key uploaded.")),
1 => process_key_single(db, i18n, tokens_stateless, rate_limiter, tpks.into_iter().next().unwrap()),
1 => process_key_single(
db,
i18n,
tokens_stateless,
rate_limiter,
tpks.into_iter().next().unwrap(),
),
_ => process_key_multiple(db, tpks),
}
}
@@ -147,14 +154,10 @@ fn log_db_merge(import_result: Result<ImportResult>) -> Result<ImportResult> {
import_result
}
fn process_key_multiple(
db: &KeyDatabase,
tpks: Vec<Cert>,
) -> response::UploadResponse {
fn process_key_multiple(db: &KeyDatabase, tpks: Vec<Cert>) -> response::UploadResponse {
let key_fprs: Vec<_> = tpks
.into_iter()
.flat_map(|tpk| Fingerprint::try_from(tpk.fingerprint())
.map(|fpr| (fpr, tpk)))
.flat_map(|tpk| Fingerprint::try_from(tpk.fingerprint()).map(|fpr| (fpr, tpk)))
.flat_map(|(fpr, tpk)| log_db_merge(db.merge(tpk)).map(|_| fpr.to_string()))
.collect();
@@ -174,17 +177,21 @@ fn process_key_single(
Ok(ImportResult::New(tpk_status)) => (tpk_status, true),
Ok(ImportResult::Updated(tpk_status)) => (tpk_status, false),
Ok(ImportResult::Unchanged(tpk_status)) => (tpk_status, false),
Err(_) => return UploadResponse::err(i18n!(i18n.catalog, "Error processing uploaded key.")),
Err(_) => {
return UploadResponse::err(i18n!(i18n.catalog, "Error processing uploaded key."))
}
};
let verify_state = {
let emails = tpk_status.email_status.iter()
.map(|(email,_)| email.clone())
let emails = tpk_status
.email_status
.iter()
.map(|(email, _)| email.clone())
.collect();
VerifyTpkState {
fpr: fp.clone(),
fpr: fp,
addresses: emails,
requested: vec!(),
requested: vec![],
}
};
@@ -194,46 +201,55 @@ fn process_key_single(
}
pub fn request_verify(
db: rocket::State<KeyDatabase>,
request_origin: RequestOrigin,
token_stateful: rocket::State<StatefulTokens>,
token_stateless: rocket::State<tokens::Service>,
mail_service: rocket::State<mail::Service>,
rate_limiter: rocket::State<RateLimiter>,
i18n: I18n,
db: &rocket::State<KeyDatabase>,
origin: &RequestOrigin,
token_stateful: &rocket::State<StatefulTokens>,
token_stateless: &rocket::State<tokens::Service>,
mail_service: &rocket::State<mail::Service>,
rate_limiter: &rocket::State<RateLimiter>,
i18n: &I18n,
token: String,
addresses: Vec<String>,
) -> response::UploadResponse {
let (verify_state, tpk_status) = match check_tpk_state(&db, &token_stateless, &i18n, &token) {
let (verify_state, tpk_status) = match check_tpk_state(db, token_stateless, i18n, &token) {
Ok(ok) => ok,
Err(e) => return UploadResponse::err(&e.to_string()),
};
if tpk_status.is_revoked {
return show_upload_verify(
&rate_limiter, token, tpk_status, verify_state, false);
return show_upload_verify(rate_limiter, token, tpk_status, verify_state, false);
}
let emails_requested: Vec<_> = addresses.into_iter()
let emails_requested: Vec<_> = addresses
.into_iter()
.map(|address| address.parse::<Email>())
.flatten()
.filter(|email| verify_state.addresses.contains(email))
.filter(|email| tpk_status.email_status.iter()
.any(|(uid_email, status)|
.filter(|email| {
tpk_status.email_status.iter().any(|(uid_email, status)| {
uid_email == email && *status == EmailAddressStatus::NotPublished
))
})
})
.collect();
for email in emails_requested {
let rate_limit_ok = rate_limiter.action_perform(format!("verify-{}", &email));
if rate_limit_ok {
if send_verify_email(&request_origin, &mail_service, &token_stateful, &i18n, &verify_state.fpr, &email).is_err() {
return UploadResponse::err(&format!("error sending email to {}", &email));
}
if rate_limit_ok
&& send_verify_email(
origin,
mail_service,
token_stateful,
i18n,
&verify_state.fpr,
&email,
)
.is_err()
{
return UploadResponse::err(&format!("error sending email to {}", &email));
}
}
show_upload_verify(&rate_limiter, token, tpk_status, verify_state, false)
show_upload_verify(rate_limiter, token, tpk_status, verify_state, false)
}
fn check_tpk_state(
@@ -241,18 +257,21 @@ fn check_tpk_state(
token_stateless: &tokens::Service,
i18n: &I18n,
token: &str,
) -> Result<(VerifyTpkState,TpkStatus)> {
let verify_state = token_stateless.check::<VerifyTpkState>(token)
.map_err(|_| anyhow!(i18n!(
i18n.catalog,
"Upload session expired. Please try again."
)))?;
) -> Result<(VerifyTpkState, TpkStatus)> {
let verify_state = token_stateless
.check::<VerifyTpkState>(token)
.map_err(|_| {
anyhow!(i18n!(
i18n.catalog,
"Upload session expired. Please try again."
))
})?;
let tpk_status = db.get_tpk_status(&verify_state.fpr, &verify_state.addresses)?;
Ok((verify_state, tpk_status))
}
fn send_verify_email(
request_origin: &RequestOrigin,
origin: &RequestOrigin,
mail_service: &mail::Service,
token_stateful: &StatefulTokens,
i18n: &I18n,
@@ -265,28 +284,27 @@ fn send_verify_email(
mail_service.send_verification(
i18n,
request_origin.get_base_uri(),
origin.get_base_uri(),
fpr.to_string(),
&email,
email,
&token_verify,
)
}
pub fn verify_confirm(
db: rocket::State<KeyDatabase>,
db: &rocket::State<KeyDatabase>,
i18n: &I18n,
token_service: rocket::State<StatefulTokens>,
token_service: &rocket::State<StatefulTokens>,
token: String,
) -> response::PublishResponse {
let (fingerprint, email) = match check_publish_token(&db, &token_service, token) {
let (fingerprint, email) = match check_publish_token(db, token_service, token) {
Ok(x) => x,
Err(_) => return PublishResponse::err(
i18n!(i18n.catalog, "Invalid verification link.")),
Err(_) => return PublishResponse::err(i18n!(i18n.catalog, "Invalid verification link.")),
};
response::PublishResponse::Ok {
fingerprint: fingerprint.to_string(),
email: email.to_string()
email: email.to_string(),
}
}
@@ -294,7 +312,7 @@ fn check_publish_token(
db: &KeyDatabase,
token_service: &StatefulTokens,
token: String,
) -> Result<(Fingerprint,Email)> {
) -> Result<(Fingerprint, Email)> {
let payload = token_service.pop_token("verify", &token)?;
let (fingerprint, email) = serde_json::from_str(&payload)?;
@@ -324,28 +342,41 @@ fn show_upload_verify(
};
}
let status: HashMap<_,_> = tpk_status.email_status
let status: HashMap<_, _> = tpk_status
.email_status
.iter()
.map(|(email,status)| {
let is_pending = (*status == EmailAddressStatus::NotPublished) &&
!rate_limiter.action_check(format!("verify-{}", &email));
.map(|(email, status)| {
let is_pending = (*status == EmailAddressStatus::NotPublished)
&& !rate_limiter.action_check(format!("verify-{}", &email));
if is_pending {
(email.to_string(), EmailStatus::Pending)
} else {
(email.to_string(), match status {
EmailAddressStatus::NotPublished => EmailStatus::Unpublished,
EmailAddressStatus::Published => EmailStatus::Published,
EmailAddressStatus::Revoked => EmailStatus::Revoked,
})
(
email.to_string(),
match status {
EmailAddressStatus::NotPublished => EmailStatus::Unpublished,
EmailAddressStatus::Published => EmailStatus::Published,
EmailAddressStatus::Revoked => EmailStatus::Revoked,
},
)
}
})
.collect();
let primary_uid = tpk_status.email_status
let primary_uid = tpk_status
.email_status
.get(0)
.map(|(email, _)| email)
.cloned();
let count_unparsed = tpk_status.unparsed_uids;
response::UploadResponse::Ok { token, key_fpr, count_unparsed, is_revoked: false, status, is_new_key, primary_uid }
response::UploadResponse::Ok {
token,
key_fpr,
count_unparsed,
is_revoked: false,
status,
is_new_key,
primary_uid,
}
}

View File

@@ -1,21 +1,23 @@
use rocket_contrib::json::{Json,JsonValue,JsonError};
use rocket::http::{ContentType, Status};
use rocket::request::Request;
use rocket::response::{self, Response, Responder};
use rocket::http::{ContentType,Status};
use rocket::State;
use rocket::response::{self, Responder, Response};
use rocket::serde::json::Json;
use rocket_i18n::{I18n, Translations};
use serde_json::json;
use std::io::Cursor;
use crate::database::{KeyDatabase, StatefulTokens, Query};
use crate::database::types::{Email, Fingerprint, KeyID};
use crate::database::{KeyDatabase, Query, StatefulTokens};
use crate::mail;
use crate::tokens;
use crate::rate_limiter::RateLimiter;
use crate::tokens;
use crate::web;
use crate::web::{HagridState, RequestOrigin, MyResponse};
use crate::web::vks;
use crate::web::vks::response::*;
use crate::web::{MyResponse, RequestOrigin};
use rocket::serde::json::Error as JsonError;
pub mod json {
use crate::web::vks::response::EmailStatus;
@@ -33,25 +35,25 @@ pub mod json {
pub keytext: String,
}
#[derive(Serialize,Deserialize)]
#[derive(Serialize, Deserialize)]
pub struct UploadResult {
pub token: String,
pub key_fpr: String,
pub status: HashMap<String,EmailStatus>,
pub status: HashMap<String, EmailStatus>,
}
}
type JsonResult = Result<JsonValue, JsonErrorResponse>;
type JsonResult = Result<serde_json::Value, JsonErrorResponse>;
#[derive(Debug)]
pub struct JsonErrorResponse(Status,String);
pub struct JsonErrorResponse(Status, String);
impl<'r> Responder<'r> for JsonErrorResponse {
fn respond_to(self, _: &Request) -> response::Result<'r> {
impl<'r> Responder<'r, 'static> for JsonErrorResponse {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
let error_json = json!({"error": self.1});
Response::build()
.status(self.0)
.sized_body(Cursor::new(error_json.to_string()))
.sized_body(None, Cursor::new(error_json.to_string()))
.header(ContentType::JSON)
.ok()
}
@@ -60,15 +62,26 @@ impl<'r> Responder<'r> for JsonErrorResponse {
fn json_or_error<T>(data: Result<Json<T>, JsonError>) -> Result<Json<T>, JsonErrorResponse> {
match data {
Ok(data) => Ok(data),
Err(JsonError::Io(_)) => Err(JsonErrorResponse(Status::InternalServerError, "i/o error!".to_owned())),
Err(JsonError::Io(_)) => Err(JsonErrorResponse(
Status::InternalServerError,
"i/o error!".to_owned(),
)),
Err(JsonError::Parse(_, e)) => Err(JsonErrorResponse(Status::BadRequest, e.to_string())),
}
}
fn upload_ok_json(response: UploadResponse) -> Result<JsonValue,JsonErrorResponse> {
fn upload_ok_json(response: UploadResponse) -> Result<serde_json::Value, JsonErrorResponse> {
match response {
UploadResponse::Ok { token, key_fpr, status, .. } =>
Ok(json!(json::UploadResult { token, key_fpr, status })),
UploadResponse::Ok {
token,
key_fpr,
status,
..
} => Ok(json!(json::UploadResult {
token,
key_fpr,
status
})),
UploadResponse::OkMulti { key_fprs } => Ok(json!(key_fprs)),
UploadResponse::Error(error) => Err(JsonErrorResponse(Status::BadRequest, error)),
}
@@ -76,73 +89,86 @@ fn upload_ok_json(response: UploadResponse) -> Result<JsonValue,JsonErrorRespons
#[post("/vks/v1/upload", format = "json", data = "<data>")]
pub fn upload_json(
db: rocket::State<KeyDatabase>,
tokens_stateless: rocket::State<tokens::Service>,
rate_limiter: rocket::State<RateLimiter>,
db: &rocket::State<KeyDatabase>,
tokens_stateless: &rocket::State<tokens::Service>,
rate_limiter: &rocket::State<RateLimiter>,
i18n: I18n,
data: Result<Json<json::UploadRequest>, JsonError>,
) -> JsonResult {
let data = json_or_error(data)?;
use std::io::Cursor;
let data_reader = Cursor::new(data.keytext.as_bytes());
let result = vks::process_key(&db, &i18n, &tokens_stateless, &rate_limiter, data_reader);
let result = vks::process_key(db, &i18n, tokens_stateless, rate_limiter, data_reader);
upload_ok_json(result)
}
#[post("/vks/v1/upload", rank = 2)]
pub fn upload_fallback(
request_origin: RequestOrigin,
) -> JsonErrorResponse {
let error_msg = format!("expected application/json data. see {}/about/api for api docs.", request_origin.get_base_uri());
pub fn upload_fallback(origin: RequestOrigin) -> JsonErrorResponse {
let error_msg = format!(
"expected application/json data. see {}/about/api for api docs.",
origin.get_base_uri()
);
JsonErrorResponse(Status::BadRequest, error_msg)
}
fn get_locale(
langs: State<Translations>,
locales: Vec<String>,
) -> I18n {
fn get_locale(langs: &rocket::State<Translations>, locales: Vec<String>) -> I18n {
locales
.iter()
.flat_map(|lang| lang.split(|c| c == '-' || c == ';' || c == '_').next())
.flat_map(|lang| langs.iter().find(|(trans, _)| trans == &lang))
.next()
.or_else(|| langs.iter().find(|(trans, _)| trans == &"en"))
.map(|(lang, catalog)| I18n { catalog: catalog.clone(), lang })
.map(|(lang, catalog)| I18n {
catalog: catalog.clone(),
lang,
})
.expect("Expected to have an english translation!")
}
#[post("/vks/v1/request-verify", format = "json", data="<data>")]
#[post("/vks/v1/request-verify", format = "json", data = "<data>")]
pub fn request_verify_json(
db: rocket::State<KeyDatabase>,
langs: State<Translations>,
request_origin: RequestOrigin,
token_stateful: rocket::State<StatefulTokens>,
token_stateless: rocket::State<tokens::Service>,
mail_service: rocket::State<mail::Service>,
rate_limiter: rocket::State<RateLimiter>,
db: &rocket::State<KeyDatabase>,
langs: &rocket::State<Translations>,
origin: RequestOrigin,
token_stateful: &rocket::State<StatefulTokens>,
token_stateless: &rocket::State<tokens::Service>,
mail_service: &rocket::State<mail::Service>,
rate_limiter: &rocket::State<RateLimiter>,
data: Result<Json<json::VerifyRequest>, JsonError>,
) -> JsonResult {
let data = json_or_error(data)?;
let json::VerifyRequest { token, addresses, locale } = data.into_inner();
let json::VerifyRequest {
token,
addresses,
locale,
} = data.into_inner();
let i18n = get_locale(langs, locale.unwrap_or_default());
let result = vks::request_verify(
db, request_origin, token_stateful, token_stateless, mail_service,
rate_limiter, i18n, token, addresses);
db,
&origin,
token_stateful,
token_stateless,
mail_service,
rate_limiter,
&i18n,
token,
addresses,
);
upload_ok_json(result)
}
#[post("/vks/v1/request-verify", rank = 2)]
pub fn request_verify_fallback(
request_origin: RequestOrigin,
) -> JsonErrorResponse {
let error_msg = format!("expected application/json data. see {}/about/api for api docs.", request_origin.get_base_uri());
pub fn request_verify_fallback(origin: RequestOrigin) -> JsonErrorResponse {
let error_msg = format!(
"expected application/json data. see {}/about/api for api docs.",
origin.get_base_uri()
);
JsonErrorResponse(Status::BadRequest, error_msg)
}
#[get("/vks/v1/by-fingerprint/<fpr>")]
pub fn vks_v1_by_fingerprint(
state: rocket::State<HagridState>,
db: rocket::State<KeyDatabase>,
db: &rocket::State<KeyDatabase>,
i18n: I18n,
fpr: String,
) -> MyResponse {
@@ -151,36 +177,26 @@ pub fn vks_v1_by_fingerprint(
Err(_) => return MyResponse::bad_request_plain("malformed fingerprint"),
};
web::key_to_response_plain(state, db, i18n, query)
web::key_to_response_plain(db, i18n, query)
}
#[get("/vks/v1/by-email/<email>")]
pub fn vks_v1_by_email(
state: rocket::State<HagridState>,
db: rocket::State<KeyDatabase>,
i18n: I18n,
email: String,
) -> MyResponse {
pub fn vks_v1_by_email(db: &rocket::State<KeyDatabase>, i18n: I18n, email: String) -> MyResponse {
let email = email.replace("%40", "@");
let query = match email.parse::<Email>() {
Ok(email) => Query::ByEmail(email),
Err(_) => return MyResponse::bad_request_plain("malformed e-mail address"),
};
web::key_to_response_plain(state, db, i18n, query)
web::key_to_response_plain(db, i18n, query)
}
#[get("/vks/v1/by-keyid/<kid>")]
pub fn vks_v1_by_keyid(
state: rocket::State<HagridState>,
db: rocket::State<KeyDatabase>,
i18n: I18n,
kid: String,
) -> MyResponse {
pub fn vks_v1_by_keyid(db: &rocket::State<KeyDatabase>, i18n: I18n, kid: String) -> MyResponse {
let query = match kid.parse::<KeyID>() {
Ok(keyid) => Query::ByKeyID(keyid),
Err(_) => return MyResponse::bad_request_plain("malformed key id"),
};
web::key_to_response_plain(state, db, i18n, query)
web::key_to_response_plain(db, i18n, query)
}

View File

@@ -4,29 +4,32 @@ use multipart::server::save::Entries;
use multipart::server::save::SaveResult::*;
use multipart::server::Multipart;
use gettext_macros::i18n;
use rocket::data::ByteUnit;
use rocket::form::Form;
use rocket::form::ValueField;
use rocket::http::ContentType;
use rocket::request::Form;
use rocket::Data;
use rocket_i18n::I18n;
use gettext_macros::i18n;
use url::percent_encoding::percent_decode;
use crate::database::{KeyDatabase, StatefulTokens, Query, Database};
use crate::mail;
use crate::tokens;
use crate::web::{RequestOrigin, MyResponse};
use crate::rate_limiter::RateLimiter;
use crate::database::{Database, KeyDatabase, Query, StatefulTokens};
use crate::i18n_helpers::describe_query_error;
use crate::mail;
use crate::rate_limiter::RateLimiter;
use crate::tokens;
use crate::web::{MyResponse, RequestOrigin};
use std::io::Read;
use std::collections::HashMap;
use std::io::Cursor;
use crate::web::vks;
use crate::web::vks::response::*;
const UPLOAD_LIMIT: u64 = 1024 * 1024; // 1 MiB.
const UPLOAD_LIMIT: ByteUnit = ByteUnit::Mebibyte(1);
mod forms {
#[derive(FromForm,Deserialize)]
#[derive(FromForm, Deserialize)]
pub struct VerifyRequest {
pub token: String,
pub address: String,
@@ -87,36 +90,52 @@ mod template {
pub address: String,
pub requested: bool,
}
}
impl MyResponse {
fn upload_response_quick(response: UploadResponse, base_uri: &str) -> Self {
fn upload_response_quick(response: UploadResponse, i18n: I18n, origin: RequestOrigin) -> Self {
match response {
UploadResponse::Ok { token, .. } => {
let uri = uri!(quick_upload_proceed: token);
let uri = uri!(quick_upload_proceed(token));
let text = format!(
"Key successfully uploaded. Proceed with verification here:\n{}{}\n",
base_uri,
origin.get_base_uri(),
uri
);
MyResponse::plain(text)
},
UploadResponse::OkMulti { key_fprs } =>
MyResponse::plain(format!("Uploaded {} keys. For verification, please upload keys individually.\n", key_fprs.len())),
UploadResponse::Error(error) => MyResponse::bad_request(
"400-plain", anyhow!(error)),
}
UploadResponse::OkMulti { key_fprs } => MyResponse::plain(format!(
"Uploaded {} keys. For verification, please upload keys individually.\n",
key_fprs.len()
)),
UploadResponse::Error(error) => {
MyResponse::bad_request("400-plain", anyhow!(error), i18n, origin)
}
}
}
fn upload_response(response: UploadResponse) -> Self {
fn upload_response(response: UploadResponse, i18n: I18n, origin: RequestOrigin) -> Self {
match response {
UploadResponse::Ok { token, key_fpr, is_revoked, count_unparsed, status, .. } =>
Self::upload_ok(token, key_fpr, is_revoked, count_unparsed, status),
UploadResponse::OkMulti { key_fprs } =>
Self::upload_ok_multi(key_fprs),
UploadResponse::Error(error) => MyResponse::bad_request(
"upload/upload", anyhow!(error)),
UploadResponse::Ok {
token,
key_fpr,
is_revoked,
count_unparsed,
status,
..
} => Self::upload_ok(
token,
key_fpr,
is_revoked,
count_unparsed,
status,
i18n,
origin,
),
UploadResponse::OkMulti { key_fprs } => Self::upload_ok_multi(key_fprs, i18n, origin),
UploadResponse::Error(error) => {
MyResponse::bad_request("upload/upload", anyhow!(error), i18n, origin)
}
}
}
@@ -125,31 +144,35 @@ impl MyResponse {
key_fpr: String,
is_revoked: bool,
count_unparsed: usize,
uid_status: HashMap<String,EmailStatus>,
uid_status: HashMap<String, EmailStatus>,
i18n: I18n,
origin: RequestOrigin,
) -> Self {
let key_link = uri!(search: &key_fpr).to_string();
let key_link = uri!(search(q = &key_fpr)).to_string();
let count_revoked = uid_status.iter()
.filter(|(_,status)| **status == EmailStatus::Revoked)
let count_revoked = uid_status
.iter()
.filter(|(_, status)| **status == EmailStatus::Revoked)
.count();
let mut email_published: Vec<_> = uid_status.iter()
.filter(|(_,status)| **status == EmailStatus::Published)
.map(|(email,_)| email.to_string())
let mut email_published: Vec<_> = uid_status
.iter()
.filter(|(_, status)| **status == EmailStatus::Published)
.map(|(email, _)| email.to_string())
.collect();
email_published.sort_unstable();
let mut email_unpublished: Vec<_> = uid_status.into_iter()
.filter(|(_,status)| *status == EmailStatus::Unpublished ||
*status == EmailStatus::Pending)
.map(|(email,status)|
template::UploadUidStatus {
address: email.to_string(),
requested: status == EmailStatus::Pending,
})
let mut email_unpublished: Vec<_> = uid_status
.into_iter()
.filter(|(_, status)| {
*status == EmailStatus::Unpublished || *status == EmailStatus::Pending
})
.map(|(email, status)| template::UploadUidStatus {
address: email,
requested: status == EmailStatus::Pending,
})
.collect();
email_unpublished
.sort_unstable_by(|fst,snd| fst.address.cmp(&snd.address));
email_unpublished.sort_unstable_by(|fst, snd| fst.address.cmp(&snd.address));
let context = template::VerificationSent {
is_revoked,
@@ -163,217 +186,223 @@ impl MyResponse {
count_unparsed_one: count_unparsed == 1,
count_unparsed,
};
MyResponse::ok("upload/upload-ok", context)
MyResponse::ok("upload/upload-ok", context, i18n, origin)
}
fn upload_ok_multi(key_fprs: Vec<String>) -> Self {
let keys = key_fprs.into_iter()
fn upload_ok_multi(key_fprs: Vec<String>, i18n: I18n, origin: RequestOrigin) -> Self {
let keys = key_fprs
.into_iter()
.map(|fpr| {
let key_link = uri!(search: &fpr).to_string();
let key_link = uri!(search(q = &fpr)).to_string();
template::UploadOkKey {
key_fpr: fpr.to_owned(),
key_fpr: fpr,
key_link,
}
})
.collect();
let context = template::UploadOkMultiple {
keys,
};
let context = template::UploadOkMultiple { keys };
MyResponse::ok("upload/upload-ok-multiple", context)
MyResponse::ok("upload/upload-ok-multiple", context, i18n, origin)
}
}
#[get("/upload")]
pub fn upload() -> MyResponse {
MyResponse::ok_bare("upload/upload")
pub fn upload(origin: RequestOrigin, i18n: I18n) -> MyResponse {
MyResponse::ok_bare("upload/upload", i18n, origin)
}
#[post("/upload/submit", format = "multipart/form-data", data = "<data>")]
pub fn upload_post_form_data(
db: rocket::State<KeyDatabase>,
tokens_stateless: rocket::State<tokens::Service>,
rate_limiter: rocket::State<RateLimiter>,
pub async fn upload_post_form_data(
db: &rocket::State<KeyDatabase>,
origin: RequestOrigin,
tokens_stateless: &rocket::State<tokens::Service>,
rate_limiter: &rocket::State<RateLimiter>,
i18n: I18n,
cont_type: &ContentType,
data: Data,
data: Data<'_>,
) -> MyResponse {
match process_post_form_data(db, tokens_stateless, rate_limiter, i18n, cont_type, data) {
Ok(response) => MyResponse::upload_response(response),
Err(err) => MyResponse::bad_request("upload/upload", err),
match process_upload(db, tokens_stateless, rate_limiter, &i18n, data, cont_type).await {
Ok(response) => MyResponse::upload_response(response, i18n, origin),
Err(err) => MyResponse::bad_request("upload/upload", err, i18n, origin),
}
}
pub fn process_post_form_data(
db: rocket::State<KeyDatabase>,
tokens_stateless: rocket::State<tokens::Service>,
rate_limiter: rocket::State<RateLimiter>,
pub async fn process_post_form_data(
db: &rocket::State<KeyDatabase>,
tokens_stateless: &rocket::State<tokens::Service>,
rate_limiter: &rocket::State<RateLimiter>,
i18n: I18n,
cont_type: &ContentType,
data: Data,
data: Data<'_>,
) -> Result<UploadResponse> {
// multipart/form-data
let (_, boundary) = cont_type
.params()
.find(|&(k, _)| k == "boundary")
.ok_or_else(|| anyhow!("`Content-Type: multipart/form-data` \
boundary param not provided"))?;
process_upload(&db, &tokens_stateless, &rate_limiter, &i18n, data, boundary)
process_upload(db, tokens_stateless, rate_limiter, &i18n, data, cont_type).await
}
#[get("/search?<q>")]
pub fn search(
db: rocket::State<KeyDatabase>,
db: &rocket::State<KeyDatabase>,
origin: RequestOrigin,
i18n: I18n,
q: String,
) -> MyResponse {
match q.parse::<Query>() {
Ok(query) => key_to_response(db, i18n, q, query),
Err(e) => MyResponse::bad_request("index", e),
Ok(query) => key_to_response(db, origin, i18n, q, query),
Err(e) => MyResponse::bad_request("index", e, i18n, origin),
}
}
fn key_to_response(
db: rocket::State<KeyDatabase>,
db: &rocket::State<KeyDatabase>,
origin: RequestOrigin,
i18n: I18n,
query_string: String,
query: Query,
) -> MyResponse {
let fp = if let Some(fp) = db.lookup_primary_fingerprint(&query) {
fp
} else if query.is_invalid() {
return MyResponse::bad_request(
"index",
anyhow!(describe_query_error(&i18n, &query)),
i18n,
origin,
);
} else {
return MyResponse::not_found(None, describe_query_error(&i18n, &query));
return MyResponse::not_found(None, describe_query_error(&i18n, &query), i18n, origin);
};
let context = template::Search{
let context = template::Search {
query: query_string,
fpr: fp.to_string(),
};
MyResponse::ok("found", context)
MyResponse::ok("found", context, i18n, origin)
}
#[put("/", data = "<data>")]
pub fn quick_upload(
db: rocket::State<KeyDatabase>,
tokens_stateless: rocket::State<tokens::Service>,
rate_limiter: rocket::State<RateLimiter>,
pub async fn quick_upload(
db: &rocket::State<KeyDatabase>,
tokens_stateless: &rocket::State<tokens::Service>,
rate_limiter: &rocket::State<RateLimiter>,
i18n: I18n,
request_origin: RequestOrigin,
data: Data,
origin: RequestOrigin,
data: Data<'_>,
) -> MyResponse {
use std::io::Cursor;
let mut buf = Vec::default();
if let Err(error) = std::io::copy(&mut data.open().take(UPLOAD_LIMIT), &mut buf) {
return MyResponse::bad_request("400-plain", anyhow!(error));
}
let buf = match data.open(UPLOAD_LIMIT).into_bytes().await {
Ok(buf) => buf.into_inner(),
Err(error) => return MyResponse::bad_request("400-plain", anyhow!(error), i18n, origin),
};
MyResponse::upload_response_quick(
vks::process_key(
&db,
&i18n,
&tokens_stateless,
&rate_limiter,
Cursor::new(buf)
), request_origin.get_base_uri())
vks::process_key(db, &i18n, tokens_stateless, rate_limiter, Cursor::new(buf)),
i18n,
origin,
)
}
#[get("/upload/<token>", rank = 2)]
pub fn quick_upload_proceed(
db: rocket::State<KeyDatabase>,
request_origin: RequestOrigin,
token_stateful: rocket::State<StatefulTokens>,
token_stateless: rocket::State<tokens::Service>,
mail_service: rocket::State<mail::Service>,
rate_limiter: rocket::State<RateLimiter>,
db: &rocket::State<KeyDatabase>,
origin: RequestOrigin,
token_stateful: &rocket::State<StatefulTokens>,
token_stateless: &rocket::State<tokens::Service>,
mail_service: &rocket::State<mail::Service>,
rate_limiter: &rocket::State<RateLimiter>,
i18n: I18n,
token: String,
) -> MyResponse {
let result = vks::request_verify(
db, request_origin, token_stateful, token_stateless, mail_service,
rate_limiter, i18n, token, vec!());
MyResponse::upload_response(result)
db,
&origin,
token_stateful,
token_stateless,
mail_service,
rate_limiter,
&i18n,
token,
vec![],
);
MyResponse::upload_response(result, i18n, origin)
}
#[post("/upload/submit", format = "application/x-www-form-urlencoded", data = "<data>")]
pub fn upload_post_form(
db: rocket::State<KeyDatabase>,
tokens_stateless: rocket::State<tokens::Service>,
rate_limiter: rocket::State<RateLimiter>,
#[post(
"/upload/submit",
format = "application/x-www-form-urlencoded",
data = "<data>"
)]
pub async fn upload_post_form(
db: &rocket::State<KeyDatabase>,
origin: RequestOrigin,
tokens_stateless: &rocket::State<tokens::Service>,
rate_limiter: &rocket::State<RateLimiter>,
i18n: I18n,
data: Data,
data: Data<'_>,
) -> MyResponse {
match process_post_form(&db, &tokens_stateless, &rate_limiter, &i18n, data) {
Ok(response) => MyResponse::upload_response(response),
Err(err) => MyResponse::bad_request("upload/upload", err),
match process_post_form(db, tokens_stateless, rate_limiter, &i18n, data).await {
Ok(response) => MyResponse::upload_response(response, i18n, origin),
Err(err) => MyResponse::bad_request("upload/upload", err, i18n, origin),
}
}
pub fn process_post_form(
pub async fn process_post_form(
db: &KeyDatabase,
tokens_stateless: &tokens::Service,
rate_limiter: &RateLimiter,
i18n: &I18n,
data: Data,
data: Data<'_>,
) -> Result<UploadResponse> {
use rocket::request::FormItems;
use std::io::Cursor;
// application/x-www-form-urlencoded
let mut buf = Vec::default();
let buf = data.open(UPLOAD_LIMIT).into_bytes().await?;
std::io::copy(&mut data.open().take(UPLOAD_LIMIT), &mut buf)?;
for ValueField { name, value } in Form::values(&*String::from_utf8_lossy(&buf)) {
let decoded_value = percent_decode(value.as_bytes())
.decode_utf8()
.map_err(|_| anyhow!("`Content-Type: application/x-www-form-urlencoded` not valid"))?;
for item in FormItems::from(&*String::from_utf8_lossy(&buf)) {
let (key, value) = item.key_value();
let decoded_value = value.url_decode().or_else(|_| {
Err(anyhow!(
"`Content-Type: application/x-www-form-urlencoded` \
not valid"))
})?;
match key.as_str() {
"keytext" => {
return Ok(vks::process_key(
&db,
&i18n,
&tokens_stateless,
&rate_limiter,
Cursor::new(decoded_value.as_bytes())
));
}
_ => { /* skip */ }
if name.to_string().as_str() == "keytext" {
return Ok(vks::process_key(
db,
i18n,
tokens_stateless,
rate_limiter,
Cursor::new(decoded_value.as_bytes()),
));
}
}
Err(anyhow!("No keytext found"))
}
fn process_upload(
async fn process_upload(
db: &KeyDatabase,
tokens_stateless: &tokens::Service,
rate_limiter: &RateLimiter,
i18n: &I18n,
data: Data,
boundary: &str,
data: Data<'_>,
cont_type: &ContentType,
) -> Result<UploadResponse> {
// multipart/form-data
let (_, boundary) = cont_type
.params()
.find(|&(k, _)| k == "boundary")
.ok_or_else(|| {
anyhow!(
"`Content-Type: multipart/form-data` \
boundary param not provided"
)
})?;
// saves all fields, any field longer than 10kB goes to a temporary directory
// Entries could implement FromData though that would give zero control over
// how the files are saved; Multipart would be a good impl candidate though
match Multipart::with_body(data.open().take(UPLOAD_LIMIT), boundary).save().temp() {
Full(entries) => {
process_multipart(db, tokens_stateless, rate_limiter, i18n, entries)
}
let data = Cursor::new(data.open(UPLOAD_LIMIT).into_bytes().await?.value);
match Multipart::with_body(data, boundary).save().temp() {
Full(entries) => process_multipart(db, tokens_stateless, rate_limiter, i18n, entries),
Partial(partial, _) => {
process_multipart(db, tokens_stateless, rate_limiter, i18n, partial.entries)
}
Error(err) => Err(err.into())
Error(err) => Err(err.into()),
}
}
@@ -387,54 +416,85 @@ fn process_multipart(
match entries.fields.get("keytext") {
Some(ent) if ent.len() == 1 => {
let reader = ent[0].data.readable()?;
Ok(vks::process_key(db, i18n, tokens_stateless, rate_limiter, reader))
Ok(vks::process_key(
db,
i18n,
tokens_stateless,
rate_limiter,
reader,
))
}
Some(_) => Err(anyhow!("Multiple keytexts found")),
None => Err(anyhow!("No keytext found")),
}
}
#[post("/upload/request-verify", format = "application/x-www-form-urlencoded", data="<request>")]
#[post(
"/upload/request-verify",
format = "application/x-www-form-urlencoded",
data = "<request>"
)]
pub fn request_verify_form(
db: rocket::State<KeyDatabase>,
request_origin: RequestOrigin,
token_stateful: rocket::State<StatefulTokens>,
token_stateless: rocket::State<tokens::Service>,
mail_service: rocket::State<mail::Service>,
rate_limiter: rocket::State<RateLimiter>,
db: &rocket::State<KeyDatabase>,
origin: RequestOrigin,
token_stateful: &rocket::State<StatefulTokens>,
token_stateless: &rocket::State<tokens::Service>,
mail_service: &rocket::State<mail::Service>,
rate_limiter: &rocket::State<RateLimiter>,
i18n: I18n,
request: Form<forms::VerifyRequest>,
) -> MyResponse {
let forms::VerifyRequest { token, address } = request.into_inner();
let result = vks::request_verify(
db, request_origin, token_stateful, token_stateless, mail_service,
rate_limiter, i18n, token, vec!(address));
MyResponse::upload_response(result)
db,
&origin,
token_stateful,
token_stateless,
mail_service,
rate_limiter,
&i18n,
token,
vec![address],
);
MyResponse::upload_response(result, i18n, origin)
}
#[post("/upload/request-verify", format = "multipart/form-data", data="<request>")]
#[post(
"/upload/request-verify",
format = "multipart/form-data",
data = "<request>"
)]
pub fn request_verify_form_data(
db: rocket::State<KeyDatabase>,
request_origin: RequestOrigin,
token_stateful: rocket::State<StatefulTokens>,
token_stateless: rocket::State<tokens::Service>,
mail_service: rocket::State<mail::Service>,
rate_limiter: rocket::State<RateLimiter>,
db: &rocket::State<KeyDatabase>,
origin: RequestOrigin,
token_stateful: &rocket::State<StatefulTokens>,
token_stateless: &rocket::State<tokens::Service>,
mail_service: &rocket::State<mail::Service>,
rate_limiter: &rocket::State<RateLimiter>,
i18n: I18n,
request: Form<forms::VerifyRequest>,
) -> MyResponse {
let forms::VerifyRequest { token, address } = request.into_inner();
let result = vks::request_verify(
db, request_origin, token_stateful, token_stateless, mail_service,
rate_limiter, i18n, token, vec!(address));
MyResponse::upload_response(result)
db,
&origin,
token_stateful,
token_stateless,
mail_service,
rate_limiter,
&i18n,
token,
vec![address],
);
MyResponse::upload_response(result, i18n, origin)
}
#[post("/verify/<token>")]
pub fn verify_confirm(
db: rocket::State<KeyDatabase>,
token_service: rocket::State<StatefulTokens>,
rate_limiter: rocket::State<RateLimiter>,
db: &rocket::State<KeyDatabase>,
origin: RequestOrigin,
token_service: &rocket::State<StatefulTokens>,
rate_limiter: &rocket::State<RateLimiter>,
i18n: I18n,
token: String,
) -> MyResponse {
@@ -442,31 +502,35 @@ pub fn verify_confirm(
match vks::verify_confirm(db, &i18n, token_service, token) {
PublishResponse::Ok { fingerprint, email } => {
rate_limiter.action_perform(rate_limit_id);
let userid_link = uri!(search: &email).to_string();
let userid_link = uri!(search(q = &email)).to_string();
let context = template::Verify {
userid: email,
key_fpr: fingerprint,
userid_link,
};
MyResponse::ok("upload/publish-result", context)
},
MyResponse::ok("upload/publish-result", context, i18n, origin)
}
PublishResponse::Error(error) => {
let error_msg = if rate_limiter.action_check(rate_limit_id) {
anyhow!(error)
} else {
anyhow!(i18n!(i18n.catalog, "This address has already been verified."))
anyhow!(i18n!(
i18n.catalog,
"This address has already been verified."
))
};
MyResponse::bad_request("400", error_msg)
MyResponse::bad_request("400", error_msg, i18n, origin)
}
}
}
#[get("/verify/<token>")]
pub fn verify_confirm_form(
token: String,
) -> MyResponse {
MyResponse::ok("upload/verification-form", template::VerifyForm {
token
})
pub fn verify_confirm_form(origin: RequestOrigin, i18n: I18n, token: String) -> MyResponse {
MyResponse::ok(
"upload/verification-form",
template::VerifyForm { token },
i18n,
origin,
)
}

18
src/web/wkd.rs Normal file
View File

@@ -0,0 +1,18 @@
use crate::database::{Database, KeyDatabase};
use crate::web::MyResponse;
// WKD queries
#[get("/.well-known/openpgpkey/<domain>/hu/<wkd_hash>")]
pub fn wkd_query(db: &rocket::State<KeyDatabase>, domain: String, wkd_hash: String) -> MyResponse {
match db.by_domain_and_hash_wkd(&domain, &wkd_hash) {
Some(key) => MyResponse::wkd(key, &wkd_hash),
None => MyResponse::not_found_plain("No key found for this email address."),
}
}
// Policy requests.
// 200 response with an empty body.
#[get("/.well-known/openpgpkey/<_domain>/policy")]
pub fn wkd_policy(_domain: String) -> MyResponse {
MyResponse::plain("".to_string())
}

View File

@@ -24,5 +24,5 @@
<p>‫هذه الخدمة تشتغل بفضل مجهودات مجتمعية. يمكنك التواصل معنا في قناة #hagrid على خادم المحادثات Freenode، وكذا #hagrid:stratum0.org المُشغَّل بواسطة Matrix. كما يمكنك أيضا الاتصال بنا بالبريد اﻹلكتروني عبر العنوان <tt>support في keys نقطة openpgp نقطة org</tt>. إن كل من يساهم في تشغيل هذه الخدمة أتوا من مشاريع مختلفة بما في ذلك OpenPGP وبالذات Sequoia-PGP، و OpenKeychain و Enigmail.</p>
<p>‫من الناحية التقنية، يعمل <span class="brand">keys.openpgp.org</span> اعتمادا على برنامج خادم المفاتيح <a href="https://gitlab.com/hagrid-keyserver/hagrid" target="_blank">Hagrid</a>، المبني على <a href="https://sequoia-pgp.org">Sequoia-PGP</a>. كما أننا نستخدم منصة الاستضافة <a href="https://eclips.is" target="_blank">eclips.js</a> الداعمة للحرية عبر الانترنت، والتي يُديرها موقع <a href="https://greenhost.net/" target="_blank">Greenhost</a>.</p>
<p>‫من الناحية التقنية، يعمل <span class="brand">keys.openpgp.org</span> اعتمادا على برنامج خادم المفاتيح <a href="https://gitlab.com/keys.openpgp.org/hagrid" target="_blank">Hagrid</a>، المبني على <a href="https://sequoia-pgp.org">Sequoia-PGP</a>. كما أننا نستخدم منصة الاستضافة <a href="https://eclips.is" target="_blank">eclips.js</a> الداعمة للحرية عبر الانترنت، والتي يُديرها موقع <a href="https://greenhost.net/" target="_blank">Greenhost</a>.</p>
</div>

View File

@@ -1,5 +1,5 @@
<div class="about">
<center><h2>حول | <a href="/about/news">اﻷخبار</a> | <a href="/about/usage">الاستخدام</a> | <a href="/about/faq">اﻷسئلة الشائعة</a> | <a href="/about/stats">اﻹحصاءات</a> | <a href="/about/privacy">حماية البيانات‬</a>
<center><h2><a href="/about">حول</a> | <a href="/about/news">اﻷخبار</a> | <a href="/about/usage">الاستخدام</a> | اﻷسئلة الشائعة | <a href="/about/stats">اﻹحصاءات</a> | <a href="/about/privacy">حماية البيانات‬</a>
</h2></center>
<p><strong>يمكنك الاطلاع على تعليمات الاستخدام موجودة في <a href="/about/usage">دليلنا</a>.</strong></p>
@@ -39,9 +39,9 @@
<p>باختصار، كلا.</p>
<p>‫إن « التوقيع من طرف آخر » هو توقيع على مفتاح بواسطة مفتاح آخر. على العموم، يتم إنشاء ذلك التوقيع عبر ختم مفتاح لشخص ما، والذي يعد حجر أساس « <a href="https://fr.wikipedia.org/wiki/Toile_de_confiance" target="_blank">شبكة الثقة</a> ». ولعدة أسباب، لا تُوزَّع تلك التوقيعات حاليا في <span class="brand">keys.openpgp.org</span>.</p>
<p>‫إن « التوقيع من طرف آخر » هو توقيع على مفتاح بواسطة مفتاح آخر. على العموم، يتم إنشاء ذلك التوقيع عبر ختم مفتاح لشخص ما، والذي يعد حجر أساس « <a href="https://en.wikipedia.org/wiki/Web_of_trust" target="_blank">شبكة الثقة</a> ». ولعدة أسباب، لا تُوزَّع تلك التوقيعات حاليا في <span class="brand">keys.openpgp.org</span>.</p>
<p>السبب الرئيس لذلك هي <strong>الرسائل المزعجة</strong>. إذ تسمح توقيعات الأطراف الأخرى إرفاق بيانات عشوائية مع المفتاح من أي كان، وهذا لن يمنع كل من له نوايا سيئة من إرفاق العديد من البيانات غير النافعة دون حد، والتي تزيد من حجم المفتاح، لدرجة تعيق استخدامه. بل الأنكى من ذلك، أنه يمكن أن تتضمن محتوىً قبيحا أو غير قانوني.</p>
<p>السبب الرئيس لذلك هي <strong>الرسائل المزعجة</strong>. إذ تسمح توقيعات الأطراف الأخرى إرفاق بيانات عشوائية مع المفتاح من أي كان، وهذا الأمر سيفتح الباب لمن له نوايا سيئة من إرفاق العديد من البيانات غير النافعة دون أي قيود، مما يُضخِّم من حجم المفتاح بشكل هائل، إلى درجة تجعل استخدامه غير ممكن. بل الأنكى من ذلك، أنه يمكن أن تتضمن محتوىً قبيحا أو غير قانوني.</p>
<p>‫هناك بعض الأفكار لتجاوز هذا المشكل. وهي مثلا، توزيع تلك التوقيعات مع المُوقِّع بنفسه بدل المُوقَّع له. بالمقابل، يمكننا أن نلزم المُوقَّع له بأن يوقع هو الآخر قبل أي توزيع، وذلك لدعم سير العمل <a href="https://wiki.debian.org/caff" target="_blank">على طريقة الأداة caff</a>. إذا رأينا اهتماما كافيا من المستخدمين بذلك، فنحن على أتم الاستعداد للتعاون مع مشاريع OpenPGP الأخرى لإيجاد حل مناسب.</p>
@@ -53,14 +53,14 @@
<h3 id="revoked-uids"><a href="#revoked-uids">لماذا لا تُوزَّع الهويات الباطلة كذلك ؟</a></h3>
<p>إذا وسم مفتاح OpenPGP أحد الهويات بأنها باطلة، فذلك يعني أنه لم تعد تلك الهوية صالحة مع ذلك المفتاح. إذ من الأفضل أيضا توزيع هذه المعلومة على كل برامج OpenPGP التي لها علم سابق بالهوية الجديدة الباطلة.</p>
<p>للأسف، لا توجد طريقة أفضل لتوزيع شهادات إبطال الهوية، بحيث لا تكشف عن هوية التي قامت بإبطالها.</p>
<p>‫لقد اقُترحَت عدة حلول لتلك المشكلة، تسمح بتوزيع شهادات إبطال الهوية دون الكشف عن الهوية المعنية بذلك. رغم هذا، لم تتم لحد الآن، الصياغة النهائية لأي مواصفات تدعمها برامج OpenPGP. نأمل في المستقبل القريب بوضع حل ملائم لكي ندعمه <span class="brand">keys.openpgp.org</span> في أقرب وقت، قدر الإمكان.</p>
<p>للأسف، لا توجد طريقة أفضل لتوزيع شهادات إبطال الهوية، بحيث لا تكشف عن الهوية التي قامت بإبطالها.</p>
<p>‫لقد اقُترحَت عدة حلول لتلك المشكلة، تسمح بتوزيع شهادات إبطال الهوية دون الكشف عن الهوية المعنية بذلك. رغم هذا، لم تتم لحد الآن، الصياغة النهائية لأي مواصفات تدعمها برامج OpenPGP. نأمل في المستقبل القريب بوضع حل ملائم لكي يدعمه <span class="brand">keys.openpgp.org</span> في أقرب وقت، قدر الإمكان.</p>
<h3 id="search-substring"><a href="#search-substring">لماذا لا يمكن البحث بواسطة جزء من العنوان الإلكتروني، مثلا باسم النطاق ؟</a></h3>
<p>‫بعض خوادم المفاتيح تدعم البحث عنها بواسطة جزء من عنوان البريد الإلكتروني. لا يقتصر الأمر على العثور على المفاتيح، بل يتعداه إلى العناوين أيضا، باستخدام طلب يشبه « مفاتيح بعناوين في gmail نقطة com ». إذ بذلك، ستُعرَض بالفعل كل العناوين المقترنة بتلك المفاتيح في خوادم المفاتيح تلك بشكل عمومي.</p>
<p>‫لذلك، فإن البحث بعنوان البريد الإلكتروني في <span class="brand">keys.openpgp.org</span> يعرض فقط المفتاح الذي يوافق بالضبط ذلك العنوان. بهذه الوسيلة، يمكن للمستخدم المعتاد العثور فقط على المفتاح المرتبط بالعنوان الذي يعرفه، لكنه من جهة أخرى لن يتمكن من إيجاد أي عناوين أخرى جديدة. هكذا، تتم الوقاية من ذوي النوايا السيئة والمحتالين، الذين يسعون للحصول بسهولة على كل عناوين البريد الإلكتروني الموجودة في الخادم.</p>
<p>‫لذلك، فإن البحث بعنوان البريد الإلكتروني في <span class="brand">keys.openpgp.org</span> يعرض فقط المفتاح الذي يوافق بالضبط العنوان المبحوث به. بهذه الوسيلة، يمكن للمستخدم المعتاد العثور فقط على المفتاح المرتبط بالعنوان الذي يعرفه، لكنه من جهة أخرى لن يتمكن من إيجاد أي عناوين أخرى جديدة. هكذا، تتم الوقاية من ذوي النوايا السيئة والمحتالين، الذين يسعون للحصول بسهولة على كل عناوين البريد الإلكتروني الموجودة في الخادم.</p>
<p>‫لقد وضعنا تلك القيود كجزء من <a href="/about/privacy">سياسة خصوصيتنا</a>، مما يعني أنه لن نقوم بتعديلها إلا بعد طلب الموافقة على ذلك من طرف المستخدمين.</p>
@@ -70,7 +70,7 @@
<h3 id="encrypt-verification-emails"><a href="#encrypt-verification-emails">لماذا لا تُعمَّى رسائل التحقُّق الإلكترونية ؟</a></h3>
هناك عدة أسباب :
هناك عدة أسباب لذلك :
<ol>
<li>الأمر أكثر تعقيدا، لنا وللمستخدمين.</li>
<li>ذلك لا يقي من الهجمات. إذ أن الذي ينوي الاختراق لن يُجديَه نفعا رفع مفتاح لا يمكنه الوصول إليه.</li>

View File

@@ -1,5 +1,5 @@
<div class="about">
<center><h2>حول | <a href="/about/news">اﻷخبار</a> | <a href="/about/usage">الاستخدام</a> | <a href="/about/faq">اﻷسئلة الشائعة</a> | <a href="/about/stats">اﻹحصاءات</a> | <a href="/about/privacy">حماية البيانات</a>
<center><h2><a href="/about">حول</a> | اﻷخبار | <a href="/about/usage">الاستخدام</a> | اﻷسئلة الشائعة | <a href="/about/stats">اﻹحصاءات</a> | حماية البيانات<a href="/about/privacy"></a>
</h2></center>
<h2 id="2019-11-12-celebrating-100k">
@@ -7,7 +7,7 @@
<a style="color: black;" href="/about/news#2019-11-12-celebrating-100k">نحتفل بتحقُّقنا من 100 000 عنوان ! 📈</a>
</h2>
<p>منذ خمسة شهور، قمنا بتشغيل هذه الخدمة. لقد وصلنا اليوم إلى مرحلة متقدمة :</p>
<p>منذ خمسة أشهر، قمنا بتشغيل هذه الخدمة. لقد وصلنا اليوم إلى مرحلة متقدمة :</p>
<center style="margin-top: 2em; margin-bottom: 2em;">
<img src="/assets/img/stats-addresses-2019-11-12.png" style="padding: 1px; border: 1px solid gray;"><br><strong>تحققنا من مائة ألف عنوان للبريد اﻹلكتروني !</strong>
</center>
@@ -15,8 +15,8 @@
<p>إننا نتقدم بالشكر لكل من يستعمل هذه الخدمة ! ونخص بالثناء جميع من زودونا بملاحظاتهم، وترجماتهم، وأيضا الذين ساهموا في برمجة التطبيق !</p>
<p>إليك بعض التحديثات التي نعمل عليها :</p>
<ul>
<li>‫لقد أصبحت هذه الصفحة متاحة على بصيغة <strong><a target="_blank" href="/atom.xml">تلقيمات Atom<img src="/assets/img/atom.svg" style="height: 0.8em;"></a></strong>.</li>
<li>كنا نعمل على إنشاء <strong><a target="_blank" href="https://gitlab.com/hagrid-keyserver/hagrid/issues/131">تقنية جديدة لإنعاش المفاتيح</a></strong> تكون أفضل حماية لخصوصية المستخدم.</li>
<li>‫لقد أصبحت هذه الصفحة متاحة بصيغة <strong><a target="_blank" href="/atom.xml">تلقيمات Atom<img src="/assets/img/atom.svg" style="height: 0.8em;"></a></strong>.</li>
<li>كنا نعمل على إنشاء <strong><a target="_blank" href="https://gitlab.com/keys.openpgp.org/hagrid/issues/131">تقنية جديدة لإنعاش المفاتيح</a></strong> تكون أفضل حماية لخصوصية المستخدم.</li>
<li>إن مواصلة <strong>التوطين</strong> تتم بأقصى جهدنا ! نرجو أن تتوفر لغات أخرى في القريب العاجل.</li>
</ul>
<p>‫إذا رغبت في رؤية <span class="brand">keys.openpgp.org</span> مترجما إلى لغتك اﻷم. يُرجى <a target="_blank" href="https://www.transifex.com/otf/hagrid/">الانضمام إلى فريق الترجمة</a> في موقع Transifex. نقدر بالأساس مساعدتك لنا على الترجمة، وخاصة إلى <strong>الروسية</strong> و<strong>اﻹيطالية</strong> و<strong>البولونية</strong> و<strong>الهولندية</strong>.</p>
@@ -24,14 +24,14 @@
<hr style="margin-top: 2em; margin-bottom: 2em;">
<h2 id="2019-09-12-three-months-later">
<div style="float: right; font-size: small; line-height: 2em;">2019-09-12 📅</div>
<a style="color: black;" href="/about/news#2019-09-12-three-months-later">‫بعد ثلاثة شهور من الخدمة ✨</a>
<a style="color: black;" href="/about/news#2019-09-12-three-months-later">‫بعد ثلاثة أشهر من الخدمة ✨</a>
</h2>
<p>‫لقد مضت ثلاثة أشهر الآن منذ <a href="/about/news#2019-06-12-launch">تشغيل</a> موقع <span class="brand">keys.openpgp.org</span>. لذلك، فنحن سعداء بإخبارك أن خدمتنا لاقت نجاحا واسعا ! 🥳</p>
<h4>تبنِّيه في البرامج العميلة</h4>
<p>‫‫لقد لاقى خادم مفاتيح <span class="brand">keys.openpgp.org</span> إقبالا واسعا من طرف المستخدمين، لذلك تبنَّته عدة برامج عميلة بسرعة. وهو يُستخدَم حاليا بشكل افتراضي في عملاء، مثل <a href="https://gpgtools.org/" target="_blank">GPGTools</a> و<a href="https://enigmail.net/" target="_blank">Enigmail</a> و<a href="https://www.openkeychain.org/" target="_blank">OpenKeychain</a> و<a href="https://github.com/firstlookmedia/gpgsync" target="_blank">GPGSync</a> وأنظمة Debian وNixOS وغيرها.</p>
<p>‫إلى حدود كتابة هذه السطور، تحققنا على ما يزيد عن 70 000 عنوان للبريد اﻹلكتروني.</p>
<p>‫إلى حدود كتابة هذه السطور، تحققنا من أكثر من 70 000 عنوان للبريد اﻹلكتروني.</p>
<center style="margin-top: 2em; margin-bottom: 2em;">
<img src="/assets/img/stats-addresses-2019-09-12.png" style="padding: 1px; border: 1px solid gray;"><br><span style="font-size: smaller;">إذا لم يكن منحنى واعدا، فلا أدري كيف أقنعك بالعكس :)</span>
</center>
@@ -40,14 +40,14 @@
<h4>كل شيء يسير بخير</h4>
<p>لا يوجد الكثير مما يتم اﻹبلاغ عنه حول عمليات اشتغال، وما ذاك إلا بالخبر الجيد في هذه الحالة ! فمنذ انطلاق الموقع، لم يحدث أي توقف واحد عن العمل، فقط علة طفيفة حدثت لوقت وجيز أدت إلى بعض التعثرات خلال الرفع. كما أن سعة الاستيعاب بقيت منخفضة.</p>
<p>‫إن حجم المواصلات مع خادمنا تُقدَّر حاليا بعشرة طلبات في الثانية (تزيد خلال النهار، وتقلل خلال عطل نهاية اﻷسبوع)، كما أننا أوصلنا ما يقارب100 000 رسالة إلكترونية خلال الشهر الماضي، دون أي مشاكل. </p>
<p>‫لقد أدخلنا بعض التحسينات الطفيفة ذات البعد العملياتي، بما في ذلك تعيين العمل بـ <a href="http://dnsviz.net/d/keys.openpgp.org/dnssec/" target="_blank">DNSSEC</a> وتطبيق <a href="/about/api#rate-limiting" target="_blank">الحد من الصبيب</a>، وإضافة لمسات نهائية على ترويسات <a href="https://developer.mozilla.org/fr/docs/Web/HTTP/CSP">سياسات أمان المحتوى</a>. كما أننا قمنا بتفعيل وضع <a href="https://blog.torproject.org/whats-new-tor-0298" target="_blank">القفزة الوحيدة</a> لأجل خدمة البصلة في تُورْ. هناك لائحة كاملة لكل ذلك <a href="https://gitlab.com/hagrid-keyserver/hagrid/merge_requests?scope=all&amp;utf8=%E2%9C%93&amp;state=merged" target="_blank">هنا</a>.</p>
<p>‫إن حجم المواصلات مع خادمنا تُقدَّر حاليا بعشرة طلبات في الثانية (تزيد خلال النهار، وتنقص خلال عطل نهاية اﻷسبوع)، كما أننا أوصلنا ما يقارب100 000 رسالة إلكترونية خلال الشهر الماضي، دون أي مشاكل. </p>
<p>‫لقد أدخلنا بعض التحسينات الطفيفة ذات البعد العملياتي، بما في ذلك تعيين العمل بـ <a href="http://dnsviz.net/d/keys.openpgp.org/dnssec/" target="_blank">DNSSEC</a> وتطبيق <a href="/about/api#rate-limiting" target="_blank">الحد من الصبيب</a>، وإضافة لمسات نهائية على ترويسات <a href="https://developer.mozilla.org/fr/docs/Web/HTTP/CSP">سياسات أمان المحتوى</a>. كما أننا قمنا بتفعيل وضع <a href="https://blog.torproject.org/whats-new-tor-0298" target="_blank">القفزة الوحيدة</a> لأجل خدمة البصلة في تُورْ. هناك لائحة كاملة لكل ذلك <a href="https://gitlab.com/keys.openpgp.org/hagrid/merge_requests?scope=all&amp;utf8=%E2%9C%93&amp;state=merged" target="_blank">هنا</a>.</p>
<h4>‫توصيل الرسائل اﻹلكترونية بأمان عبر MTA-STS</h4>
<p>‫من أهم التحسينات التي يجدر إلقاء الانتباه إليها هو <a href="https://www.hardenize.com/blog/mta-sts">MTA-STS</a>، الذي يُجوِّد من أمان الرسائل اﻹلكترونية الصادرة.</p>
<p>‫من أهم التحسينات التي يجدر لفت الانتباه إليها هو <a href="https://www.hardenize.com/blog/mta-sts">MTA-STS</a>، الذي يُجوِّد من أمان الرسائل اﻹلكترونية الصادرة.</p>
<p>‫على الرغم من سعة انتشار HTTPS عالميا في عصرنا هذا، إلا أن اﻷمر مع اﻷسف لا ينطبق على الرسائل اﻹلكترونية، وذلك لأن عدة خوادم لا تدعم التعمية بالمرة، أو أنها تستخدم شهادات ذاتية التوقيع بدل شهادة مُعترَف بها (كشهادات Let's Encrypt). وبما أن توصيل الرسائل هو ما يهم المستخدمين أكثر من قلة أمانها، فإن العديد من الرسائل ما تزال تُسلَّم دون أي تعمية.</p>
<p>‫بفضل MTA-STS، يمكن أن يحدد المكلفون بالنطاقات (عبر HTTPS) أن خادمهم لعلب الرسائل اﻹلكترونية <em>يدعم</em> التعمية. فعند تعذُّر ربط اتصال آمن مع هذا الصنف من الخوادم، سوف يُؤجَّل إيصال الرسائل، أو قد يُرفَض ذلك، بدل معالجتها بطريقة غير آمنة.</p>
<p>‫هذا اﻷمر في غاية اﻷهمية بالنسبة لخدمة مثل <span class="brand">keys.openpgp.org</span>. إذ لو كانت التعمية غير متينة، فيمكن لأعداء اعتراض رسائل التحقق بكل سهولة. لكن إذا كانت ميزة MTA-STS مُشغَّلة عند مزودي الخدمة، ستكون لديك الثقة بأن توصيل كل رسالة يتم بأمان، نحو الخادم الصحيح لعلب الرسائل اﻹلكترونية.</p>
<p>‫هذا اﻷمر في غاية اﻷهمية بالنسبة لخدمة مثل <span class="brand">keys.openpgp.org</span>. إذ لو كانت التعمية غير متينة، فيمكن للأعداء اعتراض رسائل التحقق بكل سهولة. لكن إذا كانت ميزة MTA-STS مُشغَّلة عند مزودي الخدمة، ستكون لديك الثقة بأن توصيل كل رسالة يتم بأمان، نحو الخادم الصحيح لعلب الرسائل اﻹلكترونية.</p>
<p>‫يمكنك <a href="https://aykevl.nl/apps/mta-sts/" target="_blank">القيام بعملية المراقبة</a> للكشف عن دعم خدمة بريدك اﻹلكتروني لميزة MTA-STS. إذا لم يكن اﻷمر كذلك، يُرجى إرسال طلب لرفع مستوى الأمان إلى مزود الخدمة الخاص بك !</p>
<h4>أشغال في طور اﻹنجاز</h4>
@@ -68,7 +68,7 @@
<ul>
<li>سريع وموثوق. لا انتظارات ولا أعطال ولا أي تضاربات في البيانات.</li>
<li>دقيق. تظهر نتائج البحث مفتاحا منفردا، مما يُسهِّل عليك العثور عليه.</li>
<li>دقيق. تُظهِر نتائج البحث مفتاحا منفردا، مما يُسهِّل عليك العثور عليه.</li>
<li>القدرة على التحقق. لا ينشر الهويات إلا بعد الموافقة على ذلك، بينما يتيح الوصول بكل حرية لبقية المعلومات التي لا ترتبط بالهوية.</li>
<li>قابلية الحذف. يمكن للمستخدمين حذف المعلومات الشخصية فقط بواسطة رسالة تأكيد.</li>
<li>‫مُبرمَج بلغة Rust، بدعم من <a href="https://sequoia-pgp.org" target="_blank">Sequoia PGP</a> - مجاني ومفتوح المصدر، بموجب اﻹصدار الثالث من الرخصة جْنُو أَفِيرُو العمومية AGPLv3.</li>
@@ -93,8 +93,8 @@
<p>‫بالخصوص، حينما يجد GnuPG (الإصدار 2.2.16، إلى حدود كتابة هذه السطور) مفتاح OpenPGP لا يحتوي على أي هوية. إذ يُظهِر الخطأ « no user ID » ولا يواصل معالجة المعلومات الجديدة التي لا تكشف عن الهوية (مثل شهادات النقض)، على الرغم من صلاحية بيانات التعمية. ولهذا، فنحن نعمل بكامل جهدنا لكي نصلح هذه المشاكل.</p>
<h4>في المستقبل</h4>
<p>‫إن طرق حماية الخصوصية في خوادم المفاتيح مازالت حديثة. كما أن لدينا العديد من اﻷفكار للتقليل من البيانات الوصفية. لكن في الوقت الحالي، نخطط فقط لجعل <span class="brand">keys.openpgp.org</span> أسرع 🐇 وأكثر مصداقية، وكذا تصحيح العلل 🐞 التي نجدها، وأيضا أخد كل <a href="/about#community">الملاحظات</a> التي تصلنا من مجتمع المستخدمين بعين الاعتبار. 👂</p>
<p>للمزيد من التوضيحات، ما عليك سوى الاطلاع على صفحات <a target="_blank" href="/about">حول</a>وصفحات <a target="_blank" href="/about/faq">اﻷسئلة الشائعة</a>. يمكنك البدء حالا عبر <a href="/upload" target="_blank">رفع مفتاحك</a> ! كما يمكنك الاطلاع على أشياء أخرى مفيدة، مثل <a target="_blank" href="/about/api">واجهة برمجتنا للتطبيقات</a> وكذا <a target="_blank" href="/about/faq#tor">خدمة البصلة</a> !</p>
<p>شكرا لك !
<p>‫إن طرق حماية الخصوصية في خوادم المفاتيح مازالت حديثة. كما أن لدينا العديد من اﻷفكار للتقليل من البيانات الوصفية. لكن في الوقت الحالي، نخطط فقط لجعل <span class="brand">keys.openpgp.org</span> أسرع 🐇 وأكثر مصداقية، وكذا تصحيح العلل 🐞 التي نجدها، وأيضا أخذ كل <a href="/about#community">الملاحظات</a> التي تصلنا من مجتمع المستخدمين بعين الاعتبار. 👂</p>
<p>للمزيد من التوضيحات، ما عليك سوى الاطلاع على صفحات <a target="_blank" href="/about">حول</a> وصفحات <a target="_blank" href="/about/faq">اﻷسئلة الشائعة</a>. يمكنك البدء حالا عبر <a href="/upload" target="_blank">رفع مفتاحك</a> ! كما يمكنك الاطلاع على أشياء أخرى مفيدة، مثل <a target="_blank" href="/about/api">واجهة برمجتنا للتطبيقات</a> وكذا <a target="_blank" href="/about/faq#tor">خدمة البصلة</a> !</p>
<p>تمتع بالخدمة !
<span style="font-size: x-large;">☕🍵</span></p>
</div>

View File

@@ -1,27 +0,0 @@
<div class="about">
<center><h2>‫حول | <a href="/about/news">اﻷخبار</a> | <a href="/about/usage">الاستخدام</a> | <a href="/about/faq">اﻷسئلة الشائعة</a> | <a href="/about/stats">اﻹحصاءات</a> | <a href="/about/privacy">حماية البيانات‬</a>
</h2></center>
<p style="text-align: left;">‫يقوم خادم المفاتيح الذي يشتغل خلف keys.openpgp.org بمعالجة وتخزين وتوزيع البيانات المتعلقة بمفاتيح OpenPGP. تُعالَج البيانات بطريقة ملائمة لنوعيتها كما يلي :</p>
<ul>
<li>
<b>عناوين البريد الإلكتروني</b><p>تتضمن <abbr title="Packet Tag 13">مُعرِّفات المستخدم</abbr> عناوين البريد الإلكتروني، وهذه اﻷخيرة هي معلومات تُمكِّن من تحديد هوية مالكها. لذلك، تُتخَذ احتياطات استثنائية لضمان استخدامها فقط بعد الموافقة على ذلك :</p>
<ul>
<li>يتطلب نشرها تأكيد <a target="_blank" href="https://fr.wikipedia.org/wiki/Opt_in">الموافقة مرتين</a> وذلك ﻹثبات ملكية عنوان البريد الإلكتروني المقصود.</li>
<li>يمكن العثور على عناوين البريد اﻹلكتروني عبر كتابتها بالكامل، وليس فقط بالأسماء المرتبطة بها.</li>
<li>لا يمكن عدّ العناوين.</li>
<li>‫من الممكن حذف العناوين عبر إثبات يسير لملكيتها، بطريقة تلقائية، تشبه التي استُخدمَت خلال عملية نشرها. إذا تعذر عليك إزالة العنوان، يكفي مراسلتنا عبر البريد اﻹلكتروني : Support في keys نقطة openpgp نقطة org.</li>
</ul>
<p>لا تُعطَى هذه البيانات لأطراف أخرى بشكل مُجمَّع (بيانات مُفرَّغة).</p>
</li>
<li>
<b>بيانات المفاتيح العمومية</b><p>إن محتويات بيانات التعمية لمفاتيح OpenPGP ليست معلومات مُحدِّدة لهوية مالكيها. يتعلق اﻷمر تحديدا ب<abbr title="Balises de paquet 6 et 14">محتويات المفاتيح العمومية</abbr>، و<abbr title="Balise de paquet 6, types de signature 0x10-0x13, 0x18, 0x19, 0x1F">التوقيعات الذاتية</abbr>، وكذا <abbr title="Balise de paquet 2, types de signature 0x20, 0x28, 0x30">توقيعات النقض</abbr>.</p>
<p>في الغالب، لا تكون هذه البيانات متاحة بشكل مُجمَّع (بيانات مُفرَّغة)، لكن يمكن إعطاؤها لأطراف أخرى بغرض التطوير أو إجراء اﻷبحاث.</p>
</li>
<li>
<b>بيانات معرِّفات المستخدمين اﻷخرى</b><p>‫قد يتضمن مفتاح OpenPGP بيانات شخصية أخرى غير عناوين البريد اﻹلكتروني، مثل <abbr title="Balise de paquet 13">مُعرِّفات المستخدمين</abbr> التي لا تحتوي على عناوين البريد اﻹلكتروني، أو <abbr title="Balise de paquet 17">صفات الصور</abbr>. لذلك، تُزال هذه البيانات خلال الرفع. إذ، لا تُخزَّن ولا تُعالَج ولا تُوزَّع أبدا، في أي حال من اﻷحوال.</p>
<p>‫إن صنف حزم OpenPGP التي لم يَجْرِ ذكرها تحديدا كما سبق، ستتم إزالتها خلال عملية الرفع. إذ، لا تُخزَّن ولا تُعالَج ولا تُوزَّع، في أي حال من اﻷحوال.</p>
</li>
</ul>
<p style="text-align: left">لا تُرسَل أبدا أي بيانات لأطراف أخرى غير التي هي متاحة في <a href="/about/api">الواجهة العمومية لبرمجة التطبيقات</a>، بما في ذلك، تلك المذكورة في سياسة المعالجة هذه.</p>
</div>

View File

@@ -1,5 +1,6 @@
<div class="about">
<center><h2>‫حول | <a href="/about/news">اﻷخبار</a> | <a href="/about/usage">الاستخدام</a> | <a href="/about/faq">اﻷسئلة الشائعة</a> | <a href="/about/stats">اﻹحصاءات</a> | <a href="/about/privacy">حماية البيانات‬</a>
<center><h2>
<a href="/about">‫حول</a> | <a href="/about/news">اﻷخبار</a> | <a href="/about/usage">الاستخدام</a> | <a href="/about/faq">اﻷسئلة الشائعة</a> | اﻹحصاءات | <a href="/about/privacy">حماية البيانات‬</a>
</h2></center>
<h3>عناوين البريد الإلكتروني المُتحقَّق منها</h3>

View File

@@ -1,17 +1,18 @@
<div class="about usage">
<center><h2>حول | <a href="/about/news">اﻷخبار</a> | <a href="/about/usage">الاستخدام</a> | <a href="/about/faq">اﻷسئلة الشائعة</a> | <a href="/about/stats">اﻹحصاءات</a> | <a href="/about/privacy">حماية البيانات‬</a>
<center><h2><a href="/about">حول</a> | <a href="/about/news">اﻷخبار</a> | الاستخدام | <a href="/about/faq">اﻷسئلة الشائعة</a> | <a href="/about/stats">اﻹحصاءات</a> | <a href="/about/privacy">حماية البيانات‬</a>
</h2></center>
<p>‫نقوم في هذه الصفحة، بجمع المعلومات المتعلقة بكيفية استخدام <span class="brand">keys.openpgp.org</span> مع مختلف المنتجات البرمجية التي تعتمد على OpenPGP.<br> مازلنا في صدد إضافة المزيد. إذا لاحظت أشياء ناقصة، يُرجى مراسلتنا حول ذلك، وسنحاول جاهدين، إضافة ما ينقص.</p>
<h2 id="web" style="padding-left: 3%;"><a href="#web">واجهة الموقع</a></h2>
<p>‫تسمح لك واجهة الموقع في <span class="brand">keys.openpgp.org</span> من :</p>
<p>‫تسمح لك واجهة الموقع في <span class="brand">keys.openpgp.org</span> بـ :</p>
<p>
</p>
<ul>
<li><a href="/">البحث</a> عن المفاتيح يدويا، بواسطة بصماتها أو عنوان البريد الإلكتروني.</li>
<li><a href="/upload">رقع</a> المفاتيح يدويا، ثم التحقق منها بعد ذلك.</li>
<li>
<a href="/">البحث</a> اليدوي عن المفاتيح، بواسطة بصمته أو عنوانه الإلكتروني.</li>
<li><a href="/upload">رفع</a> المفاتيح يدويا، ثم التحقق منها بعد ذلك.</li>
<li><a href="/manage">إدارة</a> مفاتيحك، ثم إزالة الهويات المنشورة.</li>
</ul>
<h2 id="enigmail">
@@ -22,25 +23,25 @@
<p>‫الدعم الكامل متاح منذ اﻹصدار 2.1 لـ Enigmail (في <a href="https://www.thunderbird.net/en-US/thunderbird/68.0beta/releasenotes/" target="_blank">Thunderbird 68</a> أو اﻷحدث) :</p>
<ul>
<li>سوف تُحدَّث المفاتيح تلقائيا.</li>
<li>حلال إنشاء مفتاحك، يمكنك رفعه ثم التحقق منه.</li>
<li>خلال إنشاء مفتاحك، يمكنك رفعه ثم التحقق منه.</li>
<li>يمكنك العثور على المفاتيح بعنوان البريد الإلكتروني.</li>
</ul>
<h2 id="gpg-suite">
<div><img src="/assets/img/gpgtools.png"></div>
<a href="#gpg-suite">GPG Suite</a>
</h2>
<p>‫يستخدم <a href="https://gpgtools.org/">GPG Suite</a> لنظام ماك <span class="brand">keys.openpgp.org</span> افتراضيا، منذ غشت 2019.</p>
<p>‫يستخدم <a href="https://gpgtools.org/">GPG Suite</a> لنظام مَاكْ <span class="brand">keys.openpgp.org</span> افتراضيا، منذ غشت 2019.</p>
<h2 id="openkeychain">
<div><img src="/assets/img/openkeychain.svg"></div>
<a href="#openkeychain">OpenKeychain</a>
</h2>
<p>‫يستخدم <a href="https://gpgtools.org/">GPG Suite</a> لنظام أندرويد <span class="brand">keys.openpgp.org</span> افتراضيا، منذ غشت 2019.</p>
<p>‫يستخدم <a href="https://gpgtools.org/">OpenKeychain</a> لنظام أندرويد <span class="brand">keys.openpgp.org</span> افتراضيا، منذ غشت 2019.</p>
<ul>
<li>سوف تُحدَّث المفاتيح تلقائيا.</li>
<li>يمكنك العثور على المفاتيح بعنوان البريد الإلكتروني.</li>
</ul>
<p>تجدر اﻹشارة إلى غياب أي دعم مُدمَج لرفع عناوين البريد الإلكتروني ولا للتحقق منها.</p>
<p>تجدر اﻹشارة إلى غياب أي دعم مُدمَج لرفع عناوين البريد الإلكتروني وأيضا غياب التحقق منها.</p>
<h2 id="pignus">
<div><img src="/assets/img/pignus.png"></div>
@@ -78,11 +79,11 @@
</ul>
<h4 id="gnupg-troubleshooting"><a href="#gnupg-troubleshooting">المُساعَفة</a></h4>
<ul>
<li>‫تتضمن بعض الملفات القديمة <tt>~/gnupg/dirmngr.conf</tt> سطرا مثل :<blockquote>hkp-cacert ~/.gnupg/sks-keyservers.netCA.pem</blockquote>
<p>تلك التهيئة لم تعد ضرورية، لكنها تمنع الشهادات المعتادة من العمل. لذلك، يُوصَى ببساطة بإزالة ذلك السطر من التهيئة.</p>
<li>‫تتضمن بعض الملفات القديمة مثل <tt>~/gnupg/dirmngr.conf</tt> سطرا يشبه :<blockquote>hkp-cacert ~/.gnupg/sks-keyservers.netCA.pem</blockquote>
<p>تلك التهيئة لم تعد ضرورية، لكنها تمنع الشهادات المعتادة من العمل. لذلك، يُوصَى ببساطة بإزالة ذلك السطر من ملف التهيئة.</p>
</li>
<li>خلال إنعاش المفاتيح، قد تظهر لك أخطاء كاﻵتي :<blockquote>gpg: key A2604867523C7ED8: no user ID</blockquote>
‫ذلك <a href="https://dev.gnupg.org/T4393" target="_blank">مشكل معروف في GnuPG</a>، ونحن نعمل مع فريق GnuPG لخل تلك المشكلة.
‫ذلك <a href="https://dev.gnupg.org/T4393" target="_blank">مشكل معروف في GnuPG</a>، ونحن نعمل مع فريق GnuPG لحل تلك المشكلة.
</li>
</ul>
<h4 id="gnupg-tor"><a href="#gnupg-tor">الاستخدام مع تُورْ</a></h4>

View File

@@ -26,5 +26,5 @@
<p>Aquest servei funciona com un treball comunitari. Podeu parlar amb nosaltres al canal #hagrid de Freenode IRC, també com #hagrid:stratum0.org a Matrix. Per suposat podeu trobar-nos per correu electrònic a <tt>support at keys dot openpgp dot org</tt>. Els companys que fan funcionar aquest servei provenen de diferents projectes dins de l'ecosistema OpenPGP, incloent-hi Sequoia-PGP, OpenKeychain, i Enigmail.</p>
<p>Tècnicament, <tt>keys.openpgp.org</tt> funciona sobre el programari servidor de claus <a href="https://gitlab.com/hagrid-keyserver/hagrid" target="_blank">Hagrid</a>, que està basat en <a href="https://sequoia-pgp.org">Sequoia-PGP</a>. Estem funcionant sobre <a href="https://eclips.is" target="_blank">eclips-is</a>, una plataforma de "hosting" que posa focus en projectes de la Internet Freedom, i gestionada per <a href="https://greenhost.net/" target="_blank">Greenhost</a>.</p>
<p>Tècnicament, <tt>keys.openpgp.org</tt> funciona sobre el programari servidor de claus <a href="https://gitlab.com/keys.openpgp.org/hagrid" target="_blank">Hagrid</a>, que està basat en <a href="https://sequoia-pgp.org">Sequoia-PGP</a>. Estem funcionant sobre <a href="https://eclips.is" target="_blank">eclips-is</a>, una plataforma de "hosting" que posa focus en projectes de la Internet Freedom, i gestionada per <a href="https://greenhost.net/" target="_blank">Greenhost</a>.</p>
</div>

View File

@@ -0,0 +1,64 @@
<div class="about">
<center><h2>Info | <a href="/about/news">Novinky</a> | Použití | <a href="/about/faq">Časté dotazy</a> | <a href="/about/stats">Statistiky</a> | <a href="/about/privacy">Soukromí</a>
</h2></center>
<p>Server <span class="brand">keys.openpgp.org</span> je veřejnou službou pro
distribuci a vyhledávání klíčů kompatibilních s protokolem OpenPGP, běžně
se označuje jako "keyserver".</p>
<p><strong>Pokyny naleznete v našem <a href="/about/usage">průvodci používáním</a>.</strong></p>
<h3>Jak to funguje</h3>
<p>OpenPGP klíč obsahuje dva typy informací:</p>
<ul>
<li>
<strong>Informace o identitě</strong> popisují části
klíče, které identifikují jeho vlastníka, známé také jako "ID uživatele".
ID uživatele obvykle obsahuje jméno a e-mailovou adresu.</li>
<li>
<strong>Ne-identitní informace</strong> jsou všechny technické
informace o samotném klíči. Patří sem velká čísla
používané pro ověřování podpisů a šifrování zpráv.
Zahrnuje také metadata, jako je datum vytvoření, údaje o datumech platnosti
a stav odvolání.</li>
</ul>
<p>Tyto informace byly vždy tradičně distribuovány.
společně. Na <span class="brand">keys.openpgp.org</span> se s nimi
zachází jinak. Zatímco kdokoli může nahrát všechny části libovolného klíče OpenPGP.
na <span class="brand">keys.openpgp.org</span>, náš keyserver
bude uchovávat a zveřejňovat pouze určité části za určitých
podmínek:</p>
<p>Veškeré informace <strong>ne-identitní údaje</strong> budou uloženy a volně
přístupné, pokud projdou kryptografickou kontrolou integrity.
Tyto části si může kdokoli kdykoli stáhnout, protože obsahují pouze
technické údaje, které nelze použít k přímé identifikaci osoby.
Dobrý OpenPGP software může používat <span class="brand">keys.openpgp.org</span>
k aktualizaci těchto informací pro každý klíč, o kterém ví.
To pomáhá uživatelům OpenPGP udržovat bezpečnou a spolehlivou komunikaci.</p>
<p><strong>Informace o identitě</strong> v klíči OpenPGP
se šíří pouze se souhlasem.
Obsahují osobní údaje a nejsou nezbytně nutné pro
klíč použít pro šifrování nebo ověření podpisu.
Jakmile vlastník udělí souhlas ověřením své e-mailové adresy,
lze klíč najít pomocí vyhledávání podle adresy.</p>
<h3 id="community">Komunita a platforma</h3>
<p>Tato služba je provozována komunitou.
Mluvit s námi můžete v #hagrid na OFTC IRC,
nebo jsem dostupní jako #hagrid:stratum0.org na Matrix.
Samozřejmě se s námi můžete spojit také prostřednictvím e-mailu,
na adrese <tt>support zavináč keys tečka openpgp tečka org</tt>.
Lidé, kteří službu provozují, přicházejí
z různých projektů v ekosystému OpenPGP,
včetně Sequoia-PGP, OpenKeychain a Enigmail.</p>
<p>Technicky
je <span class="brand">keys.openpgp.org</span> provozován na keyserver softwaru <a href="https://gitlab.com/keys.openpgp.org/hagrid" target="_blank">Hagrid</a>,
který je založen na <a href="https://sequoia-pgp.org">Sequoia-PGP</a>.
Běžíme na <a href="https://eclips.is" target="_blank">eclips.is</a>, hostingové platformě zaměřené na Internet Freedom projekty, tu spravuje <a href="https://greenhost.net/" target="_blank">Greenhost</a>.</p>
</div>

View File

@@ -0,0 +1,31 @@
<div class="about">
<center><h2>
<a href="/about">Info</a> | <a href="/about/news">Novinky</a> | <a href="/about/usage">Použití</a> | <a href="/about/faq">Časté dotazy</a> | Statistiky | <a href="/about/privacy">Soukromí</a>
</h2></center>
<h3>Ověřené emailové adresy</h3>
<p>Jednoduchá statistika celkového počtu aktuálně ověřených e-mailových adres. 📈</p>
<p>
</p>
<center><img src="/about/stats/month.png"></center>
<p>
</p>
<center><img src="/about/stats/year.png"></center>
<h3>Průměrná zátěž</h3>
<p>"Průměrná zátěž" serveru je statistický údaj o jeho vytíženosti. Jednoduše řečeno:</p>
<ul>
<li>0.0 znamená, že server <span class="brand">keys.openpgp.org</span> je zcela nečinný</li>
<li>1.0 znamená, že je poměrně zaneprázdněný</li>
<li>4.0 a více znamená, že fakt maká 🔥</li>
</ul>
<p>
</p>
<center><img src="/about/stats/load_week.png"></center>
</div>

View File

@@ -0,0 +1,157 @@
<div class="about usage">
<center><h2>
<a href="/about">Info</a> | <a href="/about/news">Novinky</a> | Použití | <a href="/about/faq">Časté dotazy</a> | <a href="/about/stats">Statistiky</a> | <a href="/about/privacy">Soukromí</a>
</h2></center>
<p>Na této stránce shromažďujeme informace o tom, jak používat
<span class="brand">keys.openpgp.org</span> s různými nástroji OpenPGP
softwarovými produkty.<br>
Stále probíhá proces přidávání dalších. Pokud vám některé chybí, prosím
napište nám a my se je pokusíme doplnit.</p>
<h2 id="web" style="padding-left: 3%;"><a href="#web">Webové rozhraní</a></h2>
<p>Webové rozhraní na <span class="brand">keys.openpgp.org</span> vám umožňuje:</p>
<p>
</p>
<ul>
<li>
<a href="/">Hledat</a> klíče manuálně, podle otisku nebo emailové adresy.</li>
<li>
<a href="/upload">Nahrát</a> klíče manuálně a po nahrání je ověřit.</li>
<li>
<a href="/manage">Spravovat</a> vaše klíče a odstraňovat zveřejněné identity.</li>
</ul>
<h2 id="enigmail">
<div><img src="/assets/img/enigmail.svg"></div>
<a href="#enigmail">Enigmail</a>
</h2>
<p><a href="https://enigmail.net" target="_blank">Enigmail</a> pro Thunderbird
implicitně používá <span class="brand">keys.openpgp.org</span> od
verze 2.0.12.</p>
<p>Plná podpora je dostupná od verze Enigmail 2.1
(pro <a href="https://www.thunderbird.net/en-US/thunderbird/68.0beta/releasenotes/" target="_blank">Thunderbird 68</a> nebo novější):</p>
<ul>
<li>Klíče budou automaticky aktualizovány.</li>
<li>Během vytváření klíče můžete nahrát a ověřit svůj klíč.</li>
<li>Klíče lze nalézt podle e-mailové adresy.</li>
</ul>
<h2 id="gpg-suite">
<div><img src="/assets/img/gpgtools.png"></div>
<a href="#gpg-suite">GPG Suite</a>
</h2>
<p><a href="https://gpgtools.org/">GPG Suite</a> pro macOS
implicitně používá <span class="brand">keys.openpgp.org</span>
od srpna 2019.</p>
<h2 id="openkeychain">
<div><img src="/assets/img/openkeychain.svg"></div>
<a href="#openkeychain">OpenKeychain</a>
</h2>
<p><a href="https://www.openkeychain.org/">OpenKeychain</a> pro Android
implicitně používá <span class="brand">keys.openpgp.org</span>
od července 2019.</p>
<ul>
<li>Klíče budou automaticky aktualizovány.</li>
<li>Klíče lze nalézt podle e-mailové adresy.</li>
</ul>
<p>Poznámka: zatím není k dispozici vestavěná podpora pro odesílání a ověřování e-mailové adresy.</p>
<h2 id="pignus">
<div><img src="/assets/img/pignus.png"></div>
<a href="#pignus">Pignus</a>
</h2>
<p><a href="https://www.frobese.de/pignus/">Pignus</a> pro iOS
implicitně používá <span class="brand">keys.openpgp.org</span>
od listopadu 2019.</p>
<ul>
<li>Vaše klíče mohou být nahrány kdykoli.</li>
<li>Klíče lze nalézt podle e-mailové adresy.</li>
</ul>
<h2 id="gnupg">
<div><img src="/assets/img/gnupg.svg"></div>
<a href="#gnupg">GnuPG</a>
</h2>
<p>Pro nastavení <a href="https://gnupg.org">GnuPG</a>
aby jako keyserver používal <span class="brand">keys.openpgp.org</span>,
přidejte tento řádek do souboru <tt>gpg.conf</tt>:</p>
<blockquote>keyserver hkps://keys.openpgp.org</blockquote>
<h4 id="gnupg-retrieve"><a href="#gnupg-retrieve">Získání klíčů</a></h4>
<ul>
<li>Vyhledání klíče uživatele podle e-mailové adresy:<blockquote>gpg --auto-key-locate keyserver --locate-keys user@example.net</blockquote>
</li>
<li>Obnovení všech vašich klíčů (např. nových certifikátů odvolání a podklíčů):<blockquote>gpg --refresh-keys</blockquote>
</li>
</ul>
<h4 id="gnupg-upload"><a href="#gnupg-upload">Nahrání vašeho klíče</a></h4>
<p>Klíče lze nahrát pomocí příkazu <tt>--send-keys</tt> v GnuPG, ale
tímto způsobem nelze ověřit informace o identitě tak, aby byl klíč
dohledatelný podle e-mailové adresy (<a href="/about">co to znamená?</a>).</p>
<ul>
<li>Můžete vyzkoušet tuto zkratku pro nahrání klíče, která vypisuje.
přímý odkaz na ověřovací stránku:<blockquote>gpg --export your_address@example.net | curl -T - {{ base_uri }}</blockquote>
</li>
<li>Můžete je také exportovat do souboru.
a vybrat tento soubor na stránce pro <a href="/upload" target="_blank">nahrání</a>:<blockquote>gpg --export your_address@example.net &gt; my_key.pub</blockquote>
</li>
</ul>
<h4 id="gnupg-troubleshooting"><a href="#gnupg-troubleshooting">Řešení potíží</a></h4>
<ul>
<li>Některé staré <tt>~/gnupg/dirmngr.conf</tt> soubory obsahují takovýto řádek:<blockquote>hkp-cacert ~/.gnupg/sks-keyservers.netCA.pem</blockquote>
<p>Toto nastavení již není nutné,
ale zabraňuje fungování běžných certifikátů.
Doporučujeme tento řádek z konfigurace jednoduše odstranit.</p>
</li>
<li>Při obnovování klíčů se mohou zobrazit následující chyby:<blockquote>gpg: key A2604867523C7ED8: no user ID</blockquote>
Jedná se o <a href="https://dev.gnupg.org/T4393" target="_blank">známý problém v GnuPG</a>.
Na vyřešení tohoto problému spolupracujeme s týmem GnuPG.
</li>
</ul>
<h4 id="gnupg-tor"><a href="#gnupg-tor">Použití přes Tor</a></h4>
<p>Pro uživatele, kteří chtějí být extra opatrní,
na stránku <span class="brand">keys.openpgp.org</span> lze vstoupit anonymně přes <a href="https://support.torproject.org/onionservices/#onionservices-2" target="_blank">onion sužbu</a>.
Pokud máte nainstalovaný
<a href="https://www.torproject.org/" target="_blank">Tor</a>,
použijte následující konfiguraci:</p>
<blockquote>keyserver hkp://zkaan2xfbuxia2wpf7ofnkbz6r5zdbbvxbunvp5g2iebopbfc4iqmbad.onion</blockquote>
<h2 style="padding-left: 3%;" id="wkd-as-a-service"><a href="#wkd-as-a-service">WKD jako služba</a></h2>
<p>Web Key Directory (WKD) je standard pro hledání klíčů OpenPGP podle e-mailové adresy prostřednictvím domény jejího poskytovatele.
Používá se ke zjišťování neznámých klíčů v některých e-mailových klientech, jako je například <a href="https://www.gpg4win.de/about.html" target="_blank">GpgOL</a>.</p>
<p><span class="brand">keys.openpgp.org</span> lze použít jako spravovanou službu WKD pro libovolnou doménu.
K tomu stačí, když doména vytvoří záznam <tt>CNAME</tt>, který deleguje její subdoménu <tt>openpgpkey</tt> na <tt>wkd.keys.openpgp.org</tt>.
To by mělo být možné provést ve webovém rozhraní libovolného hostitele DNS.</p>
<p>Po povolení domény budou její ověřené adresy automaticky k dispozici pro vyhledávání prostřednictvím WKD.</p>
<p><tt>CNAME</tt> záznam by měl vypadat takto:</p>
<blockquote>$ drill openpgpkey.example.org1<br>
...<br>
openpgpkey.example.org. 300 IN CNAME wkd.keys.openpgp.org.</blockquote>
<p>K dispozici je jednoduchá kontrola stavu pro testování služby:</p>
<blockquote>$ curl 'https://wkd.keys.openpgp.org/status/?domain=openpgpkey.example.org'<br>
CNAME lookup ok: openpgpkey.example.org resolves to wkd.keys.openpgp.org<br>
</blockquote>
<p>Pro testování získání klíčů:</p>
<blockquote>$ gpg --locate-keys --auto-key-locate clear,nodefault,wkd address@example.org<br>
</blockquote>
<h2 style="padding-left: 3%;">API</h2>
<p>Nabízíme API pro integrovanou podporu v OpenPGP aplikacích. Podívejte se na
naši <a href="/about/api">dokumentaci API</a>.</p>
<h2 style="padding-left: 3%;">Další</h2>
<p>Chybí vám průvodce pro vaši oblíbenou implementaci? Tuto stránku
se snažíme vylepšovat. Napište nám na adresu
<span class="email">support zavináč keys tečka openpgp tečka org</span>, pokud
chcete pomoci!</p>
</div>

View File

@@ -2,7 +2,7 @@
<center><h2>Übersicht | <a href="/about/news">News</a> | <a href="/about/usage">Nutzung</a> | <a href="/about/faq">FAQ</a> | <a href="/about/stats">Statistik</a> | <a href="/about/privacy">Privacy Policy</a>
</h2></center>
<p>Der <tt>keys.openpgp.org</tt> Server ist ein öffentlicher Service für die Verteilung von OpenPGP-Schlüsseln, üblicherweise als "Keyserver" bezeichnet.</p>
<p>Der <span class="brand">keys.openpgp.org</span> Server ist ein öffentlicher Service für die Verteilung von OpenPGP-Schlüsseln, üblicherweise als "Keyserver" bezeichnet.</p>
<p><strong>Für Details zur Nutzung, siehe <a href="/about/usage">Nutzungshinweise</a></strong></p>
@@ -12,18 +12,26 @@
<ul>
<li>
<strong>Identitäten</strong> beschreiben die Teile des Schlüssels, welche den Besitzer identifizieren. Sie sind auch bekannt als "User IDs", und enhalten typischerweise einen Namen und eine E-Mail-Adresse.</li>
<strong>Identitäten</strong> beschreiben die Teile des Schlüssels, welche den Besitzer identifizieren. Sie sind auch bekannt als "User IDs", und enhalten typischerweise einen Namen und eine Email-Adresse.</li>
<li>Die <strong>Nicht-Identitäts-Informationen</strong> enthalten techische Details über den Schlüssel an sich. Dies sind insbesondere die mathematischen Objekte, mit deren Hilfe Signaturen berechnet und Nachrichten verschlüsselt werden. Auch enthält dies Metadaten des Schlüssels wie den Zeitpunkt der Erstellung, ein eventuelles Ablaufdatum, und den Widerrufsstatus.</li>
</ul>
<p>Traditionell wurden diese Bestandteile von Keyservern gemeinsam ausgeliefert. Auf <span class="brand">keys.openpgp.org</span> werden sie jedoch getrennt behandelt: Während Nicht-Identität-Informationen jedes Schlüssels frei hochgeladen werden können, werden Identitäts-Informationen nur unter bestimmten Bedingungen gespeichert und weiter veröffentlicht:</p>
<p>Die <strong>Nicht-Identitäts-Informationen</strong> eines Schlüssels sind rein technischer Natur, und können nicht zur direkten Identifikation von Personen verwendet werden. Sie werden nach Prüfung der kryptografischen Integrität ohne weitere Bestätigung gespeichert und verteilt. Fortgeschrittene OpenPGP-Anwendungen können <span class="brand">keys.openpgp.org</span> verwenden, um diese Informationen in allen bekannten Schlüsseln auf dem neusten Stand zu halten, und eine sichere und zuverlässige Kommunikation sicherzustellen.</p>
<p>Die <strong>Identitäts-Informationen </strong> eines OpenPGP-Schlüssels enthalten persönliche Daten ihres Besitzers. Sie werden ausschließlich mit dessen Zustimmung veröffentlicht, welche mit einer simplen Bestätigung via E-Mail abgefragt wird. Insbesondere kann ein Schlüssel nur mit Zustimmung anhand seiner E-Mail-Adressen in der Suche gefunden werden.</p>
<p>Die <strong>Identitäts-Informationen </strong> eines OpenPGP-Schlüssels enthalten persönliche Daten ihres Besitzers. Sie werden ausschließlich mit dessen Zustimmung veröffentlicht, welche mit einer simplen Bestätigung via Email abgefragt wird. Insbesondere kann ein Schlüssel nur mit Zustimmung anhand seiner Email-Adressen in der Suche gefunden werden.</p>
<h3 id="community">Community und Plattform</h3>
<p>Dieser Dienst wird ehrenamtlich unterhalten. Die Betreiber dieses Dienstes kommen aus unterschiedlichen OpenPGP-Projekten, insbesondere Sequoia-PGP, OpenKeychain, und Enigmail. Du kannst uns unter #hagrid im Freenode IRC erreichen, oder #hagrid:stratum0.org auf Matrix. Natürlich sind wir auch per E-Mail verfügbar, unter <tt>support at keys punkt openpgp punkt org</tt>.</p>
<p>Dieser Dienst wird als Gemeinschaftsprojekt betrieben.
Du kannst mit uns in
#hagrid auf dem OFTC IRC sprechen,
auch als #hagrid:stratum0.org auf Matrix erreichbar.
Natürlich kannst du uns auch unter,
<tt>support at keys dot openpgp punkt org</tt> per E-Mail erreichen.
Die Leute, die den Dienst betreiben, kommen
aus verschiedenen Projekten im OpenPGP Ökosystem,
wie Sequoia-PGP, OpenKeychain, und Enigmail.</p>
<p>Technisch verwendet <tt>keys.openpgp.org</tt> die <a href="https://gitlab.com/hagrid-keyserver/hagrid" target="_blank">Hagrid</a> Keyserver-Software, welche auf <a href="https://sequoia-pgp.org">Sequoia-PGP</a> basiert. Der Dienst wird gehostet auf der <a href="https://eclips.is" target="_blank">eclips.is</a> Plattform, die von <a href="https://greenhost.net/" target="_blank">Greenhost</a> unterhalten wird und sich auf Internet-Freedom-Projekte spezialisiert.</p>
<p>Technisch verwendet <tt>keys.openpgp.org</tt> die <a href="https://gitlab.com/keys.openpgp.org/hagrid" target="_blank">Hagrid</a> Keyserver-Software, welche auf <a href="https://sequoia-pgp.org">Sequoia-PGP</a> basiert. Der Dienst wird gehostet auf der <a href="https://eclips.is" target="_blank">eclips.is</a> Plattform, die von <a href="https://greenhost.net/" target="_blank">Greenhost</a> unterhalten wird und sich auf Internet-Freedom-Projekte spezialisiert.</p>
</div>

View File

@@ -3,6 +3,8 @@
<a href="/about">Übersicht</a> | <a href="/about/news">News</a> | <a href="/about/usage">Nutzung</a> | FAQ | <a href="/about/stats">Statistik</a> | <a href="/about/privacy">Privacy Policy</a>
</h2></center>
<p><strong>Für Details zur Nutzung, siehe <a href="/about/usage">Nutzungshinweise</a>.</strong></p>
<h3 id="sks-pool"><a href="#sks-pool">Ist dieser Server Teil des "SKS Pools"?</a></h3>
<p>Nein. Das Föderations-Modell des SKS Pools hat mehrere Probleme bezüglich Zuverlässigkeit, Widerstandsfähigkeit gegen Vandalismus, und Nutzbarkeit. Wir werden in Zukunft möglicherweise eine ähnlich verteilte Infrastruktur einführen, aber <span class="brand">keys.openpgp.org</span> wird nie Teil des SKS Pools werden.</p>
@@ -87,8 +89,32 @@ wir können die Einschränkung nicht ohne das Einverständnis des Nutzers abänd
</ol>
<h3 id="older-gnupg"><a href="#older-gnupg">Ich erhalte beim Aktualisieren von Schlüsseln mittels GnuPG eine Fehlermeldung. Handelt es sich um einen Bug?</a></h3>
<p>Dies ist ein Problem in aktuellen Versionen von GnuPG. Bei dem Versuch einen Schlüssel von <span class="brand">keys.openpgp.org</span> zu aktualisieren, der keine <a href="/about">Identitäts-Informationen</a>beinhaltet, bricht GnuPG den Import mit der folgenden Fehlermeldung ab:</p>
<blockquote>$ gpg --receive-keys EB85BB5FA33A75E15E944E63F231550C4F47E38E1
gpg: key EB85BB5FA33A75E15E944E63F231550C4F47E38E: no user ID</blockquote>
<p>Wir arbeiten mit dem GnuPG-Team an einer Lösung.</p>
<p>GnuPG sieht Schlüssel, die keine Identitätsinformationen beinhalten, als ungültig und weigert sich die Schlüssel zu importieren.
Allerdings kann ein Schlüssel, der <a href="/about">keine überprüfte E-Mail-Adresse beinhaltet</a> trotzdem nützliche Informationen beinhalten.
So ist es noch möglich zu prüfen, ob der Schlüssel zurückgerufen ist oder nicht.</p>
<p>Im Juni 2019 hat die <span class="brand">keys.openpgp.org</span>-Gruppe einen Patch erstellt, der es GnuPG erlaubt Aktualisierungen von Schlüsseln ohne Identitätsinformationen zu verarbeiten.
Dieser Patch wurde schnell in vielen Distributionen von GnuPG, wie Debian, Fedora, NixOS und GPG Suite for macOS, übernommen.</p>
<p>Im März 2020 hat die GnuPG-Gruppe den Patch abgelehnt und hat den Problemstatus auf "Wontfix" gesetzt.
Das bedeutet, dass <strong>GnuPG-Versionen ohne den Patch keine Aktualisierungen von <span class="brand">keys.openpgp.org</span> für Schlüssel ohne eine überprüfte E-Mail Adresse empfangen können</strong>.
Sie können die Diskussion im Problem <a href="https://dev.gnupg.org/T4393#133689">T4393</a> auf dem GnuPG Fehler-Tracker verfolgen.</p>
<p>Sie können mit den folgenden Anweisungen überprüfen, ob Ihre Version von GnuPG betroffen ist.</p>
<blockquote>
<span style="font-size: larger;">Test-Schlüssel importieren:</span><br><br>
$ curl https://keys.openpgp.org/assets/uid-test.pub.asc | gpg --import<br>
gpg: key F231550C4F47E38E: "Alice Lovelace &lt;alice@openpgp.example&gt;" imported<br>
gpg: Total number processed: 1<br>
gpg: imported: 1<br><br>
</blockquote>
<blockquote>
<span style="font-size: larger;">Mit dem Patch wird der Schlüssel aktualisiert, wenn er lokal bekannt ist:</span><br><br>
$ gpg --recv-keys EB85BB5FA33A75E15E944E63F231550C4F47E38E<br>
gpg: key F231550C4F47E38E: "Alice Lovelace &lt;alice@openpgp.example&gt;" not changed<br>
gpg: Total number processed: 1<br>
gpg: unchanged: 1<br><br>
</blockquote>
<blockquote>
<span style="font-size: larger;">Ohne den Patch wird ein Schlüssel ohne Identitätsinformationen immer abgelehnt:</span><br><br>
$ gpg --recv-keys EB85BB5FA33A75E15E944E63F231550C4F47E38E<br>
gpg: key EB85BB5FA33A75E15E944E63F231550C4F47E38E: no user ID<br>
</blockquote>
</div>

View File

@@ -18,7 +18,7 @@ Und ganz besonders an die, die uns Rückmeldungen, Übersetzungen und sogar Code
<p>Ein paar Updates über Dinge an den wir gerade arbeiten:</p>
<ul>
<li>Diese Nachrichtenseite ist als <strong><a target="_blank" href="/atom.xml">Atom Feed<img src="/assets/img/atom.svg" style="height: 0.8em;"></a></strong>verfügbar.</li>
<li>Wir haben an einem <strong><a target="_blank" href="https://gitlab.com/hagrid-keyserver/hagrid/issues/131">neuen Weg zum Aktualisieren von Schlüsseln</a></strong> gearbeitet, der die Privatsphäre der Nutzer besser schützt.</li>
<li>Wir haben an einem <strong><a target="_blank" href="https://gitlab.com/keys.openpgp.org/hagrid/issues/131">neuen Weg zum Aktualisieren von Schlüsseln</a></strong> gearbeitet, der die Privatsphäre der Nutzer besser schützt.</li>
<li>Die Arbeit an der <strong>Lokalisisierung</strong> ist in vollem Gange!
Wir hoffen, dass wir bald ein paar Sprachen einsatzfähig haben!</li>
</ul>
@@ -91,7 +91,7 @@ und dem Einrichten des
<a href="https://blog.torproject.org/whats-new-tor-0298" target="_blank">single-hop</a>
Modus in unserem Tor Onion Dienst.
Die komplette Liste findet Ihr
<a href="https://gitlab.com/hagrid-keyserver/hagrid/merge_requests?scope=all&amp;utf8=%E2%9C%93&amp;state=merged" target="_blank">hier</a>.
<a href="https://gitlab.com/keys.openpgp.org/hagrid/merge_requests?scope=all&amp;utf8=%E2%9C%93&amp;state=merged" target="_blank">hier</a>.
</p>
<h4>Sicherer E-Mail-Versand mit MTA-STS</h4>

View File

@@ -1,34 +0,0 @@
<div class="about">
<center><h2>
<a href="/about">Über</a> | <a href="/about/news">News</a> | <a href="/about/usage">Nutzung</a> | <a href="/about/faq">FAQ</a> | <a href="/about/stats">Statistik</a> | Privacy Policy</h2></center>
<p style="text-align: left;">Der öffentliche Schlüsselserver, der auf keys.openpgp.org läuft, verarbeitet, speichert und
verteilt OpenPGP-Schlüssel-Daten. Die konkrete Art der Verarbeitung
unterscheidet sich je nach Typ wie folgt:</p>
<ul>
<li>
<b>E-Mail Adressen</b><p>E-Mail Adressen, die in <abbr title="Packet Tag 13">Nutzer IDs</abbr> enthalten sind, sind personenbezogene Informationen.
Es wird besonders darauf geachtet, dass diese nur mit einer Einwilligung verarbeitet werden.</p>
<ul>
<li>Das Veröffentlichen benötigt eine <a target="_blank" href="https://en.wikipedia.org/wiki/Opt-in_email#Confirmed_opt-in_(COI)_/_Double_opt-in_(DOI)">Double-Opt-In</a> Bestätigung, um den Besitz der fraglichen E-Mail Adresse nachzuweisen.</li>
<li>Adressen kann man über die exakte E-Mail Adresse suchen,
nicht aber über den damit verbundenen Namen.</li>
<li>Das Auflisten von Adressen ist nicht möglich.</li>
<li>Das Löschen von Adressen ist über einen einfachen Besitznachweis, ähnlich wie beim Veröffentlichen, möglich. Um eine Adresse zu entfernen bei der das nicht möglich ist, wenden sie sich bitte an support at keys Punkt openpgpg Punkt org.</li>
</ul>
<p>Diese Daten werden niemals gesammelt (als "Dump") an Dritte weitergegeben.</p>
</li>
<li>
<b>Öffentliche Schlüssel</b><p>Der kryptografische Inhalt von OpenPGP-Schlüssel wird nicht als personenbezogene Informationen gewertet. Das beinhaltet besonders
<abbr title="Packet Tags 6 and 14">Material öffentlicher Schlüssel</abbr>,
<abbr title="Packet Tag 2, Signature types 0x10-0x13, 0x18, 0x19, 0x1F">eigene Signaturen</abbr> und
<abbr title="Packet Tag 2, Signature types 0x20, 0x28, 0x30">Widerrufssignaturen</abbr>.</p>
<p>Diese Daten sind normalerweise nicht gesammelt (als "Dump") verfügbar, können aber bei Anforderung an Dritte zum Zweck der Weiterentwicklung oder Forschung weitergegeben werden.</p>
</li>
<li>
<b>Andere Benutzerkennungen</b><p>Ein OpenPGP Schlüssel kann andere persönliche Daten als E-Mail Adressen wie <abbr title="Packet Tag 13">Benutzerkennungen</abbr>, die keine E-Mail Adressen enthalten, oder <abbr title="Packet Tag 17">Bildanhänge</abbr> enthalten. Diese Daten werden beim Hochladen entfernt und niemals in irgend einer Weise gespeichert, verarbeitet oder weitergegeben.</p>
<p>OpenPGP Packet-Typen die nicht explizit oben genannt wurden werden beim Hochladen entfernt sowie nie gespeichert, verarbeitet oder in irgendeiner Form weitergegeben.</p>
</li>
</ul>
<p style="text-align: left">Daten werden niemals an Dritte außerhalb des Bereichs der <a href="/about/api">öffentlichen API Schnittstellen</a> und wie dieser Richtlinie beschrieben weitergegeben.</p>
</div>

View File

@@ -5,9 +5,21 @@
<p>Auf dieser Seite sammeln wir Anleitungen zur Nutzung von <span class="brand">keys.openpgp.org</span> mit unterschiedlichen OpenPGP-Anwendungen. Wir sind noch dabei, weitere Anleitungen hinzuzufügen - falls du eine bestimmte vermisst, lass es uns einfach wissen.</p>
<h2>
<h2 id="web" style="padding-left: 3%;"><a href="#web">Web-Interface</a></h2>
<p>Das Web-Interface auf <span class="brand">keys.openpgp.org</span> erlaubt die folgenden Operationen:</p>
<p>
</p>
<ul>
<li>Manuelle <a href="/">Suche</a> nach Schlüsseln, anhand eines Fingerprints oder einer Email-Adresse.</li>
<li>
<a href="/upload">Hochladen</a> von Schlüsseln, mit Bestätigung nach dem Hochladen.</li>
<li>
<a href="/manage">Verwalten</a> von Schlüsseln, insbesondere das Entfernen veröffentlichter Identitäten.</li>
</ul>
<h2 id="enigmail">
<div><img src="/assets/img/enigmail.svg"></div>
Enigmail
<a href="#enigmail">Enigmail</a>
</h2>
<p><a href="https://enigmail.net" target="_blank">Enigmail</a> für Thunderbird verwendet <span class="brand">keys.openpgp.org</span> als voreingestellten Keyserver seit Version 2.0.12.</p>
<p>Alle Features sind verfügbar ab Enigmail 2.1 (für <a href="https://www.thunderbird.net/en-US/thunderbird/68.0beta/releasenotes/" target="_blank">Thunderbird 68</a> und neuer):</p>
@@ -16,15 +28,15 @@
<li>Schlüssel können während der Generierung hochgeladen werden, inkl. Adress-Bestätigung.</li>
<li>Schlüssel können anhand von Email-Adressen gesucht werden.</li>
</ul>
<h2>
<h2 id="gpg-suite">
<div><img src="/assets/img/gpgtools.png"></div>
GPG Suite
<a href="#gpg-suite">GPG Suite</a>
</h2>
<p><a href="https://gpgtools.org/">GPG Suite</a> für macOS verwendet <span class="brand">keys.openpgp.org</span> voreingestellt seit August 2019.</p>
<h2>
<h2 id="openkeychain">
<div><img src="/assets/img/openkeychain.svg"></div>
OpenKeychain
<a href="#openkeychain">OpenKeychain</a>
</h2>
<p><a href="https://www.openkeychain.org/">OpenKeychain</a> für Android verwendet <span class="brand">keys.openpgp.org</span> voreingestellt seit Juli 2019.</p>
<ul>
@@ -33,9 +45,9 @@
</ul>
<p>Bislang gibt es allerdings keine integrierte Unterstützung für das Bestätigen von Email-Adressen.</p>
<h2>
<h2 id="pignus">
<div><img src="/assets/img/pignus.png"></div>
Pignus
<a href="#pignus">Pignus</a>
</h2>
<p><a href="https://www.frobese.de/pignus/">Pignus</a> für iOS
verwendet <span class="brand">keys.openpgp.org</span> voreingestellt
@@ -44,9 +56,9 @@
<li>Schlüssel können zu jedem Zeitpunkt hochgeladen werden.</li>
<li>Schlüssel können anhand von Email-Adressen gesucht werden.</li>
</ul>
<h2>
<h2 id="gnupg">
<div><img src="/assets/img/gnupg.svg"></div>
GnuPG
<a href="#gnupg">GnuPG</a>
</h2>
<p>Um <a href="https://gnupg.org">GnuPG</a> mit <span class="brand">keys.openpgp.org</span> zu konfigurieren, füge diese Zeile in der <tt>gpg.conf</tt> Datei hinzu:</p>
@@ -83,7 +95,7 @@
<blockquote>keyserver hkp://zkaan2xfbuxia2wpf7ofnkbz6r5zdbbvxbunvp5g2iebopbfc4iqmbad.onion</blockquote>
<h2 style="padding-left: 3%;" id="wkd-as-a-service"><a style="color: #050505;" href="#wkd-as-a-service">WKD as a Service</a></h2>
<h2 style="padding-left: 3%;" id="wkd-as-a-service"><a href="#wkd-as-a-service">"WKD as a Service"</a></h2>
<p>Der "Web Key Directory"-Standard (WKD) ermöglicht das automatische Auffinden von OpenPGP-Schlüsseln für eine gegebene Email-Adresse, über die Domain des Email-Anbieters.
Dieser Standard wird von einigen Email-Apps unterstützt, unter anderem <a href="https://www.gpg4win.de/about-de.html" target="_blank">GpgOL</a>.</p>
@@ -105,11 +117,11 @@ Diese Einrichtung sollte im Web-Interface jedes DNS-Hosters möglich sein.</p>
<blockquote>$ gpg --locate-keys --auto-key-locate clear,nodefault,wkd address@example.org<br>
</blockquote>
<h2 style="margin-left: 3%;">API</h2>
<h2 style="padding-left: 3%;">API</h2>
<p>Es gibt eine Schnittstelle (API) für integrierte Unterstützung in OpenPGP-Anwendungen. Siehe dazu unsere <a href="/about/api">API-Dokumentation</a>.</p>
<h2 style="margin-left: 3%;">Andere Client-Software</h2>
<h2 style="padding-left: 3%;">Andere Client-Software</h2>
<p>Fehlt eine Anleitung für die Anwendung, die du verwendest? Schick eine Email an <span class="email">support at keys punkt openpgp punkt org</span>, und wir werden versuchen eine entsprechende Anleitung hinzuzufügen.</p>

View File

@@ -0,0 +1,67 @@
<div class="about">
<center><h2>Acerca de | <a href="/about/news">Noticias</a> | <a href="/about/usage">Uso</a> | <a href="/about/faq">Preguntas más frecuentes</a> | <a href="/about/stats">Estadísticas</a> | <a href="/about/privacy">Privacidad</a>
</h2></center>
<p>El servidor <span class="brand">keys.openpgp.org</span> es un servicio público para la
distribución y descubrimiento de claves compatibles con OpenPGP, comúnmente
llamado "servidor de claves" ("keyserver").</p>
<p><strong>Para más instrucciones, mira nuestra <a href="/about/usage">guía de uso</a>.</strong></p>
<h3>Cómo funciona</h3>
<p>Una clave OpenPGP contiene dos tipos de información:</p>
<ul>
<li>
<strong>Información de identidad</strong> describe las partes de
una clave que identifican a su propietario, también llamada "IDs de Usuario".
Un ID de Usuario típicamente incluye un nombre y una dirección de correo.</li>
<li>
<strong>Información no identificadora</strong> es toda la información
técnica acerca de la clave misma. Esto incluye los números largos
usados para verificar firmas y cifrar mensajes.
También incluye metadatos como la fecha de creación, algunas fechas
de expiración, y estado de revocación.</li>
</ul>
<p>Tradicionalmente, estos datos siempre han sido distribuidos
juntos. En <span class="brand">keys.openpgp.org</span>, son
tratados diferente. Mientras que cualquiera puede subir todas las partes de cualquier clave OpenPGP
a <span class="brand">keys.openpgp.org</span>, nuestro servidor de claves
sólo retendrá y publicará partes específicas bajo
condiciones específicas:</p>
<p>Cualquier <strong>información no identificadora</strong> será almacenada y libremente
redistribuida, si pasa una verificación de integridad criptográfica.
Cualquiera puede descargar estas partes en cualquier momento as mientras sólo contengan
datos técnicos que no puedan ser usados para directamente identificar a una persona.
Los buenos softwares con OpenPGP pueden usar <span class="brand">keys.openpgp.org</span>
para mantener esta información actualizada para cualquier clave conocida.
Esto ayuda a los usuarios de OpenPGP a mantener una comunicación segura y confiable.</p>
<p>La <strong>información de identidad</strong> de una clave OpenPGP
solo es distribuida con consentimiento.
Contiene datos personales, y no es estrictamente necesaria para
que una clave sea usada para cifrar o verificar firmas.
Una vez que el propietario da consentimiento verificando su dirección de correo,
la clave podrá ser encontrada vía búsqueda por dirección.</p>
<h3 id="community">Comunidad y plataforma</h3>
<p>Este servicio es mantenido como un esfuerzo comunitario.
Puedes contactarnos en
#hagrid en OFTC IRC,
también alcanzable como #hagrid:stratum0.org en Matrix.
Por supuesto también puedes contactarnos vía correo electrónico,
en <tt>support at keys dot openpgp dot org</tt>.
Los chicos que están manteniendo esto provienen
de varios proyectos en el ecosistema OpenPGP,
incluyendo Sequoia-PGP, OpenKeychain, y Enigmail.</p>
<p>Técnicamente,
<span class="brand">keys.openpgp.org</span> se ejecuta en el software de servidor de claves <a href="https://gitlab.com/keys.openpgp.org/hagrid" target="_blank">Hagrid</a>,
que está basado en <a href="https://sequoia-pgp.org">Sequoia-PGP</a>.
Nosotros nos ejecutamos en <a href="https://eclips.is" target="_blank">eclips.is</a>,
una plataforma de hosting enfocada en proyectos de Libertad de Internet,
que es administrado por <a href="https://greenhost.net/" target="_blank">Greenhost</a>.</p>
</div>

View File

@@ -0,0 +1,31 @@
<div class="about">
<center><h2>
<a href="/about">Acerca</a> | <a href="/about/news">Noticias</a> | <a href="/about/usage">Cómo usar</a> | <a href="/about/faq">FAQ</a> | Estadísticas | <a href="/about/privacy">Privacidad</a>
</h2></center>
<h3>Correos electrónicos verificados</h3>
<p>Una estadística sencilla del número total de correos electrónicos que están verificados actualmente. 📈</p>
<p>
</p>
<center></center>
<p>
</p>
<center></center>
<h3>Promedio de Carga</h3>
<p>El "promedio de carga" de un servidor es una estadística de que tan ocupado sea. En pocas palabras:</p>
<ul>
<li>0.0 significa que el servidor de <span class="brand">keys.openpgp.org</span> está completamente inactivo </li>
<li>1.0 es bastante atareado</li>
<li>4.0 y arriba significan que está en llamas 🔥</li>
</ul>
<p>
</p>
<center></center>
</div>

View File

@@ -0,0 +1,158 @@
<div class="about usage">
<center><h2>
<a href="/about">Acerca de</a> | <a href="/about/news">Noticias</a> | Uso | <a href="/about/faq">Preguntas más frecuentes</a> | <a href="/about/stats">Estadísticas</a> | <a href="/about/privacy">Privacidad</a>
</h2></center>
<p>En esta página, recolectamos información de cómo usar
<span class="brand">keys.openpgp.org</span> con diferentes productos de
software OpenPGP.<br>
Aún estamos en proceso de añadir más. Si notas que falta alguno, por favor
escríbenos y trataremos de añadirlo.</p>
<h2 id="web" style="padding-left: 3%;"><a href="#web">Interfaz Web</a></h2>
<p>La interfaz web en <span class="brand">keys.openpgp.org</span> te permite:</p>
<p>
</p>
<ul>
<li>
<a href="/">Buscar</a> claves manualmente, por huella digital o dirección de correo electrónico.</li>
<li>
<a href="/upload">Subir</a> claves manualmente, y verificarlas después de subirlas.</li>
<li>
<a href="/manage">Administrar</a> tus claves, y eliminar identidades publicadas.</li>
</ul>
<h2 id="enigmail">
<div><img src="/assets/img/enigmail.svg"></div>
<a href="#enigmail">Enigmail</a>
</h2>
<p><a href="https://enigmail.net" target="_blank">Enigmail</a> para Thunderbird
usa <span class="brand">keys.openpgp.org</span> por defecto desde
la versión 2.0.12.</p>
<p>Soporte completo disponible desde Enigmail 2.1
(para <a href="https://www.thunderbird.net/en-US/thunderbird/68.0beta/releasenotes/" target="_blank">Thunderbird 68</a> o superior):</p>
<ul>
<li>Las claves serán actualizadas automáticamente.</li>
<li>Durante la creación de la clave, puedes subirla y verificarla.</li>
<li>Las claves pueden descubrirse por dirección de correo electrónico.</li>
</ul>
<h2 id="gpg-suite">
<div><img src="/assets/img/gpgtools.png"></div>
<a href="#gpg-suite">GPG Suite</a>
</h2>
<p><a href="https://gpgtools.org/">GPG Suite</a> para macOS
usa <span class="brand">keys.openpgp.org</span> por defecto
desde agosto de 2019.</p>
<h2 id="openkeychain">
<div><img src="/assets/img/openkeychain.svg"></div>
<a href="#openkeychain">OpenKeychain</a>
</h2>
<p><a href="https://www.openkeychain.org/">OpenKeychain</a> para Android
usa <span class="brand">keys.openpgp.org</span> por defecto
desde julio de 2019.</p>
<ul>
<li>Las claves serán actualizadas automáticamente.</li>
<li>Las claves pueden descubrirse por dirección de correo electrónico.</li>
</ul>
<p>Ten en cuenta que aún no hay soporte incorporado para subir y verificar direcciones de correo.</p>
<h2 id="pignus">
<div><img src="/assets/img/pignus.png"></div>
<a href="#pignus">Pignus</a>
</h2>
<p><a href="https://www.frobese.de/pignus/">Pignus</a> para iOS
usa <span class="brand">keys.openpgp.org</span> por defecto
desde noviembre de 2019.</p>
<ul>
<li>Tus claves pueden ser subidas en cualquier momento.</li>
<li>Las claves pueden descubrirse por dirección de correo electrónico.</li>
</ul>
<h2 id="gnupg">
<div><img src="/assets/img/gnupg.svg"></div>
<a href="#gnupg">GnuPG</a>
</h2>
<p>Para configurar que <a href="https://gnupg.org">GnuPG</a>
use <span class="brand">keys.openpgp.org</span> como servidor de claves,
añade esta línea a tu archivo <tt>gpg.conf</tt>:</p>
<blockquote>keyserver hkps://keys.openpgp.org</blockquote>
<h4 id="gnupg-retrieve"><a href="#gnupg-retrieve">Obteniendo claves</a></h4>
<ul>
<li>Para obtener la clave de un usuario, por dirección de correo:<blockquote>gpg --auto-key-locate keyserver --locate-keys user@example.net</blockquote>
</li>
<li>Para actualizar todas tus claves (P. ej. nuevos certificados de revocación y subclaves):<blockquote>gpg --refresh-keys</blockquote>
</li>
</ul>
<h4 id="gnupg-upload"><a href="#gnupg-upload">Subiendo tu clave</a></h4>
<p>Las claves pueden ser subidas con el comando de GnuPG <tt>--send-keys</tt>, pero
la información de identidad no puede ser verificada de esa forma para hacer que la clave
se pueda buscar por dirección de correo (<a href="/about">¿qué significa esto?</a>).</p>
<ul>
<li>Puedes probar este atajo para subir tu clave, el cual regresa
un enlace directo a la página de verificación:<blockquote>gpg --export your_address@example.net | curl -T - {{ base_uri }}</blockquote>
</li>
<li>Alternativamente, puedes exportarlas a un archivo
y seleccionar ese archivo en la página <a href="/upload" target="_blank">para subirla</a>:<blockquote>gpg --export your_address@example.net &gt; my_key.pub</blockquote>
</li>
</ul>
<h4 id="gnupg-troubleshooting"><a href="#gnupg-troubleshooting">Solución de problemas</a></h4>
<ul>
<li>Algunos archivos <tt>~/gnupg/dirmngr.conf</tt> viejos contienen una línea como esta:<blockquote>hkp-cacert ~/.gnupg/sks-keyservers.netCA.pem</blockquote>
<p>Esta configuración ya no es necesaria,
pero evita que los certificados regulares funcionen.
Es recomendado simplemente remover esta línea de la configuración.</p>
</li>
<li>Mientras actualizas tus claves, puede que veas errores como estos:<blockquote>gpg: key A2604867523C7ED8: no user ID</blockquote>
Este es un <a href="https://dev.gnupg.org/T4393" target="_blank">problema conocido en GnuPG</a>.
Estamos trabajando con el equipo de GnuPG para resolverlo.
</li>
</ul>
<h4 id="gnupg-tor"><a href="#gnupg-tor">Uso vía Tor</a></h4>
<p>Para los usuarios que quieren ser extra cuidadosos,
<span class="brand">keys.openpgp.org</span> puede ser alcanzado anónimamente como un
<a href="https://support.torproject.org/onionservices/#onionservices-2" target="_blank">servicio cebolla (onion)</a>.
Si tienes
<a href="https://www.torproject.org/" target="_blank">Tor</a>
instalado, usa la siguiente configuración:</p>
<blockquote>keyserver hkp://zkaan2xfbuxia2wpf7ofnkbz6r5zdbbvxbunvp5g2iebopbfc4iqmbad.onion</blockquote>
<h2 style="padding-left: 3%;" id="wkd-as-a-service"><a href="#wkd-as-a-service">WKD como Servicio</a></h2>
<p>El Directorio Web de Claves (Web Key Directory - WKD) es un estándar para el descubrimiento de claves OpenPGP por dirección de correo electrónico, vía el dominio de su proveedor de correo.
Es usado para descubrir claves desconocidas en algunos clientes de corro, como <a href="https://www.gpg4win.de/about.html" target="_blank">GpgOL</a>.</p>
<p><span class="brand">keys.openpgp.org</span> puede ser usado como a servicio WKD administrado para cualquier dominio.
Para hacerlo, el dominio simplemente necesita un registro <tt>CNAME</tt> para delegar su subdominio <tt>openpgpkey</tt> a <tt>wkd.keys.openpgp.org</tt>.
Debería ser posible hacer esto en la interfaz web de cualquier host de DNS.</p>
<p>Una vez activado para un dominio, sus direcciones verificadas automáticamente estarán disponibles para buscarse vía WKD.</p>
<p>El registro <tt>CNAME</tt> debería verse de esta forma:</p>
<blockquote>$ drill openpgpkey.example.org<br>
...<br>
openpgpkey.example.org. 300 IN CNAME wkd.keys.openpgp.org.</blockquote>
<p>Hay un verificador de estado simple para probar el servicio:</p>
<blockquote>$ curl 'https://wkd.keys.openpgp.org/status/?domain=openpgpkey.example.org'<br>
CNAME lookup ok: openpgpkey.example.org resolves to wkd.keys.openpgp.org<br>
</blockquote>
<p>Para probar la obtención de claves:</p>
<blockquote>$ gpg --locate-keys --auto-key-locate clear,nodefault,wkd address@example.org<br>
</blockquote>
<h2 style="padding-left: 3%;">API</h2>
<p>Ofrecemos una API para soporte integrado en aplicaciones OpenPGP. Mira
nuestra <a href="/about/api">documentación de API</a>.</p>
<h2 style="padding-left: 3%;">Otros</h2>
<p>¿No hay una guía para tu implementación favorita? Este sitio es
un trabajo-en-proceso, y estamos buscando mejorarlo. ¡escríbenos a
<span class="email">support at keys dot openpgp dot org</span> si
quieres ayudar!</p>
</div>

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