summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorSean Whitton <spwhitton@spwhitton.name>2017-01-25 08:06:21 -0700
committerSean Whitton <spwhitton@spwhitton.name>2017-01-25 08:06:21 -0700
commit2dd86c51b250d7c1bb08062ff4e404f6d001885b (patch)
treefd39d6ebf3db007efd6eef1e44acbc8cb4f3ed72
parentfb044f42ec0e01ccb500245a59e3259824e0c498 (diff)
parentcb565cd8c1c770be8a1603d46e2b704ed473ee41 (diff)
downloadkeysafe-2dd86c51b250d7c1bb08062ff4e404f6d001885b.tar.gz
Merge tag '0.20170122'
tagging package keysafe version 0.20170122
-rw-r--r--.gitignore1
-rw-r--r--CHANGELOG11
-rw-r--r--Cost.hs6
-rw-r--r--TODO2
-rw-r--r--UI.hs2
-rw-r--r--doc/details.mdwn365
-rw-r--r--doc/faq.mdwn88
-rw-r--r--doc/index.mdwn86
-rw-r--r--doc/news/version_0.20160927.mdwn20
-rw-r--r--doc/news/version_0.20161006.mdwn10
-rw-r--r--doc/news/version_0.20161007.mdwn9
-rw-r--r--doc/news/version_0.20161022.mdwn12
-rw-r--r--doc/news/version_0.20161107.mdwn14
-rw-r--r--doc/screenshots.mdwn14
-rw-r--r--doc/screenshots/0.pngbin0 -> 21991 bytes
-rw-r--r--doc/screenshots/1.pngbin0 -> 22953 bytes
-rw-r--r--doc/screenshots/2.pngbin0 -> 32345 bytes
-rw-r--r--doc/screenshots/3.pngbin0 -> 30236 bytes
-rw-r--r--doc/screenshots/4.pngbin0 -> 27423 bytes
-rw-r--r--doc/screenshots/5.pngbin0 -> 20439 bytes
-rw-r--r--doc/screenshots/6.pngbin0 -> 11822 bytes
-rw-r--r--doc/screenshots/restore/1.pngbin0 -> 18277 bytes
-rw-r--r--doc/screenshots/restore/2.pngbin0 -> 25758 bytes
-rw-r--r--doc/screenshots/restore/3.pngbin0 -> 17254 bytes
-rw-r--r--doc/screenshots/restore/4.pngbin0 -> 20842 bytes
-rw-r--r--doc/servers.mdwn194
-rw-r--r--doc/todo.mdwn4
-rw-r--r--keysafe.16
-rw-r--r--keysafe.cabal34
-rw-r--r--keysafe.service2
30 files changed, 857 insertions, 23 deletions
diff --git a/.gitignore b/.gitignore
index 60a6dcc..a199c88 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
dist/*
.stack-work/*
keysafe
+doc/.ikiwiki
diff --git a/CHANGELOG b/CHANGELOG
index d209b28..8d8036b 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,14 @@
+keysafe (0.20170122) unstable; urgency=medium
+
+ * Adjust cabal bounds to allow building with ghc 8.0.
+ However, the stack.yaml is still using an old LTS version
+ to avoid polynomial's failure to build with ghc 8.0
+ (https://github.com/mokus0/polynomial/issues/8)
+ * Clarify that dollars in cost estimates are USD.
+ * Keysafe has a new website, https://keysafe.branchable.com/
+
+ -- Joey Hess <id@joeyh.name> Sun, 22 Jan 2017 09:44:17 -0400
+
keysafe (0.20161107) unstable; urgency=medium
* The third keysafe server is now available, provided by Purism.
diff --git a/Cost.hs b/Cost.hs
index dc2438e..a18a088 100644
--- a/Cost.hs
+++ b/Cost.hs
@@ -92,6 +92,7 @@ estimateAttackCost dc opcost = centsToDollars $ costcents
newtype Cents = Cents Integer
deriving (Num, Integral, Enum, Real, Ord, Eq, Show)
+-- | USD
newtype Dollars = Dollars Integer
deriving (Num, Integral, Enum, Real, Ord, Eq)
@@ -103,12 +104,13 @@ instance Show Dollars where
, (1000, "thousand")
]
where
- go [] = "$" ++ show n
+ go [] = fmt (show n)
go ((d, u):us)
| n >= d =
let n' = n `div` d
- in "$" ++ show n' ++ " " ++ u
+ in fmt (show n' ++ " " ++ u)
| otherwise = go us
+ fmt d = "$" ++ d ++ " (USD)"
centsToDollars :: Cents -> Dollars
centsToDollars (Cents c) = Dollars (c `div` 100)
diff --git a/TODO b/TODO
index e0190cc..18426bf 100644
--- a/TODO
+++ b/TODO
@@ -70,5 +70,5 @@ Wishlist:
shares was used, the attacker could upload other objects where they would
be found before the real objects. This could be used to prevent
restore from working. (It also makes a malicious data attack (as described
- in https://joeyh.name/keysafe/details/) possible by attackers who do not
+ in https://keysafe.branchable.com/details/) possible by attackers who do not
control the servers.
diff --git a/UI.hs b/UI.hs
index 4212468..6cab667 100644
--- a/UI.hs
+++ b/UI.hs
@@ -23,7 +23,7 @@ selectUI needgui
ok <- isAvailable zenityUI
if ok
then return zenityUI
- else error "zenitty is not installed, GUI not available"
+ else error "zenity is not installed, GUI not available"
| otherwise = do
l <- availableUIs
case l of
diff --git a/doc/details.mdwn b/doc/details.mdwn
new file mode 100644
index 0000000..e0f85e5
--- /dev/null
+++ b/doc/details.mdwn
@@ -0,0 +1,365 @@
+[[!toc]]
+
+## Storing a key
+
+* Input password and name from user. The name is a combination of their own
+ name and a more obscure name (such as the name of their high-school
+ sweetheart).
+* Get the keyid of the key. (This can be any a public value
+ unique to the private key, eg a gpg keyid. It's only used to allow
+ storing multiple keys under a given name. If a gpg public key is not on the
+ keyservers, or the key is not a gpg key, can use "" for the keyid.)
+* Generate N by argon2(name, salt=keyid), tuned to take 10 minutes.
+* Generate N1-N3 by sha256 of N+1,2,3
+* Generate decryption puzzle P, a byte chosen at random.
+* Generate K by argon2(password, salt=name+P), tuned to take 0.195 minutes.
+* AES encrypt (data + checksum) with K as the key.
+* Shamir the encrypted key with N=2, M=3, yeilding S1-S3.
+* Servers reject attempts to store an object under a name that is
+ already in use.
+* Servers do not allow enumerating all objects stored,
+ and require a proof of work to handle any request.
+* Upload S1-S3 to separate servers under the names N1-N3.
+ If any of the uploads is rejected as name already in use,
+ ask user to enter a different name or password.
+
+So, storing a key takes 10 minutes.
+
+## Recovering a key
+
+* Input password and name from user.
+* Calculate N and N1-N2
+* Request N1-N3 from servers until two objects are available.
+* Shamir recombine the objects.
+* Guess a value for P.
+* Generate K by argon2(password, salt=name+P)
+* AES decrypt
+* Repeat with new P until checksum verifies.
+
+This takes 10 minutes to calculate N, plus on average 128 guesses of P.
+Total recovery time varies from 10 minutes to 60 minutes, with an
+average of 35 minutes.
+
+## Difficulty of brute forcing a single encrypted key
+
+* Assume we know the name and keyid, or have otherwise found a way to
+ determine the shards of a key. Download and recombine the shards.
+* Guess a password.
+* For each possible value of P, AES decrypt with
+ K = argon2(password, salt=name+P), and check if checksum verifies.
+ This takes 0.195 minutes * 256 = 50 minutes total.
+* Repeat for next password.
+
+So, for a password with N entropy, the number of CPU-years of work
+is to crack it is: `2^(N-1)*50/60/24/365`
+
+* Strong password (50 entropy): 53553077761 CPU-years
+* Weak password (30 entropy): 51072 CPU-years
+* Super-weak password (19 entropy): 25 CPU-years
+
+So, if an attacker is able to find the right shards for a secret key, it's
+feasible for them to crack super-weak and weak passwords, assuming the
+secret key is worth the cost of doing do. Stronger passwords quickly
+become infeasible to crack.
+
+## Attack methods
+
+An attacker who wants to target a particular person can guess the name they
+used, derive N1-N3, download two of S1-S3 and start brute forcing the
+password soon after the object is stored. This is the most likely attack
+method, so any user who could potentially be targeted like this should
+choose a strong password. A name that attackers are unlikely to guess
+prevents this attack, which is why keysafe prompts for not only the
+user's name, but also a more obscure name. Each name guess that the
+attacker makes takes 10 minutes of CPU time to generate N, as well
+as whatever proof of work the servers require.
+
+The sharding prevents a single malicious server from blindly
+guessing weak passwords across its entire collection of objects.
+It takes two servers colluding to try to recombine their shards.
+
+If recombining two shards yielded data which could be checked to see if
+it's valid, then it would become fairly inexpensive to try all combinations
+of shards, and obtain all the encrypted keys for further cracking. So it's
+important that there not be any checksum or header in addition to the AES
+encrypted data. (AES encrypted data cannot be distinguised from random
+garbage except by block size.) Get that right, and with N keysafe users, an
+attacker would need to try `2^(N-1)` combinations of shards to find one on
+average, and would need to brute force the password of each combination.
+With only 20 keysafe users, assuming all users have super-weak passwords,
+this attack takes 13107200 years of CPU work `(2^19*25)` to crack one. With
+50 users, this jumps to quadrillions of years of CPU work.
+
+Colluding servers can try to correlate related objects based on access
+patterns and recombine pairs of those, and then brute force the password.
+The correlation needs to be very fine-grained for this to work.
+If 15 users' objects are all bucketed together by the correlation,
+then the attacker has 16384 combinations to try on average before
+finding a correct combination. Multiply by 5 years CPU work for cracking
+only super-weak passwords.
+
+Colluding servers who want to target a particular person
+can guess their N1-N3, check if those objects exist on the server, and
+begin brute-forcing the password. This is not much cheaper than the same
+attack performed by a third-party attacker, except that the attacker
+doesn't need to wait for an object to download from the server to check if
+it exists.
+
+A state-level entity may try to subpoena the entire contents of keysafe
+servers. Once 2 servers are compromised, the state-level entity can try the
+same attacks that colluding servers can use (but probably cannot use
+attacks involving correlation because the server operators should not be
+retaining the data needed for correlation). Of course, they probably have
+many more resources to throw at the problem. But with only 50 keysafe
+users, recombining shards and trying super-weak passwords would be
+prohibitively expensive as detailed above. So, a state-level entity
+will probably only find it feasible to target particular people.
+Since such an attack can be performed by anyone as discussed above,
+there seems to actually be no incentive for a state-level to subpoena data.
+
+A state-level entity's best bet at getting lots of keys is probably to use
+their resources to compromise keysafe servers, and modify them to log data.
+Then a correlation attack can be done as discussed above.
+
+A different kind of attack: Legal/extralegal action to get a
+particular person's key removed from storage servers to try to
+deny them access to it. Or, to entirely take down storage servers.
+
+### Malicious data attack
+
+Two servers could collude to serve up malicious data to try to exploit the
+user's system.
+
+For example, if the user is using their gpg key to encrypt emails,
+and they restore a different gpg key, they might use it to encrypt with and
+then what they said could be decrypted by the attacker.
+
+To perform this attack, the attacker first has to manage to crack the user's
+password. Then they can replace the objects with malicious versions, encrypted
+with the same password.
+
+So, this is not too useful for gpg key replacement, since the attacker
+must already know the secret key. However, perhaps they could exploit bugs
+in gpg to compromise the user's system.
+
+## Server list
+
+There's a server list shipped with the client, giving their tor onion address
+and the organization providing the server.
+
+Three of the servers in the list are recommended servers.
+Shards are stored on these unless overridden by other configuration.
+
+When recovering a key, the client tries the recommended servers first. But,
+if it fails to recover the key using those, it goes on to try other servers
+on the list. This way we don't need to remember which servers the shards
+were stored on, and can change the recommended servers when necessary.
+
+See [[servers]] for more on the server list.
+
+## Servers
+
+Servers run exclusively as tor hidden services. This prevents them from
+finding out the IP addresses of clients, as well as providing transport
+level encryption.
+
+Servers should avoid keeping any logs, and should santize
+the timestamps of any files stored on them. (Eg set back to epoch
+and store on filesystem mounted with noatime.)
+
+Only small objects are accepted to be stored. This is to prevent this from
+being used as a general purpose data storage system, and only be useful
+for storing keys.
+
+However, `gpg --export-secret-key` can be hundreds of KB in size
+when a key has a lot of signatures etc. While this size can be cut
+down to 4 KB using `paperkey` to only include the secret key, it's
+desirable to store the public and secret key together. This way,
+a user does not have to publish the key to keyservers, which makes some
+attack methods much less likely to try to crack their key.
+
+So, the object size should be at least a few tens of KB. 64kb seems
+reasonable. If keysafe needs to store a larger key, it can chunk it up.
+
+Objects shoud be padded to a fixed size before they are encrypted, to
+prevent attackers from correlating and targeting particular objects based
+on size.
+
+## client-server Proof of Work
+
+The Proof of Work prevents servers being flooded with requests.
+This is particularly important to prevent misuse of keysafe servers
+to store large data on them. It's also somewhat useful to prevent attackers
+guessing the name someone used to store a key; but the cost of generating
+N from a name makes the server's proof of work only a secondary line of
+defense against such an attack. Finally, PoW is useful to protect against
+DOS attacks.
+
+Assuming that the client communicates with the server over http:
+
+ PUT /keysafe/objects/N
+ GET /keysafe/objects/N
+
+The server's response can be either the result of the request,
+or a proof of work requirement, which specifies the difficulty D
+(number of 0's needed), random salt RS, and the number of argon2
+iterations. The client provides the proof of work in a query parameter,
+which is a string S such that argon2(N,S+RS) starts with a given number
+of 0's.
+
+(The server should only be able to control the number of iterations,
+not other argon2 parameters, to avoid forcing the client to use too much
+memory. Normally, the server will keep iterations small, probably 1,
+since it does need to calculate the argon2 hash once itself.)
+
+The server can use a [token bucket](https://en.wikipedia.org/wiki/Token_bucket)
+to throttle requests to a given rate. In fact, there can be a whole
+series of token buckets B0,B1.., for increasing difficulty proofs of work.
+
+A request without a proof of work is checked in B0. If that bucket is empty,
+the server responds with a proof of work requirement D=1, and
+the client makes a new request whose proof of work allows access to B1.
+If the server is under load, B1 might also be empty, and so the client
+will have to try again with D=2 to access B2, and so on.
+
+If there are 4 buckets, and each bucket refills at the rate of a
+token every minute, then the maximum allowed throughput is 4 requests
+per minute. If calculating the proof of work takes 2^D seconds on average,
+then it will take on average 16 minutes work to access B4.
+
+The server can generate a different RS for each request, and can
+insert them into a bloom filter to keep track of ones it has given out.
+Bloom filter false positives are not a problem because they are quite
+rare and so it is not efficient for a client to make up RS in hope that
+there will be a false positive.
+
+To guard against DOS attacks that reuse proofs of work, the server can
+maintain a second bloom filter, putting RS into it once it's used, and
+rejecting attempts that reuse a RS. Since the bloom filter will
+(with a low probability) yield a false positive, the server should reject
+an attempt by generating a new RS' and requesting a new proof of work from
+the client.
+
+## Avoiding correlations
+
+As noted above, the more objects that are stored, the more secure
+keysafe becomes, because it becomes harder to correlate objects
+that belong to a user.
+
+Ways the server could draw correlations include:
+
+* Time that the data was stored.
+ (Avoid by waiting some period of time between uploading
+ various objects, probably on the order of days. If every keysafe uploads
+ its objects at midnight GMT, the server will find it hard to correlate
+ them.)
+* IP address used to store the data.
+ (Using tor avoids this.)
+* When the data was retrieved.
+ (Avoid by waiting some period of time between retrieving shards.
+ Although, this makes key recovery take longer, which could be
+ frustrating..)
+* A user ID associated with the data.
+ (Avoid by allowing anyone to store data and don't associate data with
+ any user ID.)
+* Same sized objects may be related shares.
+ (Avoid by making all objects stored be a standard size.)
+
+## Detecting corrupt data
+
+ ori> if a single server is compromised, it can return bogus data when you request its fragment of the shared secret
+ ori> if you only have three servers, you can't know which two to trust, short of just trying
+ ori> you end up with three possible ways to reconstruct the secrets, A-B, A-C, B-C. only one is legit.
+
+This could also happen due to error not compromise. Or even due to
+asking the wrong server for an object and getting back a dummy response.
+
+To guard against this, include a sha256sum of the secret key in the
+data that is sharded. This way the validity of the restored key can be
+verified.
+
+Note that this may make it marginally easier for brute force attacks, since
+they can use that checksum to detect when the attack is successful. Only
+marginally easier because it's not hard to fingerprint a gpg secret key.
+Even the minimised raw key output by paperkey can be recognised by a parser.
+
+## Versioning
+
+The algorithms and parameters chosen by keysafe could turn out to be
+wrong, or need adjusting to keep up with technology. While we'd like to
+avoid this by getting the design right, it's important to have a plan in
+case it becomes necessary.
+
+The simplest approach would be to include a version number with each shard.
+But, this causes a problem: When switching to a new version, the early
+atopters of that version are a small group, and so it becomes easier to
+correlate their shards.
+
+The version number could instead be included in the data that is sharded.
+This avoids the above problem, but leads to an even worse problem:
+An attacker can look for the version number after combining some random
+shards, and if they see it, they know they picked shards that are actually
+related. As described in "Attack methods", if an attacker can do that,
+it becomes easy for two colliding servers to find the right combinations of
+all shards.
+
+A safe alternative to embedding a version number anywhere in the data is
+to adjust the parameters of the argon2 hash used to generate the shard
+names. Maintain a list of versions and their shard name argon2
+parameters (as well as other parameters etc). During key recovery,
+keysafe can try different versions in turn, use argon2 to generate the
+shard names, and see if those shards can be downloaded. Once it finds
+shards, it knows the version that created them, and the other parameters
+of how the data is encoded.
+
+The only downside to this method is that each new version added to the list
+makes recovering data from all older versions take 10 minutes longer, as
+the argon2 hash has to be run an additional time.
+
+Note that the argon2 hash parameters to vary between versions should not be
+merely the number of rounds, as that would allow an attacker to hash for
+each version's number of rounds in one pass. Instead, vary the hash
+memory parameter.
+
+## Ideas
+
+Some ideas for improvements to keysafe.
+
+### Assisted Password-based Key Derivation
+
+An idea from [Nigori](http://www.links.org/files/nigori/nigori-protocol-01.html#anchor15):
+
+If the user has an account at some server, the server can contribute part
+of the data used to generate the key encryption key K. So not only is the
+user's keysafe password needed for key recovery, but the user has to
+authenticate to the server. As well as adding entropy to K, the server
+can do rate limiting to prevent password guessing.
+
+Risks include:
+
+* The server becomes a target to be compromised.
+* If the server goes down, the user loses the ability to recover their gpg
+ key from keysafe.
+
+### Entangled objects
+
+An idea from Anthony Towns:
+
+Two or more objects stored on a keysafe server can be entangled.
+When one of them is requested, the server should delete the others.
+
+This could be used in several ways:
+
+* Keysafe could upload random objects entangled with a key's object,
+ and keep local records of the names of the random objects. Then if the
+ user wants to stop storing a key on keysafe servers, keysafe can request
+ the entangled objects to (hopefully) delete the real objects from the
+ server.
+* Keysafe could pick or prompt for some tripwire names that an attacker
+ might try if they were looking for a user's data. Then an attacker
+ risks deleting the data stored on the server. Although this also has
+ DOS potential.
+* The user could pick a real name and fake name and keysafe uploads
+ objects for both. If the user is being forced to give up their keysafe
+ name and password, they could provide the fake name, and if it were
+ used, their data would get deleted from the keysafe servers.
diff --git a/doc/faq.mdwn b/doc/faq.mdwn
new file mode 100644
index 0000000..4512be7
--- /dev/null
+++ b/doc/faq.mdwn
@@ -0,0 +1,88 @@
+[[!toc]]
+
+### How does keysafe compare with paperkey?
+
+Using paperkey to print out your gpg key and locking it in a safe is a great
+solution to gpg key backup. It requires a printer, a safe, and typing in many
+numbers to restore the key. It avoids all attack methods except physical
+theft, raids, and compromised printers. Gold standard.
+
+Using keysafe is analagous to storing the paperkey printout in a safety
+deposit box at a bank, and using a really really strong gpg passphrase.
+Since we don't trust a single bank (server), we shred the printout and
+evenly distribute the shreds among several banks. While the banks know the
+safety deposit boxes belong to you and could put the shreds back together,
+a keysafe server has very little identifying information to go on (it only
+knows when you made the deposit).
+
+### I copy secring.gpg to dropbox to back it up. Why bother with keysafe?
+
+So, you rely on your gpg passphrase for security.
+
+Gpg uses between 1024 and 65011712 rounds of SHA-1 hashing of
+the passphrase, with the default probably being 65536.
+In 2012, a GPU could calculate 2 billion SHA-1 hashes per second,
+so this is not much of an impediment to password cracking at all.
+
+Assuming 100 gpg passphrases can be tried per second, and gpg is
+configured to use the maximum rounds (which it normally is NOT):
+
+* Strong passphrase (50 entropy): 21421231 GPU-years
+* Weak passphrase (30 entropy): 10 GPU-years
+* Super-weak passphrase (19 entropy): 2 GPU-days
+
+So this might be secure enough for some, but only with a really good
+passphrase. Probably, most people who copy their secring.gpg to
+the cloud have either a weakish passphrase, or have not tuned gpg to
+use maximum SHA-1 rounds, or both. So, they can probably be cracked in
+days to weeks.
+
+Compare these numbers with the cost to crack keysafe passwords
+(explained in [[details]]):
+
+* Strong password (50 entropy): 53553077761 CPU-years
+* Weak password (30 entropy): 51072 CPU-years
+* Super-weak password (19 entropy): 25 CPU-years
+
+(Note that is a comparison between GPU years and CPU years; SHA-1 used by
+gpg can easily be run fast on GPUs, while keysafe uses argon2 which is
+designed to be GPU-resistent.)
+
+Big difference! Indeed, the design of gpg prevents a really expensive hash
+being used to protect against passphrase cracking, because it would slow down
+day-to-day use of gpg. Keysafe can make cracking the password much more
+expensive because it's only used for backup and restore.
+
+### Is keysafe suitable for backing up bitcoin wallets?
+
+Not recommended. Use a brain wallet.
+
+Keysafe might be more secure than a paper wallet in some situations.
+It's happened before that someone has stumbled over someone else's
+list of electrum words among their papers. Using keysafe avoids such
+scenarios.
+
+#### How is keysafe different from other systems for private key backup/sync?
+
+Here are some similar systems, and intial impressions (which may be
+inaccurate; corrections welcomed):
+
+* [Whiteout](https://blog.whiteout.io/2014/07/07/secure-pgp-key-sync-a-proposal/)
+ uses a 24 letter code to encrypt the secret key. This has to be written
+ down to back it up. Since it is high-entropy (256 bits), the encryption
+ can be fast, and so it can be used to sync private keys between devices.
+* [Nigori](http://www.links.org/files/nigori/nigori-protocol-01.html)
+ uses a password to encrypt and SSS to split the private key.
+ Its main protection against password guessing seems be to be
+ its "Assisted Password-based Key Derivation", where some separate server
+ that the user has an account at supplies part of the encryption key after
+ the user has authenticated at the server. This allows the server to
+ rate-limit logins and so avoid password cracking (except by the server).
+ (This idea is worth keysafe stealing!)
+* [LEAP](https://leap.se/en/docs/tech/hard-problems#availability-problem)'s
+ [Soledad](https://leap.se/soledad) uses a large secret to encrypt
+ private keys. This can be stored in a recovery document in a recovery
+ database, which anyone can make requests from. To prevent attacks,
+ the recovery database delays responses, and a recovery code is needed
+ to access a document. The recovery code is a 16 character code that the
+ user has to write down.
diff --git a/doc/index.mdwn b/doc/index.mdwn
new file mode 100644
index 0000000..e0cda48
--- /dev/null
+++ b/doc/index.mdwn
@@ -0,0 +1,86 @@
+Keysafe securely backs up a gpg secret key or other short secret to the cloud.
+
+This is not intended for storing Debian Developer keys that yield root on
+ten million systems. It's about making it possible for users to use gpg who
+currently don't, and who would find it too hard to use `paperkey` to back
+up and restore their key as they reinstall their laptop.
+
+Not yet ready for production use! Needs security review!
+May run over your dog! Not suitable for bitcoin keys!
+
+## Screenshots
+
+See [[screenshots]]. (Keysafe can also run in text mode in a terminal.)
+
+## How it works, basically
+
+The secret key is encrypted using a password, and is split into three
+shards, and each is uploaded to a server run by a different entity. Any two
+of the shards are sufficient to recover the original key. So any one server
+can go down and you can still recover the key.
+
+Keysafe checks your password strength (using the excellent but not perfect
+[zxcvbn library](https://github.com/tsyrogit/zxcvbn-c)),
+and shows an estimate of the cost to crack your password,
+before backing up the key.
+
+[[screenshots/4.png]]
+(Above is for the password "makesad spindle stick")
+
+Keysafe is designed so that it should take millions of dollars of computer
+time to crack any fairly good password. (This is accomplished using
+[Argon2](https://en.wikipedia.org/wiki/Argon2).)
+With a truely good password, such as four random words, the cracking cost
+should be many trillions of dollars.
+
+The password is the most important line of defense, but keysafe's design
+also makes it hard for an attacker to even find your encrypted secret key.
+
+For a more in-depth explanation, and some analysis of different attack
+vectors (and how keysafe thwarts them), see [[details]].
+Also, there's a [[FAQ]].
+
+## News
+
+[[!inline pages="code/keysafe/news/* and !*/Discussion" show="3"]]
+
+## Installation
+
+Keysafe is now available in Debian experimental. Install it from there, or
+from source.
+
+## Git repository
+
+`git clone git://keysafe.branchable.com/ keysafe`
+
+All tags and commits in this repository are gpg signed, and you should
+verify the signature before using it.
+
+## Building from source
+
+You should first install Haskell's stack tool, the readline and argon2
+libraries, and zenity. For example, on a Debian system:
+
+ sudo apt-get install haskell-stack libreadline-dev libargon2-0-dev zenity
+
+Then to build and install keysafe:
+
+ stack install keysafe
+
+Note that there is a manpage, but stack doesn't install it yet.
+
+## Reporting bugs
+
+Post to [[todo]] or email <id@joeyh.name>
+
+## Servers
+
+See [[servers]] for information on the keysafe servers.
+
+## License
+
+Keysafe is licensed under the terms of the AGPL 3+
+
+## Thanks
+
+Thanks to Anthony Towns for his help with keysafe's design.
diff --git a/doc/news/version_0.20160927.mdwn b/doc/news/version_0.20160927.mdwn
new file mode 100644
index 0000000..1787aa5
--- /dev/null
+++ b/doc/news/version_0.20160927.mdwn
@@ -0,0 +1,20 @@
+keysafe 0.20160927 released with [[!toggle text="these changes"]]
+[[!toggleable text="""
+ * Makefile: Avoid rebuilding on make install, so that sudo make install works.
+ * Added --chaff-max-delay option for slower chaffing.
+ * Fix embedded copy of Argon2 to not use Word64, fixing build on 32 bit
+ systems.
+ * Randomize the server list.
+ * Don't upload more than neededshares-1 shares to Alternate servers
+ without asking the user if they want to do this potentially dangerous
+ action.
+ * Added a second keysafe server to the server list. It's provided
+ by Marek Isalski at Faelix. Currently located in UK, but planned move
+ to CH. Currently at Alternate level until verification is complete.
+ * Server: --motd can be used to provide a Message Of The Day.
+ * Added --check-servers mode, which is useful both at the command line
+ to see what servers keysafe knows about, and as a cron job.
+ * Server: Round number of objects down to the nearest thousand, to avoid
+ leaking too much data about when objects are uploaded to servers.
+ * Filter out escape sequences and any other unusual characters when
+ writing all messages to the console."""]] \ No newline at end of file
diff --git a/doc/news/version_0.20161006.mdwn b/doc/news/version_0.20161006.mdwn
new file mode 100644
index 0000000..2758b34
--- /dev/null
+++ b/doc/news/version_0.20161006.mdwn
@@ -0,0 +1,10 @@
+keysafe 0.20161006 released with [[!toggle text="these changes"]]
+[[!toggleable text="""
+ * New --add-storage-directory and --add-server options, which can be used
+ to make keysafe backup/restore using additional locations.
+ * Removed --store-local option; use --add-storage-directory instead.
+ * Fix bugs with entry of gpg keyid in the keysafe.log.
+ * Fix bug in --autostart that caused the full gpg keyid to be
+ used to generate object names, which made restores would only work
+ when --gpgkeyid was specifid.
+ * Remove embedded copy of argon2 binding, depend on fixed version of package."""]] \ No newline at end of file
diff --git a/doc/news/version_0.20161007.mdwn b/doc/news/version_0.20161007.mdwn
new file mode 100644
index 0000000..a7e8468
--- /dev/null
+++ b/doc/news/version_0.20161007.mdwn
@@ -0,0 +1,9 @@
+keysafe 0.20161007 released with [[!toggle text="these changes"]]
+[[!toggleable text="""
+ * Check if --store-local directory is writable.
+ * Removed dependency on crypto-random.
+ * Added a LSB init script, for non-systemd systems.
+ (It currently uses Debian's start-stop-daemon, so would need porting
+ for other distributions.)
+ * /etc/default/keysafe is read by both the systemd service file and the
+ init script, and contains configuration for the keysafe server."""]] \ No newline at end of file
diff --git a/doc/news/version_0.20161022.mdwn b/doc/news/version_0.20161022.mdwn
new file mode 100644
index 0000000..e54f26e
--- /dev/null
+++ b/doc/news/version_0.20161022.mdwn
@@ -0,0 +1,12 @@
+keysafe 0.20161022 released with [[!toggle text="these changes"]]
+[[!toggleable text="""
+ * Add keywords to desktop file.
+ Thanks, Sean Whitton
+ * Fix use of .IP macro in manpage.
+ Thanks, Sean Whitton
+ * Fix some mispellings.
+ Thanks, Sean Whitton
+ * Makefile: Propagate LDFLAGS, CFLAGS, and CPPFLAGS through ghc.
+ * Makefile: Allow setting BUILDER=./Setup to build w/o cabal or stack.
+ * Makefile: Allow setting BUILDEROPTIONS=-j1 to avoid concurrent
+ build, which should make build reproducible."""]] \ No newline at end of file
diff --git a/doc/news/version_0.20161107.mdwn b/doc/news/version_0.20161107.mdwn
new file mode 100644
index 0000000..d98987e
--- /dev/null
+++ b/doc/news/version_0.20161107.mdwn
@@ -0,0 +1,14 @@
+keysafe 0.20161107 released with [[!toggle text="these changes"]]
+[[!toggleable text="""
+ * The third keysafe server is now available, provided by Purism.
+ * Purism's keysafe server has been vetted to Recommended level!
+ * Change default for --port to 4242.
+ * Fix --check-server to not fail when the server has not had anything
+ stored on it yet.
+ * --upload-queued: Exit nonzero if unable to upload all queued objects.
+ * --autostart: If unable to upload all queued objects initially,
+ delay between 1 and 2 hours and try again.
+ * Better suggestion when user is having difficulty thinking of a strong
+ enough password.
+ * Defer requesting secret key from gpg until just before backup, so the
+ user knows why gpg is asking for this secret key to be backed up."""]] \ No newline at end of file
diff --git a/doc/screenshots.mdwn b/doc/screenshots.mdwn
new file mode 100644
index 0000000..3e35cf2
--- /dev/null
+++ b/doc/screenshots.mdwn
@@ -0,0 +1,14 @@
+
+[[screenshots/1.png]]
+
+[[screenshots/2.png]]
+
+[[screenshots/3.png]]
+
+(Entered password was "makesad spindle stick")
+
+[[screenshots/4.png]]
+
+[[screenshots/5.png]]
+
+[[screenshots/6.png]]
diff --git a/doc/screenshots/0.png b/doc/screenshots/0.png
new file mode 100644
index 0000000..7cfe206
--- /dev/null
+++ b/doc/screenshots/0.png
Binary files differ
diff --git a/doc/screenshots/1.png b/doc/screenshots/1.png
new file mode 100644
index 0000000..7759d6a
--- /dev/null
+++ b/doc/screenshots/1.png
Binary files differ
diff --git a/doc/screenshots/2.png b/doc/screenshots/2.png
new file mode 100644
index 0000000..becd319
--- /dev/null
+++ b/doc/screenshots/2.png
Binary files differ
diff --git a/doc/screenshots/3.png b/doc/screenshots/3.png
new file mode 100644
index 0000000..58d7523
--- /dev/null
+++ b/doc/screenshots/3.png
Binary files differ
diff --git a/doc/screenshots/4.png b/doc/screenshots/4.png
new file mode 100644
index 0000000..418844a
--- /dev/null
+++ b/doc/screenshots/4.png
Binary files differ
diff --git a/doc/screenshots/5.png b/doc/screenshots/5.png
new file mode 100644
index 0000000..89e1a22
--- /dev/null
+++ b/doc/screenshots/5.png
Binary files differ
diff --git a/doc/screenshots/6.png b/doc/screenshots/6.png
new file mode 100644
index 0000000..d750141
--- /dev/null
+++ b/doc/screenshots/6.png
Binary files differ
diff --git a/doc/screenshots/restore/1.png b/doc/screenshots/restore/1.png
new file mode 100644
index 0000000..8fd1f43
--- /dev/null
+++ b/doc/screenshots/restore/1.png
Binary files differ
diff --git a/doc/screenshots/restore/2.png b/doc/screenshots/restore/2.png
new file mode 100644
index 0000000..e682486
--- /dev/null
+++ b/doc/screenshots/restore/2.png
Binary files differ
diff --git a/doc/screenshots/restore/3.png b/doc/screenshots/restore/3.png
new file mode 100644
index 0000000..7c7e5dc
--- /dev/null
+++ b/doc/screenshots/restore/3.png
Binary files differ
diff --git a/doc/screenshots/restore/4.png b/doc/screenshots/restore/4.png
new file mode 100644
index 0000000..4d07070
--- /dev/null
+++ b/doc/screenshots/restore/4.png
Binary files differ
diff --git a/doc/servers.mdwn b/doc/servers.mdwn
new file mode 100644
index 0000000..3e82c7e
--- /dev/null
+++ b/doc/servers.mdwn
@@ -0,0 +1,194 @@
+There are currently not enough keysafe servers. We need at least 3 for
+keysafe to work. Please contact <id@joeyh.name> if you would like to run a
+keysafe server.
+
+## Server categories
+
+Keysafe's server list puts servers in three categories:
+
+1. **Recommended**: Servers that meet all best practices for security and
+ are run by a well-known, trusted entity.
+
+ Keysafe prefers to store data only on Recommended servers when possible.
+
+2. **Alternate**: Servers that are not secured well enough to be
+ Recommended.
+
+ Keysafe will store data on Alternate servers if it has to, but will
+ avoid storing enough data to allow the key to be recovered using only
+ the data stored on Alternate servers.
+
+ For example, with 2 of 3 shares needed to restore a key, keysafe can
+ store 1 share on an Alternate server, and the other 2 shares on two
+ Recommended servers.
+
+3. **Untrusted**: Servers that are not secured well or are run by an untrusted
+ entity.
+
+ Keysafe will never store data on Untrusted servers.
+
+ If a server becomes untrusted and keysafe stored data on it in the past,
+ keysafe will warn the user about this problem.
+
+ The only time keysafe will use untrusted servers is if it's restoring a
+ key, and cannot find enough shares on Recommended/Alternate
+ servers, and has to fall back to downloading from an Untrusted server.
+
+## Server list
+
+### Recommended
+
+#### hlmjmeth356s5ekm.onion
+
+ -----BEGIN PGP SIGNED MESSAGE-----
+ Hash: SHA1
+
+ The keysafe server hlmjmeth356s5ekm.onion is provided and administered by
+ Purism. It is located in the EU (Cyprus).
+
+ We intend to run this server for at least 10 years (through 2027),
+ or failing that, to transition any data stored on it to another
+ server that is of similar or higher security.
+
+ Our warrant canary is <https://puri.sm/warrant-canary/>,
+ and is updated quarterly.
+ -----BEGIN PGP SIGNATURE-----
+ Version: GnuPG v1
+
+ iQIcBAEBAgAGBQJYF8U4AAoJECPPLj0lRRT30CkP/Rn2TAeriNWO9wZcr0OHyX7B
+ TJcgLy3pZXbGn6T6qmJqg3K22fTKJ7CX0dfIM+WLI9FfBtnT95q1rnzywhBGPXzj
+ eD3g7r3QinIfMLBQTKyc9Ik5132uenD5h72ggVl3D+kuWv622IhaAaiVkuHc5KoR
+ 3/S+ImkcS/gz83UNTXnWdMs0V8+eqAjpWeYQS8Ih28AECI9f+xUUH//V9Ii/4Usv
+ E3Y0hbqj8kSi4/Q6IwmFiJTKZ1FpccKhl6GIYUSLwJMJDHoI46M/AaZy0Xx9pLcU
+ niSELai/7/0fY4N0TY2CbZUgH7FEhi0k8cCsGF7yTA6dqya8deKQKdUdDllcHayv
+ +GOAqijiSTPrRox4TPMMdurPXTsJxeJuxVdS75Lw2cFk+JaaIVS/3XEyeuGpaVKW
+ wSTltyFkMx9ur5cCPT2rxoRN78HuqgiHda/Jd4c2pny7GwpUEYAznQQaBYEl2jlQ
+ /Go3ZudpnWfBRRe7znazhA6mIatPY61GrNIebVlET6/NCw9sZFRjHXY3pMw1u/TY
+ 4eP0UQpBUed4/sot5vsZVwbn8e6eFh0S4HTdl5x1G8jN8nUZVdJJjOtACrONW+TG
+ CLSNDkMgQ5slBmtZm+MzL2VYkFHCMmPerNXY1DhHjMyfLpQEIN+bho+mIyc5h/W/
+ Br5jFZujcQ0u7GzqvaDB
+ =RmK4
+ -----END PGP SIGNATURE-----
+
+### Alternate
+
+#### keysafe.joeyh.name
+
+ -----BEGIN PGP SIGNED MESSAGE-----
+ Hash: SHA256
+
+ keysafe.joeyh.name is provided by me, Joey Hess.
+
+ I intend to run this server for at least 10 years (through 2027),
+ or failing that, to transition any data stored on it to another
+ server that is of similar or higher security.
+
+ It is a Digital Ocean VPS, located in Indonesia. I can't tell if the
+ hosting provider is accessing the contents of the server, and so
+ this server is not securely hosted enough to be Recommended.
+ -----BEGIN PGP SIGNATURE-----
+
+ iQIcBAEBCAAGBQJXx0qFAAoJEMkQ2SIlEuPHyGMQALSLL7LZEpTi+zf2kPYGoBMQ
+ 3z3FDB9B6SaF4uN3r+XlAw2Vzas2KVLCbNkO+np7tLzC0qdY5dBLDI7+ZJXiKi2v
+ iqxKICl0E8+ih8JOe0JWfoysO974I1DesEI7X6VUewwNpd35OgCuIL5RmknKrX4I
+ x7gUfsONiojUKgOT0yMErUfw3VNYB0Kbzw4Xic66eIkFl5z6APMknjqvOC1196v9
+ BW0rSM+OsthB9xkj7ULKQv+1LrxmwNu0+FL62qNKGObbXHayfLBGm8TT9Y7etQYD
+ 3zRDiUfa0m2aYu7ZRx5HSIgExVVd3YosDUFA4xsIb6N4wBbP1zS2TG2Zo5o/+3gt
+ BerkQL/xkMWhIMVCYp1hWc47MenHk1MJU5EhS+duL/fnlqW2HcFanM+fOv+/ZWt6
+ da2mdjSR95Ekq22BXN9eHO54AFJKLWYNdT9E5W2rlwqUoC4dqsqYGT3XWnAaKHC/
+ he9+B/wdEf7165Qy+MKo/36Ib7pfhPQv4hip2cuMP9w0E6JoKZusBV5AdxRvGAGf
+ GvUhvNog6v9/t+cqUp6dSTT2WVllkXJ/5deGJYLzZMJjZS3cZ75ZKr8OD5oQxr+m
+ 7oL6BDvxha7Q4qHo/RZgxyd/qZ7zWHTT6Tn6qNCBGUi4b6Etb0kEd5Os66WoLCSK
+ lhmhvShr0WRqB8fWYPkc
+ =SNGN
+ -----END PGP SIGNATURE-----
+</pre>
+
+#### thirdserver
+
+ Provided by Marek Isalski at [Faelix](http://www.faelix.net/).
+ Currently located in UK, but planned move to CH.
+ Vetting to Recommended level in progress.
+
+## Detailed requirements
+
+### Alternate
+
+* Keysafe port only exposed via tor hidden service.
+* Dedicated to only running keysafe, no other services. (Other than tor and
+ ssh for admin)
+* The set of people who administer the server, and procedures for giving
+ others access is well-defined.
+* Noone who has access to the server also has access to any Recommended
+ server.
+* Commitment to either keep the server running long-term (ie, 10+ years),
+ or transition the data to a replacement server that meets these
+ requirements and that must not contain any related shards.
+* No other open ports (other than ssh).
+* Ssh authentication only by ssh key, not password.
+* Either off-server backup, or replication of shards to additional disks.
+ (rsync to additional local disks would work perfectly well and avoids
+ the complications of RAID)
+* Any off-server backup is strongly encrypted.
+ (There's a trade-off here; any backup widens the attack surface.
+ It may be better to run some servers without backups and adjust the
+ number of shards needed to recover keys; a server losing its data
+ need not be catastrophic.)
+* Any backup should take care to not leak information about what objects
+ were present on the server at multiple times in the past. That would
+ let an attacker who can access the backups make guesses about shares
+ belong with other shares stored on other servers in the same time period.
+ See [[details]] for how that makes it somewhat easier for an attacker.
+
+ keysafe --backup-server can be used to generate encrypted files to back up,
+ in a way that is designed to avoid these problems.
+
+* Similarly, the filesystem and storage system should not allow rolling back
+ to old snapshots.
+
+### Recommended
+
+* Everything in Alternate, to start with.
+* Run by a well known and trustworthy entity.
+* Noone who has access to the server also has access to any other
+ Recommended or Alternate server.
+* Warrant canary.
+* Hardware is hosted in-house. A VM at a cloud provider is right out
+ because the provider could be made to give access to it without the
+ server operator knowing about it. Which would bypass the warrant canary.
+* The keysafe data store and any swap partitions are encrypted,
+ and have to be manually unlocked when the server is booted.
+
+## Server scaling
+
+Each key takes a minimum of 64 KiB to store, perhaps more for gpg keys
+with lots of signatures. So 10 GiB of disk is sufficient for 160 thousand
+users, which is enough for a small keysafe server.
+
+The keysafe server uses very little memory and CPU. It does rate limiting
+with client-side proof-of-work to prevent it being abused for
+general-purpose data storage.
+
+There is some disk IO overhead, because keysafe updates the mtime and ctime
+of all shards stored on the server, as frequently as every 30 minutes.
+Once a large number of shards are stored, this could become a significant
+source of disk IO.
+
+## Server setup
+
+It's early days still, but keysafe's server code works well enough.
+
+* `git clone git://keysafe.branchable.com/ keysafe`
+* Be sure to verify the gpg signature of the git repository!
+* You will need to install keysafe from source; see its INSTALL file.
+ Use `make install` to install it, including a systemd service file.
+* `systemctl enable keysafe.service`
+* Install tor and set up a tor hidden service. Keysafe listens on port 4242
+ by default, so use that port.
+* Configure the server to meet all the requirements for Alternate or
+ Required.
+* Once ready, email id@joeyh.name to get added to keysafe's server list.
+
+Here's a the [[code/propellor]] config for my own keysafe server:
+<http://source.propellor.branchable.com/?p=source.git;a=blob;f=joeyconfig.hs;h=15a00f7c2dffa15ed275fdd44e84e2edcc226559;hb=b9f87f0c08d94c5d43224a2c6bbacb332ebfc1b6#l460>
+--[[Joey]]
diff --git a/doc/todo.mdwn b/doc/todo.mdwn
new file mode 100644
index 0000000..6fa6e18
--- /dev/null
+++ b/doc/todo.mdwn
@@ -0,0 +1,4 @@
+This is keysafe's todo list. Link items to [[todo/done]] when done.
+
+[[!inline pages="./todo/* and !./todo/done and !link(done)
+and !*/Discussion" actions=yes postform=yes show=0 archive=yes]]
diff --git a/keysafe.1 b/keysafe.1
index a09bf6f..7d0ee2b 100644
--- a/keysafe.1
+++ b/keysafe.1
@@ -28,8 +28,8 @@ the secret key with the password in a way that takes a lot of computation
to decrypt. This makes it hard for an attacker to crack your password,
because each guess they make costs them.
.PP
-Keysafe is designed so that it should take millions of dollars of computer
-time to crack any fairly good password. With a truly good
+Keysafe is designed so that it should take millions of dollars (US)
+of computer time to crack any fairly good password. With a truly good
password, such as four random words, the cracking cost should be many
trillions of dollars. Keysafe checks your password strength (using the
zxcvbn library), and shows an estimate of the cost to crack your password,
@@ -161,6 +161,6 @@ The server's Message Of The Day.
Avoid using expensive cryptographic operations to secure data.
Use for testing only, not with real secret keys.
.SH SEE ALSO
-<https://joeyh.name/code/keysafe/>
+<https://keysafe.branchable.com/>
.SH AUTHOR
Joey Hess <id@joeyh.name>
diff --git a/keysafe.cabal b/keysafe.cabal
index a72ab81..064a0e8 100644
--- a/keysafe.cabal
+++ b/keysafe.cabal
@@ -1,12 +1,12 @@
Name: keysafe
-Version: 0.20161107
+Version: 0.20170122
Cabal-Version: >= 1.8
Maintainer: Joey Hess <joey@kitenet.net>
Author: Joey Hess
Stability: Experimental
Copyright: 2016 Joey Hess
License: AGPL-3
-Homepage: https://joeyh.name/code/keysafe/
+Homepage: https://keysafe.branchable.com/
Category: Utility
Build-Type: Custom
Synopsis: back up a secret key securely to the cloud
@@ -33,33 +33,38 @@ Executable keysafe
Main-Is: keysafe.hs
GHC-Options: -threaded -Wall -fno-warn-tabs -O2
Build-Depends:
- base (>= 4.5 && < 5.0)
+ -- These are core cryptographic dependencies. It's possible that
+ -- changes to these could break backup/restore, so when loosening
+ -- the version ranges, it's important to run keysafe --test
+ secret-sharing == 1.0.*
+ , argon2 == 1.2.*
+ , raaz == 0.0.2
+ , base (>= 4.5 && < 5.0)
, bytestring == 0.10.*
+ , text == 1.2.*
+ -- Changes to these dependencies should not impact the data that
+ -- keysafe backs up and restores.
, deepseq == 1.4.*
, random == 1.1.*
- , secret-sharing == 1.0.*
- , raaz == 0.0.2
- , time == 1.6.*
+ , time (>= 1.5 && < 1.7)
, containers == 0.5.*
- , binary == 0.8.*
- , text == 1.2.*
, utf8-string == 1.0.*
, unix == 2.7.*
, filepath == 1.4.*
, split == 0.2.*
, directory == 1.2.*
- , process == 1.4.*
+ , process (>= 1.2 && < 1.5)
, optparse-applicative == 0.12.*
, readline == 1.0.*
, zxcvbn-c == 1.0.*
- , servant == 0.8.*
- , servant-server == 0.8.*
- , servant-client == 0.8.*
+ , servant (>= 0.7 && < 0.9)
+ , servant-server (>= 0.7 && < 0.9)
+ , servant-client (>= 0.7 && < 0.9)
, aeson == 0.11.*
, wai == 3.2.*
, warp == 3.2.*
, http-client == 0.4.*
- , transformers == 0.5.*
+ , transformers (>= 0.4 && < 0.6)
, stm == 2.4.*
, socks == 0.5.*
, network == 2.6.*
@@ -75,7 +80,6 @@ Executable keysafe
, exceptions == 0.8.*
, random-shuffle == 0.0.*
, MonadRandom == 0.4.*
- , argon2 == 1.2.*
Other-Modules:
AutoStart
BackupLog
@@ -121,4 +125,4 @@ Executable keysafe
source-repository head
type: git
- location: git://git.joeyh.name/keysafe.git
+ location: git://keysafe.branchable.com/
diff --git a/keysafe.service b/keysafe.service
index 1a81058..85cfcbb 100644
--- a/keysafe.service
+++ b/keysafe.service
@@ -1,6 +1,6 @@
[Unit]
Description=keysafe server
-Documentation=https://joeyh.name/code/keysafe/
+Documentation=https://keysafe.branchable.com/
[Service]
Environment='DAEMON_PARAMS=--port 4242 --store-directory=/var/lib/keysafe/'