Client secret store codec
1. Encrypt or hash client secrets in storage
Connect2id server deployments with a backend
database which doesn’t encrypt its
data can implement server-side encryption of the client secrets. This is made
possible by a dedicated plugin interface
(SPI) for transforming the
client_secret
before writing it to the database, and then again upon its
retrieval.
Encryption, or alternatively, hashing the client secrets, is the recommended security measures to minimise the risk of a credential leak from the database.
Recommended methods:
While hashing is easier to implement because there is no symmetric key to be
managed, it cannot be used with a client using client_secret_jwt
authentication or an HMAC JOSE algorithm that requires the Connect2id server to
have the secret in clear text. This means clients registered for the following
must use encryption instead of hashing of the client_secret
:
- Clients registered for client_secret_jwt authentication.
- Clients registered or ID token, UserInfo, request object (JAR) or JARM with the JWS algorithms HS256, HS384 or HS512.
- Clients registered to receive or submit JWTs encrypted with a shared AES key derived from the secret, the JWE algorithm dir.
Connect2id server deployment can also choose to dispense with the client secrets entirely, a superior security strategy, by allowing only public key based authentication - private_key_jwt, or the tls_client_auth / self_signed_tls_client_auth methods in RFC 8705.
The codec plugin SPI can also be used to import client secrets with arbitrary format from another server.
2. Client secret codec SPI
To plug in your own custom codec implement the ClientSecretStoreCodec SPI defined in the Connect2id server toolkit:
Git repo | https://bitbucket.org/connect2id/server-sdk |
---|
Features of the client secret codec SPI:
- Supports arbitrary encoding of
client_secret
values before writing them to the database. - Supports arbitrary decoding of
client_secret
values after retrieving them from the database. - Supports separate encoding / decoding of
client_secret
values for clients imported from another server using thepreferred_client_secret
parameter. - Provides access to the registered information for the client so that encoding decisions (e.g. encrypt or hash) can be made.
- Provides access to the Connect2id server JWK set where the client secret encryption AES key(s) can be stored.
- The seamless client secret rollover applies also to secrets transformed via this SPI.
If the Connect2id server detects an SPI implementation it will log its loading
under OP0138
.
INFO main MAIN - [OP0138] Loaded client secret store codec: com.nimbusds.openid.connect.provider.spi.secrets.impl.BCryptSecretCodec
3. Example codec
Sample BCrypt hashing codec with the BCrypt Java library:
import at.favre.lib.crypto.bcrypt.BCrypt;
import com.nimbusds.openid.connect.provider.spi.secrets.*;
import com.nimbusds.oauth2.sdk.auth.Secret;
import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
public class BCryptCodec implements ClientSecretStoreCodec {
@Override
public String encode(final Secret secret, final SecretCodecContext ctx) {
return BCrypt
.withDefaults()
.hashToString(4, secret.getValue().toCharArray());
}
@Override
public DecodedSecret decode(final String storedValue, final SecretCodecContext ctx) {
return DecodedSecret.createForHashedSecret(
otherSecret -> {
BCrypt.Result result = BCrypt
.verifyer()
.verify(
otherSecret.getValue().toCharArray(),
storedValue.toCharArray());
return result.verified;
}
);
}
}
Example transformation with BCrypt:
Plain text 256-bit secret, BASE64URL encoded: 5k4NOArtKpDYeBoxDoVwXswsIApyibpMIBWRgLdSyNM
BCrypt: $2a$04$a9uQ9Ka0usxqTCp/1je2iuS.qnVsXKe0Gjhh5kPEhnbInkseODhgS
4. Effect on the client registration API
If the codec cannot reverse the client secret because it is hashed the
client information responses
will contain a client_secret
, but its value will be set to null
.
Example response indicating a hashed client secret:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
"client_id" : "s6BhdRkqt3",
"client_secret" : null,
"client_secret_expires_at" : 1577858400
}
5. How to return the encoded secret in the client registration API
To return the encoded (stored) client secret in client information responses the codec plugin must call the withEncodedValue method before returning the DecodedSecret.
@Override
public DecodedSecret decode(final String storedValue, final SecretCodecContext ctx) {
return DecodedSecret.createForHashedSecret(
otherSecret -> {
BCrypt.Result result = BCrypt
.verifyer()
.verify(
otherSecret.getValue().toCharArray(),
storedValue.toCharArray());
return result.verified;
}
).withEncodedValue(storedValue);
}
The encoded value will then appear in a custom client_secret_encoded
field.
Example response including the hashed client secret value:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache
{
"client_id" : "s6BhdRkqt3",
"client_secret" : null,
"client_secret_encoded" : "$2a$04$a9uQ9Ka0usxqTCp/1je2iuS.qnVsXKe0Gjhh5kPEhnbInkseODhgS",
"client_secret_expires_at" : 1577858400
}
Note, the client_secret_encoded
will appear only in client
read responses.
6. How to import hashed client secrets
The custom preferred_client_secret
field in a client registration request can
be used to import hashed or
otherwise encoded secrets from the server of another vendor.
POST /clients HTTP/1.1
Host: demo.c2id.com
Content-Type: application/json
Authorization: Bearer ztucZS1ZyFKgh0tUEruUtiSTXhnexmd6
{
"grant_types" : [ "password" ],
"preferred_client_id" : "123456",
"preferred_client_secret" : "$2a$04$a9uQ9Ka0usxqTCp/1je2iuS.qnVsXKe0Gjhh5kPEhnbInkseODhgS"
}
The value of the preferred_client_secret
will appear as input to the
encodedImported
method of the ClientSecretCodec.