Hello,
I already sent this to Bitwarden support, pasting it here as well.
I can think of several implementations to handle the OTP code without exposing the OTP seed, first we need a feature…
Bitwaden Core Feature: OTP code derivation service hook
Add a feature to specify OTP code derivation service as external Restful service with two methods:
- Seal OTP seed
- Derive OTP code out of sealed OTP seed
Authentication will performed using short term public key signed JWT issued by Bitwarden based on the vault access permission.
The service serve as a driver to provide a custom logic to perform the seal and derivation.
We may need to modify OTP seed field in vault to be opaque long string instead of structured one as sealed seed may be long.
The existing implementation will be applied, if no Restful OTP derivation service URL is registered.
openapi: 3.0.0
info:
title: OTP derivation service
description: |-
This is an example of OTP derivation service that may be used
by password managers to derive OTP code while not exposing the
OTP seed to the client.
contact:
name: Alon Bar-Lev
email: [email protected]
version: 0.0.0
paths:
/sealOTPseed:
post:
summary: Seal OTP seed
description: Seal a OTP seed by the service keys
operationId: sealOTPseed
requestBody:
description: Seal a OTP seed by the service keys
content:
application/json:
schema:
type: object
required:
- seed
properties:
name:
type: string
description: a name for the seed
type:
type: string
default: TOTP
seed:
type: string
example: SFDF3 SFDF3 SFDF3 44FS G432
algorithm:
type: string
default: SHA1
tokenLength:
type: integer
default: 6
required: true
responses:
'400':
$ref: '#/components/responses/UnsupportedOTP'
'401':
$ref: '#/components/responses/UnauthorizedError'
'200':
description: Successful operation
content:
application/json:
schema:
type: object
properties:
sealedSecret:
type: string
example: cGFzc3dvcmQcGFzc3dvcmQcGFzc3dvcmQcGFzc3dvcmQ
/deriveOTPtoken:
post:
summary: Derive OTP token
description: Derive OTP token out of sealed seed
operationId: deriveOTPtoken
requestBody:
description: Derive OTP token out of sealed seed
content:
application/json:
schema:
type: object
properties:
sealedSeed:
type: string
example: cGFzc3dvcmQcGFzc3dvcmQcGFzc3dvcmQcGFzc3dvcmQ
required: true
responses:
'400':
$ref: '#/components/responses/InvalidOPTSeal'
'401':
$ref: '#/components/responses/UnauthorizedError'
'200':
description: Successful operation
content:
application/json:
schema:
type: object
required:
- token
properties:
token:
type: string
example: 123456
expiration:
type: string
format: date-time
sealedSeed:
type: string
description: |
A new seed to be provided in next iteration
This is an advanced feature which requires writable
vault at user side.
example: 3112332c3dvcmQcGFzc3dvcmQcGFzc3dvcmQcGFzc3EErrdvcmQ
components:
responses:
UnauthorizedError:
description: Access token is missing or invalid
UnsupportedOTP:
description: OTP settings are unsupported or invalid
InvalidOPTSeal:
description: The provided OTP seal is unsupported
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
security:
- bearerAuth: []
Bitwaden UI Feature: OTP user interface modifications
- User enters OTP seed:
- If no OTP code derivation service URL is registered to the collection/object: store the OTP seed as-is
- Else: Send the OTP seed to the service seal method and apply the output as OTP seed in vault
- User resolves OTP code:
- If no OTP code derivation service URL is registered to the collection/object: Derive the OTP code from the OTP seed locally.
- Else: Send the OTP seed to the service and apply output as OTP code
Advanced:
When user enters TOTP seed, user may choose to enter the sealed seed instead of the plaintext, when entering the sealed seed there is no need to call the service.
Solution principals
- Do not modify current product behavior, if no OTP code derivation Restful URL is provided the current logic applies.
- Access control to the OTP seed will be managed by the Bitwarden, no change in this regard.
- Implementation may choose not to make the OTP seed available at client side.
- OTP seed is not available to Bitwarden (aka zero-knowledge), unless Bitwarden implements an OTP code derivation Restful service.
- There may be multiple OTP code derivation services deployed each one may protect different set of keys if it will be possible to specify a URL per collection/entry.
Restful OTP code derivation service implementation
AWS based example of customer’s Restful service implementation:
- AWS lambda that uses KMS CMK to encrypt the OTP seed and metadata and encode using base64.
- AWS lambda that implements the Restful interface to validate the Bitwarden ticket and decrypt the TOTP seed and metadata and derive the code.
Enclave based example of Restful service implementation: Instead of lambda + KMS use enclave + platform generated key, this can run within HSM or within other secure environments such as Intel SGX.
Bitwarden may provide a default implementation of the OTP code derivation service, the default implementation will use Bitwarden CMK encryption, it may be as secure as the device Bitwarden uses. As Bitwarden have access to the OTP seed but not to the password, it still provide a good separation of concerns, better than having both the password and the TOTP seed exposed to the client.
In any case this implementation will allow customers to replace the service with their own implementation.
There may be more complex solution in which code derivation can be done using keys that are derived from Bitwarden and the customer, however, I think the above outlined solution is simple and provides a decent solution.
Regards,
Alon Bar-Lev