Hi, I’m looking to use Bitwarden secrets manager as a secure store programmatically, but running it in lightweight js edge environments where wasm support is finicky (for the examples in this post I’m using cloudflare workers). What I have so far:
You generate an access token with correct permission to project
access token is in format 0.<client id uuid>.<client secret>:<base64>
I assume the base64 is access token’s unique encryption key (#3)
sequenceDiagram
participant worker
participant bw as bw-api
worker->>bw: POST https://identity.bitwarden.com/connect/token with accessToken
bw-->>worker: Response with access_token
worker->>bw: GET https://api.bitwarden.com/organizations/:orgId/secrets with jwt
bw-->>worker: Response with projects and secrets
worker->>bw: POST https://api.bitwarden.com/secrets/get-by-ids with jwt and secret ids
bw-->>worker: Return secret values
So where I’m stuck:
The encrypted values all seem to be in the format of 2.<base64>|<base64>|<base64>. The 2nd base64 seems to be the ciphertext as that’s the only one that changes related to the saved secret length. The bws cli does the decryption of these values all client side, these are the only network requests it makes.
So which algorithms and which values do I plug into node:crypto or Web Crypto to be able to decrypt and encrypt secrets back and forth from code to bitwarden?
Disclaimer: I wrote a response (below) before I realized that you had posted in the Secrets Manager section of the forum, so my answers are all related to the Bitwarden Password Manager. Unfortunately, I don’t know the extent to which the Secrets Manager and the Password Manager use the same encryption algorithms — but if I had to guess, I wouldn’t be surprised if the information in my comment also applies to the Secrets Manager.
[Original Response]
Basically, the 2. means that AES-CBS-256 encryption with HMAC-SHA256 is being used, and the threee Base-64 components are the initial value, the ciphertext, and the MAC.
The encryption algorithms are described in the documentation, here and here. You may also find Bitwarden’s Interactive Cryptography Page (and its source code) to be useful. In addition, all of the source code for Bitwarden’s clients can be examined on GitHub.
Thanks @grb. For the encryption algorithms, secrets manager and password manager are the same, but dealing with the api responses and what to plug into where and was the main issue. However, I now got it fully working. Summing up the full process below for anyone who stumbles upon this later:
Generate an access token in the web vault with the correct permissions.
Access tokens are in format 0.<client_id>.<client_secret>.<16 byte access token encryption key>
Identity exchange:
Process is identical as password manager but the scope is api.secrets.
access_token is the jwt used for all future api requests
encrypted_payload is the organization symmetric key
sequenceDiagram
participant worker
participant bw as bw-api
worker->>bw: POST https://identity.bitwarden.com/connect/token with accessToken
Note over worker,bw: Extract `client_id` and `client_secret` from access token
bw-->>worker: Response with access_token and encrypted_payload
The encrypted_payload is algorithm 2 so we need to derive a 64 byte key from the 16 byte access token key.
We do this by inflating with hkdf iterations until it reaches 32 (sha256 output length)
This key is now used with the same symmetric decryption process and on success, results in a json:
With the jwt as the Bearer auth from step 2, make http requests to the secrets endpoints. A typical flow would include
sequenceDiagram
participant worker
participant bw as bw-api
worker->>bw: GET https://api.bitwarden.com/organizations/:orgId/secrets
Note over worker,bw: If you don't have the `orgId`, it is in the decoded JWT as `organization`
bw-->>worker: Response with projects and secrets
alt Batch api
worker->>bw: POST https://api.bitwarden.com/secrets/get-by-ids with secret ids
Note over worker,bw: json body with 1 key (`ids`) and value is secret id array
else Individual
worker->>bw: GET https://api.bitwarden.com/secrets/:secretId
end
bw-->>worker: Return secret value(s)
Now follow the same standard symmetric decryption process on key, value, and note to get your secret values.
This is the process I followed (and works at the time of writing this post) to use secrets manager programmatically without the cli and it works within any JS edge environment only needing the built in Web Crypto API.
Disclaimer: While this is actually how the bws cli tool actually works behind the scenes, Bitwarden support states:
This is not officially supported. However, there are plans to provide API access in future releases, though I’m not able to provide an ETA for this
Footnote: I have this fully working in a cloudflare workers using only built-in node:buffer, TextDecoder/TextEncoder, and crypto.subtle. While I won’t release it as an npm package, I will link the code that does all of this once I’ve cleaned, commented, and polished it up (right now it is…well let’s just say it works )