4958c5d7b6
Change-Id: Ib6b876ff83aeba0d2346990ab2e9869cbf519284 Reviewed-on: https://review.haiku-os.org/c/haiku/+/6641 Tested-by: Commit checker robot <no-reply+buildbot@haiku-os.org> Reviewed-by: Adrien Destugues <pulkomandy@pulkomandy.tk>
232 lines
9.5 KiB
Plaintext
232 lines
9.5 KiB
Plaintext
/*
|
|
* Copyright 2020 Haiku, Inc. All rights reserved.
|
|
* Distributed under the terms of the MIT License.
|
|
*
|
|
* Authors:
|
|
* Niels Sascha Reedijk, niels.reedijk@gmail.com
|
|
*/
|
|
|
|
/*!
|
|
\page app_keystore Password and Key Storage API
|
|
|
|
Haiku R1 introduces the first version of a system-wide key store service,
|
|
allows you as developer to outsource some of the credential and certificate
|
|
management, as well as providing an infrastructure that enables sharing
|
|
these artifacts between applications.
|
|
|
|
\warning The implementation in Haiku R1 is limited and is a promise of more
|
|
to come. Functionality beyond storing and sharing passwords is for the
|
|
future. Also please read the security section of this document,
|
|
especially when you are working with artifacts that are more sensitive.
|
|
In many cases you will find that this system service will work just as
|
|
well as making your own implementation, but there are instances in
|
|
which you may chose not to use it.
|
|
|
|
\section app_keystore_overview 1. Highlevel Overview (components)
|
|
|
|
The implementation is based around the following concepts:
|
|
- The \b keystore is the centralized repository for your keys. It is
|
|
managed by the \b keystore_server and it contains one or more
|
|
\b keyrings.
|
|
- A \b keyring is a collection of keys. There is always a
|
|
\b master \b keyring, which cannot be removed. Access is organized
|
|
around keyrings. From a user's perspective, when an application wants
|
|
to access keys in a keyring, the user will have to grant permission to
|
|
that application to access the keyring. A keyring is identified by a
|
|
name, which needs to be unique on the user's system.
|
|
- A keyring contains \b keys. These are the smallest unit in the system.
|
|
A key can be anything that you want to safeguard in the keystore. Keys
|
|
are identified by the combination of an identifier and a secondary
|
|
identifier. These should be unique within a keyring.
|
|
- The final piece is the concept of \b permissions. In the current
|
|
implementation, an application needs to ask permission to access a
|
|
keyring. The \c keystore_server will validate the permissions, and if
|
|
necessary prompt the user to grant one-time or a permant access. If the
|
|
user only allows it once, access is granted until the application
|
|
terminates.
|
|
|
|
As a user of the API, you will mostly be working with \ref BKeyStore, as
|
|
the access point that queries and modifies keyrings and keys. An individual
|
|
key is represented by the \ref BKey object.
|
|
|
|
\section app_keystore_security 2. Security
|
|
|
|
The current implementation of this API should be considered low-security.
|
|
The most important thing to know is that there is \b no \b encryption
|
|
applied when storing the keys and keyrings to the drive. This means that
|
|
the data can be read by any malicious actor that can access the drive of
|
|
your machine.
|
|
|
|
This should also puts the current locking mechanism in perspective. While
|
|
the \c keystore_server will prompt the user to grant (or deny) access to
|
|
your application, when it wants to access a keyring, this again does not
|
|
prevent any malicious actor to bypass the \c keystore_server and directly
|
|
read from (and write to!) the file.
|
|
|
|
When considering on whether to use the current API, there are a few things
|
|
to think about:
|
|
- First, consider whether you should store the keys at all. Passwords to
|
|
services with extremely sensitive personal or financial information,
|
|
such as email passwords or credentials to financial institutions, should
|
|
not be stored at all. Prompt your user for the credentials when needed,
|
|
and don't keep them for later use.
|
|
- Secondly, if you are storing credentials for use with web services,
|
|
check if the service you are using supports using access tokens. Many
|
|
APIs have them, and often use it in combination with some form of
|
|
permission system or scoping, making it possible for you to keep access
|
|
as limited as possible. Furthermore, the user often has the ability to
|
|
revoke access to a token, in case they think it is compromised.
|
|
- When you assess that you really do need to store the credentials, make
|
|
a determination first about whether or not the credentials should have
|
|
some form of encryption. For now you should consider looking for another
|
|
solution to storing sensitive data, but contributions to improve this
|
|
API are very welcome. It is beyond the scope of this document to discuss
|
|
strategies around encryption.
|
|
- When you assess the risk is low enough not to employ encryption
|
|
strategies, you may consider using this API. It is particularly
|
|
recommended if you will be sharing the credentials with more than one
|
|
application.
|
|
|
|
\warning In the end, it is up to you as a developer to be conscious of any
|
|
choices you make when it comes to user data, and credentials are no
|
|
different. When you decide that the Password and Key API does not fit
|
|
your needs, choose a framework or library that does fit your purpose.
|
|
|
|
\section app_keystore_usage 3. Practical use of the API
|
|
|
|
Below are two distinct examples on how you may use the API.
|
|
|
|
\subsection app_keystore_usage_web The Langlaufer Web Browser
|
|
|
|
We are working on the infamous Langlaufer web browser, and we are adding
|
|
a feature where we autocomplete user names and passwords. It is decided to
|
|
use the Password and Key Storage API to do our key management. Whenever we
|
|
land on a web page with a login screen, we will try to see if we have
|
|
credentials for that web page. Part of the requirements is that we support
|
|
more than one set of credentials for a web page.
|
|
|
|
It is decided that the application will store the user credentials in it's
|
|
own keyring, as we do not want to interfere with any other keys in the
|
|
master key. Additionally, we will use both the primary and secondary
|
|
identifier fields. The primary will contain the hostname of the website,
|
|
and the secondary will contain the user name.
|
|
|
|
One final design note is that all the calls to the \c keystore_server are
|
|
synchronous, meaning they will block until there is a response back from
|
|
the keystore. In the case that a user needs to be prompted for a password,
|
|
the call will be blocked until they make a decision. That is why any calls
|
|
on \ref BKeyStore should be done on a separate worker thread, instead of
|
|
within the logic of a Window.
|
|
|
|
For clarity, the example below displays the interaction with the
|
|
\ref BKeyStore and \ref BKey classes through some utility functions. It is
|
|
up to the reader to put that in a separate working thread.
|
|
|
|
\code{.cpp}
|
|
#include <Key.h>
|
|
#include <KeyStore.h>
|
|
|
|
const char *kLanglauferKeyringName = "Langlaufer";
|
|
|
|
BObjectList<BPasswordKey>
|
|
GetKeysForWebsite(const char *baseUrl) {
|
|
// There may be more than one match, so we use the iteration methods.
|
|
BKeyStore keyStore;
|
|
uint32 cookie;
|
|
BPasswordKey currentKey;
|
|
BObjectList<BPasswordKey> list;
|
|
bool next = true;
|
|
|
|
while(next) {
|
|
status_t status = keyStore.GetNextKey(kLanglauferKeyringName,
|
|
B_KEY_TYPE_PASSWORD, B_KEY_PURPOSE_WEB, cookie, currentKey);
|
|
switch(status) {
|
|
case B_OK:
|
|
// Try to see if the key matches the website
|
|
if (currentKey.Identifier() == baseUrl) {
|
|
// Add the item to the list.
|
|
list.AddItem(new BPasswordKey(currentKey));
|
|
}
|
|
break;
|
|
case B_BAD_VALUE:
|
|
// The keyring does not exist, create it, and end the
|
|
// search
|
|
CreateKeyring();
|
|
next = false;
|
|
break;
|
|
default:
|
|
// Something else went wrong, like the user did not give
|
|
// authorization, or we are at the end of the list.
|
|
// Bail out the search at this point.
|
|
next = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
void
|
|
CreateKeyring() {
|
|
BKeyStore keyStore;
|
|
// Ignore the return value in the next line, it may fail but that won't
|
|
// interrupt the flow of our program.
|
|
keyStore.AddKeyring(kLanglauferKeyringName);
|
|
}
|
|
|
|
void
|
|
AddOrReplaceKey(const char *baseUrl, const char *user, const char *password) {
|
|
BKeyStore keyStore;
|
|
BPasswordKey key;
|
|
|
|
// Fill out the key with existing data, or create new data
|
|
if (keyStore.GetKey(kLanglauferKeyringName, B_KEY_TYPE_PASSWORD, baseUrl, user, &key) == B_OK) {
|
|
// Remove the existing key
|
|
keyStore.RemoveKey(kLanglauferKeyringName, key);
|
|
// Update the password
|
|
key.SetPassword(password);
|
|
} else {
|
|
key.SetTo(password, B_KEY_PURPOSE_WEB, user, password);
|
|
}
|
|
|
|
// Store the updated/new key in the keyring
|
|
keyStore.AddKey(kLanglauferKeyringName, key);
|
|
}
|
|
\endcode
|
|
|
|
\subsection app_keystore_usage_coolwebservice The CoolWebService Tool Suite
|
|
|
|
We are working on a set of tools that interface with a cool web service.
|
|
Instead of building one monolithic application, we make several small tools
|
|
with specific jobs for this cool web service. One of the tools does the
|
|
authentication, and stores the key in the master keyring on the system. The
|
|
other tools use this key to access the API.
|
|
|
|
Each tool requires the authentication token to be set up properly. That's
|
|
why in the \ref BApplication::ReadyToRun() hook we check for the
|
|
availability of the key. If it is not available, or it does not work, the
|
|
user will be redirected to the authentication tool. The key will be stored
|
|
as a password. It will be identified by the identifier "CoolWebService".
|
|
|
|
\code{.cpp}
|
|
void
|
|
CoolPushTool::ReadyToRun() {
|
|
BKeyStore keyStore;
|
|
BPasswordKey key;
|
|
|
|
if (keyStore.GetKey(B_KEY_TYPE_PASSWORD, "CoolWebService", key) != B_OK) {
|
|
// Terminate the application and re-authenticate
|
|
...
|
|
}
|
|
|
|
// Extract the key
|
|
BString accessToken = key.Password();
|
|
|
|
// Validate the key, and if succesful, continue
|
|
...
|
|
}
|
|
\endcode
|
|
|
|
*/
|