diff --git a/.github/renovate.json b/.github/renovate.json index 42f5f93f2..ae4ad0e9d 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -2,6 +2,7 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:base", + "github>bitwarden/renovate-config:pin-actions", ":combinePatchMinorReleases", ":dependencyDashboard", ":maintainLockFilesWeekly", diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 361769ef9..9b1b1c952 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -46,7 +46,7 @@ jobs: run: cross build -p bitwarden-uniffi --release --target=${{ matrix.settings.target }} - name: Upload artifact - uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0 + uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 with: name: android-${{ matrix.settings.target }} path: ./target/${{ matrix.settings.target }}/release/libbitwarden_uniffi.so diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index abcbcbedf..70922b786 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -99,7 +99,7 @@ jobs: run: zip -j ./bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip ./target/${{ matrix.settings.target }}/release/bws - name: Upload artifact - uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0 + uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 with: name: bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip path: ./bws-${{ matrix.settings.target }}-${{ env._PACKAGE_VERSION }}.zip @@ -142,7 +142,7 @@ jobs: run: zip ./bws-macos-universal-${{ env._PACKAGE_VERSION }}.zip ./bws-macos-universal/bws - name: Upload artifact - uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0 + uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 with: name: bws-macos-universal-${{ env._PACKAGE_VERSION }}.zip path: ./bws-macos-universal-${{ env._PACKAGE_VERSION }}.zip @@ -177,7 +177,7 @@ jobs: sed -i.bak 's/\$NAME\$/Bitwarden Secrets Manager CLI/g' THIRDPARTY.html - name: Upload artifact - uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0 + uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 with: name: THIRDPARTY.html path: ./crates/bws/THIRDPARTY.html diff --git a/.github/workflows/build-dotnet.yml b/.github/workflows/build-dotnet.yml index 568683a58..232efee9d 100644 --- a/.github/workflows/build-dotnet.yml +++ b/.github/workflows/build-dotnet.yml @@ -71,7 +71,7 @@ jobs: working-directory: languages/csharp/Bitwarden.Sdk - name: Upload NuGet package - uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0 + uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 with: name: Bitwarden.Sdk.0.0.1.nupkg path: | diff --git a/.github/workflows/build-napi.yml b/.github/workflows/build-napi.yml index 1581a8d66..40e6ffaef 100644 --- a/.github/workflows/build-napi.yml +++ b/.github/workflows/build-napi.yml @@ -84,7 +84,7 @@ jobs: run: ${{ matrix.settings.build }} - name: Upload artifact - uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0 + uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 with: name: sdk-bitwarden-napi-${{ matrix.settings.target }} path: ${{ github.workspace }}/crates/bitwarden-napi/sdk-napi.*.node diff --git a/.github/workflows/build-python-wheels.yml b/.github/workflows/build-python-wheels.yml index e69f3eec1..dace2d047 100644 --- a/.github/workflows/build-python-wheels.yml +++ b/.github/workflows/build-python-wheels.yml @@ -66,30 +66,30 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup Node - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0 + uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 with: node-version: 18 - name: Install rust - uses: dtolnay/rust-toolchain@439cf607258077187679211f12aa6f19af4a0af7 # stable + uses: dtolnay/rust-toolchain@be73d7920c329f220ce78e0234b8f96b7ae60248 # stable with: toolchain: stable targets: ${{ matrix.settings.target }} - name: Cache cargo registry - uses: Swatinem/rust-cache@3cf7f8cc28d1b4e7d01e3783be10a97d55d483c8 # v2.7.1 + uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 with: key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.os }} - name: Retrieve schemas - uses: actions/download-artifact@f44cd7b40bfd40b6aa1cc1b9b5b7bf03d3c67110 # v4.1.0 + uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1 with: name: schemas.py path: ${{ github.workspace }}/languages/python/bitwarden_sdk - name: Build wheels if: ${{ matrix.settings.target != 'x86_64-unknown-linux-gnu' }} - uses: PyO3/maturin-action@b9e8f88fd4448fdecf5095864cdc7e39a544aa9f # v1.40.7 + uses: PyO3/maturin-action@a3013db91b2ef2e51420cfe99ee619c8e72a17e6 # v1.40.8 with: target: ${{ matrix.settings.target }} args: --release --find-interpreter --sdist @@ -99,7 +99,7 @@ jobs: - name: Build wheels (Linux - x86_64) if: ${{ matrix.settings.target == 'x86_64-unknown-linux-gnu' }} - uses: PyO3/maturin-action@b9e8f88fd4448fdecf5095864cdc7e39a544aa9f # v1.40.7 + uses: PyO3/maturin-action@a3013db91b2ef2e51420cfe99ee619c8e72a17e6 # v1.40.8 with: target: ${{ matrix.settings.target }} args: --release --find-interpreter --sdist diff --git a/.github/workflows/build-rust-cross-platform.yml b/.github/workflows/build-rust-cross-platform.yml index 52c44abeb..7495457df 100644 --- a/.github/workflows/build-rust-cross-platform.yml +++ b/.github/workflows/build-rust-cross-platform.yml @@ -41,7 +41,7 @@ jobs: run: cargo build --target ${{ matrix.settings.target }} --release - name: Upload Artifact - uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0 + uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 with: name: libbitwarden_c_files-${{ matrix.settings.target }} path: | diff --git a/.github/workflows/generate_schemas.yml b/.github/workflows/generate_schemas.yml index 55c9c07d9..a18b4910a 100644 --- a/.github/workflows/generate_schemas.yml +++ b/.github/workflows/generate_schemas.yml @@ -37,48 +37,48 @@ jobs: run: npm run schemas - name: Upload ts schemas artifact - uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0 + uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 with: name: schemas.ts path: ${{ github.workspace }}/languages/js/sdk-client/src/schemas.ts if-no-files-found: error - name: Upload c# schemas artifact - uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0 + uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 with: name: schemas.cs path: ${{ github.workspace }}/languages/csharp/Bitwarden.Sdk/schemas.cs if-no-files-found: error - name: Upload python schemas artifact - uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0 + uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 with: name: schemas.py path: ${{ github.workspace }}/languages/python/bitwarden_sdk/schemas.py if-no-files-found: error - name: Upload ruby schemas artifact - uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0 + uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 with: name: schemas.rb path: ${{ github.workspace }}/languages/ruby/bitwarden_sdk/lib/schemas.rb if-no-files-found: error - name: Upload json schemas artifact - uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0 + uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 with: name: sdk-schemas-json path: ${{ github.workspace }}/support/schemas/* if-no-files-found: error - name: Upload Go schemas artifact - uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0 + uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 with: name: schemas.go path: ${{ github.workspace }}/languages/go/schema.go - name: Upload java schemas artifact - uses: actions/upload-artifact@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0 + uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 with: name: sdk-schemas-java path: ${{ github.workspace }}/languages/java/src/main/java/com/bitwarden/sdk/schema/* diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index dbf357d3b..2f8989f91 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -24,11 +24,16 @@ jobs: with: toolchain: stable + - name: Install rust nightly + run: | + rustup toolchain install nightly + rustup component add rustfmt --toolchain nightly-x86_64-unknown-linux-gnu + - name: Cache cargo registry uses: Swatinem/rust-cache@23bce251a8cd2ffc3c1075eaa2367cf899916d84 # v2.7.3 - name: Cargo fmt - run: cargo fmt --check + run: cargo +nightly fmt --check - name: Set up Node uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1 diff --git a/.github/workflows/publish-ruby.yml b/.github/workflows/publish-ruby.yml index 545e43f76..291834022 100644 --- a/.github/workflows/publish-ruby.yml +++ b/.github/workflows/publish-ruby.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Set up Ruby - uses: ruby/setup-ruby@b203567269b5bbc256dbc1c84f7495913f977353 # v1.167.0 + uses: ruby/setup-ruby@5daca165445f0ae10478593083f72ca2625e241d # v1.169.0 with: ruby-version: 3.2 diff --git a/Cargo.lock b/Cargo.lock index bfbb5cbb0..6e20c1f3d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -352,7 +352,6 @@ dependencies = [ "bitwarden-crypto", "bitwarden-generators", "chrono", - "data-encoding", "getrandom 0.2.11", "hmac", "log", @@ -507,6 +506,7 @@ dependencies = [ "async-lock", "bitwarden", "bitwarden-crypto", + "bitwarden-generators", "chrono", "env_logger", "schemars", @@ -725,9 +725,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.16" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58e54881c004cec7895b0068a0a954cd5d62da01aef83fa35b1e594497bf5445" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" dependencies = [ "clap_builder", "clap_derive", @@ -735,9 +735,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.16" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59cb82d7f531603d2fd1f507441cdd35184fa81beff7bd489570de7f773460bb" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" dependencies = [ "anstream", "anstyle", @@ -747,9 +747,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.4.6" +version = "4.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97aeaa95557bd02f23fbb662f981670c3d20c5a26e69f7354b28f57092437fcd" +checksum = "eaf7dcb7c21d8ca1a2482ee0f1d341f437c9a7af6ca6da359dc5e1b164e98215" dependencies = [ "clap", ] @@ -1042,12 +1042,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "data-encoding" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" - [[package]] name = "deadpool" version = "0.9.5" diff --git a/crates/bitwarden-api-api/src/apis/access_policies_api.rs b/crates/bitwarden-api-api/src/apis/access_policies_api.rs index 7e538c0b4..9eb40719d 100644 --- a/crates/bitwarden-api-api/src/apis/access_policies_api.rs +++ b/crates/bitwarden-api-api/src/apis/access_policies_api.rs @@ -27,21 +27,24 @@ pub enum AccessPoliciesIdPutError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_id_access_policies_people_potential_grantees_get`] +/// struct for typed errors of method +/// [`organizations_id_access_policies_people_potential_grantees_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsIdAccessPoliciesPeoplePotentialGranteesGetError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_id_access_policies_projects_potential_grantees_get`] +/// struct for typed errors of method +/// [`organizations_id_access_policies_projects_potential_grantees_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsIdAccessPoliciesProjectsPotentialGranteesGetError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_id_access_policies_service_accounts_potential_grantees_get`] +/// struct for typed errors of method +/// [`organizations_id_access_policies_service_accounts_potential_grantees_get`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsIdAccessPoliciesServiceAccountsPotentialGranteesGetError { diff --git a/crates/bitwarden-api-api/src/apis/collections_api.rs b/crates/bitwarden-api-api/src/apis/collections_api.rs index 02ac6eb16..c95ac5529 100644 --- a/crates/bitwarden-api-api/src/apis/collections_api.rs +++ b/crates/bitwarden-api-api/src/apis/collections_api.rs @@ -62,7 +62,8 @@ pub enum OrganizationsOrgIdCollectionsIdDeletePostError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_org_id_collections_id_delete_user_org_user_id_post`] +/// struct for typed errors of method +/// [`organizations_org_id_collections_id_delete_user_org_user_id_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsOrgIdCollectionsIdDeleteUserOrgUserIdPostError { @@ -97,7 +98,8 @@ pub enum OrganizationsOrgIdCollectionsIdPutError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_org_id_collections_id_user_org_user_id_delete`] +/// struct for typed errors of method +/// [`organizations_org_id_collections_id_user_org_user_id_delete`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsOrgIdCollectionsIdUserOrgUserIdDeleteError { diff --git a/crates/bitwarden-api-api/src/apis/groups_api.rs b/crates/bitwarden-api-api/src/apis/groups_api.rs index 46ed9afa5..d52fcd200 100644 --- a/crates/bitwarden-api-api/src/apis/groups_api.rs +++ b/crates/bitwarden-api-api/src/apis/groups_api.rs @@ -48,7 +48,8 @@ pub enum OrganizationsOrgIdGroupsIdDeletePostError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_org_id_groups_id_delete_user_org_user_id_post`] +/// struct for typed errors of method +/// [`organizations_org_id_groups_id_delete_user_org_user_id_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsOrgIdGroupsIdDeleteUserOrgUserIdPostError { diff --git a/crates/bitwarden-api-api/src/apis/organization_connections_api.rs b/crates/bitwarden-api-api/src/apis/organization_connections_api.rs index 8bf41ffd1..ee75716dc 100644 --- a/crates/bitwarden-api-api/src/apis/organization_connections_api.rs +++ b/crates/bitwarden-api-api/src/apis/organization_connections_api.rs @@ -20,14 +20,16 @@ pub enum OrganizationsConnectionsEnabledGetError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_connections_organization_connection_id_delete`] +/// struct for typed errors of method +/// [`organizations_connections_organization_connection_id_delete`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsConnectionsOrganizationConnectionIdDeleteError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_connections_organization_connection_id_delete_post`] +/// struct for typed errors of method +/// [`organizations_connections_organization_connection_id_delete_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsConnectionsOrganizationConnectionIdDeletePostError { diff --git a/crates/bitwarden-api-api/src/apis/organization_sponsorships_api.rs b/crates/bitwarden-api-api/src/apis/organization_sponsorships_api.rs index 203174fdf..e3f1ccf24 100644 --- a/crates/bitwarden-api-api/src/apis/organization_sponsorships_api.rs +++ b/crates/bitwarden-api-api/src/apis/organization_sponsorships_api.rs @@ -27,21 +27,24 @@ pub enum OrganizationSponsorshipSponsoredSponsoredOrgIdDeleteError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organization_sponsorship_sponsored_sponsored_org_id_remove_post`] +/// struct for typed errors of method +/// [`organization_sponsorship_sponsored_sponsored_org_id_remove_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationSponsorshipSponsoredSponsoredOrgIdRemovePostError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organization_sponsorship_sponsoring_org_id_families_for_enterprise_post`] +/// struct for typed errors of method +/// [`organization_sponsorship_sponsoring_org_id_families_for_enterprise_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationSponsorshipSponsoringOrgIdFamiliesForEnterprisePostError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organization_sponsorship_sponsoring_org_id_families_for_enterprise_resend_post`] +/// struct for typed errors of method +/// [`organization_sponsorship_sponsoring_org_id_families_for_enterprise_resend_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationSponsorshipSponsoringOrgIdFamiliesForEnterpriseResendPostError { @@ -62,7 +65,8 @@ pub enum OrganizationSponsorshipSponsoringOrganizationIdDeleteError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organization_sponsorship_sponsoring_organization_id_delete_post`] +/// struct for typed errors of method +/// [`organization_sponsorship_sponsoring_organization_id_delete_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationSponsorshipSponsoringOrganizationIdDeletePostError { diff --git a/crates/bitwarden-api-api/src/apis/organization_users_api.rs b/crates/bitwarden-api-api/src/apis/organization_users_api.rs index 5a8e99916..804a29019 100644 --- a/crates/bitwarden-api-api/src/apis/organization_users_api.rs +++ b/crates/bitwarden-api-api/src/apis/organization_users_api.rs @@ -174,14 +174,16 @@ pub enum OrganizationsOrgIdUsersInvitePostError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_org_id_users_organization_user_id_accept_init_post`] +/// struct for typed errors of method +/// [`organizations_org_id_users_organization_user_id_accept_init_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsOrgIdUsersOrganizationUserIdAcceptInitPostError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_org_id_users_organization_user_id_accept_post`] +/// struct for typed errors of method +/// [`organizations_org_id_users_organization_user_id_accept_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsOrgIdUsersOrganizationUserIdAcceptPostError { @@ -230,7 +232,8 @@ pub enum OrganizationsOrgIdUsersRevokePutError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organizations_org_id_users_user_id_reset_password_enrollment_put`] +/// struct for typed errors of method +/// [`organizations_org_id_users_user_id_reset_password_enrollment_put`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationsOrgIdUsersUserIdResetPasswordEnrollmentPutError { diff --git a/crates/bitwarden-api-api/src/apis/self_hosted_organization_sponsorships_api.rs b/crates/bitwarden-api-api/src/apis/self_hosted_organization_sponsorships_api.rs index dc2da4103..60b8a6d28 100644 --- a/crates/bitwarden-api-api/src/apis/self_hosted_organization_sponsorships_api.rs +++ b/crates/bitwarden-api-api/src/apis/self_hosted_organization_sponsorships_api.rs @@ -13,21 +13,24 @@ use reqwest; use super::{configuration, Error}; use crate::apis::ResponseContent; -/// struct for typed errors of method [`organization_sponsorship_self_hosted_sponsoring_org_id_delete`] +/// struct for typed errors of method +/// [`organization_sponsorship_self_hosted_sponsoring_org_id_delete`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationSponsorshipSelfHostedSponsoringOrgIdDeleteError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organization_sponsorship_self_hosted_sponsoring_org_id_delete_post`] +/// struct for typed errors of method +/// [`organization_sponsorship_self_hosted_sponsoring_org_id_delete_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationSponsorshipSelfHostedSponsoringOrgIdDeletePostError { UnknownValue(serde_json::Value), } -/// struct for typed errors of method [`organization_sponsorship_self_hosted_sponsoring_org_id_families_for_enterprise_post`] +/// struct for typed errors of method +/// [`organization_sponsorship_self_hosted_sponsoring_org_id_families_for_enterprise_post`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum OrganizationSponsorshipSelfHostedSponsoringOrgIdFamiliesForEnterprisePostError { diff --git a/crates/bitwarden-api-api/src/models/sso_configuration_data.rs b/crates/bitwarden-api-api/src/models/sso_configuration_data.rs index 0e2890ad9..2d898488e 100644 --- a/crates/bitwarden-api-api/src/models/sso_configuration_data.rs +++ b/crates/bitwarden-api-api/src/models/sso_configuration_data.rs @@ -17,7 +17,10 @@ pub struct SsoConfigurationData { skip_serializing_if = "Option::is_none" )] pub member_decryption_type: Option, - /// Legacy property to determine if KeyConnector was enabled. Kept for backwards compatibility with old configs that will not have the new Bit.Core.Auth.Models.Data.SsoConfigurationData.MemberDecryptionType when deserialized from the database. + /// Legacy property to determine if KeyConnector was enabled. Kept for backwards compatibility + /// with old configs that will not have the new + /// Bit.Core.Auth.Models.Data.SsoConfigurationData.MemberDecryptionType when deserialized from + /// the database. #[serde( rename = "keyConnectorEnabled", skip_serializing_if = "Option::is_none" diff --git a/crates/bitwarden-cli/Cargo.toml b/crates/bitwarden-cli/Cargo.toml index 702232730..bded30904 100644 --- a/crates/bitwarden-cli/Cargo.toml +++ b/crates/bitwarden-cli/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" rust-version = "1.57" [dependencies] -clap = { version = "4.4.16", features = ["derive"] } +clap = { version = "4.4.18", features = ["derive"] } color-eyre = "0.6" inquire = "0.6.2" supports-color = "2.1.0" diff --git a/crates/bitwarden-crypto/src/aes.rs b/crates/bitwarden-crypto/src/aes.rs index cb4bf3102..e948fdc18 100644 --- a/crates/bitwarden-crypto/src/aes.rs +++ b/crates/bitwarden-crypto/src/aes.rs @@ -32,7 +32,8 @@ pub(crate) fn decrypt_aes256( .decrypt_padded_mut::(&mut data) .map_err(|_| CryptoError::KeyDecrypt)?; - // Data is decrypted in place and returns a subslice of the original Vec, to avoid cloning it, we truncate to the subslice length + // Data is decrypted in place and returns a subslice of the original Vec, to avoid cloning it, + // we truncate to the subslice length let decrypted_len = decrypted_key_slice.len(); data.truncate(decrypted_len); diff --git a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs index 4c1dc3c8c..ea969f4f9 100644 --- a/crates/bitwarden-crypto/src/enc_string/asymmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/asymmetric.rs @@ -7,17 +7,18 @@ use serde::Deserialize; use super::{from_b64_vec, split_enc_string}; use crate::{ error::{CryptoError, EncStringParseError, Result}, - AsymmetricCryptoKey, KeyDecryptable, + rsa::encrypt_rsa2048_oaep_sha1, + AsymmetricCryptoKey, AsymmetricEncryptable, KeyDecryptable, }; /// # Encrypted string primitive /// -/// [AsymmetricEncString] is a Bitwarden specific primitive that represents an asymmetrically encrypted string. -/// They are used together with the KeyDecryptable and KeyEncryptable traits to encrypt and decrypt data using -/// [AsymmetricCryptoKey]s. +/// [AsymmetricEncString] is a Bitwarden specific primitive that represents an asymmetrically +/// encrypted string. They are used together with the KeyDecryptable and KeyEncryptable traits to +/// encrypt and decrypt data using [AsymmetricCryptoKey]s. /// -/// The flexibility of the [AsymmetricEncString] type allows for different encryption algorithms to be used -/// which is represented by the different variants of the enum. +/// The flexibility of the [AsymmetricEncString] type allows for different encryption algorithms to +/// be used which is represented by the different variants of the enum. /// /// ## Note /// @@ -30,8 +31,8 @@ use crate::{ /// /// ## Serialization /// -/// [AsymmetricEncString] implements [Display] and [FromStr] to allow for easy serialization and uses a -/// custom scheme to represent the different variants. +/// [AsymmetricEncString] implements [Display] and [FromStr] to allow for easy serialization and +/// uses a custom scheme to represent the different variants. /// /// The scheme is one of the following schemes: /// - `[type].[data]` @@ -54,7 +55,8 @@ pub enum AsymmetricEncString { Rsa2048_OaepSha1_HmacSha256_B64 { data: Vec, mac: Vec }, } -/// To avoid printing sensitive information, [AsymmetricEncString] debug prints to `AsymmetricEncString`. +/// To avoid printing sensitive information, [AsymmetricEncString] debug prints to +/// `AsymmetricEncString`. impl std::fmt::Debug for AsymmetricEncString { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("AsymmetricEncString").finish() @@ -136,6 +138,15 @@ impl serde::Serialize for AsymmetricEncString { } impl AsymmetricEncString { + /// Encrypt and produce a [AsymmetricEncString::Rsa2048_OaepSha1_B64] variant. + pub fn encrypt_rsa2048_oaep_sha1( + data_dec: &[u8], + key: &dyn AsymmetricEncryptable, + ) -> Result { + let enc = encrypt_rsa2048_oaep_sha1(key.to_public_key(), data_dec)?; + Ok(AsymmetricEncString::Rsa2048_OaepSha1_B64 { data: enc }) + } + /// The numerical representation of the encryption type of the [AsymmetricEncString]. const fn enc_type(&self) -> u8 { match self { diff --git a/crates/bitwarden-crypto/src/enc_string/symmetric.rs b/crates/bitwarden-crypto/src/enc_string/symmetric.rs index b9e4ee9e9..ed832b32b 100644 --- a/crates/bitwarden-crypto/src/enc_string/symmetric.rs +++ b/crates/bitwarden-crypto/src/enc_string/symmetric.rs @@ -12,9 +12,9 @@ use crate::{ /// # Encrypted string primitive /// -/// [EncString] is a Bitwarden specific primitive that represents a symmetrically encrypted string. They are -/// are used together with the [KeyDecryptable] and [KeyEncryptable] traits to encrypt and decrypt -/// data using [SymmetricCryptoKey]s. +/// [EncString] is a Bitwarden specific primitive that represents a symmetrically encrypted string. +/// They are are used together with the [KeyDecryptable] and [KeyEncryptable] traits to encrypt and +/// decrypt data using [SymmetricCryptoKey]s. /// /// The flexibility of the [EncString] type allows for different encryption algorithms to be used /// which is represented by the different variants of the enum. diff --git a/crates/bitwarden-crypto/src/error.rs b/crates/bitwarden-crypto/src/error.rs index 059cc88f1..cf9a9b048 100644 --- a/crates/bitwarden-crypto/src/error.rs +++ b/crates/bitwarden-crypto/src/error.rs @@ -52,6 +52,8 @@ pub enum RsaError { CreatePublicKey, #[error("Unable to create private key")] CreatePrivateKey, + #[error("Rsa error, {0}")] + Rsa(#[from] rsa::Error), } /// Alias for `Result`. diff --git a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs index a3e06800b..560e7e4cc 100644 --- a/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs +++ b/crates/bitwarden-crypto/src/keys/asymmetric_crypto_key.rs @@ -1,14 +1,53 @@ -use rsa::RsaPrivateKey; +use rsa::{pkcs8::DecodePublicKey, RsaPrivateKey, RsaPublicKey}; use super::key_encryptable::CryptoKey; use crate::error::{CryptoError, Result}; -/// An asymmetric encryption key. Used to encrypt and decrypt [`EncString`](crate::EncString) +/// Trait to allow both [`AsymmetricCryptoKey`] and [`AsymmetricPublicCryptoKey`] to be used to +/// encrypt [AsymmetricEncString](crate::AsymmetricEncString). +pub trait AsymmetricEncryptable { + fn to_public_key(&self) -> &RsaPublicKey; +} + +/// An asymmetric public encryption key. Can only encrypt +/// [AsymmetricEncString](crate::AsymmetricEncString), usually accompanied by a +/// [AsymmetricCryptoKey] +pub struct AsymmetricPublicCryptoKey { + pub(crate) key: RsaPublicKey, +} + +impl AsymmetricPublicCryptoKey { + /// Build a public key from the SubjectPublicKeyInfo DER. + pub fn from_der(der: &[u8]) -> Result { + Ok(Self { + key: rsa::RsaPublicKey::from_public_key_der(der) + .map_err(|_| CryptoError::InvalidKey)?, + }) + } +} + +impl AsymmetricEncryptable for AsymmetricPublicCryptoKey { + fn to_public_key(&self) -> &RsaPublicKey { + &self.key + } +} + +/// An asymmetric encryption key. Contains both the public and private key. Can be used to both +/// encrypt and decrypt [`AsymmetricEncString`](crate::AsymmetricEncString). pub struct AsymmetricCryptoKey { pub(crate) key: RsaPrivateKey, } impl AsymmetricCryptoKey { + /// Generate a random AsymmetricCryptoKey (RSA-2048). + pub fn generate(rng: &mut R) -> Self { + let bits = 2048; + + Self { + key: RsaPrivateKey::new(rng, bits).expect("failed to generate a key"), + } + } + pub fn from_pem(pem: &str) -> Result { use rsa::pkcs8::DecodePrivateKey; Ok(Self { @@ -36,7 +75,6 @@ impl AsymmetricCryptoKey { pub fn to_public_der(&self) -> Result> { use rsa::pkcs8::EncodePublicKey; Ok(self - .key .to_public_key() .to_public_key_der() .map_err(|_| CryptoError::InvalidKey)? @@ -45,6 +83,12 @@ impl AsymmetricCryptoKey { } } +impl AsymmetricEncryptable for AsymmetricCryptoKey { + fn to_public_key(&self) -> &RsaPublicKey { + self.key.as_ref() + } +} + impl CryptoKey for AsymmetricCryptoKey {} // We manually implement these to make sure we don't print any sensitive data @@ -58,7 +102,9 @@ impl std::fmt::Debug for AsymmetricCryptoKey { mod tests { use base64::{engine::general_purpose::STANDARD, Engine}; - use super::AsymmetricCryptoKey; + use crate::{ + AsymmetricCryptoKey, AsymmetricEncString, AsymmetricPublicCryptoKey, KeyDecryptable, + }; #[test] fn test_asymmetric_crypto_key() { @@ -102,4 +148,61 @@ DnqOsltgPomWZ7xVfMkm9niL2OA= assert_eq!(der_key.to_der().unwrap(), der_key_vec); assert_eq!(pem_key.to_der().unwrap(), der_key_vec); } + + #[test] + fn test_encrypt_public_decrypt_private() { + let private_key = STANDARD + .decode(concat!( + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCu9xd+vmkIPoqH", + "NejsFZzkd1xuCn1TqGTT7ANhAEnbI/yaVt3caI30kwUC2WIToFpNgu7Ej0x2TteY", + "OgrLrdcC4jy1SifmKYv/v3ZZxrd/eqttmH2k588panseRwHK3LVk7xA+URhQ/bjL", + "gPM59V0uR1l+z1fmooeJPFz5WSXNObc9Jqnh45FND+U/UYHXTLSomTn7jgZFxJBK", + "veS7q6Lat7wAnYZCF2dnPmhZoJv+SKPltA8HAGsgQGWBF1p5qxV1HrAUk8kBBnG2", + "paj0w8p5UM6RpDdCuvKH7j1LiuWffn3b9Z4dgzmE7jsMmvzoQtypzIKaSxhqzvFO", + "od9V8dJdAgMBAAECggEAGGIYjOIB1rOKkDHP4ljXutI0mCRPl3FMDemiBeppoIfZ", + "G/Q3qpAKmndDt0Quwh/yfcNdvZhf1kwCCTWri/uPz5fSUIyDV3TaTRu0ZWoHaBVj", + "Hxylg+4HRZUQj+Vi50/PWr/jQmAAVMcrMfcoTl82q2ynmP/R1vM3EsXOCjTliv5B", + "XlMPRjj/9PDBH0dnnVcAPDOpflzOTL2f4HTFEMlmg9/tZBnd96J/cmfhjAv9XpFL", + "FBAFZzs5pz0rwCNSR8QZNonnK7pngVUlGDLORK58y84tGmxZhGdne3CtCWey/sJ4", + "7QF0Pe8YqWBU56926IY6DcSVBuQGZ6vMCNlU7J8D2QKBgQDXyh3t2TicM/n1QBLk", + "zLoGmVUmxUGziHgl2dnJiGDtyOAU3+yCorPgFaCie29s5qm4b0YEGxUxPIrRrEro", + "h0FfKn9xmr8CdmTPTcjJW1+M7bxxq7oBoU/QzKXgIHlpeCjjnvPJt0PcNkNTjCXv", + "shsrINh2rENoe/x79eEfM/N5eQKBgQDPkYSmYyALoNq8zq0A4BdR+F5lb5Fj5jBH", + "Jk68l6Uti+0hRbJ2d1tQTLkU+eCPQLGBl6fuc1i4K5FV7v14jWtRPdD7wxrkRi3j", + "ilqQwLBOU6Bj3FK4DvlLF+iYTuBWj2/KcxflXECmsjitKHLK6H7kFEiuJql+NAHU", + "U9EFXepLBQKBgQDQ+HCnZ1bFHiiP8m7Zl9EGlvK5SwlnPV9s+F1KJ4IGhCNM09UM", + "ZVfgR9F5yCONyIrPiyK40ylgtwqQJlOcf281I8irUXpsfg7+Gou5Q31y0r9NLUpC", + "Td8niyePtqMdGjouxD2+OHXFCd+FRxFt4IMi7vnxYr0csAVAXkqWlw7PsQKBgH/G", + "/PnQm7GM3BrOwAGB8dksJDAddkshMScblezTDYP0V43b8firkTLliCo5iNum357/", + "VQmdSEhXyag07yR/Kklg3H2fpbZQ3X7tdMMXW3FcWagfwWw9C4oGtdDM/Z1Lv23J", + "XDR9je8QV4OBGul+Jl8RfYx3kG94ZIfo8Qt0vP5hAoGARjAzdCGYz42NwaUk8n94", + "W2RuKHtTV9vtjaAbfPFbZoGkT7sXNJVlrA0C+9f+H9rOTM3mX59KrjmLVzde4Vhs", + "avWMShuK4vpAiDQLU7GyABvi5CR6Ld+AT+LSzxHhVe0ASOQPNCA2SOz3RQvgPi7R", + "GDgRMUB6cL3IRVzcR0dC6cY=", + )) + .unwrap(); + + let public_key = STANDARD + .decode(concat!( + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArvcXfr5pCD6KhzXo7BWc", + "5Hdcbgp9U6hk0+wDYQBJ2yP8mlbd3GiN9JMFAtliE6BaTYLuxI9Mdk7XmDoKy63X", + "AuI8tUon5imL/792Wca3f3qrbZh9pOfPKWp7HkcByty1ZO8QPlEYUP24y4DzOfVd", + "LkdZfs9X5qKHiTxc+VklzTm3PSap4eORTQ/lP1GB10y0qJk5+44GRcSQSr3ku6ui", + "2re8AJ2GQhdnZz5oWaCb/kij5bQPBwBrIEBlgRdaeasVdR6wFJPJAQZxtqWo9MPK", + "eVDOkaQ3Qrryh+49S4rln3592/WeHYM5hO47DJr86ELcqcyCmksYas7xTqHfVfHS", + "XQIDAQAB", + )) + .unwrap(); + + let private_key = AsymmetricCryptoKey::from_der(&private_key).unwrap(); + let public_key = AsymmetricPublicCryptoKey::from_der(&public_key).unwrap(); + + let plaintext = "Hello, world!"; + let encrypted = + AsymmetricEncString::encrypt_rsa2048_oaep_sha1(plaintext.as_bytes(), &public_key) + .unwrap(); + let decrypted: String = encrypted.decrypt_with_key(&private_key).unwrap(); + + assert_eq!(plaintext, decrypted); + } } diff --git a/crates/bitwarden-crypto/src/keys/device_key.rs b/crates/bitwarden-crypto/src/keys/device_key.rs new file mode 100644 index 000000000..6588944fe --- /dev/null +++ b/crates/bitwarden-crypto/src/keys/device_key.rs @@ -0,0 +1,126 @@ +use crate::{ + error::Result, AsymmetricCryptoKey, AsymmetricEncString, EncString, KeyDecryptable, + KeyEncryptable, SymmetricCryptoKey, UserKey, +}; + +/// Device Key +/// +/// Encrypts the DevicePrivateKey +/// Allows the device to decrypt the UserKey, via the DevicePrivateKey. +#[derive(Debug)] +pub struct DeviceKey(SymmetricCryptoKey); + +#[derive(Debug)] +pub struct TrustDeviceResponse { + pub device_key: DeviceKey, + /// UserKey encrypted with DevicePublicKey + pub protected_user_key: AsymmetricEncString, + /// DevicePrivateKey encrypted with [DeviceKey] + pub protected_device_private_key: EncString, + /// DevicePublicKey encrypted with [UserKey](super::UserKey) + pub protected_device_public_key: EncString, +} + +impl DeviceKey { + /// Generate a new device key + /// + /// Note: Input has to be a SymmetricCryptoKey instead of UserKey because that's what we get + /// from EncSettings. + pub fn trust_device(user_key: &SymmetricCryptoKey) -> Result { + let mut rng = rand::thread_rng(); + let device_key = DeviceKey(SymmetricCryptoKey::generate(&mut rng)); + + let device_private_key = AsymmetricCryptoKey::generate(&mut rng); + + // Encrypt both the key and mac_key of the user key + let data = user_key.to_vec(); + + let protected_user_key = + AsymmetricEncString::encrypt_rsa2048_oaep_sha1(&data, &device_private_key)?; + + let protected_device_public_key = device_private_key + .to_public_der()? + .encrypt_with_key(user_key)?; + + let protected_device_private_key = device_private_key + .to_der()? + .encrypt_with_key(&device_key.0)?; + + Ok(TrustDeviceResponse { + device_key, + protected_user_key, + protected_device_private_key, + protected_device_public_key, + }) + } + + /// Decrypt the user key using the device key + pub fn decrypt_user_key( + &self, + protected_device_private_key: EncString, + protected_user_key: AsymmetricEncString, + ) -> Result { + let device_private_key: Vec = protected_device_private_key.decrypt_with_key(&self.0)?; + let device_private_key = AsymmetricCryptoKey::from_der(device_private_key.as_slice())?; + + let dec: Vec = protected_user_key.decrypt_with_key(&device_private_key)?; + let user_key: SymmetricCryptoKey = dec.as_slice().try_into()?; + + Ok(UserKey(user_key)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::derive_symmetric_key; + + #[test] + fn test_trust_device() { + let key = derive_symmetric_key("test"); + + let result = DeviceKey::trust_device(&key).unwrap(); + + let decrypted = result + .device_key + .decrypt_user_key( + result.protected_device_private_key, + result.protected_user_key, + ) + .unwrap(); + + assert_eq!(key.key, decrypted.0.key); + assert_eq!(key.mac_key, decrypted.0.mac_key); + } + + #[test] + fn test_decrypt_user_key() { + // Example keys from desktop app + let user_key: &[u8] = &[ + 109, 128, 172, 147, 206, 123, 134, 95, 16, 36, 155, 113, 201, 18, 186, 230, 216, 212, + 173, 188, 74, 11, 134, 131, 137, 242, 105, 178, 105, 126, 52, 139, 248, 91, 215, 21, + 128, 91, 226, 222, 165, 67, 251, 34, 83, 81, 77, 147, 225, 76, 13, 41, 102, 45, 183, + 218, 106, 89, 254, 208, 251, 101, 130, 10, + ]; + let user_key = SymmetricCryptoKey::try_from(user_key).unwrap(); + + let key_data: &[u8] = &[ + 114, 235, 60, 115, 172, 156, 203, 145, 195, 130, 215, 250, 88, 146, 215, 230, 12, 109, + 245, 222, 54, 217, 255, 211, 221, 105, 230, 236, 65, 52, 209, 133, 76, 208, 113, 254, + 194, 216, 156, 19, 230, 62, 32, 93, 87, 7, 144, 156, 117, 142, 250, 32, 182, 118, 187, + 8, 247, 7, 203, 201, 65, 147, 206, 247, + ]; + let device_key = DeviceKey(key_data.try_into().unwrap()); + + let protected_user_key: AsymmetricEncString = "4.f+VbbacRhO2q4MOUSdt1AIjQ2FuLAvg4aDxJMXAh3VxvbmUADj8Ct/R7XEpPUqApmbRS566jS0eRVy8Sk08ogoCdj1IFN9VsIky2i2X1WHK1fUnr3UBmXE3tl2NPBbx56U+h73S2jNTSyet2W18Jg2q7/w8KIhR3J41QrG9aGoOTN93to3hb5W4z6rdrSI0e7GkizbwcIA0NH7Z1JyAhrjPm9+tjRjg060YbEbGaWTAOkZWfgbLjr8bY455DteO2xxG139cOx7EBo66N+YhjsLi0ozkeUyPQkoWBdKMcQllS7jCfB4fDyJA05ALTbk74syKkvqFxqwmQbg+aVn+dcw==".parse().unwrap(); + + let protected_device_private_key: EncString = "2.GyQfUYWW6Byy4UV5icFLxg==|EMiU7OTF79N6tfv3+YUs5zJhBAgqv6sa5YCoPl6yAETh7Tfk+JmbeizxXFPj5Q1X/tcVpDZl/3fGcxtnIxg1YtvDFn7j8uPnoApOWhCKmwcvJSIkt+qvX3lELNBwZXozSiy7PbQ0JbCMe2d4MkimR5k8+lE9FB3208yYK7nOJhlrsUCnOekCYEU9/4NCMA8tz8SpITx/MN4JJ1TQ/KjPJYLt+3JNUxK47QlgREWQvyVzCRt7ZGtcgIJ/U1qycAWMpEg9NkuV8j5QRA1S7VBsA6qliJwys5+dmTuIOmOMwdKFZDc4ZvWoRkPp2TSJBu7L8sSAgU6mmDWac8iQ+9Ka/drdfwYLrH8GAZvURk79tSpRrT7+PAFe2QdUtliUIyiqkh8iJVjZube4hRnEsRuX9V9b+UdtAr6zAj7mugO/VAu5T9J38V79V2ohG3NtXysDeKLXpAlkhjllWXeq/wret2fD4WiwqEDj0G2A/PY3F3OziIgp0UKc00AfqrPq8OVK3A+aowwVqdYadgxyoVCKWJ8unJeAXG7MrMQ9tHpzF6COoaEy7Wwoc17qko33zazwLZbfAjB4oc8Ea26jRKnJZP56sVZAjOSQQMziAsA08MRaa/DQhgRea1+Ygba0gMft8Dww8anN2gQBveTZRBWyqXYgN3U0Ity5gNauT8RnFk9faqVFt2Qxnp0JgJ+PsqEt5Hn4avBRZQQ7o8VvPnxYLDKFe3I2m6HFYFWRhOGeDYxexIuaiF2iIAYFVUmnDuWpgnUiL4XJ3KHDsjkPzcV3z4D2Knr/El2VVXve8jhDjETfovmmN28+i2e29PXvKIymTskMFpFCQPc7wBY/Id7pmgb3SujKYNpkAS2sByDoRir0my49DDGfta0dENssJhFd3x+87fZbEj3cMiikg2pBwpTLgmfIUa5cVZU2s8JZ9wu7gaioYzvX+elHa3EHLcnEUoJTtSf9kjb+Nbq4ktMgYAO2wIC96t1LvmqK4Qn2cOdw5QNlRqALhqe5V31kyIcwRMK0AyIoOPhnSqtpYdFiR3LDTvZA8dU0vSsuchCwHNMeRUtKvdzN/tk+oeznyY/mpakUESN501lEKd/QFLtJZsDZTtNlcA8fU3kDtws4ZIMR0O5+PFmgQFSU8OMobf9ClUzy/wHTvYGyDuSwbOoPeS955QKkUKXCNMj33yrPr+ioHQ1BNwLX3VmMF4bNRBY/vr+CG0/EZi0Gwl0kyHGl0yWEtpQuu+/PaROJeOraWy5D1UoZZhY4n0zJZBt1eg3FZ2rhKv4gdUc50nZpeNWE8pIqZ6RQ7qPJuqfF1Z+G73iOSnLYCHDiiFmhD5ivf9IGkTAcWcBsQ/2wcSj9bFJr4DrKfsbQ4CkSWICWVn/W+InKkO6BTsBbYmvte5SvbaN+UOtiUSkHLBCCr8273VNgcB/hgtbUires3noxYZJxoczr+i7vdlEgQnWEKrpo0CifsFxGwYS3Yy2K79iwvDMaLPDf73zLSbuoUl6602F2Mzcjnals67f+gSpaDvWt7Kg9c/ZfGjq8oNxVaXJnX3gSDsO+fhwVAtnDApL+tL8cFfxGerW4KGi9/74woH+C3MMIViBtNnrpEuvxUW97Dg5nd40oGDeyi/q+8HdcxkneyFY=|JYdol19Yi+n1r7M+06EwK5JCi2s/CWqKui2Cy6hEb3k=".parse().unwrap(); + + let decrypted = device_key + .decrypt_user_key(protected_device_private_key, protected_user_key) + .unwrap(); + + assert_eq!(decrypted.0.key, user_key.key); + assert_eq!(decrypted.0.mac_key, user_key.mac_key); + } +} diff --git a/crates/bitwarden-crypto/src/keys/mod.rs b/crates/bitwarden-crypto/src/keys/mod.rs index 4f53456ef..285e58b55 100644 --- a/crates/bitwarden-crypto/src/keys/mod.rs +++ b/crates/bitwarden-crypto/src/keys/mod.rs @@ -1,18 +1,19 @@ mod key_encryptable; pub use key_encryptable::{KeyDecryptable, KeyEncryptable}; - mod master_key; pub use master_key::{HashPurpose, Kdf, MasterKey}; - mod shareable_key; pub use shareable_key::derive_shareable_key; - mod symmetric_crypto_key; #[cfg(test)] pub use symmetric_crypto_key::derive_symmetric_key; pub use symmetric_crypto_key::SymmetricCryptoKey; mod asymmetric_crypto_key; -pub use asymmetric_crypto_key::AsymmetricCryptoKey; +pub use asymmetric_crypto_key::{ + AsymmetricCryptoKey, AsymmetricEncryptable, AsymmetricPublicCryptoKey, +}; mod user_key; pub use user_key::UserKey; +mod device_key; +pub use device_key::{DeviceKey, TrustDeviceResponse}; diff --git a/crates/bitwarden-crypto/src/keys/shareable_key.rs b/crates/bitwarden-crypto/src/keys/shareable_key.rs index a13f17155..eb5353401 100644 --- a/crates/bitwarden-crypto/src/keys/shareable_key.rs +++ b/crates/bitwarden-crypto/src/keys/shareable_key.rs @@ -14,7 +14,8 @@ pub fn derive_shareable_key( ) -> SymmetricCryptoKey { // Because all inputs are fixed size, we can unwrap all errors here without issue - // TODO: Are these the final `key` and `info` parameters or should we change them? I followed the pattern used for sends + // TODO: Are these the final `key` and `info` parameters or should we change them? I followed + // the pattern used for sends let res = Hmac::::new_from_slice(format!("bitwarden-{}", name).as_bytes()) .unwrap() .chain_update(secret) diff --git a/crates/bitwarden-crypto/src/rsa.rs b/crates/bitwarden-crypto/src/rsa.rs index ff665c247..bee88a655 100644 --- a/crates/bitwarden-crypto/src/rsa.rs +++ b/crates/bitwarden-crypto/src/rsa.rs @@ -1,12 +1,13 @@ use base64::{engine::general_purpose::STANDARD, Engine}; use rsa::{ pkcs8::{EncodePrivateKey, EncodePublicKey}, - RsaPrivateKey, RsaPublicKey, + Oaep, RsaPrivateKey, RsaPublicKey, }; +use sha1::Sha1; use crate::{ error::{Result, RsaError}, - EncString, SymmetricCryptoKey, + CryptoError, EncString, SymmetricCryptoKey, }; /// RSA Key Pair @@ -42,3 +43,12 @@ pub(crate) fn make_key_pair(key: &SymmetricCryptoKey) -> Result { private: protected, }) } + +pub(super) fn encrypt_rsa2048_oaep_sha1(public_key: &RsaPublicKey, data: &[u8]) -> Result> { + let mut rng = rand::thread_rng(); + + let padding = Oaep::new::(); + public_key + .encrypt(&mut rng, padding, data) + .map_err(|e| CryptoError::RsaError(e.into())) +} diff --git a/crates/bitwarden-generators/src/passphrase.rs b/crates/bitwarden-generators/src/passphrase.rs index 4094e4b70..f8329a80c 100644 --- a/crates/bitwarden-generators/src/passphrase.rs +++ b/crates/bitwarden-generators/src/passphrase.rs @@ -26,7 +26,8 @@ pub struct PassphraseGeneratorRequest { pub word_separator: String, /// When set to true, capitalize the first letter of each word in the generated passphrase. pub capitalize: bool, - /// When set to true, include a number at the end of one of the words in the generated passphrase. + /// When set to true, include a number at the end of one of the words in the generated + /// passphrase. pub include_number: bool, } @@ -45,7 +46,8 @@ const MINIMUM_PASSPHRASE_NUM_WORDS: u8 = 3; const MAXIMUM_PASSPHRASE_NUM_WORDS: u8 = 20; /// Represents a set of valid options to generate a passhprase with. -/// To get an instance of it, use [`PassphraseGeneratorRequest::validate_options`](PassphraseGeneratorRequest::validate_options) +/// To get an instance of it, use +/// [`PassphraseGeneratorRequest::validate_options`](PassphraseGeneratorRequest::validate_options) struct ValidPassphraseGeneratorOptions { pub(super) num_words: u8, pub(super) word_separator: String, @@ -54,7 +56,8 @@ struct ValidPassphraseGeneratorOptions { } impl PassphraseGeneratorRequest { - /// Validates the request and returns an immutable struct with valid options to use with the passphrase generator. + /// Validates the request and returns an immutable struct with valid options to use with the + /// passphrase generator. fn validate_options(self) -> Result { // TODO: Add password generator policy checks @@ -173,7 +176,8 @@ mod tests { let input = PassphraseGeneratorRequest { num_words: 4, - word_separator: "👨🏻‍❤️‍💋‍👨🏻".into(), // This emoji is 35 bytes long, but represented as a single character + word_separator: "👨🏻‍❤️‍💋‍👨🏻".into(), /* This emoji is 35 bytes long, but represented + * as a single character */ capitalize: false, include_number: true, } diff --git a/crates/bitwarden-generators/src/password.rs b/crates/bitwarden-generators/src/password.rs index c7a8fc252..6cab6dd65 100644 --- a/crates/bitwarden-generators/src/password.rs +++ b/crates/bitwarden-generators/src/password.rs @@ -85,7 +85,8 @@ impl CharSet { self.include_if(true, other) } - /// Includes the given characters in the set if the predicate is true. Any duplicate items will be ignored + /// Includes the given characters in the set if the predicate is true. Any duplicate items will + /// be ignored pub fn include_if(mut self, predicate: bool, other: impl IntoIterator) -> Self { if predicate { self.0.extend(other); @@ -122,7 +123,8 @@ impl Distribution for CharSet { } /// Represents a set of valid options to generate a password with. -/// To get an instance of it, use [`PasswordGeneratorRequest::validate_options`](PasswordGeneratorRequest::validate_options) +/// To get an instance of it, use +/// [`PasswordGeneratorRequest::validate_options`](PasswordGeneratorRequest::validate_options) struct PasswordGeneratorOptions { pub(super) lower: (CharSet, usize), pub(super) upper: (CharSet, usize), @@ -134,7 +136,8 @@ struct PasswordGeneratorOptions { } impl PasswordGeneratorRequest { - /// Validates the request and returns an immutable struct with valid options to use with the password generator. + /// Validates the request and returns an immutable struct with valid options to use with the + /// password generator. fn validate_options(self) -> Result { // TODO: Add password generator policy checks diff --git a/crates/bitwarden-generators/uniffi.toml b/crates/bitwarden-generators/uniffi.toml new file mode 100644 index 000000000..923dd5e17 --- /dev/null +++ b/crates/bitwarden-generators/uniffi.toml @@ -0,0 +1,8 @@ +[bindings.kotlin] +package_name = "com.bitwarden.generators" +generate_immutable_records = true + +[bindings.swift] +ffi_module_name = "BitwardenGeneratorsFFI" +module_name = "BitwardenGenerators" +generate_immutable_records = true diff --git a/crates/bitwarden-json/src/command.rs b/crates/bitwarden-json/src/command.rs index 855a30399..8da5e8cbd 100644 --- a/crates/bitwarden-json/src/command.rs +++ b/crates/bitwarden-json/src/command.rs @@ -33,7 +33,6 @@ pub enum Command { /// This command is not capable of handling authentication requiring 2fa or captcha. /// /// Returns: [PasswordLoginResponse](bitwarden::auth::login::PasswordLoginResponse) - /// PasswordLogin(PasswordLoginRequest), #[cfg(feature = "internal")] @@ -42,7 +41,6 @@ pub enum Command { /// This command is for initiating an authentication handshake with Bitwarden. /// /// Returns: [ApiKeyLoginResponse](bitwarden::auth::login::ApiKeyLoginResponse) - /// ApiKeyLogin(ApiKeyLoginRequest), #[cfg(feature = "secrets")] @@ -51,7 +49,6 @@ pub enum Command { /// This command is for initiating an authentication handshake with Bitwarden. /// /// Returns: [ApiKeyLoginResponse](bitwarden::auth::login::ApiKeyLoginResponse) - /// AccessTokenLogin(AccessTokenLoginRequest), #[cfg(feature = "internal")] @@ -59,14 +56,12 @@ pub enum Command { /// Get the API key of the currently authenticated user /// /// Returns: [UserApiKeyResponse](bitwarden::platform::UserApiKeyResponse) - /// GetUserApiKey(SecretVerificationRequest), #[cfg(feature = "internal")] /// Get the user's passphrase /// /// Returns: String - /// Fingerprint(FingerprintRequest), #[cfg(feature = "internal")] @@ -74,7 +69,6 @@ pub enum Command { /// Retrieve all user data, ciphers and organizations the user is a part of /// /// Returns: [SyncResponse](bitwarden::platform::SyncResponse) - /// Sync(SyncRequest), #[cfg(feature = "secrets")] @@ -92,7 +86,6 @@ pub enum SecretsCommand { /// Retrieve a secret by the provided identifier /// /// Returns: [SecretResponse](bitwarden::secrets_manager::secrets::SecretResponse) - /// Get(SecretGetRequest), /// > Requires Authentication @@ -100,7 +93,6 @@ pub enum SecretsCommand { /// Retrieve secrets by the provided identifiers /// /// Returns: [SecretsResponse](bitwarden::secrets_manager::secrets::SecretsResponse) - /// GetByIds(SecretsGetRequest), /// > Requires Authentication @@ -108,15 +100,14 @@ pub enum SecretsCommand { /// Creates a new secret in the provided organization using the given data /// /// Returns: [SecretResponse](bitwarden::secrets_manager::secrets::SecretResponse) - /// Create(SecretCreateRequest), /// > Requires Authentication /// > Requires using an Access Token for login or calling Sync at least once - /// Lists all secret identifiers of the given organization, to then retrieve each secret, use `CreateSecret` + /// Lists all secret identifiers of the given organization, to then retrieve each secret, use + /// `CreateSecret` /// /// Returns: [SecretIdentifiersResponse](bitwarden::secrets_manager::secrets::SecretIdentifiersResponse) - /// List(SecretIdentifiersRequest), /// > Requires Authentication @@ -124,7 +115,6 @@ pub enum SecretsCommand { /// Updates an existing secret with the provided ID using the given data /// /// Returns: [SecretResponse](bitwarden::secrets_manager::secrets::SecretResponse) - /// Update(SecretPutRequest), /// > Requires Authentication @@ -132,7 +122,6 @@ pub enum SecretsCommand { /// Deletes all the secrets whose IDs match the provided ones /// /// Returns: [SecretsDeleteResponse](bitwarden::secrets_manager::secrets::SecretsDeleteResponse) - /// Delete(SecretsDeleteRequest), } @@ -145,7 +134,6 @@ pub enum ProjectsCommand { /// Retrieve a project by the provided identifier /// /// Returns: [ProjectResponse](bitwarden::secrets_manager::projects::ProjectResponse) - /// Get(ProjectGetRequest), /// > Requires Authentication @@ -153,7 +141,6 @@ pub enum ProjectsCommand { /// Creates a new project in the provided organization using the given data /// /// Returns: [ProjectResponse](bitwarden::secrets_manager::projects::ProjectResponse) - /// Create(ProjectCreateRequest), /// > Requires Authentication @@ -161,7 +148,6 @@ pub enum ProjectsCommand { /// Lists all projects of the given organization /// /// Returns: [ProjectsResponse](bitwarden::secrets_manager::projects::ProjectsResponse) - /// List(ProjectsListRequest), /// > Requires Authentication @@ -169,7 +155,6 @@ pub enum ProjectsCommand { /// Updates an existing project with the provided ID using the given data /// /// Returns: [ProjectResponse](bitwarden::secrets_manager::projects::ProjectResponse) - /// Update(ProjectPutRequest), /// > Requires Authentication @@ -177,6 +162,5 @@ pub enum ProjectsCommand { /// Deletes all the projects whose IDs match the provided ones /// /// Returns: [ProjectsDeleteResponse](bitwarden::secrets_manager::projects::ProjectsDeleteResponse) - /// Delete(ProjectsDeleteRequest), } diff --git a/crates/bitwarden-napi/package-lock.json b/crates/bitwarden-napi/package-lock.json index e10a23742..27e945f60 100644 --- a/crates/bitwarden-napi/package-lock.json +++ b/crates/bitwarden-napi/package-lock.json @@ -55,9 +55,9 @@ } }, "node_modules/@napi-rs/cli": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.17.0.tgz", - "integrity": "sha512-/M7MZ3dIqyFs6c0Bxtk+SOobPq6vYWYqBLYCOKx3dYWqoyJNBEgmDKUTrxIZu9eHw9Ill3WyEoHPqS9P99cd8A==", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.18.0.tgz", + "integrity": "sha512-lfSRT7cs3iC4L+kv9suGYQEezn5Nii7Kpu+THsYVI0tA1Vh59LH45p4QADaD7hvIkmOz79eEGtoKQ9nAkAPkzA==", "dev": true, "bin": { "napi": "scripts/index.js" diff --git a/crates/bitwarden-napi/src/client.rs b/crates/bitwarden-napi/src/client.rs index d794b18f4..712cd4ffd 100644 --- a/crates/bitwarden-napi/src/client.rs +++ b/crates/bitwarden-napi/src/client.rs @@ -29,7 +29,8 @@ pub struct BitwardenClient(JsonClient); impl BitwardenClient { #[napi(constructor)] pub fn new(settings_input: Option, log_level: Option) -> Self { - // This will only fail if another logger was already initialized, so we can ignore the result + // This will only fail if another logger was already initialized, so we can ignore the + // result let _ = env_logger::Builder::from_default_env() .filter_level(convert_level(log_level.unwrap_or(LogLevel::Info))) .try_init(); diff --git a/crates/bitwarden-napi/tsconfig.json b/crates/bitwarden-napi/tsconfig.json index f977e0759..8ec7e00d4 100644 --- a/crates/bitwarden-napi/tsconfig.json +++ b/crates/bitwarden-napi/tsconfig.json @@ -7,7 +7,7 @@ "strict": true, "noImplicitAny": true, "esModuleInterop": true, - "declaration": true + "declaration": true, }, - "include": ["src-ts", "src-ts/bitwarden_client", "src-ts/index.ts"] + "include": ["src-ts", "src-ts/bitwarden_client", "src-ts/index.ts"], } diff --git a/crates/bitwarden-uniffi/Cargo.toml b/crates/bitwarden-uniffi/Cargo.toml index eadaaf8d2..6dc940062 100644 --- a/crates/bitwarden-uniffi/Cargo.toml +++ b/crates/bitwarden-uniffi/Cargo.toml @@ -25,6 +25,9 @@ bitwarden = { path = "../bitwarden", features = ["mobile", "internal"] } bitwarden-crypto = { path = "../bitwarden-crypto", version = "=0.1.0", features = [ "mobile", ] } +bitwarden-generators = { path = "../bitwarden-generators", version = "=0.1.0", features = [ + "mobile", +] } [build-dependencies] uniffi = { version = "=0.25.2", features = ["build"] } diff --git a/crates/bitwarden-uniffi/src/auth/mod.rs b/crates/bitwarden-uniffi/src/auth/mod.rs index f67aa3a02..62c791967 100644 --- a/crates/bitwarden-uniffi/src/auth/mod.rs +++ b/crates/bitwarden-uniffi/src/auth/mod.rs @@ -1,7 +1,9 @@ use std::sync::Arc; -use bitwarden::auth::{password::MasterPasswordPolicyOptions, RegisterKeyResponse}; -use bitwarden_crypto::{HashPurpose, Kdf}; +use bitwarden::auth::{ + password::MasterPasswordPolicyOptions, AuthRequestResponse, RegisterKeyResponse, +}; +use bitwarden_crypto::{AsymmetricEncString, HashPurpose, Kdf}; use crate::{error::Result, Client}; @@ -91,4 +93,20 @@ impl ClientAuth { .validate_password(password, password_hash.to_string()) .await?) } + + /// Initialize a new auth request + pub async fn new_auth_request(&self, email: String) -> Result { + Ok(self.0 .0.write().await.auth().new_auth_request(&email)?) + } + + /// Approve an auth request + pub async fn approve_auth_request(&self, public_key: String) -> Result { + Ok(self + .0 + .0 + .write() + .await + .auth() + .approve_auth_request(public_key)?) + } } diff --git a/crates/bitwarden-uniffi/src/crypto.rs b/crates/bitwarden-uniffi/src/crypto.rs index e54aae435..92afcbb87 100644 --- a/crates/bitwarden-uniffi/src/crypto.rs +++ b/crates/bitwarden-uniffi/src/crypto.rs @@ -12,7 +12,8 @@ pub struct ClientCrypto(pub(crate) Arc); #[uniffi::export(async_runtime = "tokio")] impl ClientCrypto { - /// Initialization method for the user crypto. Needs to be called before any other crypto operations. + /// Initialization method for the user crypto. Needs to be called before any other crypto + /// operations. pub async fn initialize_user_crypto(&self, req: InitUserCryptoRequest) -> Result<()> { Ok(self .0 @@ -24,7 +25,8 @@ impl ClientCrypto { .await?) } - /// Initialization method for the organization crypto. Needs to be called after `initialize_user_crypto` but before any other crypto operations. + /// Initialization method for the organization crypto. Needs to be called after + /// `initialize_user_crypto` but before any other crypto operations. pub async fn initialize_org_crypto(&self, req: InitOrgCryptoRequest) -> Result<()> { Ok(self .0 @@ -49,13 +51,15 @@ impl ClientCrypto { .await?) } - /// Generates a PIN protected user key from the provided PIN. The result can be stored and later used - /// to initialize another client instance by using the PIN and the PIN key with `initialize_user_crypto`. + /// Generates a PIN protected user key from the provided PIN. The result can be stored and later + /// used to initialize another client instance by using the PIN and the PIN key with + /// `initialize_user_crypto`. pub async fn derive_pin_key(&self, pin: String) -> Result { Ok(self.0 .0.write().await.crypto().derive_pin_key(pin).await?) } - /// Derives the pin protected user key from encrypted pin. Used when pin requires master password on first unlock. + /// Derives the pin protected user key from encrypted pin. Used when pin requires master + /// password on first unlock. pub async fn derive_pin_user_key(&self, encrypted_pin: EncString) -> Result { Ok(self .0 diff --git a/crates/bitwarden-uniffi/src/error.rs b/crates/bitwarden-uniffi/src/error.rs index b6175eb3b..5eef9bbd5 100644 --- a/crates/bitwarden-uniffi/src/error.rs +++ b/crates/bitwarden-uniffi/src/error.rs @@ -1,6 +1,7 @@ use std::fmt::{Display, Formatter}; -// Name is converted from *Error to *Exception, so we can't just name the enum Error because Exception already exists +// Name is converted from *Error to *Exception, so we can't just name the enum Error because +// Exception already exists #[derive(uniffi::Error, Debug)] #[uniffi(flat_error)] pub enum BitwardenError { diff --git a/crates/bitwarden-uniffi/src/uniffi_support.rs b/crates/bitwarden-uniffi/src/uniffi_support.rs index a91e3de5f..663d5c41e 100644 --- a/crates/bitwarden-uniffi/src/uniffi_support.rs +++ b/crates/bitwarden-uniffi/src/uniffi_support.rs @@ -1,6 +1,7 @@ -use bitwarden_crypto::EncString; +use bitwarden_crypto::{AsymmetricEncString, EncString}; // Forward the type definitions to the main bitwarden crate type DateTime = chrono::DateTime; uniffi::ffi_converter_forward!(DateTime, bitwarden::UniFfiTag, crate::UniFfiTag); uniffi::ffi_converter_forward!(EncString, bitwarden::UniFfiTag, crate::UniFfiTag); +uniffi::ffi_converter_forward!(AsymmetricEncString, bitwarden::UniFfiTag, crate::UniFfiTag); diff --git a/crates/bitwarden-wasm/src/client.rs b/crates/bitwarden-wasm/src/client.rs index 7d3d991db..b9f6723a6 100644 --- a/crates/bitwarden-wasm/src/client.rs +++ b/crates/bitwarden-wasm/src/client.rs @@ -26,8 +26,8 @@ fn convert_level(level: LogLevel) -> Level { } } -// Rc> is to avoid needing to take ownership of the Client during our async run_command function -// https://github.com/rustwasm/wasm-bindgen/issues/2195#issuecomment-799588401 +// Rc> is to avoid needing to take ownership of the Client during our async run_command +// function https://github.com/rustwasm/wasm-bindgen/issues/2195#issuecomment-799588401 #[wasm_bindgen] pub struct BitwardenClient(Rc>); diff --git a/crates/bitwarden/Cargo.toml b/crates/bitwarden/Cargo.toml index 22ee6eaef..51c892ae1 100644 --- a/crates/bitwarden/Cargo.toml +++ b/crates/bitwarden/Cargo.toml @@ -37,7 +37,6 @@ chrono = { version = ">=0.4.26, <0.5", features = [ "serde", "std", ], default-features = false } -data-encoding = ">=2.5.0, <3.0" # We don't use this directly (it's used by rand), but we need it here to enable WASM support getrandom = { version = ">=0.2.9, <0.3", features = ["js"] } hmac = ">=0.12.1, <0.13" diff --git a/crates/bitwarden/src/admin_console/policy.rs b/crates/bitwarden/src/admin_console/policy.rs index 1b4acc310..9a4b0d16f 100644 --- a/crates/bitwarden/src/admin_console/policy.rs +++ b/crates/bitwarden/src/admin_console/policy.rs @@ -20,18 +20,20 @@ pub struct Policy { #[derive(Serialize_repr, Deserialize_repr, Debug, JsonSchema)] #[repr(u8)] pub enum PolicyType { - TwoFactorAuthentication = 0, // Requires users to have 2fa enabled - MasterPassword = 1, // Sets minimum requirements for master password complexity - PasswordGenerator = 2, // Sets minimum requirements/default type for generated passwords/passphrases + TwoFactorAuthentication = 0, // Requires users to have 2fa enabled + MasterPassword = 1, // Sets minimum requirements for master password complexity + PasswordGenerator = 2, /* Sets minimum requirements/default type for generated + * passwords/passphrases */ SingleOrg = 3, // Allows users to only be apart of one organization RequireSso = 4, // Requires users to authenticate with SSO PersonalOwnership = 5, // Disables personal vault ownership for adding/cloning items DisableSend = 6, // Disables the ability to create and edit Bitwarden Sends SendOptions = 7, // Sets restrictions or defaults for Bitwarden Sends - ResetPassword = 8, // Allows orgs to use reset password : also can enable auto-enrollment during invite flow - MaximumVaultTimeout = 9, // Sets the maximum allowed vault timeout + ResetPassword = 8, /* Allows orgs to use reset password : also can enable + * auto-enrollment during invite flow */ + MaximumVaultTimeout = 9, // Sets the maximum allowed vault timeout DisablePersonalVaultExport = 10, // Disable personal vault export - ActivateAutofill = 11, // Activates autofill with page load on the browser extension + ActivateAutofill = 11, // Activates autofill with page load on the browser extension } impl TryFrom for Policy { diff --git a/crates/bitwarden/src/auth/auth_request.rs b/crates/bitwarden/src/auth/auth_request.rs new file mode 100644 index 000000000..facdc8f82 --- /dev/null +++ b/crates/bitwarden/src/auth/auth_request.rs @@ -0,0 +1,137 @@ +use base64::{engine::general_purpose::STANDARD, Engine}; +use bitwarden_crypto::{ + fingerprint, AsymmetricCryptoKey, AsymmetricEncString, AsymmetricPublicCryptoKey, +}; +#[cfg(feature = "mobile")] +use bitwarden_crypto::{KeyDecryptable, SymmetricCryptoKey}; +use bitwarden_generators::{password, PasswordGeneratorRequest}; + +use crate::{error::Error, Client}; + +#[cfg_attr(feature = "mobile", derive(uniffi::Record))] +pub struct AuthRequestResponse { + /// Base64 encoded private key + /// This key is temporarily passed back and will most likely not be available in the future + pub private_key: String, + /// Base64 encoded public key + pub public_key: String, + /// Fingerprint of the public key + pub fingerprint: String, + /// Access code + pub access_code: String, +} + +/// Initiate a new auth request. +/// +/// Generates a private key and access code. The pulic key is uploaded to the server and transmitted +/// to another device. Where the user confirms the validity by confirming the fingerprint. The user +/// key is then encrypted using the public key and returned to the initiating device. +pub(crate) fn new_auth_request(email: &str) -> Result { + let mut rng = rand::thread_rng(); + + let key = AsymmetricCryptoKey::generate(&mut rng); + + let spki = key.to_public_der()?; + + let fingerprint = fingerprint(email, &spki)?; + let b64 = STANDARD.encode(&spki); + + Ok(AuthRequestResponse { + private_key: STANDARD.encode(key.to_der()?), + public_key: b64, + fingerprint, + access_code: password(PasswordGeneratorRequest { + length: 25, + lowercase: true, + uppercase: true, + numbers: true, + special: false, + ..Default::default() + })?, + }) +} + +/// Decrypt the user key using the private key generated previously. +#[cfg(feature = "mobile")] +pub(crate) fn auth_request_decrypt_user_key( + private_key: String, + user_key: AsymmetricEncString, +) -> Result { + let key = AsymmetricCryptoKey::from_der(&STANDARD.decode(private_key)?)?; + let key: String = user_key.decrypt_with_key(&key)?; + + Ok(key.parse()?) +} + +/// Approve an auth request. +/// +/// Encrypts the user key with a public key. +pub(crate) fn approve_auth_request( + client: &mut Client, + public_key: String, +) -> Result { + let public_key = AsymmetricPublicCryptoKey::from_der(&STANDARD.decode(public_key)?)?; + + let enc = client.get_encryption_settings()?; + let key = enc.get_key(&None).ok_or(Error::VaultLocked)?; + + Ok(AsymmetricEncString::encrypt_rsa2048_oaep_sha1( + &key.to_vec(), + &public_key, + )?) +} + +#[test] +fn test_auth_request() { + let request = new_auth_request("test@bitwarden.com").unwrap(); + + let secret = + "w2LO+nwV4oxwswVYCxlOfRUseXfvU03VzvKQHrqeklPgiMZrspUe6sOBToCnDn9Ay0tuCBn8ykVVRb7PWhub2Q=="; + + let private_key = + AsymmetricCryptoKey::from_der(&STANDARD.decode(&request.private_key).unwrap()).unwrap(); + + let encrypted = + AsymmetricEncString::encrypt_rsa2048_oaep_sha1(secret.as_bytes(), &private_key).unwrap(); + + let decrypted = auth_request_decrypt_user_key(request.private_key, encrypted).unwrap(); + + assert_eq!(decrypted.to_base64(), secret); +} + +#[cfg(test)] +mod tests { + use std::num::NonZeroU32; + + use bitwarden_crypto::Kdf; + + use super::*; + use crate::client::{LoginMethod, UserLoginMethod}; + + #[test] + fn test_approve() { + let mut client = Client::new(None); + client.set_login_method(LoginMethod::User(UserLoginMethod::Username { + client_id: "123".to_owned(), + email: "test@bitwarden.com".to_owned(), + kdf: Kdf::PBKDF2 { + iterations: NonZeroU32::new(600_000).unwrap(), + }, + })); + + let user_key = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=".parse().unwrap(); + let private_key ="2.yN7l00BOlUE0Sb0M//Q53w==|EwKG/BduQRQ33Izqc/ogoBROIoI5dmgrxSo82sgzgAMIBt3A2FZ9vPRMY+GWT85JiqytDitGR3TqwnFUBhKUpRRAq4x7rA6A1arHrFp5Tp1p21O3SfjtvB3quiOKbqWk6ZaU1Np9HwqwAecddFcB0YyBEiRX3VwF2pgpAdiPbSMuvo2qIgyob0CUoC/h4Bz1be7Qa7B0Xw9/fMKkB1LpOm925lzqosyMQM62YpMGkjMsbZz0uPopu32fxzDWSPr+kekNNyLt9InGhTpxLmq1go/pXR2uw5dfpXc5yuta7DB0EGBwnQ8Vl5HPdDooqOTD9I1jE0mRyuBpWTTI3FRnu3JUh3rIyGBJhUmHqGZvw2CKdqHCIrQeQkkEYqOeJRJVdBjhv5KGJifqT3BFRwX/YFJIChAQpebNQKXe/0kPivWokHWwXlDB7S7mBZzhaAPidZvnuIhalE2qmTypDwHy22FyqV58T8MGGMchcASDi/QXI6kcdpJzPXSeU9o+NC68QDlOIrMVxKFeE7w7PvVmAaxEo0YwmuAzzKy9QpdlK0aab/xEi8V4iXj4hGepqAvHkXIQd+r3FNeiLfllkb61p6WTjr5urcmDQMR94/wYoilpG5OlybHdbhsYHvIzYoLrC7fzl630gcO6t4nM24vdB6Ymg9BVpEgKRAxSbE62Tqacxqnz9AcmgItb48NiR/He3n3ydGjPYuKk/ihZMgEwAEZvSlNxYONSbYrIGDtOY+8Nbt6KiH3l06wjZW8tcmFeVlWv+tWotnTY9IqlAfvNVTjtsobqtQnvsiDjdEVtNy/s2ci5TH+NdZluca2OVEr91Wayxh70kpM6ib4UGbfdmGgCo74gtKvKSJU0rTHakQ5L9JlaSDD5FamBRyI0qfL43Ad9qOUZ8DaffDCyuaVyuqk7cz9HwmEmvWU3VQ+5t06n/5kRDXttcw8w+3qClEEdGo1KeENcnXCB32dQe3tDTFpuAIMLqwXs6FhpawfZ5kPYvLPczGWaqftIs/RXJ/EltGc0ugw2dmTLpoQhCqrcKEBDoYVk0LDZKsnzitOGdi9mOWse7Se8798ib1UsHFUjGzISEt6upestxOeupSTOh0v4+AjXbDzRUyogHww3V+Bqg71bkcMxtB+WM+pn1XNbVTyl9NR040nhP7KEf6e9ruXAtmrBC2ah5cFEpLIot77VFZ9ilLuitSz+7T8n1yAh1IEG6xxXxninAZIzi2qGbH69O5RSpOJuJTv17zTLJQIIc781JwQ2TTwTGnx5wZLbffhCasowJKd2EVcyMJyhz6ru0PvXWJ4hUdkARJs3Xu8dus9a86N8Xk6aAPzBDqzYb1vyFIfBxP0oO8xFHgd30Cgmz8UrSE3qeWRrF8ftrI6xQnFjHBGWD/JWSvd6YMcQED0aVuQkuNW9ST/DzQThPzRfPUoiL10yAmV7Ytu4fR3x2sF0Yfi87YhHFuCMpV/DsqxmUizyiJuD938eRcH8hzR/VO53Qo3UIsqOLcyXtTv6THjSlTopQ+JOLOnHm1w8dzYbLN44OG44rRsbihMUQp+wUZ6bsI8rrOnm9WErzkbQFbrfAINdoCiNa6cimYIjvvnMTaFWNymqY1vZxGztQiMiHiHYwTfwHTXrb9j0uPM=|09J28iXv9oWzYtzK2LBT6Yht4IT4MijEkk0fwFdrVQ4=".parse().unwrap(); + client + .initialize_user_crypto("asdfasdfasdf", user_key, private_key) + .unwrap(); + + let public_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnRtpYLp9QLaEUkdPkWZX6TrMUKFoSaFamBKDL0NlS6xwtETTqYIxRVsvnHii3Dhz+fh3aHQVyBa1rBXogeH3MLERzNADwZhpWtBT9wKCXY5o0fIWYdZV/Nf0Y+0ZoKdImrGPLPmyHGfCqrvrK7g09q8+3kXUlkdAImlQqc5TiYwiHBfUQVTBq/Ae7a0FEpajx1NUM4h3edpCYxbvnpSTuzMgbmbUUS4gdCaheA2ibYxy/zkLzsaLygoibMyGNl9Y8J5n7dDrVXpUKZTihVfXwHfEZwtKNunWsmmt8rEJWVpguUDEDVSUogoxQcNaCi7KHn9ioSip76hg1jLpypO3WwIDAQAB"; + + // Verify fingerprint + let pbkey = STANDARD.decode(public_key).unwrap(); + let fingerprint = fingerprint("test@bitwarden.com", &pbkey).unwrap(); + assert_eq!(fingerprint, "spill-applaud-sweep-habitable-shrunk"); + + approve_auth_request(&mut client, public_key.to_owned()).unwrap(); + } +} diff --git a/crates/bitwarden/src/auth/client_auth.rs b/crates/bitwarden/src/auth/client_auth.rs index d4379d1eb..aaa387741 100644 --- a/crates/bitwarden/src/auth/client_auth.rs +++ b/crates/bitwarden/src/auth/client_auth.rs @@ -1,9 +1,13 @@ +#[cfg(feature = "internal")] +use bitwarden_crypto::{AsymmetricEncString, DeviceKey, TrustDeviceResponse}; + #[cfg(feature = "secrets")] use crate::auth::login::{login_access_token, AccessTokenLoginRequest, AccessTokenLoginResponse}; use crate::{auth::renew::renew_token, error::Result, Client}; #[cfg(feature = "internal")] use crate::{ auth::{ + auth_request::{approve_auth_request, new_auth_request}, login::{ login_api_key, login_password, send_two_factor_email, ApiKeyLoginRequest, ApiKeyLoginResponse, PasswordLoginRequest, PasswordLoginResponse, @@ -13,9 +17,10 @@ use crate::{ password_strength, satisfies_policy, validate_password, MasterPasswordPolicyOptions, }, register::{make_register_keys, register}, - RegisterKeyResponse, RegisterRequest, + AuthRequestResponse, RegisterKeyResponse, RegisterRequest, }, client::Kdf, + error::Error, }; pub struct ClientAuth<'a> { @@ -97,6 +102,27 @@ impl<'a> ClientAuth<'a> { pub async fn validate_password(&self, password: String, password_hash: String) -> Result { validate_password(self.client, password, password_hash).await } + + pub fn new_auth_request(&self, email: &str) -> Result { + new_auth_request(email) + } + + pub fn approve_auth_request(&mut self, public_key: String) -> Result { + approve_auth_request(self.client, public_key) + } + + pub async fn trust_device(&self) -> Result { + trust_device(self.client) + } +} + +#[cfg(feature = "internal")] +fn trust_device(client: &Client) -> Result { + let enc = client.get_encryption_settings()?; + + let user_key = enc.get_key(&None).ok_or(Error::VaultLocked)?; + + Ok(DeviceKey::trust_device(user_key)?) } impl<'a> Client { @@ -172,7 +198,7 @@ mod tests { .login_access_token(&AccessTokenLoginRequest { access_token: "0.ec2c1d46-6a4b-4751-a310-af9601317f2d.C2IgxjjLF7qSshsbwe8JGcbM075YXw:X8vbvA0bduihIDe/qrzIQQ==".into(), state_file: None, - },) + }) .await .unwrap(); assert!(res.authenticated); diff --git a/crates/bitwarden/src/auth/login/password.rs b/crates/bitwarden/src/auth/login/password.rs index e5d579aa2..f873ace97 100644 --- a/crates/bitwarden/src/auth/login/password.rs +++ b/crates/bitwarden/src/auth/login/password.rs @@ -94,9 +94,11 @@ pub struct PasswordLoginResponse { pub reset_master_password: bool, /// Whether or not the user is required to update their master password pub force_password_reset: bool, - /// The available two factor authentication options. Present only when authentication fails due to requiring a second authentication factor. + /// The available two factor authentication options. Present only when authentication fails due + /// to requiring a second authentication factor. pub two_factor: Option, - /// The information required to present the user with a captcha challenge. Only present when authentication fails due to requiring validation of a captcha challenge. + /// The information required to present the user with a captcha challenge. Only present when + /// authentication fails due to requiring validation of a captcha challenge. pub captcha: Option, } diff --git a/crates/bitwarden/src/auth/mod.rs b/crates/bitwarden/src/auth/mod.rs index e80ab28cf..23b64eaf9 100644 --- a/crates/bitwarden/src/auth/mod.rs +++ b/crates/bitwarden/src/auth/mod.rs @@ -6,13 +6,18 @@ pub mod login; pub mod password; pub mod renew; pub use jwt_token::JWTToken; - #[cfg(feature = "internal")] mod register; #[cfg(feature = "internal")] use bitwarden_crypto::{HashPurpose, MasterKey}; #[cfg(feature = "internal")] pub use register::{RegisterKeyResponse, RegisterRequest}; +#[cfg(feature = "internal")] +mod auth_request; +#[cfg(feature = "mobile")] +pub(crate) use auth_request::auth_request_decrypt_user_key; +#[cfg(feature = "internal")] +pub use auth_request::AuthRequestResponse; #[cfg(feature = "internal")] use crate::{client::Kdf, error::Result}; diff --git a/crates/bitwarden/src/client/access_token.rs b/crates/bitwarden/src/client/access_token.rs index 4a6d5ed8c..d7cad2fca 100644 --- a/crates/bitwarden/src/client/access_token.rs +++ b/crates/bitwarden/src/client/access_token.rs @@ -88,7 +88,8 @@ mod tests { use crate::client::AccessToken; - // Encryption key without base64 padding, we generate it with padding but ignore it when decoding + // Encryption key without base64 padding, we generate it with padding but ignore it when + // decoding let t = "0.ec2c1d46-6a4b-4751-a310-af9601317f2d.C2IgxjjLF7qSshsbwe8JGcbM075YXw:X8vbvA0bduihIDe/qrzIQQ"; assert!(AccessToken::from_str(t).is_ok()); diff --git a/crates/bitwarden/src/client/client_settings.rs b/crates/bitwarden/src/client/client_settings.rs index 172baf733..2f0637b4a 100644 --- a/crates/bitwarden/src/client/client_settings.rs +++ b/crates/bitwarden/src/client/client_settings.rs @@ -1,8 +1,8 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -/// Basic client behavior settings. These settings specify the various targets and behavior of the Bitwarden Client. -/// They are optional and uneditable once the client is initialized. +/// Basic client behavior settings. These settings specify the various targets and behavior of the +/// Bitwarden Client. They are optional and uneditable once the client is initialized. /// /// Defaults to /// diff --git a/crates/bitwarden/src/client/encryption_settings.rs b/crates/bitwarden/src/client/encryption_settings.rs index c8126b4fe..8b4c5197e 100644 --- a/crates/bitwarden/src/client/encryption_settings.rs +++ b/crates/bitwarden/src/client/encryption_settings.rs @@ -45,9 +45,10 @@ impl EncryptionSettings { } } - /// Initialize the encryption settings with the decrypted user key and the encrypted user private key - /// This should only be used when unlocking the vault via biometrics or when the vault is set to lock: "never" - /// Otherwise handling the decrypted user key is dangerous and discouraged + /// Initialize the encryption settings with the decrypted user key and the encrypted user + /// private key This should only be used when unlocking the vault via biometrics or when the + /// vault is set to lock: "never" Otherwise handling the decrypted user key is dangerous and + /// discouraged #[cfg(feature = "internal")] pub(crate) fn new_decrypted_key( user_key: SymmetricCryptoKey, @@ -105,7 +106,8 @@ impl EncryptionSettings { } pub(crate) fn get_key(&self, org_id: &Option) -> Option<&SymmetricCryptoKey> { - // If we don't have a private key set (to decode multiple org keys), we just use the main user key + // If we don't have a private key set (to decode multiple org keys), we just use the main + // user key if self.private_key.is_none() { return Some(&self.user_key); } diff --git a/crates/bitwarden/src/lib.rs b/crates/bitwarden/src/lib.rs index 5468be855..28eb521de 100644 --- a/crates/bitwarden/src/lib.rs +++ b/crates/bitwarden/src/lib.rs @@ -46,7 +46,6 @@ //! Ok(()) //! } //! ``` -//! #[cfg(feature = "mobile")] uniffi::setup_scaffolding!(); diff --git a/crates/bitwarden/src/mobile/crypto.rs b/crates/bitwarden/src/mobile/crypto.rs index c32a1d1d8..bd71af2be 100644 --- a/crates/bitwarden/src/mobile/crypto.rs +++ b/crates/bitwarden/src/mobile/crypto.rs @@ -47,13 +47,22 @@ pub enum InitUserCryptoMethod { Pin { /// The user's PIN pin: String, - /// The user's symmetric crypto key, encrypted with the PIN. Use `derive_pin_key` to obtain this. + /// The user's symmetric crypto key, encrypted with the PIN. Use `derive_pin_key` to obtain + /// this. pin_protected_user_key: EncString, }, + AuthRequest { + /// Private Key generated by the `crate::auth::new_auth_request`. + request_private_key: String, + /// User Key protected by the private key provided in `AuthRequestResponse`. + protected_user_key: AsymmetricEncString, + }, } #[cfg(feature = "internal")] pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequest) -> Result<()> { + use crate::auth::auth_request_decrypt_user_key; + let login_method = crate::client::LoginMethod::User(crate::client::UserLoginMethod::Username { client_id: "".to_string(), email: req.email, @@ -78,6 +87,13 @@ pub async fn initialize_user_crypto(client: &mut Client, req: InitUserCryptoRequ } => { client.initialize_user_crypto_pin(&pin, pin_protected_user_key, private_key)?; } + InitUserCryptoMethod::AuthRequest { + request_private_key, + protected_user_key, + } => { + let user_key = auth_request_decrypt_user_key(request_private_key, protected_user_key)?; + client.initialize_user_crypto_decrypted_key(user_key, private_key)?; + } } Ok(()) diff --git a/crates/bitwarden/src/mobile/vault/client_totp.rs b/crates/bitwarden/src/mobile/vault/client_totp.rs index 1d4ffd486..3d3b80f98 100644 --- a/crates/bitwarden/src/mobile/vault/client_totp.rs +++ b/crates/bitwarden/src/mobile/vault/client_totp.rs @@ -13,7 +13,6 @@ impl<'a> ClientVault<'a> { /// - A base32 encoded string /// - OTP Auth URI /// - Steam URI - /// pub fn generate_totp( &'a self, key: String, diff --git a/crates/bitwarden/src/platform/secret_verification_request.rs b/crates/bitwarden/src/platform/secret_verification_request.rs index b501d0181..e05926620 100644 --- a/crates/bitwarden/src/platform/secret_verification_request.rs +++ b/crates/bitwarden/src/platform/secret_verification_request.rs @@ -4,10 +4,11 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct SecretVerificationRequest { - /// The user's master password to use for user verification. If supplied, this will be used for verification - /// purposes. + /// The user's master password to use for user verification. If supplied, this will be used for + /// verification purposes. pub master_password: Option, - /// Alternate user verification method through OTP. This is provided for users who have no master password due to - /// use of Customer Managed Encryption. Must be present and valid if master_password is absent. + /// Alternate user verification method through OTP. This is provided for users who have no + /// master password due to use of Customer Managed Encryption. Must be present and valid if + /// master_password is absent. pub otp: Option, } diff --git a/crates/bitwarden/src/platform/sync.rs b/crates/bitwarden/src/platform/sync.rs index 870b88ab9..c1a039137 100644 --- a/crates/bitwarden/src/platform/sync.rs +++ b/crates/bitwarden/src/platform/sync.rs @@ -69,7 +69,8 @@ pub struct DomainResponse { #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] pub struct SyncResponse { - /// Data about the user, including their encryption keys and the organizations they are a part of + /// Data about the user, including their encryption keys and the organizations they are a part + /// of pub profile: ProfileResponse, pub folders: Vec, pub collections: Vec, diff --git a/crates/bitwarden/src/tool/client_generator.rs b/crates/bitwarden/src/tool/client_generator.rs index bf7c66ef3..16c786f9b 100644 --- a/crates/bitwarden/src/tool/client_generator.rs +++ b/crates/bitwarden/src/tool/client_generator.rs @@ -1,9 +1,10 @@ -use bitwarden_generators::{ - passphrase, password, username, PassphraseGeneratorRequest, PasswordGeneratorRequest, - UsernameGeneratorRequest, -}; +use bitwarden_generators::{passphrase, password, username}; -use crate::{error::Result, Client}; +use crate::{ + error::Result, + generators::{PassphraseGeneratorRequest, PasswordGeneratorRequest, UsernameGeneratorRequest}, + Client, +}; pub struct ClientGenerator<'a> { pub(crate) client: &'a crate::Client, @@ -61,10 +62,11 @@ impl<'a> ClientGenerator<'a> { } /// Generates a random username. - /// There are different username generation strategies, which can be customized using the `input` parameter. + /// There are different username generation strategies, which can be customized using the + /// `input` parameter. /// - /// Note that most generation strategies will be executed on the client side, but `Forwarded` will use third-party - /// services, which may require a specific setup or API key. + /// Note that most generation strategies will be executed on the client side, but `Forwarded` + /// will use third-party services, which may require a specific setup or API key. /// /// ``` /// use bitwarden::{Client, generators::{UsernameGeneratorRequest}, error::Result}; diff --git a/crates/bitwarden/src/vault/cipher/attachment.rs b/crates/bitwarden/src/vault/cipher/attachment.rs index 3e1e47025..40f32dcfb 100644 --- a/crates/bitwarden/src/vault/cipher/attachment.rs +++ b/crates/bitwarden/src/vault/cipher/attachment.rs @@ -4,9 +4,8 @@ use bitwarden_crypto::{ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use crate::error::{Error, Result}; - use super::Cipher; +use crate::error::{Error, Result}; #[derive(Serialize, Deserialize, Debug, JsonSchema)] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -63,7 +62,8 @@ impl<'a> KeyEncryptable for Attachm let mut attachment = self.attachment; - // Because this is a new attachment, we have to generate a key for it, encrypt the contents with it, and then encrypt the key with the cipher key + // Because this is a new attachment, we have to generate a key for it, encrypt the contents + // with it, and then encrypt the key with the cipher key let attachment_key = SymmetricCryptoKey::generate(rand::thread_rng()); let encrypted_contents = self.contents.encrypt_with_key(&attachment_key)?; attachment.key = Some(attachment_key.to_vec().encrypt_with_key(ciphers_key)?); @@ -136,7 +136,6 @@ impl TryFrom for Attachment #[cfg(test)] mod tests { use base64::{engine::general_purpose::STANDARD, Engine}; - use bitwarden_crypto::{EncString, KeyDecryptable, SymmetricCryptoKey}; use crate::vault::{ diff --git a/crates/bitwarden/src/vault/cipher/card.rs b/crates/bitwarden/src/vault/cipher/card.rs index 05c3aa631..cd61a17d8 100644 --- a/crates/bitwarden/src/vault/cipher/card.rs +++ b/crates/bitwarden/src/vault/cipher/card.rs @@ -47,12 +47,12 @@ impl KeyEncryptable for CardView { impl KeyDecryptable for Card { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(CardView { - cardholder_name: self.cardholder_name.decrypt_with_key(key)?, - exp_month: self.exp_month.decrypt_with_key(key)?, - exp_year: self.exp_year.decrypt_with_key(key)?, - code: self.code.decrypt_with_key(key)?, - brand: self.brand.decrypt_with_key(key)?, - number: self.number.decrypt_with_key(key)?, + cardholder_name: self.cardholder_name.decrypt_with_key(key).ok().flatten(), + exp_month: self.exp_month.decrypt_with_key(key).ok().flatten(), + exp_year: self.exp_year.decrypt_with_key(key).ok().flatten(), + code: self.code.decrypt_with_key(key).ok().flatten(), + brand: self.brand.decrypt_with_key(key).ok().flatten(), + number: self.number.decrypt_with_key(key).ok().flatten(), }) } } diff --git a/crates/bitwarden/src/vault/cipher/cipher.rs b/crates/bitwarden/src/vault/cipher/cipher.rs index 010f3cd8a..34f01ca60 100644 --- a/crates/bitwarden/src/vault/cipher/cipher.rs +++ b/crates/bitwarden/src/vault/cipher/cipher.rs @@ -46,7 +46,8 @@ pub struct Cipher { pub folder_id: Option, pub collection_ids: Vec, - /// More recent ciphers uses individual encryption keys to encrypt the other fields of the Cipher. + /// More recent ciphers uses individual encryption keys to encrypt the other fields of the + /// Cipher. pub key: Option, pub name: EncString, @@ -182,22 +183,22 @@ impl KeyDecryptable for Cipher { folder_id: self.folder_id, collection_ids: self.collection_ids.clone(), key: self.key.clone(), - name: self.name.decrypt_with_key(key)?, - notes: self.notes.decrypt_with_key(key)?, + name: self.name.decrypt_with_key(key).ok().unwrap_or_default(), + notes: self.notes.decrypt_with_key(key).ok().flatten(), r#type: self.r#type, - login: self.login.decrypt_with_key(key)?, - identity: self.identity.decrypt_with_key(key)?, - card: self.card.decrypt_with_key(key)?, - secure_note: self.secure_note.decrypt_with_key(key)?, + login: self.login.decrypt_with_key(key).ok().flatten(), + identity: self.identity.decrypt_with_key(key).ok().flatten(), + card: self.card.decrypt_with_key(key).ok().flatten(), + secure_note: self.secure_note.decrypt_with_key(key).ok().flatten(), favorite: self.favorite, reprompt: self.reprompt, organization_use_totp: self.organization_use_totp, edit: self.edit, view_password: self.view_password, - local_data: self.local_data.decrypt_with_key(key)?, - attachments: self.attachments.decrypt_with_key(key)?, - fields: self.fields.decrypt_with_key(key)?, - password_history: self.password_history.decrypt_with_key(key)?, + local_data: self.local_data.decrypt_with_key(key).ok().flatten(), + attachments: self.attachments.decrypt_with_key(key).ok().flatten(), + fields: self.fields.decrypt_with_key(key).ok().flatten(), + password_history: self.password_history.decrypt_with_key(key).ok().flatten(), creation_date: self.creation_date, deleted_date: self.deleted_date, revision_date: self.revision_date, @@ -298,8 +299,8 @@ impl KeyDecryptable for Cipher { organization_id: self.organization_id, folder_id: self.folder_id, collection_ids: self.collection_ids.clone(), - name: self.name.decrypt_with_key(key)?, - sub_title: self.get_decrypted_subtitle(key)?, + name: self.name.decrypt_with_key(key).ok().unwrap_or_default(), + sub_title: self.get_decrypted_subtitle(key).ok().unwrap_or_default(), r#type: self.r#type, favorite: self.favorite, reprompt: self.reprompt, diff --git a/crates/bitwarden/src/vault/cipher/field.rs b/crates/bitwarden/src/vault/cipher/field.rs index 6bb106c32..db896474f 100644 --- a/crates/bitwarden/src/vault/cipher/field.rs +++ b/crates/bitwarden/src/vault/cipher/field.rs @@ -55,8 +55,8 @@ impl KeyEncryptable for FieldView { impl KeyDecryptable for Field { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(FieldView { - name: self.name.decrypt_with_key(key)?, - value: self.value.decrypt_with_key(key)?, + name: self.name.decrypt_with_key(key).ok().flatten(), + value: self.value.decrypt_with_key(key).ok().flatten(), r#type: self.r#type, linked_id: self.linked_id, }) diff --git a/crates/bitwarden/src/vault/cipher/identity.rs b/crates/bitwarden/src/vault/cipher/identity.rs index c4154aeba..f59166eec 100644 --- a/crates/bitwarden/src/vault/cipher/identity.rs +++ b/crates/bitwarden/src/vault/cipher/identity.rs @@ -83,24 +83,24 @@ impl KeyEncryptable for IdentityView { impl KeyDecryptable for Identity { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(IdentityView { - title: self.title.decrypt_with_key(key)?, - first_name: self.first_name.decrypt_with_key(key)?, - middle_name: self.middle_name.decrypt_with_key(key)?, - last_name: self.last_name.decrypt_with_key(key)?, - address1: self.address1.decrypt_with_key(key)?, - address2: self.address2.decrypt_with_key(key)?, - address3: self.address3.decrypt_with_key(key)?, - city: self.city.decrypt_with_key(key)?, - state: self.state.decrypt_with_key(key)?, - postal_code: self.postal_code.decrypt_with_key(key)?, - country: self.country.decrypt_with_key(key)?, - company: self.company.decrypt_with_key(key)?, - email: self.email.decrypt_with_key(key)?, - phone: self.phone.decrypt_with_key(key)?, - ssn: self.ssn.decrypt_with_key(key)?, - username: self.username.decrypt_with_key(key)?, - passport_number: self.passport_number.decrypt_with_key(key)?, - license_number: self.license_number.decrypt_with_key(key)?, + title: self.title.decrypt_with_key(key).ok().flatten(), + first_name: self.first_name.decrypt_with_key(key).ok().flatten(), + middle_name: self.middle_name.decrypt_with_key(key).ok().flatten(), + last_name: self.last_name.decrypt_with_key(key).ok().flatten(), + address1: self.address1.decrypt_with_key(key).ok().flatten(), + address2: self.address2.decrypt_with_key(key).ok().flatten(), + address3: self.address3.decrypt_with_key(key).ok().flatten(), + city: self.city.decrypt_with_key(key).ok().flatten(), + state: self.state.decrypt_with_key(key).ok().flatten(), + postal_code: self.postal_code.decrypt_with_key(key).ok().flatten(), + country: self.country.decrypt_with_key(key).ok().flatten(), + company: self.company.decrypt_with_key(key).ok().flatten(), + email: self.email.decrypt_with_key(key).ok().flatten(), + phone: self.phone.decrypt_with_key(key).ok().flatten(), + ssn: self.ssn.decrypt_with_key(key).ok().flatten(), + username: self.username.decrypt_with_key(key).ok().flatten(), + passport_number: self.passport_number.decrypt_with_key(key).ok().flatten(), + license_number: self.license_number.decrypt_with_key(key).ok().flatten(), }) } } diff --git a/crates/bitwarden/src/vault/cipher/login.rs b/crates/bitwarden/src/vault/cipher/login.rs index 139750d51..ea5b1f357 100644 --- a/crates/bitwarden/src/vault/cipher/login.rs +++ b/crates/bitwarden/src/vault/cipher/login.rs @@ -98,11 +98,11 @@ impl KeyDecryptable for LoginUri { impl KeyDecryptable for Login { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(LoginView { - username: self.username.decrypt_with_key(key)?, - password: self.password.decrypt_with_key(key)?, + username: self.username.decrypt_with_key(key).ok().flatten(), + password: self.password.decrypt_with_key(key).ok().flatten(), password_revision_date: self.password_revision_date, - uris: self.uris.decrypt_with_key(key)?, - totp: self.totp.decrypt_with_key(key)?, + uris: self.uris.decrypt_with_key(key).ok().flatten(), + totp: self.totp.decrypt_with_key(key).ok().flatten(), autofill_on_page_load: self.autofill_on_page_load, }) } diff --git a/crates/bitwarden/src/vault/collection.rs b/crates/bitwarden/src/vault/collection.rs index a78209fc9..d20e5d729 100644 --- a/crates/bitwarden/src/vault/collection.rs +++ b/crates/bitwarden/src/vault/collection.rs @@ -51,7 +51,7 @@ impl KeyDecryptable for Collection { id: self.id, organization_id: self.organization_id, - name: self.name.decrypt_with_key(key)?, + name: self.name.decrypt_with_key(key).ok().unwrap_or_default(), external_id: self.external_id.clone(), hide_passwords: self.hide_passwords, diff --git a/crates/bitwarden/src/vault/folder.rs b/crates/bitwarden/src/vault/folder.rs index a6b9790bc..17d1d40aa 100644 --- a/crates/bitwarden/src/vault/folder.rs +++ b/crates/bitwarden/src/vault/folder.rs @@ -43,7 +43,7 @@ impl KeyDecryptable for Folder { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { Ok(FolderView { id: self.id, - name: self.name.decrypt_with_key(key)?, + name: self.name.decrypt_with_key(key).ok().unwrap_or_default(), revision_date: self.revision_date, }) } diff --git a/crates/bitwarden/src/vault/password_history.rs b/crates/bitwarden/src/vault/password_history.rs index 333420b6a..2ec20116b 100644 --- a/crates/bitwarden/src/vault/password_history.rs +++ b/crates/bitwarden/src/vault/password_history.rs @@ -41,7 +41,7 @@ impl KeyDecryptable for PasswordHistory key: &SymmetricCryptoKey, ) -> Result { Ok(PasswordHistoryView { - password: self.password.decrypt_with_key(key)?, + password: self.password.decrypt_with_key(key).ok().unwrap_or_default(), last_used_date: self.last_used_date, }) } diff --git a/crates/bitwarden/src/vault/send.rs b/crates/bitwarden/src/vault/send.rs index 8ed405b4c..aba67e00c 100644 --- a/crates/bitwarden/src/vault/send.rs +++ b/crates/bitwarden/src/vault/send.rs @@ -197,8 +197,9 @@ impl KeyEncryptable for SendFileView { impl LocateKey for Send {} impl KeyDecryptable for Send { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { - // For sends, we first decrypt the send key with the user key, and stretch it to it's full size - // For the rest of the fields, we ignore the provided SymmetricCryptoKey and the stretched key + // For sends, we first decrypt the send key with the user key, and stretch it to it's full + // size For the rest of the fields, we ignore the provided SymmetricCryptoKey and + // the stretched key let k: Vec = self.key.decrypt_with_key(key)?; let key = Send::derive_shareable_key(&k)?; @@ -206,15 +207,15 @@ impl KeyDecryptable for Send { id: self.id, access_id: self.access_id.clone(), - name: self.name.decrypt_with_key(&key)?, - notes: self.notes.decrypt_with_key(&key)?, + name: self.name.decrypt_with_key(&key).ok().unwrap_or_default(), + notes: self.notes.decrypt_with_key(&key).ok().flatten(), key: Some(URL_SAFE_NO_PAD.encode(k)), new_password: None, has_password: self.password.is_some(), r#type: self.r#type, - file: self.file.decrypt_with_key(&key)?, - text: self.text.decrypt_with_key(&key)?, + file: self.file.decrypt_with_key(&key).ok().flatten(), + text: self.text.decrypt_with_key(&key).ok().flatten(), max_access_count: self.max_access_count, access_count: self.access_count, @@ -230,8 +231,9 @@ impl KeyDecryptable for Send { impl KeyDecryptable for Send { fn decrypt_with_key(&self, key: &SymmetricCryptoKey) -> Result { - // For sends, we first decrypt the send key with the user key, and stretch it to it's full size - // For the rest of the fields, we ignore the provided SymmetricCryptoKey and the stretched key + // For sends, we first decrypt the send key with the user key, and stretch it to it's full + // size For the rest of the fields, we ignore the provided SymmetricCryptoKey and + // the stretched key let key = Send::get_key(&self.key, key)?; Ok(SendListView { @@ -253,8 +255,9 @@ impl KeyDecryptable for Send { impl LocateKey for SendView {} impl KeyEncryptable for SendView { fn encrypt_with_key(self, key: &SymmetricCryptoKey) -> Result { - // For sends, we first decrypt the send key with the user key, and stretch it to it's full size - // For the rest of the fields, we ignore the provided SymmetricCryptoKey and the stretched key + // For sends, we first decrypt the send key with the user key, and stretch it to it's full + // size For the rest of the fields, we ignore the provided SymmetricCryptoKey and + // the stretched key let k = match (self.key, self.id) { // Existing send, decrypt key (Some(k), _) => URL_SAFE_NO_PAD diff --git a/crates/bitwarden/src/vault/totp.rs b/crates/bitwarden/src/vault/totp.rs index 223c8c504..6ba754034 100644 --- a/crates/bitwarden/src/vault/totp.rs +++ b/crates/bitwarden/src/vault/totp.rs @@ -1,7 +1,6 @@ use std::{collections::HashMap, str::FromStr}; use chrono::{DateTime, Utc}; -use data_encoding::BASE32_NOPAD; use hmac::{Hmac, Mac}; use reqwest::Url; use schemars::JsonSchema; @@ -129,19 +128,6 @@ impl FromStr for Totp { /// - OTP Auth URI /// - Steam URI fn from_str(key: &str) -> Result { - fn decode_secret(secret: &str) -> Result> { - // Sanitize the secret to only contain allowed characters - let secret = secret - .to_uppercase() - .chars() - .filter(|c| BASE32_CHARS.contains(*c)) - .collect::(); - - BASE32_NOPAD - .decode(secret.as_bytes()) - .map_err(|e| e.to_string().into()) - } - let params = if key.starts_with("otpauth://") { let url = Url::parse(key).map_err(|_| "Unable to parse URL")?; let parts: HashMap<_, _> = url.query_pairs().collect(); @@ -166,26 +152,26 @@ impl FromStr for Totp { .and_then(|v| v.parse().ok()) .map(|v: u32| v.max(1)) .unwrap_or(DEFAULT_PERIOD), - secret: decode_secret( + secret: decode_b32( &parts .get("secret") .map(|v| v.to_string()) .ok_or("Missing secret in otpauth URI")?, - )?, + ), } } else if let Some(secret) = key.strip_prefix("steam://") { Totp { algorithm: Algorithm::Steam, digits: 5, period: DEFAULT_PERIOD, - secret: decode_secret(secret)?, + secret: decode_b32(secret), } } else { Totp { algorithm: DEFAULT_ALGORITHM, digits: DEFAULT_DIGITS, period: DEFAULT_PERIOD, - secret: decode_secret(key)?, + secret: decode_b32(key), } }; @@ -220,12 +206,43 @@ fn derive_binary(hash: Vec) -> u32 { | hash[offset + 3] as u32 } +/// This code is migrated from our javascript implementation and is not technically a correct base32 +/// decoder since we filter out various characters, and use exact chunking. +fn decode_b32(s: &str) -> Vec { + let s = s.to_uppercase(); + + let mut bits = String::new(); + for c in s.chars() { + if let Some(i) = BASE32_CHARS.find(c) { + bits.push_str(&format!("{:05b}", i)); + } + } + let mut bytes = Vec::new(); + + for chunk in bits.as_bytes().chunks_exact(8) { + let byte_str = std::str::from_utf8(chunk).unwrap(); + let byte = u8::from_str_radix(byte_str, 2).unwrap(); + bytes.push(byte); + } + + bytes +} + #[cfg(test)] mod tests { use chrono::Utc; use super::*; + #[test] + fn test_decode_b32() { + let res = decode_b32("WQIQ25BRKZYCJVYP"); + assert_eq!(res, vec![180, 17, 13, 116, 49, 86, 112, 36, 215, 15]); + + let res = decode_b32("ABCD123"); + assert_eq!(res, vec![0, 68, 61]); + } + #[test] fn test_generate_totp() { let cases = vec![ @@ -234,6 +251,14 @@ mod tests { ("PIUDISEQYA", "829846"), // non padded ("PIUDISEQYA======", "829846"), // padded ("PIUD1IS!EQYA=", "829846"), // sanitized + // Steam + ("steam://HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ", "7W6CJ"), + ("steam://ABCD123", "N26DF"), + // Various weird lengths + ("ddfdf", "932653"), + ("HJSGFJHDFDJDJKSDFD", "000034"), + ("xvdsfasdfasdasdghsgsdfg", "403786"), + ("KAKFJWOSFJ12NWL", "093430"), ]; let time = Some( @@ -277,18 +302,4 @@ mod tests { assert_eq!(response.code, "730364".to_string()); assert_eq!(response.period, 60); } - - #[test] - fn test_generate_steam() { - let key = "steam://HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ".to_string(); - let time = Some( - DateTime::parse_from_rfc3339("2023-01-01T00:00:00.000Z") - .unwrap() - .with_timezone(&Utc), - ); - let response = generate_totp(key, time).unwrap(); - - assert_eq!(response.code, "7W6CJ".to_string()); - assert_eq!(response.period, 30); - } } diff --git a/crates/bw/Cargo.toml b/crates/bw/Cargo.toml index 0ea4877f4..210a6b80a 100644 --- a/crates/bw/Cargo.toml +++ b/crates/bw/Cargo.toml @@ -13,7 +13,7 @@ Bitwarden Password Manager CLI keywords = ["bitwarden", "password-manager", "cli"] [dependencies] -clap = { version = "4.4.16", features = ["derive", "env"] } +clap = { version = "4.4.18", features = ["derive", "env"] } color-eyre = "0.6" env_logger = "0.10.1" inquire = "0.6.2" diff --git a/crates/bws/Cargo.toml b/crates/bws/Cargo.toml index c334d9c01..c2530af63 100644 --- a/crates/bws/Cargo.toml +++ b/crates/bws/Cargo.toml @@ -20,8 +20,8 @@ chrono = { version = "0.4.31", features = [ "clock", "std", ], default-features = false } -clap = { version = "4.4.16", features = ["derive", "env", "string"] } -clap_complete = "4.4.6" +clap = { version = "4.4.18", features = ["derive", "env", "string"] } +clap_complete = "4.4.8" color-eyre = "0.6" comfy-table = "^7.1.0" directories = "5.0.1" diff --git a/crates/bws/src/render.rs b/crates/bws/src/render.rs index 83c5cd532..f9563c81b 100644 --- a/crates/bws/src/render.rs +++ b/crates/bws/src/render.rs @@ -42,7 +42,8 @@ pub(crate) fn serialize_response, const N: usiz match output { Output::JSON => { let mut text = serde_json::to_string_pretty(&data).unwrap(); - // Yaml/table/tsv serializations add a newline at the end, so we do the same here for consistency + // Yaml/table/tsv serializations add a newline at the end, so we do the same here for + // consistency text.push('\n'); pretty_print("json", &text, color); } @@ -110,7 +111,8 @@ fn pretty_print(language: &str, data: &str, color: bool) { } } -// We're using const generics for the array lengths to make sure the header count and value count match +// We're using const generics for the array lengths to make sure the header count and value count +// match pub(crate) trait TableSerialize: Sized { fn get_headers() -> [&'static str; N]; fn get_values(&self) -> Vec<[String; N]>; diff --git a/crates/sdk-schemas/src/main.rs b/crates/sdk-schemas/src/main.rs index 0cfc045a0..f9f074485 100644 --- a/crates/sdk-schemas/src/main.rs +++ b/crates/sdk-schemas/src/main.rs @@ -3,8 +3,9 @@ use std::{fs::File, io::Write}; use anyhow::Result; use schemars::{schema::RootSchema, schema_for, JsonSchema}; -/// Creates a json schema file for any type passed in using Schemars. The filename and path of the generated -/// schema file is derived from the namespace passed into the macro or supplied as the first argument. +/// Creates a json schema file for any type passed in using Schemars. The filename and path of the +/// generated schema file is derived from the namespace passed into the macro or supplied as the +/// first argument. /// /// The schema filename is given by the last namespace element and trims off any `>` characters. /// This means the filename will represent the last _generic_ type of the type given. @@ -30,7 +31,8 @@ use schemars::{schema::RootSchema, schema_for, JsonSchema}; /// ``` /// write_schema_for!(response::two_factor_login_response::two_factor_providers::TwoFactorProviders); /// ``` -/// will generate `TwoFactorProviders.json` at `{{pwd}}/response/two_factor_login_response/TwoFactorProviders.json` +/// will generate `TwoFactorProviders.json` at +/// `{{pwd}}/response/two_factor_login_response/TwoFactorProviders.json` /// /// ## Path specified /// diff --git a/languages/js/sdk-client/package-lock.json b/languages/js/sdk-client/package-lock.json index 144a79f64..f5447f5fe 100644 --- a/languages/js/sdk-client/package-lock.json +++ b/languages/js/sdk-client/package-lock.json @@ -39,9 +39,9 @@ } }, "node_modules/@types/node": { - "version": "18.19.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.7.tgz", - "integrity": "sha512-IGRJfoNX10N/PfrReRZ1br/7SQ+2vF/tK3KXNwzXz82D32z5dMQEoOlFew18nLSN+vMNcLY4GrKfzwi/yWI8/w==", + "version": "18.19.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.8.tgz", + "integrity": "sha512-g1pZtPhsvGVTwmeVoexWZLTQaOvXwoSq//pTL0DHeNzUDrFnir4fgETdhjhIxjVnN+hKOuh98+E1eMLnUXstFg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" diff --git a/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt index dd79ff870..f51d0309e 100644 --- a/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt +++ b/languages/kotlin/app/src/main/java/com/bitwarden/myapplication/MainActivity.kt @@ -27,9 +27,9 @@ import com.bitwarden.core.Folder import com.bitwarden.core.InitOrgCryptoRequest import com.bitwarden.core.InitUserCryptoMethod import com.bitwarden.core.InitUserCryptoRequest -import com.bitwarden.core.Kdf import com.bitwarden.core.Uuid import com.bitwarden.crypto.HashPurpose +import com.bitwarden.crypto.Kdf import com.bitwarden.myapplication.ui.theme.MyApplicationTheme import com.bitwarden.sdk.Client import io.ktor.client.HttpClient diff --git a/languages/kotlin/doc.md b/languages/kotlin/doc.md index ccf193301..fd98e463e 100644 --- a/languages/kotlin/doc.md +++ b/languages/kotlin/doc.md @@ -138,6 +138,28 @@ password, use the email OTP. **Output**: std::result::Result<,BitwardenError> +### `new_auth_request` + +Initialize a new auth request + +**Arguments**: + +- self: +- email: String + +**Output**: std::result::Result + +### `approve_auth_request` + +Approve an auth request + +**Arguments**: + +- self: +- public_key: String + +**Output**: std::result::Result + ## ClientAttachments ### `encrypt_buffer` @@ -1227,6 +1249,32 @@ implementations. + + authRequest + object + + + + + + + + + + + + + + + + + + + + +
KeyTypeDescription
request_private_keystringPrivate Key generated by the `crate::auth::new_auth_request`.
protected_user_keyUser Key protected by the private key provided in `AuthRequestResponse`.
+ + ## `InitUserCryptoRequest` diff --git a/languages/swift/build.sh b/languages/swift/build.sh index 87f9cecdb..69bc9a881 100755 --- a/languages/swift/build.sh +++ b/languages/swift/build.sh @@ -28,16 +28,12 @@ cargo run -p uniffi-bindgen generate \ --out-dir tmp/bindings # Move generated swift bindings -mv ./tmp/bindings/BitwardenCore.swift ./Sources/BitwardenSdk/ -mv ./tmp/bindings/BitwardenCrypto.swift ./Sources/BitwardenSdk/ -mv ./tmp/bindings/BitwardenSDK.swift ./Sources/BitwardenSdk/ +mv ./tmp/bindings/*.swift ./Sources/BitwardenSdk/ # Massage the generated files to fit xcframework mkdir tmp/Headers -mv ./tmp/bindings/BitwardenCoreFFI.h ./tmp/Headers/ -mv ./tmp/bindings/BitwardenCryptoFFI.h ./tmp/Headers/ -mv ./tmp/bindings/BitwardenFFI.h ./tmp/Headers/ -cat ./tmp/bindings/BitwardenFFI.modulemap ./tmp/bindings/BitwardenCoreFFI.modulemap ./tmp/bindings/BitwardenCryptoFFI.modulemap > ./tmp/Headers/module.modulemap +mv ./tmp/bindings/*.h ./tmp/Headers/ +cat ./tmp/bindings/*.modulemap > ./tmp/Headers/module.modulemap # Build xcframework xcodebuild -create-xcframework \ diff --git a/package-lock.json b/package-lock.json index 3e47d00a0..07b2080f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,9 @@ "version": "0.0.0", "license": "SEE LICENSE IN LICENSE", "devDependencies": { - "@openapitools/openapi-generator-cli": "2.7.0", + "@openapitools/openapi-generator-cli": "2.9.0", "handlebars": "^4.7.8", - "prettier": "3.2.2", + "prettier": "3.2.4", "quicktype-core": "23.0.81", "rimraf": "5.0.5", "ts-node": "10.9.2", @@ -178,45 +178,93 @@ "node": ">=8" } }, - "node_modules/@nestjs/axios": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-0.1.0.tgz", - "integrity": "sha512-b2TT2X6BFbnNoeteiaxCIiHaFcSbVW+S5yygYqiIq5i6H77yIU3IVuLdpQkHq8/EqOWFwMopLN8jdkUT71Am9w==", + "node_modules/@nuxtjs/opencollective": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz", + "integrity": "sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==", "dev": true, "dependencies": { - "axios": "0.27.2" + "chalk": "^4.1.0", + "consola": "^2.15.0", + "node-fetch": "^2.6.1" }, + "bin": { + "opencollective": "bin/opencollective.js" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/@openapitools/openapi-generator-cli": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.9.0.tgz", + "integrity": "sha512-KQpftKeiMoH5aEI/amOVLFGkGeT3DyA7Atj7v7l8xT3O9xnIUpoDmMg0WBTEh+NHxEwEAITQNDzr+JLjkXVaKw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@nestjs/axios": "3.0.1", + "@nestjs/common": "10.3.0", + "@nestjs/core": "10.3.0", + "@nuxtjs/opencollective": "0.3.2", + "axios": "1.6.5", + "chalk": "4.1.2", + "commander": "8.3.0", + "compare-versions": "4.1.4", + "concurrently": "6.5.1", + "console.table": "0.10.0", + "fs-extra": "10.1.0", + "glob": "7.2.3", + "inquirer": "8.2.6", + "lodash": "4.17.21", + "reflect-metadata": "0.1.13", + "rxjs": "7.8.1", + "tslib": "2.6.2" + }, + "bin": { + "openapi-generator-cli": "main.js" + }, + "engines": { + "node": ">=10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/openapi_generator" + } + }, + "node_modules/@openapitools/openapi-generator-cli/node_modules/@nestjs/axios": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.1.tgz", + "integrity": "sha512-VlOZhAGDmOoFdsmewn8AyClAdGpKXQQaY1+3PGB+g6ceurGIdTxZgRX3VXc1T6Zs60PedWjg3A82TDOB05mrzQ==", + "dev": true, "peerDependencies": { - "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0", + "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0", + "axios": "^1.3.1", "reflect-metadata": "^0.1.12", "rxjs": "^6.0.0 || ^7.0.0" } }, - "node_modules/@nestjs/common": { - "version": "9.3.11", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-9.3.11.tgz", - "integrity": "sha512-IFZ2G/5UKWC2Uo7tJ4SxGed2+aiA+sJyWeWsGTogKVDhq90oxVBToh+uCDeI31HNUpqYGoWmkletfty42zUd8A==", + "node_modules/@openapitools/openapi-generator-cli/node_modules/@nestjs/common": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.3.0.tgz", + "integrity": "sha512-DGv34UHsZBxCM3H5QGE2XE/+oLJzz5+714JQjBhjD9VccFlQs3LRxo/epso4l7nJIiNlZkPyIUC8WzfU/5RTsQ==", "dev": true, "dependencies": { "iterare": "1.2.1", - "tslib": "2.5.0", - "uid": "2.0.1" + "tslib": "2.6.2", + "uid": "2.0.2" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/nest" }, "peerDependencies": { - "cache-manager": "<=5", "class-transformer": "*", "class-validator": "*", "reflect-metadata": "^0.1.12", "rxjs": "^7.1.0" }, "peerDependenciesMeta": { - "cache-manager": { - "optional": true - }, "class-transformer": { "optional": true }, @@ -225,16 +273,10 @@ } } }, - "node_modules/@nestjs/common/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "dev": true - }, - "node_modules/@nestjs/core": { - "version": "9.3.11", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-9.3.11.tgz", - "integrity": "sha512-CI27a2JFd5rvvbgkalWqsiwQNhcP4EAG5BUK8usjp29wVp1kx30ghfBT8FLqIgmkRVo65A0IcEnWsxeXMntkxQ==", + "node_modules/@openapitools/openapi-generator-cli/node_modules/@nestjs/core": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.0.tgz", + "integrity": "sha512-N06P5ncknW/Pm8bj964WvLIZn2gNhHliCBoAO1LeBvNImYkecqKcrmLbY49Fa1rmMfEM3MuBHeDys3edeuYAOA==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -242,18 +284,18 @@ "fast-safe-stringify": "2.1.1", "iterare": "1.2.1", "path-to-regexp": "3.2.0", - "tslib": "2.5.0", - "uid": "2.0.1" + "tslib": "2.6.2", + "uid": "2.0.2" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/nest" }, "peerDependencies": { - "@nestjs/common": "^9.0.0", - "@nestjs/microservices": "^9.0.0", - "@nestjs/platform-express": "^9.0.0", - "@nestjs/websockets": "^9.0.0", + "@nestjs/common": "^10.0.0", + "@nestjs/microservices": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "@nestjs/websockets": "^10.0.0", "reflect-metadata": "^0.1.12", "rxjs": "^7.1.0" }, @@ -269,63 +311,27 @@ } } }, - "node_modules/@nestjs/core/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "dev": true - }, - "node_modules/@nuxtjs/opencollective": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz", - "integrity": "sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==", + "node_modules/@openapitools/openapi-generator-cli/node_modules/axios": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", + "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", "dev": true, "dependencies": { - "chalk": "^4.1.0", - "consola": "^2.15.0", - "node-fetch": "^2.6.1" - }, - "bin": { - "opencollective": "bin/opencollective.js" - }, - "engines": { - "node": ">=8.0.0", - "npm": ">=5.0.0" + "follow-redirects": "^1.15.4", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, - "node_modules/@openapitools/openapi-generator-cli": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.7.0.tgz", - "integrity": "sha512-ieEpHTA/KsDz7ANw03lLPYyjdedDEXYEyYoGBRWdduqXWSX65CJtttjqa8ZaB1mNmIjMtchUHwAYQmTLVQ8HYg==", + "node_modules/@openapitools/openapi-generator-cli/node_modules/uid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", + "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", "dev": true, - "hasInstallScript": true, "dependencies": { - "@nestjs/axios": "0.1.0", - "@nestjs/common": "9.3.11", - "@nestjs/core": "9.3.11", - "@nuxtjs/opencollective": "0.3.2", - "chalk": "4.1.2", - "commander": "8.3.0", - "compare-versions": "4.1.4", - "concurrently": "6.5.1", - "console.table": "0.10.0", - "fs-extra": "10.1.0", - "glob": "7.1.6", - "inquirer": "8.2.5", - "lodash": "4.17.21", - "reflect-metadata": "0.1.13", - "rxjs": "7.8.0", - "tslib": "2.0.3" - }, - "bin": { - "openapi-generator-cli": "main.js" + "@lukeed/csprng": "^1.0.0" }, "engines": { - "node": ">=10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/openapi_generator" + "node": ">=8" } }, "node_modules/@pkgjs/parseargs": { @@ -462,16 +468,6 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, - "node_modules/axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", - "dev": true, - "dependencies": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -922,9 +918,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", "dev": true, "funding": [ { @@ -1013,15 +1009,15 @@ } }, "node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" }, @@ -1117,9 +1113,9 @@ "dev": true }, "node_modules/inquirer": { - "version": "8.2.5", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", - "integrity": "sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==", + "version": "8.2.6", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", + "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", "dev": true, "dependencies": { "ansi-escapes": "^4.2.1", @@ -1136,12 +1132,26 @@ "string-width": "^4.1.0", "strip-ansi": "^6.0.0", "through": "^2.3.6", - "wrap-ansi": "^7.0.0" + "wrap-ansi": "^6.0.1" }, "engines": { "node": ">=12.0.0" } }, + "node_modules/inquirer/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -1470,9 +1480,9 @@ } }, "node_modules/prettier": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.2.tgz", - "integrity": "sha512-HTByuKZzw7utPiDO523Tt2pLtEyK7OibUD9suEJQrPUCYQqrHr74GGX6VidMrovbf/I50mPqr8j/II6oBAuc5A==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", + "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -1493,6 +1503,12 @@ "node": ">= 0.6.0" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "node_modules/quicktype-core": { "version": "23.0.81", "resolved": "https://registry.npmjs.org/quicktype-core/-/quicktype-core-23.0.81.tgz", @@ -1678,20 +1694,14 @@ } }, "node_modules/rxjs": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", - "integrity": "sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, "dependencies": { "tslib": "^2.1.0" } }, - "node_modules/rxjs/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1918,9 +1928,9 @@ } }, "node_modules/tslib": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", - "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, "node_modules/type-fest": { @@ -1961,18 +1971,6 @@ "node": ">=0.8.0" } }, - "node_modules/uid": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.1.tgz", - "integrity": "sha512-PF+1AnZgycpAIEmNtjxGBVmKbZAQguaa4pBUq6KNaGEcpzZ2klCNZLM34tsjp76maN00TttiiUf6zkIBpJQm2A==", - "dev": true, - "dependencies": { - "@lukeed/csprng": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", diff --git a/package.json b/package.json index 2becd41f2..14b5692a0 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,9 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "devDependencies": { - "@openapitools/openapi-generator-cli": "2.7.0", + "@openapitools/openapi-generator-cli": "2.9.0", "handlebars": "^4.7.8", - "prettier": "3.2.2", + "prettier": "3.2.4", "quicktype-core": "23.0.81", "rimraf": "5.0.5", "ts-node": "10.9.2", diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 000000000..bb3baeccd --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,7 @@ +# Wrap comments and increase the width of comments to 100 +comment_width = 100 +wrap_comments = true + +# Sort and group imports +group_imports = "StdExternalCrate" +imports_granularity = "Crate"