The Delegated Account Recovery Rigid Reusable Ruby (aka D.a.r.r.r.r. or "Darrrr") library is meant to be used as the fully-complete plumbing in your Rack application when implementing the Delegated Account Recovery specification. This library is currently used for the implementation at GitHub.
Along with a fully featured library, a proof of concept application is provided in this repo.
An account provider (e.g. GitHub) is someone who stores a token with someone else (a recovery provider e.g. Facebook) in order to grant access to an account.
In config/initializers or any location that is run during application setup, add a file:
Darrrr.authority = "http://localhost:9292"
Darrrr.privacy_policy = "#{Darrrr.authority}/articles/github-privacy-statement/"
Darrrr.icon_152px = "#{Darrrr.authority}/icon.png"
# See script/setup for instructions on how to generate keys
Darrrr::AccountProvider.configure do |config|
config.signing_private_key = ENV["ACCOUNT_PROVIDER_PRIVATE_KEY"]
config.symmetric_key = ENV["TOKEN_DATA_AES_KEY"]
config.tokensign_pubkeys_secp256r1 = [ENV["ACCOUNT_PROVIDER_PUBLIC_KEY"]]
config.save_token_return = "#{Darrrr.authority}/account-provider/save-token-return"
config.recover_account_return = "#{Darrrr.authority}/account-provider/recover-account-return"
end
Darrrr::RecoveryProvider.configure do |config|
config.signing_private_key = ENV["RECOVERY_PROVIDER_PRIVATE_KEY"]
config.countersign_pubkeys_secp256r1 = [ENV["RECOVERY_PROVIDER_PUBLIC_KEY"]]
config.token_max_size = 8192
config.save_token = "#{Darrrr.authority}/recovery-provider/save-token"
config.recover_account = "#{Darrrr.authority}/recovery-provider/recover-account"
endThe delegated recovery spec depends on publicly available endpoints serving standard configs. These responses can be cached but are not by default. To configure your cache store, provide the reference:
Darrrr.cache = Dalli::Client.new('localhost:11211', options)The spec disallows http URIs for basic security, but sometimes we don't have this setup locally.
Darrrr.allow_unsafe_urls = trueIn order to allow a site to act as a provider, it must be "registered" on boot to prevent unauthorized providers from managing tokens.
# Only configure this if you are acting as a recovery provider
Darrrr.register_account_provider("https://github.com")
# Only configure this if you are acting as an account provider
Darrrr.register_recovery_provider("https://www.facebook.com")Create a module that responds to Module.sign, Module.verify, Module.decrypt, and Module.encrypt. You can use the template below. I recommend leaving the #verify method as is unless you have a compelling reason to override it.
Set Darrrr.custom_encryptor = MyCustomEncryptor
Darrrr.with_encryptor(MyCustomEncryptor) do
# perform DAR actions using MyCustomEncryptor as the crypto provider
token = Darrrr.this_account_provider.generate_recovery_token(data: "foo", audience: recovery_provider)
endmodule MyCustomEncryptor
class << self
# Encrypts the data in an opaque way
#
# data: the secret to be encrypted
#
# returns a byte array representation of the data
def encrypt(data)
end
# Decrypts the data
#
# ciphertext: the byte array to be decrypted
#
# returns a string
def decrypt(ciphertext)
end
# payload: binary serialized recovery token (to_binary_s).
#
# key: the private EC key used to sign the token
#
# returns signature in ASN.1 DER r + s sequence
def sign(payload, key)
end
# payload: token in binary form
# signature: signature of the binary token
# key: the EC public key used to verify the signature
#
# returns true if signature validates the payload
def verify(payload, signature, key)
# typically, the default verify function should be used to ensure compatibility
Darrrr::DefaultEncryptor.verify(payload, signature, key)
end
end
endI strongly suggest you read the specification, specifically section 3.1 (save-token) and 3.5 (recover account) as they contain the most dangerous operations.
NOTE: this is NOT meant to be a complete implementation, it is just the starting point. Crucial aspects such as authentication, audit logging, out of band notifications, and account provider persistence are not implemented.
- Account Provider (save-token-return, recover-account-return)
- Recovery Provider (save-token, recover-account)
- Configuration endpoint (
/.well-known/delegated-account-recovery/configuration)
Specifically, the gem exposes the following APIs for manipulating tokens.
- Account Provider
- Generating a token
- Signing (
#seal) a token - Verifying (
#unseal) a countersigned token
- Recovery Provider
- Verifying (
#unseal) a token - Countersigning a token
- Verifying (
Local development assumes a Mac OS environment with homebrew available. Postgres and phantom JS will be installed.
Run ./script/bootstrap then run ./script/server
- Visit
http://localhost:9292/account-provider- (Optionally) Record the random number for verification
- Click "connect to http://localhost:9292"
- You'll see some debug information on the page.
- Click "setup recovery".
- If recovery setup was successful, click "Recovery Setup Successful"
- Click the "recover now?" link
- You'll see an intermediate page, where more debug information is presented. Click "recover token"
- You should be sent back to your host
- And see something like
Recovered data: <the secret from step 1>
- And see something like
Run ./script/test to run all tests.
Use heroku config:set to set the environment variables listed in script/setup. Additionally, run:
heroku config:set HOST_URL=$(heroku info -s | grep web_url | cut -d= -f2)
Push your app to heroku:
git push heroku <branch-name>:master
Migrate the database:
heroku run rake db:migrate
Use the app!
heroku restart
heroku open
- Add support for
token-statusendpoints as defined by the spec - Add async API as defined by the spec
- Implement token binding as part of the async API
See script/setup for the environment variables that need to be set.
See CONTRIBUTING.md
darrrr is licensed under the MIT license.