keystar

package module
v0.0.0-...-7eb8b13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 31, 2021 License: NCSA Imports: 2 Imported by: 0

README

KeyStar, an HTTP and In-process Key Management Solution

KeyStar is a lightweight key management utility that provides a RESTful HTTP server. Additionally, KeyStar also provides an in-process key management library for Golang applications along with cryptographic wrapper utilities that make use of its internal key management. Presently, it only supports file system backed storage, but plans are in progress to support bbolt, SQLite, and eventually S3-compatible backends. Other options may be explored as time permits or requirements change (this includes etcd).

Available in this document are the following sections; missing sections will be added as time allows:

Why KeyStar?

There are several key management libraries currently available across different languages and platforms. However, many of these libraries require complex configuration or have hard dependencies on other software that limit their utility for smaller projects. While KeyStar can be used for projects of any size, it was optimized for use as a key management service for small and mid-sized applications. KeyStar is perfect for projects that are too large to manage keys themselves, where key management might be more complex than necessary (large uWSGI applications with multiple processes), and where highly generalized key managers may impose a significant burden on the project's requirements, both in terms of complexity--as mentioned--and by consuming valuable developer time.

In particular, if you have a project that requires an external key management solution that can be queried during application start up--or periodically to refresh keys--but you have no need for extensive authentication, authorization, or multi-tenancy applications, KeyStar may be a better fit. This is especially true if your application is also written in Go: KeyStar can be included as part of your project and loaded directly. Optionally, in-process KeyStar instances can be configured to launch their own key management server(s) for remote clients or those written in other languages.

KeyStar also includes some helpful wrappers around Golang's cryptographic primitives for things like encrypted-and-signed data, highly scalable token generation (think password resets, etc), and other utilities to make the mundane use of crypto less boilerplate and more focused on the task at hand.

Command Line

Most access to KeyStar (including KeyStar clients and servers) can be managed from the keystar command and its sub-commands. Additionally, some initial configuration for setting up key spaces can only be performed with the keystar command.

As of the current version, the following sub-commands are defined:

  • admin - KeyStar administrative tools.
  • client - Client tools and utilities.
  • keys - Key management tools, generators, and utilities.
  • license - Prints any appropriate LICENSE texts for KeyStar and dependent libraries.
  • server - KeyStar HTTP API server.

Note: Some commands are not yet finished or implemented. This is expected to change in the near future when KeyStar reaches its first official "release."

Configuration

Before KeyStar may be used, it is necessary to configure it. The current distribution of KeyStar should include a keystar.yaml.sample file that defines the host, port, and storage backend URI for the server. Copy the keystar.yaml.sample file to keystar.yaml and modify it accordingly.

KeyStar can be launched from any user account, or multiple accounts, on the system and provides mechanisms for discovering its configuration from any of a variety of locations. In particular, KeyStar searches:

  • The current working directory.
  • (Linux) The existence or contents of XDG_CONFIG_HOME/keystar/keystar.yaml, XDG_CONFIG_DIRS entries with the path /keystar/keystar.yaml, and /etc/keystar/keystar.yaml.
  • (Windows) The existence or contents of %APPDATA%/Roaming/keystar/keystar.yaml

for its configuration file and will read it from the first location it finds, in that order.

Future versions of KeyStar may include a configuration that allows additional options, such as multiple servers, key spaces, or client profiles.

Initialization

Once KeyStar's configuration file, keystar.yaml, has been edited appropriately, the next step is to initialize the configured key space(s). To do so,run the keystar admin init command. This will return with either an error message if the initialization process failed, or it will indicate success and print the system account ID and secret tokens.

SAVE THE ACCOUNT ID AND TOKEN PAIR! Although you may use keystar admin init (and, eventually, other commands) to print out these values from the configured instance, you will need these to authenticate with the server when using the HTTP API.

Other Commands

Other commands KeyStar provides that may be of use are fairly limited, but we expect to add a variety of client and key commands for convenience. This section will be updated as these commands find their way into KeyStar's lexicon.

Of particular note as of this writing is the command keystar client authenticate. This command will read the system account ID token and its secret token, process the appropriate challenge-response cycle, and return a complete string for use as the Authorization: Bearer HTTP header. The output from this command can be pasted as an argument to cURL's -H flag and may be used to test or interact with the HTTP API.

API Concepts

Correct use of KeyStar requires understanding a few core concepts defining how it works and how it stores key information internally. Not all of these concepts are required for both the in-process and HTTP APIs but understanding most of the stack may nevertheless be useful.

Before we discuss the key data hierarchy, it is necessary to discuss authentication and KeyStar-level accounting.

Authentication and Tenancy

KeyStar defines two modes of operation for its authentication and accounting processes: Single-user occupancy or multi-tenancy. In single-user mode, user namespaces are disabled with only the global namespace and its cohorts accessible from clients. In multi-tenancy mode, accounts can be created that have access to their own "global" namespace and named namespaces, or they can be extended permissions to access the system-wide global namespace.

By using multi-tenancy mode, it is possible to support multiple applications within a single KeyStar instance by restricting some applications to their own namespace(s), granting access to only the global namespace, or granting an inheritance hierarchy to an application so keys not found in its local namespace will bubble up into the global namespace until they're located (or not).

However, single-user occupancy is likely the mode users will find most useful to their own application. Single-user occupancy is easier to configure, requires no additional maintenance (other than API key generation), yet still provides access to a variety of features for separating keys and key rings into their own distinct silos via namespacing. For in-process use of KeyStar, single-user occupancy is likely the best option.

Key Storage Hierarchy

KeyStar establishes the following hierarchy for key membership, in order:

  • Key spaces
  • Namespaces (global, named, and user)
  • Key rings
  • Keys

From top-to-bottom, this defines the a) file-system level storage or backend storage that is unique to each instance, b) namespaces that isolate individual keys and key rings from each other, and c) key rings containing related keys and the keys themselves.

In the following sections, we discuss each of these concepts.

Key Spaces

At the top level of KeyStar data structures is the key space. Key spaces contain all namespace identifiers: The global namespace, named global namespaces, and the associated user naemspaces. A single key space interacts with one (and only) one backend; this means that multiple key spaces could be defined within a single KeyStar instances that utilize multiple backends, directories, or other data stores. Key spaces also contain their own unique authentication and could be used to isolate separate instances, or enable multiple instances with single-user occupancy or multi-tenancy individually.

Namespaces

From the key space, we travel next to its subordinates: The namespace collection. Namespaces are unique key ring and key storage clusters segregated into the global namespace, which is the default for most key ring and key creation; named global namespaces, which are separate namespaces under the global namespace; and the user global and named user namespaces (available only if multi-tenancy is enabled).

Namespace access is defined by its type. The global namespace is used, by default, accessible strictly to the administrative account or its API keys. Under multi-tenancy modes, permissions may be given to individual accounts to access both their own namespaces or the system-wide global namespaces; or permissions may be restricted to prevent user accounts from accessing information outside their own namespace silos.

To provide a brief illustration of how namespaces can be used to separate key purposes within an application, consider the following:

  • Global namespace -> Application keys
  • Named global namespace -> "tokens" -> Application user tokens

In this instance, the application-level keys are stored directly within the global namespace, and therefore accessible from .KeySpace().Global(). We're also storing the application's user tokens in a separate global namespace (called "tokens") which would be accessible from .KeySpace().GlobalNamespace("tokens").

If this instance were configured for multi-tenancy, KeyStar accounts could be used for other services, such as separate applications; in this example, our main application would have access to the global namespace, and supporting applications would use KeyStar's "user" namespaces. (Do not confuse your application users with KeyStar's accounts!)

Key Space and Accounting Limitations

As of this writing, there are some limitations due to an incomplete implementation: First, a single KeyStar instance can only encapsulate a single KeySpace; second, KeyStar does not support multi-tenancy modes and can only be used as a single-occupancy application with the global or named global namespaces. "User" or "account" namespaces are not available. We plan on implementing both authentication, and therefore account namespaces, sometime after implementing the ability to configure multiple key spaces. This is because each key space will be able to contain its own accounting and will be isolated from other key spaces.

Key Rings

Subordinate to the namespace is the concept of key rings. Key rings are separate containers that hold keys intended for related purposes. This might include things like application crypto keys, signing keys, or secret keys. Keys within a given key ring all share the same expiration attributes (time-to-live) and automatic deletion or rotation settings. Note that one of the primary limitations of KeyStar is that you cannot control expiration of individual keys. While this may eventually be changed, we believe that controlling expiration at the key ring level delineates a clear boundary between related keys. Further, storing related keys within the same key ring provides a more transparent solution for quickly and cleanly rotating all application keys.

Another interesting side effect of storing related keys within a given key ring is thus: If you have an application that generates user tokens, such as for account password resets or the likes, creating a separate namespace for your users followed by a per-user key ring confers easier management of that user's key set.

The concept of key ring-level attributes rather than key-level attributes isn't set in stone and may eventually change.

Key rings also expose other utilities to client code, such as the ability to easily create a composite key or list the entire contents of the key ring. Key rings make it easy to delete all related keys with a single command and can be used to quickly disable a set of related keys.

Keys

Finally, we get to the topic of KeyStar's ultimate purpose: Key management and keys. Individually, keys are nothing more than a collection of metadata and a base64-encoded series of bytes. However, these keys can have important purposes, such as use for an application's secret key, encrypting or signing payloads, or token generation. Keys can be described as existing in three flavors: Standard keys, composite keys, and custom keys.

Standard Keys

Most applications will make use of KeyStar's "standard" keys at some point in time. In particular, for signing binary chunks or similar, standard keys may be used for virtually any purpose. These are the simplest key and comprise only a single binary chunk

Composite Keys

Composite keys are a sort of "meta" key that are implemented identically to "standard" keys (above) but have special meaning when combined. In most cryptographic solutions, two keys are required to successfully encrypt and validate a payload: The cipher key, which is used to encrypt or decrypt the data; and the MAC key, which is used to sign or verify the encrypted payload. KeyStar takes this a step further by exposing utility functions for creating composite keys that contain both and can have their lengths defined separately.

Internally, composite keys are merely standard keys with a unique suffix defining their purpose. Some API calls make no distinction between composite keys and their standard counterparts, and may expose this feature when listing a key ring's content.

Custom Keys

Less often used but still useful is the concept of a custom key. A custom key is a key whose encoded data chunk may contain base64-encoded binary data, string data, or anything in between. This data is user-submitted and stored identically to other key types with the exception that key decoding may fail. Calling .Bytes() on a custom key whose content is unknown may cause the base64 decoding process to fail and return an error. Therefore, custom keys are slightly more inconvenient than other types, primarily because they are user-defined. Custom keys are also ignored during key rotation.

Golang (in-process) API

KeyStar provides a direct API for use with in-process key management. This is the same interface used by the HTTP API server; therefore, the HTTP API service is itself a KeyStar client.

Please be warned: The API is currently in a state of flux and isn't expected to stabilize until an official version tag is released--probably around v0.1 or greater.

API documentation will be posted eventually. For now, enjoy some examples.

Step-by-Step Examples

KeyStar clients start with the initialization of KeyStar:

import (
  "git.pluggableideas.com/destrealm/go/keystar"
  kt "git.pluggableideas.com/destrealm/go/keystar/types"
)

func main() {
  ks := keystar.NewKeyStar(
    &kt.Options{
      BackendURI: "file://.keys",
    }
  )

}

This configures KeyStar to use the file backend with a key storage directory of .keys.

Next, namespaces are managed via the KeySpace struct, obtained via a call to .KeySpace(). Future versions may allow for the configuration of multiple KeySpaces. To obtain the global namespace or a named global namespace:


ks := keystar.NewKeyStar(
  &kt.Options{
    BackendURI: "file://.keys",
  }
)
global := ks.KeySpace().Global()
named, err := ks.KeySpace().GlobalNamespace("testing")
if err != nil {
  // Handle errors during the creation of named global namespaces.
}

Once a namespace is obtained, key rings and keys can be managed accordingly:

import (
  "git.pluggableideas.com/destrealm/go/keystar/types/api"
)

// Get or create a key ring from the global namespace (a TTL of "0" means no
// expiration):
kr, err := global.GetOrCreateKeyRing("test-keyring", 0)
if err != nil {
  // Handle key ring creation or retrieval errors.
}

// Get or create a key 32 bytes in length:
key, err := kr.GetOrCreate("demo", 32)
if err != nil {
  // Handle key creation or retrieval errors.
}

// Get or create a composite key with a cipher key length of 32 and an HMAC key
// length of 128:
ckey, err := kr.GetOrCreateComposite("demo-composite", 32, 128)
if err != nil {
  // Handler key creation or retrieval errors.
}

// List all keys.
if keys, err := kr.List(); err == nil {
  for _, k := range keys {
    fmt.Println("Key: %s (%d bytes): %s", k.Name, k.Length, k.Encoded)
  }
}

// To retrieve the contents of a key, decoded into a byte slice:
b := key.MustGetBytes()

// If the idea of a panic while decoding a key worries you, errors can be
// handled manually:
if b, err := key.Bytes(); err != nil {
  // ...
}

// Key decoding should never panic under ordinary conditions, but custom keys
// (created as follows) will fail if the `Encoded` value is not base64-encoded.
custom := &api.Key{
  Name: "sample-custom",
  Custom: true,
  Encoded: "This is a custom key.",
}
custom.Length = len(custom.Encoded)

// ... do other things.

// ...then add the pre-generated key to the key ring.
if err = kr.Add(custom); err != nil {
  // Handle errors...
}

HTTP API

KeyStar's HTTP API makes use of the GET, POST, PUT, DELETE, and PATCH verbs. Some creative license is taken in our interpretation of what these verbs mean, and their behaviors deviate slightly due to the limits imposed by storing or retrieving random data like keys to ensure idempotency[^1]. In particular, such deviations are present in POST and PUT. POST instructs KeyStar to create a key or generate an error if the key already exists. PUT is used instead to create or retrieve a key and will always return the same result for each query or generate an error if the request body contains key metadata that doesn't match the key's on-disk data structures (this behavior may change to a replacement rather than error in the future).

Overview and Endpoints

This section illustrates actions, endpoints, and verbs. The following are routes defined by KeyStar:

Authentication and authorization
Action Verb Endpoint
Request authentication token GET /authorize/{id:string}[?duration={int}]
Submit challenge response POST /authorize/{id:string}
Key Management: Global Namespace
Action Verb Endpoint(s)
Retrieve key GET /keyring/{keyring:string}
/keyring/{keyring:string}/{key:string}
/{namespace:string}/keyring/{keyring:string}
/{namespace:string}/keyring/{keyring:string}/{key:string}
Update key PATCH /keyring/{keyring:string}/{key:string}
/{namespace:string}/keyring/{keyring:string}/{key:string}
Create or retrieve key PUT /keyring/{keyring:string}/{key:string}
/{namespace:string}/keyring/{keyring:string}/{key:string}
Create or fail if exists POST /keyring
/{namespace:string}/keyring
Rotate key ring POST /rotate/{keyring:string}
/{namespace:string}/rotate/{keyring:string}
Delete key or key ring DELETE /keyring
/{namespace:string}/keyring
Delete key DELETE /keyring/{keyring:string}/{key:string}
/{namespace:string}/keyring/{keyring:string}/{key:string}
Create using template POST /template/
/tmeplate/{namespace:string}

Note: All key management endpoints can be prefixed with the path /global/ to avoid confusion that may be caused when user namespaces are introduced. For example, retrieving a key can be performed via either the /keyring/{keyring:string} or /global/keyring/{keyring:string} endpoints.

Utility Requests
Action Verb Endpoint(s)
Generate random bytes GET /generate/bytes
Generate a key entitied POST /generate/key
Generate a composite key POST /generate/composite-key
Sign submitted data POST /generate/signature

Note that signing submitted data via the /generate/sign endpoint does not allow the submission of arbitrary data by anyone. All utility endpoints are authenticated and require the appropriate Authorization: Bearer header to be present for the given user. Moreover, uploaded data is limited to the default POST body size limit (usually 10 megabytes). If more data is required, it's a better idea to submit a signature of the data and sign that.

Authentication API

KeyStar's authentication API is based around a challenge-response mechanism whereby the requesting client submits its ID token, the server responds with a random base64-encoded string of bytes as its challenge, and the client then signs this challenge with its own key using HMAC SHA512_256. Since both the client and server share the same secret tokens for a given client ID, the server is then able to validate the submitted signature using its copy. If the authentication process is successful, a token will be returned for use as an Authorization: Bearer token that will validate all future requests. The bearer token must be present in all requests to endpoints other than /authorize.

The initial authentication request must be submitted to the /authorize/{id:string} endpoint using a GET request where {id:string} is the client's identification token. This will return a response similar to the following:

{
    "challenge": "<base64 encoded-challenge token>"
}

Challenge tokens will only persist for 5 minutes (300 seconds). If a more limited-duration token is required, the optional query string variable duration may be specified. For example, requesting a challenge token that is valid for no more than 10 seconds may be obtained by querying /authorize/{id:string}?duration=10.

Clients must then submit a POST request containing the following response to the /authorize/{id:string} endpoint:

{
    "challenge": "<original base64-encoded challenge token>",
    "response": "<base64-encoded signature>",
    "algorithm": "(sha512_256|sha512_385|sha512|sha256):default:sha512_256"
}

If the algorithm field is not specified, challenge authorization will default to sha512_256. As of this writing, only sha512_256 is implemented with others soon to be included.

After the challenge-response has been submitted via POST, the server will return one of several possible values:

  • 400, bad request; indicates the request was missing an appropriate field or could not be parsed, such as invalid JSON or base64-encoding.
  • 500, internal server error; indicates that an error occurred while validating the challenge that the server could not account for. This may be due to improper configuration or failure to run keystar admin init.
  • 200, plus a JSON-encoded response (see below).

Once successful, the server will return a JSON response containing a base64-encoded token for use in the Authorization: Bearer client header:

{
    "authorization": "<base64-encoded authorization token>"
}

This token may be used more or less indefinitely. Timeouts or requests to limit the token's duration will eventually be supported.

Identification tokens for single-occupancy configurations are analogous to the tokens created for the administrative account and are generated with the keystar admin init command. This command can be run multiple times if the system keys have already been generated to retrieve the administrative ID and secret tokens. Note that for multi-tenancy configurations, tokens may have to be generated by the administrative account. Details on how user authentication will work in the future are currently being fleshed out. Specialized API tokens are planned in the future that will be analogous to ID/secret pairs and will be usable in either single occupancy or multi-tenant configurations, with the administrative or user accounts able to create more specialized tokens with their own permissions.

Key Management API

Key management is broken down into eight separate categories:

Each request URI supports optional namespace members, as a string, which define unique namespaces for a given set of key rings. Presently, two types of namespace containers are planned: Global, which is currently available via the /global/ root path (or, optionally, leaving the root path absent); and /user/, which will define a user-level namespace or global namespace. Only the global namespace is currently implemented.

Retrieval

Key retrieval can be used to retrieval all keys that are members of a specified key ring, individual keys, or composite keys. As mention earlier, composite keys are a special type of key of which two independent components comprise the parent "key." These component keys are intended for use as cipher and HMAC keys for cryptographic functions (encrypting and decrypting) and signing (signature creation and validation). At present, KeyStar makes no distinction between standard and composite keys, storing both as independent members of a given key ring.

Endpoints

API retrieval is performed via GET requests made to the endpoints:

  • /keyring/{keyring:string}[?key=<name:string>[&type=composite]]
  • /keyring/{keyring:string}/{key:string}[?type=composite]
  • /{namespace:string}/keyring/{keyring:string}[?key=<name:string>[&type=composite]]
  • /{namespace:string}/keyring/{keyring:string}/{key:string}[?type=composite]

As illustrated above, endpoints that do not specify the key name as part of the request URI can have the name provided via a query variable called key. Optionally, composite keys may be requested by appending the query string type=composite. Presently, composite and key are the only valid values for type; all other values will result in a 400 status code returned by the server.

If no name is provided, KeyStar will return all keys associated with the requested key ring.

The {namespace:string} URI prefix instructs KeyStar to return keys assigned to the given namespace.

Return Types

Key retrieval returns two schemas depending on the value of key, whether provided via the request URI or query string. Individual fields may be absent if their value is considered empty (zero, empty string, or false).

For specific keys (this is the Key schema):

Field name Type Always Present Description
name string yes Key name
length integer yes Length in bytes
created string yes ISO 8601 timestamp in UTC (date + time)
encoded string yes Base64-encoded key data
ttl integer no Time to live before expiration
delete_after integer no Delete key after expiration (seconds)
rotate_after integer no Rotate key after expiration (seconds)

For composite keys the individual key schema is as above but is contained within an encapsulating parent object as follows and individual key members do not contain a name field:

Field name Type Always Present Description
name string yes Composite key name
cipher Key yes Cipher key contents; uses above schema
hmac Key yes HMAC key contents; uses above schema

In the above table, the type Key is a placeholder for the stand alone key schema but is absent the name field.

For listing the contents of a key ring, the returned JSON schema is an array containing Key types. Composite keys are currently not handled separately as there is currently no way to differentiate them from ordinary keys without persisting additional data with the key. Further, composite keys may be stored with names identical to stand alone keys:

Status Codes

Key retrieval presently returns the following HTTP status codes:

Code Condition or Description
400 If an incorrect key type was specified
401 If the Authorization: Bearer token was absent
404 If the specified namespace, key ring, or key do not exist
500 If an error was encountered or the response could not be encoded
404 If the namespace or key ring could not be found or keys could not be listed
500 If the key ring could not be rotated or the response could not be encoded
Creation or Retrieval (PUT)

KeyStar provides two options for creating keys--this is one such option. By submitting key options via a PUT request, KeyStar will attempt to create the key with the specified configuration, or if the key already exists, it will return the key matching the request. At present, if the specified key already exists but its creation options differed from those requested by the client, an error will be returned. This may be changed in the future to follow PUT semantics more precisely.

This method of key creation is known as "create-or-retrieve."

Endpoints

API create-or-retrieval for keys may be done by submitting PUT requests to the following endpoints:

  • /keyring/{keyring:string}/{key:string}[?type=composite]
  • /{namespace:string}/keyring/{keyring:string}/{key:string}[?type=composite]

where both endpoints may be prefixed with /global to specify the global namespace and to avoid confusion when user namespaces are introduced. Omitting the /global prefix also selects the global namespace.

For key creation, both the key ring and key names must be specified. An optional namespace identifier may be supplied as well to create a key ring within the requested namespace. All of the options, namespace, keyring, and key are string values. These values will be URL-decoded before submission to the backend and may contain unicode characters.

Composite keys may be generated with this endpoint by supplying the query string type=composite. Presently, composite and key are the only valid values for type; all other values will result in a 400 status code.

PUT requests must contain the headers Content-Type: application/json or Content-Type: text/json to be correctly parsed. application/json is preferred. If the Content-Type header is not present, the server will return a 400 BAD REQUEST.

Create-or-retrieve endpoints must be supplied with a JSON-encoded request body. For individual keys, the schema may contain the following fields:

Field name Type Required Description
length integer yes Requested key length in bytes
ttl integer no Time to live in seconds; expires when exhausted
delete_after integer no Delete key after expiration (seconds)
rotate_after integer no Rotate key after expiration (seconds)

For composite keys, the schema may instead contain:

Field name Type Required Description
cipher_length integer yes Cipher key length in bytes
hmac_length integer yes HMAC key length in bytes
ttl integer no Time to live in seconds; expires when exhausted
delete_after integer no Delete key after expiration (seconds)
rotate_after integer no Rotate key after expiration (seconds)

Note: Only the length field or cipher_length and hmac_length fields are required for ordinary keys and composite keys, respectively. All other fields are optional and may be omitted.

Return Types

Create-or-retrieve returns a schema similar to GET requests.

For stand alone keys:

Field name Type Always Present Description
name string yes Key name
length integer yes Key length
created string yes ISO 8601 timestamp in UTC (date + time)
encoded string yes Base64-encoded key data
ttl integer no Time to live before expiration (seconds)
delete_after integer no Delete key after expiration (seconds)
rotate_after integer no Rotate key after expiration (seconds)

For composite keys the individual key schema is as above but is contained within an encapsulating parent object as follows and individual key members do not contain a name field:

Field name Type Always Present Description
name string yes Composite key name
cipher Key yes Cipher key contents; uses above schema
hmac Key yes HMAC key contents; uses above schema

In the above table, the type Key is a placeholder for the stand alone key schema but is absent the name field.

Examples

In the following examples, we use cURL to create an assortment of keys. The server is configured to use localhost and the default port (9911). For brevity we omit the Authorization: Bearer and Content-Type: application/json headers. If you're following along with these examples, you will need to run the command keystar client authenticate, copy the generated Authorization: Bearer header and supply the appropriate content type, e.g.:

$ curl -H "Authorization: Bearer <token string>" -H 'Content-Type: application/json' ... <url>

Example 1: Create a key with a length of 32 and no TTL within the key ring "testing" using the key name "demo":

$ curl -d '{"length":32}' -X PUT http://localhost:9911/keyring/testing/demo

Example 2: Create a composite key named "demo-composite" with a cipher key of length 32 and an HMAC key of length 128 within a key ring named "test-composite:"

$ curl -d '{"cipher_length":32,"hmac_length":128}' -X PUT http://localhost:9911/keyring/test-composite/demo-composite?type=composite

Example 3: Create a key named "ttl-demo" under the key ring "expires" with a length of 16 and a TTL of 5 minutes (300 seconds):

$ curl -d '{"length":16,"ttl":300}' -X PUT http://localhost:9911/keyring/expires/ttl-demo

Example 4: Create a new key ring "expires" under the namespace "demo" with a key named identically above to example 3 (ttl-demo) with similar attributes except for the length:

$ curl -d '{"length":32,"ttl":300}' -X PUT http://localhost:9911/demo/keyring/expires/ttl-demo
Status Codes

Create-or-retrieve returns the following HTTP status codes:

Code Condition or description
400 If a required field is missing, the request body could not be parsed, or an attempt is made to overwrite a custom key
401 If the Authorization: Bearer header is absent
404 If a failure occurs while loading the key ring, namespace, or key[^2]
500 If a failure occurs while updating the key or encoding the response
Creation or Failure (POST)

Creation-or-failure is the second method KeyStar exposes for creating keys. This method, using POST, will attempt to create a key with the specified name and properties, and will fail if the key already exists on disk. This method is useful if you wish to avoid overwriting existing keys.

Endpoints

Although similar to the create-or-retrieval API, the create-or-failure API does not allow specifying the key ring or key name in the request URI. Instead, these values must be supplied via the POST request body.

  • /keyring[?type=composite]
  • /{namespace:string}/keyring[?type=composite]

As with create-or-retrieve, both endpoints may be prefixed with /global for specifying the global namespace. Omitting this prefix is allowable and synonymous.

Composite keys may be generated with this endpoint by supplying the query string type=composite with the request. Presently, composite and key are the only valid values for type; all other values will result in a 400 BAD REQUEST status code.

POST requests, as with PUT, must contain the headers Content-Type: application/json or Content-Type: text/json to be correctly parsed. application/json is preferred. if the Content-Type header is omitted, the server will return a 400 BAD REQUEST.

Create-or-failure must be supplied with a JSON-encoded request body. For individual keys, the schema is similar to PUT but must include the name and keyring fields:

Field name Type Required Description
name string yes Key name
keyring string yes Key ring
length integer yes Requested key length in bytes
ttl integer no Time to live in seconds; expires when exhausted
delete_after integer no Delete key after expiration (seconds)
rotate_after integer no Rotate key after expiration (seconds)

For composite keys, the schema may instead contain:

Field name Type Required Description
name string yes Key name
keyring string yes Key ring
cipher_length integer yes Cipher key length in bytes
hmac_length integer yes HMAC key length in bytes
ttl integer no Time to live in seconds; expires when exhausted
delete_after integer no Delete key after expiration (seconds)
rotate_after integer no Rotate key after expiration (seconds)
Return Types

Create-or-failure returns a schema similar to GET and PUT requests.

For stand alone keys:

Field name Type Always Present Description
name string yes Key name
length integer yes Key length
created string yes ISO 8601 timestamp in UTC (date + time)
encoded string yes Base64-encoded key data
ttl integer no Time to live before expiration (seconds)
delete_after integer no Delete key after expiration (seconds)
rotate_after integer no Rotate key after expiration (seconds)

For composite keys the individual key schema is as above but is contained within an encapsulating parent object as follows and individual key members do not contain a name field:

Field name Type Always Present Description
name string yes Composite key name
cipher Key yes Cipher key contents; uses above schema
hmac Key yes HMAC key contents; uses above schema

In the above table, the type Key is a placeholder for the stand alone key schema but is absent the name field.

Examples

In the following examples, we use cURL to create an assortment of keys. The server is configured to use localhost and the default port (9911). For brevity we omit the Authorization: Bearer and Content-Type: application/json headers. If you're following along with these examples, you will need to run the command keystar client authenticate, copy the generated Authorization: Bearer header and supply the appropriate content type, e.g.:

$ curl -H "Authorization: Bearer <token string>" -H 'Content-Type: application/json' ... <url>

Reminder: If the request method (-X) is not specified, cURL will default to POST.

Example 1: Create a key with a length of 32 and no TTL within the key ring "testing" using the key name "demo":

$ curl -d '{"keyring":"testing","name":"demo","length":32}' http://localhost:9911/keyring

Example 2: Create a composite key named "demo-composite" with a cipher key of length 32 and an HMAC key of length 128 within a key ring named "test-composite:"

$ curl -d '{"keyring":"test-composite","name":"demo-composite","cipher_length":32,"hmac_length":128}' http://localhost:9911/keyring?type=composite

Example 3: Create a key named "ttl-demo" under the key ring "expires" with a length of 16 and a TTL of 5 minutes (300 seconds):

$ curl -d '{"keyring":"expires","name":"ttl-demo","length":16,"ttl":300}' http://localhost:9911/keyring

Example 4: Create a new key ring "expires" under the namespace "demo" with a key named identically above to example 3 (ttl-demo) with similar attributes except for the length:

$ curl -d '{"keyring":"expires","name":"ttl-demo","length":32,"ttl":300}' http://localhost:9911/demo/keyring
Status Codes

Create-or-failure returns the following HTTP status codes:

Code Condition or description
400 If a required field is missing or the request body could not be parsed
401 If the required Authorization: Bearer token is absent
404 If the specified namespace could not be found or retrieving the key ring fails[^3]
500 If the key could not be created or encoded to JSON during the response cycle
Update

KeyStar's update semantics use a slightly modified JSON PATCH (RFC 6902) format. In particular, not all operations are applicable to KeyStar's storage mechanics, nor is the "path" field fully analogous to those attributes exposed on our data structures and is not entirley useful for modifying keys. (For example, keys cannot be modified directly, only the key ring.)

Our modifications therefore limit "path" to only those values that can be modified following key creation, and we limit the operations to test, add, replace (synonym for add), and remove.

Endpoints

The update API for keys may be performed by submitting PATCH requests containing RFC 6902-formatted JSON patch sets to the following endpoints:

  • /keyring/{keyring:string}/{key:string}
  • /{namespace:string}/keyring/{keyring:string}/{key:string}

where both endpoints may be prefixed with /global to specify the global namespace and to avoid confusion when user namespaces are introduced. Omitting the /global prefix also selects the global namespace.

Acceptable operations are:

Operation Description
test Tests the specified value against the specified key ring or key
add Updates the value of the specified field provided by path
replace Synonym for add
remove Resets the specified values to their defaults

The path field for PATCH updates may be one of ttl, delete_after, rotate_after, length, or encoded. However, the precise inclusion of these fields depends on the operation. The following is a support matrix for operations and what fields they allow:

Operation Fields Remarks
test ttl, delete_after, rotate_after, length Tests the values of these fields specifically, and returns an error if no match is found
add encoded, ttl, delete_after, rotate_afte Allows changing of the specified fields; encoded is supported only for custom keys
replace same as add same as add
rmeove ttl, delete_after, rotate_after Only those fields that can be sensibly "reset" are included; even for custom keys, encoded cannot be reset, and the key must instead be deleted

Other operations such as move or copy will return errors.

Return Types

The update API returns a schema similar to GET, PUT, and POST requests.

For stand alone keys:

Field name Type Always Present Description
name string yes Key name
length integer yes Key length
created string yes ISO 8601 timestamp in UTC (date + time)
encoded string yes Base64-encoded key data
ttl integer no Time to live before expiration (seconds)
delete_after integer no Delete key after expiration (seconds)
rotate_after integer no Rotate key after expiration (seconds)

Currently, composite keys are not supported and therefore cannot be changed via this interface. You may have noticed an absence of the expected query stringof key names. variables.

Status Codes

The update API returns the following HTTP status codes:

Code Condition or Description
400 If a malformed patch set was submitted or the request could not be parsed
401 If the Authorization: Bearer token was absent
404 If the namespace, key ring, or key could not be found
500 If the update fails for any reason or the response could not be encoded
Rotation

Key rotation is a mechanism for cycling out old secrets and replacing them with new data. This is generally used if a key is to be retired from use or if it is suspected that the key data has been leaked. KeyStar provides a mechanism for rotating keys via the HTTP API so clients can perform this task manually if required.

At present, automatic rotation is not supported. Likewise, multiple key versions are not currently stored nor is any method exposed for retrieving prior copies of a rotated key. Both of these features are planned in the near future.

Endpoints

Key rotation may be performed by submitting a POST request to the following endpoints:

  • /rotate/{keyring:string}
  • /{namespace:string}/rotate/{keyring:string}

Note that only the keyring and namespace values may be present. As an artifact of how KeyStar stores keys within key rings, individual keys cannot be rotated; only the entire key ring can be rotated manually. Automatic key rotation will eventually provide a method for rotation only those keys that have expired, but this is not currently exposed via the HTTP API for manual intervention.

Currently, to trigger a key rotation, an empty POST request body must be submitted to one of the above endpoints, depending on the key ring's location in the namespace hierarchy. Eventually, additional options may be provided for changing rotation behaviors or for rotating only those keys that have expired.

Return Types

Key rotation returns a schema similar to GET requests listing all keys. Individual keys are defined by the following schema in a JSON-encoded format:

Field name Type Always Present Description
name string yes Key name
length integer yes Key length
created string yes ISO 8601 timestamp in UTC (date + time)
encoded string yes Base64-encoded key data
ttl integer no Time to live before expiration (seconds)
delete_after integer no Delete key after expiration (seconds)
rotate_after integer no Rotate key after expiration (seconds)

As with other request methods, empty fields (those containing a zero, an empty string, or other "falsey" values) are omitted.

For listing the contents of a key ring, the returned JSON schema is an array containing Key types. Composite keys are currently not handled separately as there is currently no way to differentiate them from ordinary keys without persisting additional data with the key. Further, composite keys may be stored with names identical to stand alone keys:

Status Codes

Key rotation currently defines the following HTTP status codes in its returned responses:

Code Condition or Description
401 If the Authorization: Bearer token was absent
404 If the namespace or key ring could not be found or keys could not be listed
500 If the key ring could not be rotated or the response could not be encoded
Key or Key Ring Deletion

Key or key ring deletion can be accomplished through distinct endpoints, each with greater granularity. This provides clients with a way of either a) submitting the entire POST data with no indication via the request URI which key or key ring is going to be deleted or b) increasing specificity for visually recognizing which key or key ring is being deleted. All of the endpoints specified below achieve the same outcome and require the same request body contents.

Endpoints
  • /keyring/
  • /{namespace:string}/keyring/
  • /keyring/{keyring:string}/
  • /{namespace:string}/keyring/{keyring:string}/
  • /keyring/{keyring:string}/{key:string}
  • /{namespace:string}/keyring/{keyring:string}/{key:string}

As with all other endpoints, each URI path may be prefixed with /global for specifying the global namespace. Omitting this prefix is allowable and synonymous.

Bear in mind that unlike other actions in the KeyStar HTTP API, each of the endpoints above both require the same request body and are handled by the same code. The only difference is immediate visual recognition of intent; therefore, it is advisable to either use the endpoint that reflects the closest "intent to delete" or use the bare endpoint and carefully commented JSON data to communicate intent.

POST requests, as with all other actions, must contain the headers Content-Type: application/json or Content-Type: text/json to be correctly parsed. application/json is preferred. if the Content-Type header is omitted, the server will return a 400 BAD REQUEST.

However, unlike other actions, DELETE does not accept query string values for specifying the type of key to delete. Instead, composite keys must be specified via the post body.

DELETE accepts the following JSON schema. Either the key ring name or the key ring name and the key name must be specified, depending on which is requested for deletion. If these values are also specified in the request URI, both must match.

Field name Type Required Description
keyring string yes Key ring to delete.
key string yes* Key to delete.
type string yes* "Composite" if deleting composite keys or empty.

Required values marked with an asterisk (*) are only required if the intent is to delete a key or a specific type of key. In particular, the key value is also required if the requested endpoint also contains the key.

At present the only valid values accepted by type arer composite and key. If type is not specified or is empty, the value is assumed to be key. Any other value will result in a 400 BAD REQUEST status code.

Return Types

On success, DELETE returns the simplest type of all the endpoints: {"status":"ok"}. No other information is provided.

This may change in future iterations to include the name(s) of the key ring or keys that were deleted.

Status Codes

DELETE defines the following HTTP status codes on failure.

Code Condition or description
400 If the request could not be parsed or a required value was absent.
404 If an invalid namespace, key ring, or key were specified.
500 If the server is unable to remove the key or key ring.
Templates

Templates provide a means of generating a default set of keys and key rings for KeyStar. This is useful for applications that need a list of specific predetermined keys after, for example, installation or their initial setup. Templates can be used to quickly established a known set of key rings and their keys for other uses as well and can be used to generate composite keys.

Endpoints

The template system exposes the following endpoints:

  • /template/
  • /template/{namespace:string}

As with most other endpoints, namespace may be specified to restrict the key ring and key creation to a specific namespace. Otherwise, the global namespace will be used instead. Likewise, both endpoints may be prefixed with /global to avoid confusion with other namespace categories, but its inclusion is not required.

Be aware that POSTing to the template endpoints is not idempotent and returns a different response depending on its internal state. Keys that do not exist, as described by the template, will be created; all others will be ignored. Responses from the endpoint will include a list of keys and their state.

There are two schemas that comprise a template request body: The outer definition and the per-key descriptor. The outer definition is defined as follows:

Field Type Required Description
version integer yes Template version identifier. Currently 1.
template array yes Key descriptors. See below.

The key descriptor is as follows, per key:

Field Type Required Description
keyring string yes Key ring name.
ttl integer no Time to live before expiration.
keys array no Key definition. Not required when creating key rings. See below.

And finally, key definitions:

Field Type Required Description
name string yes Key name.
length integer yes* Length in bytes. Required for non-composite keys.
cipher integer yes* Cipher key length. Required for composite keys.
hmac integer yes* HMAC key length. Required for composite keys.
composite boolean no true for composite keys; false or absent otherwise.

The above schemas are information dense and contain cross-references. Therefore an example may be more helpful.

In the following illustration, we create an empty key ring tokens; a key ring application containing two keys, one which is a composite key, and another that is a stand alone key; and finally, we create another key ring named users that contains three separate keys.

{"version": 1, "template": [
  {"keyring": "tokens", "ttl": 86400},
  {"keyring": "application", "keys": [
    {"name": "auth", "cipher": 32, "hmac": 128, "composite": true},
    {"name": "download", "length": 128},
  ]},
  {"keyring": "users", "ttl": 86400, "keys": [
    {"name": "user1", "length": 32},
    {"name": "user2", "length": 32},
    {"name": "user3", "length": 32}
  ]}
]}
Return Types

Template requests return a schema that communicates whether or not the request failed for a given key ring or key. Responses contain an array where each entry is defined as:

Field Type Required Description
keyring string yes Key ring name.
key string no Key name. Only provided if defined in the request.
state string yes Key state; one of ok, exists, or failed.
Status Codes

Template requests may return one of the following HTTP status codes:

Code Condition or description
400 If the request body could not be parsed or an invalid version was specified
401 If the Authorization: Bearer header was absent.
404 If the requested namespace (when submitted to the namespaced endpoint) does not exist
500 If the server cannot create the key ring, key, or the response could not be encoded
Utility API

The utility API provides convenience interfaces for generating keys, randomness, and a handful of other helpful tools. To provide additional protections against unwanted access, all of these endpoints require authentication via the Authorization: Bearer header. None of these endpoints persist submitted data or generated responses. In the case of the utility key generation functions, the submission semantics are similar to those used by PUT and POST.

Endpoints

The following endpoints are defined by the utility API:

  • /generate/bytes
  • /generate/key
  • /generate/composite-key
  • /generate/signature

We'll discuss each of these endpoints in sequence.

Byte Generation

The byte generation endpoint /generate/bytes accepts only GET requests and requires the query string variable count to be present. If count is absent or contains invalid values, this endpoint will return a 400 BAD REQUEST.

Byte generation returns a simple JSON structure containing the following schema:

Field Type Description
bytes string Base64-encoded random bytes

Future implementations may provide options for changing the encoding type.

No more than 65536 bytes may be requested at a time.

This endpoint may be configured by administrators to rate limit requests.

Examples
Key Generation

Key generation offers semantics similar to PUT and POST with the exception that none of the generated keys are stored. These endpoints are mostly useful for testing clients or parsers and provide additional features for manipulating fields that aren't present with the key management API.

These endpoints accept JSON schemas identical to those of PUT and POST in the key management API, but the returned values may be manipulated with the addition of query string variables as outlined below.

For standard key creation, the submitted JSON object must adhere to this structure:

Field name Type Required Description
length integer yes Requested key length in bytes
name string no Optional key name to return in data
ttl integer no Time to live in seconds; expires when exhausted
delete_after integer no Delete key after expiration (seconds)
rotate_after integer no Rotate key after expiration (seconds)

For composite key creation, the submitted JSON must be as follows:

Field name Type Required Description
cipher_length integer yes Cipher key length in bytes
hmac_length integer yes HMAC key length in bytes
name string no Optional key name to return in data
ttl integer no Time to live in seconds; expires when exhausted
delete_after integer no Delete key after expiration (seconds)
rotate_after integer no Rotate key after expiration (seconds)

Note the absence of key and key ring names.

To control key behavior, the following query string variables may be set:

Field name Type Description
created string Sets the creation date to the specified value.
count integer Generates count keys using the submitted data as a template.
randomize boolean Randomize fields in returned data. Requires count > 0.

Both key generation endpoints accept either POST or PUT and behave the same accordingly. As with the key management API, fields that are set to a zero (or empty) value are omitted from the returned data.

Pay particular attention to the count and randomize query values. If count is set to a value greater than 0 and randomize is true, all key data returned will contain random values regardless of the request body content. Otherwise, count will merely return similar copies of the requested key, randomizing only the encoded data.

count cannot be larger than 10 and key lengths cannot exceed 65536.

This endpoint may be configured by administrators to rate limit requests.

Signature Generation

Signature generation provides a means of signing input data with the authenticated user's secret key. Other keys may be specified as part of the request, but the primary use case for this endpoint is to provide a means of testing client signatures.

This endpoint only accepts POST requests and may be limited to a request body of no more than 10 megabytes. For larger files, consider signing only the hash.

The POST request body must contain the JSON-formatted schema:

Field name Type Required Description
data string yes Source data to sign. Must be a string or base64-encoded.
type string no Data type; one of "string" or "base64." Default: "base64."
keyring string no Key ring name if using a key other than the authenticated user's secret key
key string no Key name if using a key other than the authenticated user's secret key
algorithm string no Signature algorithm. One of "sha512", "sha512_224", "sha512_256", "sha224", or "sha256".

Responses from this endpoint will contain the following JSON-formatted schema:

Field name Type Description
signature string Signature derived from submitted data
algorithm string Algorithm used to generate signature

Cryptographic Utilities

KeyStar provides an assortment of cryptographic utilities. These interface directly with KeyStar's key types (primarily composite keys for convenience) and provide features such as encrypt-then-MAC and time-based token generation. Wrappers such as these may be used for encrypting cookies or user session data; in the case of time-based token generation, this may be useful for generating password reset tokens and similar without littering the host application database with ephemeral data. As a bonus example, KeyStar's key namespacing features allow applications to create isolated key stores specifically for generating user tokens that automatically invalidate via key rotation, rendering them completely unusable in the event an adversary attempts to attack the token signature to control its contents.

[^1]: This needs to be a word.

[^2]: A 500-level error may be more appropriate for this circumstance, but we elected to use 404 instead since it's otherwise impossible to differentiate to the client the nature of the error. 404 responses to PUT requests may require administrators to enable the debug log to determine the nature of the error. This response type may be changed in a future iteration to something more appropriate.

[^3]: As with the previous footnote, a 500-level error may be more appropriate for this circumstance. Although it may make little sense from the perspective of a pure-RESTful API, at least it's consistent!

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewOptions

func NewOptions(backend string) *kt.Options

Options for conveying configuration information to KeyStar.

Types

type KeyStar

type KeyStar struct {
	// contains filtered or unexported fields
}

KeyStar is the primary entrypoint for clients and servers. All KeyStar services are based on this client (including the API server), and in-process implementations should start here.

func NewKeyStar

func NewKeyStar(options *kt.Options) (*KeyStar, error)

func (*KeyStar) KeySpace

func (ks *KeyStar) KeySpace() *kt.KeySpace

KeySpace returns the configured key space for this KeyStar instance.

API fragility warning: Future versions may be able to retain or configure multiple KeySpace instances. Do not expect this method to always accept or return the same KeySpace for each call. It may eventually require a string identifying a symbolic name for any given KeySpace.

Directories

Path Synopsis
cmd
fs
mem
api

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL