1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
|
Custodia API
============
Custodia uses a RESTful interface to give access to secrets.
Authentication and authorization are fully pluggable and are
therefore, not standardized.
Access paths are also not standardized, although the common practice
is to mount the secrets api under the /secrets URI
The Custodia API uses JSON to format requests and replies.
Key/Request formats
===================
A key is a dictionary that contains the 'type' and 'value' of a key.
Simple
------
Format:
{ type: "simple", value: <arbitrary> }
The Simple type is an arbitrary value holder. It is recommend but not
required to base64 encode binary values or non-string values.
The value must be representable as a valid JSON string. Keys are
validated before being stored, unknown key types or invalid JSON values
are refused and an error is returned.
NOTE: As an alternative it is possible to send a simple "raw" value by setting
the Content-type of the request to "application/octet-stream". In this case the
value will be base64 encoded when received and can be accessed as a base64
encoded value in the JSON string when the default GET format is used.
Sending the "Accept: application/octet-stream" header will instead cause the
GET operation to return just the raw value that was originally sent.
Key Exchange Message
--------------------
the Key Exchange Message format builds on the JSON Web Signature and the
JSON Web Encryption specification to build respectively the request and
reply messages.
The aim is to provide the Custodia server the means to encrypt the reply
to a client that proves possession of private key.
Format:
- Query arguments for GET:
type=kem
value=Message
- JSON Value for PUT/GET Reply:
{"type:"kem","value":"Message"}
The Message for a GET is a JWT (JWS):
(flattened/decoded here for clarity)
{ "protected": { "kid": <public-key-dentifier>,
"alg": "a valid alg name"},
"claims": { "sub": <name-of-secret>,
"exp": <unix-timestamp indicating expiration time>,
["value": <arbitrary> ]},
"signature": "XYZ...." }
Attributes:
- public-key-identifier: This is the kid of a key that must be known to the
Custodia service. If opportunistic encryption is desired, and the requesting
client is authenticated in other ways a "jku" header could be used instead,
and a key fetched on the fly. This is not recommended for the general case and
is not currently supported by the implementation.
- name-of-secret: this repeats the name of the secret embedded in the GET,
This is used to prevent substitution attacks where a client is intercepted
and its signed request is reused to request a different key.
- unix-timestamp: used to limit replay attacks, indicated expiration time,
and should be no further than 5 minutes in the future, with leeway up to 10
minutes to account for clock skews
Additional claims may be present, for example a 'value'.
The Message for a GET reply or a PUT is a JWS Encoded message (see above)
nested in a JWE Encoded message:
(flattened/decoded here for clarity):
{ "protected": { "kid": <public-key-dentifier>,
"alg": "a valid alg name",
"enc": "a valid enc type"},
"encrypted_key": <JWE_Encrypted_Key>,
"iv": <Initialization Vector>,
"ciphertext": <Encrypted Content>,
"tag": <Authentication Tag> }
Attributes:
- public-key-identifier: Must be the server public key identifier.
reply (see above). Or the server public key for a PUT.
- The inner JWS payload will typically contain a 'value' that is
an arbitrary key.
example: { type: "simple", value: <arbitrary> }
REST API
========
Objects
-------
There are essentially 2 objects recognized by the API:
- keys
- key containers
Key containers can be nested and named arbitrarily, however depending on
authorization schemes used the basic container is often named after a group or
a user in order to make authorization checks simpler.
Getting keys
------------
A GET operation with the name of the key:
GET /secrets/name/of/key
A query parameter named 'type' can be provided, in that case the key is
returned only if it matches the requested type.
Returns:
- 200 and a JSON formatted key in case of success.
- 401 if authentication is necessary
- 403 if access to the key is forbidden
- 404 if no key was found
- 406 not acceptable, key type unknown/not permitted
- 501 if the API is not supported
Storing keys
------------
A PUT operation with the name of the key:
PUT /secrets/name/of/key
The Content-Type MUST be 'application/json'
The Content-Length MUST be specified, and the body MUST be
a key in one of the valid formats described above.
Returns:
- 201 in case of success.
- 400 if the request format is invalid
- 401 if authentication is necessary
- 403 if access to the key is forbidden
- 404 one of the elements of the path is not a valid container
- 405 if the target is a directory instead of a key (path ends in '/')
- 406 not acceptable, key type unknown/not permitted
- 409 if the key already exists
- 501 if the API is not supported
Deleting keys
-------------
A DELETE operation with the name of the key:
DELETE /secrets/name/of/key
Returns:
- 204 in case of success.
- 401 if authentication is necessary
- 403 if access to the key is forbidden
- 404 if no key was found
- 406 not acceptable, type unknown/not permitted
- 501 if the API is not supported
Listing containers
------------------
A GET operation on a path that ends in a '/' translates into
a listing for a container.
GET /secrets/container/
Implementations may assume a default container if none is explicitly
provided: GET /secrets/ may return only keys under /<user-default>/*
Returns:
- 200 in case of success and a dictionary containing a list of all keys
in the container and all subcontainers.
- 401 if authentication is necessary
- 403 if access to the key is forbidden
- 404 if no key was found
- 406 not acceptable, type unknown/not permitted
- 501 if the API is not supported
Creating containers
-------------------
A POST operation on a path will create a container with that name.
A trailing '/' is required
POST /secrets/mycontainer/
Default containers may be automatically created by an implementation.
Returns:
- 200 if the container already exists
- 201 in case of success.
- 400 if the request format is invalid
- 401 if authentication is necessary
- 403 if access to the key is forbidden
- 404 one of the elements of the path is not a valid container
- 406 not acceptable, type unknown/not permitted
- 501 if the API is not supported
Deleting containers
-------------------
A DELETE operation with the name of the container:
DELETE /secrets/mycontainer/
Returns:
- 204 in case of success.
- 401 if authentication is necessary
- 403 if access to the container is forbidden
- 404 if no container was found
- 406 not acceptable, type unknown/not permitted
- 409 if the container is not empty
- 501 if the API is not supported
|