From 649a7e6dfe2c7ed37d7644a99dc0d49133c17b55 Mon Sep 17 00:00:00 2001 From: Oleksandr Deundiak Date: Mon, 14 Aug 2023 14:39:18 +0200 Subject: [PATCH 1/5] feat(rust): switch to new `Identity` design List of commits before the big squash: - feat(rust): extend `v2` identity api - feat(rust): improve verify credential api - feat(rust): extend `Identifier` api - fix(rust): fix `Identity`-related builders - feat(rust): improve `Identifier` serialization - feat(rust): use `v2` identity in `ockam` crate - feat(rust): use `v2` identity in `ockam_abac` crate - feat(rust): use `v2` identity in `ockam_api` crate - feat(rust): use `v2` identity in `ockam_command` crate - feat(rust): replace `controller.id` with temporary value - feat(rust): remove const-time comparison for `Identifier` - chore(rust): fix clippy warnings - test(rust): replace fixture of enrollment ticket - feat(rust): add `import_private_identity` method - feat(rust): check if public key matched on private identity import - feat(rust): rewrite `Vault` - feat(rust): update `ockam_identity` to new `Vault` structure - feat(rust): disable old identity implementation to avoid updating it with new `Vault` - test(rust): delete old identity implementation tests - feat(rust): update `ockam` to new `Vault` structure - feat(rust): update `ockam_api` to new `Vault` structure - feat(rust): update `ockam_command` to new `Vault` structure - feat(rust): update `AWS` vault - fix(rust): change p256 public key length to 65 - fix(rust): fix `import_private_identity` fn - test(rust): fix compilation for aws `ockam_command` test - feat(rust): replace old identity implementation with the new one - fix(rust): fix `no_std` build - test(rust): replace all-zero test identity with a random - feat(rust): change `import_private_identity` to accept binary instead of hex - chore(rust): decrease level of logging for some messages - feat(rust): save `Identity` to the storage during import - test(rust): update kafka integration test - feat(rust): implement and use lazy purpose key creation - fix(rust): fix `expected_identifier` check during credentials verification - feat(rust): remove deprecated `PROJECT_ID` from credentials check - feat(rust): add `encoding_utils` to `ockam_identity` - feat(rust): improve display implementation for identity-related models - feat(rust): add `create` to software vaults - chore(rust): organize vault imports - feat(rust): add sync interface for `SoftwareVerifyingVault` - feat(rust): update ffi according to the new `Vault` design - feat(rust): improve `ockam/src/node.rs` - chore(rust): fix `get_started` examples - feat(rust): improve aes length checks - test(rust): fix secure_channel/vault integration test - feat(rust): remove `PurposeKeys` from `SecureChannels` - feat(rust): improve `Identity` verification - test(rust): improve `Identity` verification tests - chore(rust): clean up identities `mod.rs` - feat(rust): delete the old key during rotation - feat(rust): add persistent `Identity` rotation - feat(rust): improve `PurposeKey` and `Credential` verification - feat(rust): add `TRUST_CONTEXT_ID_UTF8` for type-safety - test(rust): fix `Identity` comparison test - chore(rust): remove `extern crate core;` from `ockam_command` - feat(rust): add key type check on identity creation with an existing key - fix(rust): rebase fixes - chore(rust): update import formats in `ockam_identity` - feat(rust): add `export_identity` and `get_identity` to `Identities` - feat(rust): add `IdentityBuilder` - test(rust): fix aws identity test - feat(rust): split `PurposeKeys` into creation and verification - feat(rust): split `Credentials` into creation and verification - feat(rust): move models conversions - feat(rust): add `PurposeKeyBuilder` - feat(rust): improve `IdentityBuilder` and `PurposeKeyBuilder` - feat(rust): better key type checks - test(rust): improve identity and purposekey tests - chore(rust): no_std fix - chore(rust): add a `FIXME` for `TrustContext` - feat(rust): split identity models into multiple files - feat(rust): split signing_vault into identity_vault and credential_vault - feat(rust): move `Vault` structure to `ockam_identity` - test(rust): update bats test to support new `Identity` - feat(rust): optimize credentials on-the-wire format - feat(rust): add ability to attest `PurposeKey` given its public key - fix(rust): remove panic from `SecretType` -> `SecretAttributes` cast - fix(rust): `no_std` build - feat(rust): remove unused vault part - feat(rust): allow 5 sec time drift during creds and pk verification --- Cargo.lock | 5 +- .../06-credentials-exchange-client.rs | 45 +- .../06-credentials-exchange-issuer.rs | 46 +- .../06-credentials-exchange-server.rs | 46 +- ...bute-based-authentication-control-plane.rs | 7 +- ...tribute-based-authentication-edge-plane.rs | 7 +- examples/rust/get_started/src/project.rs | 14 +- examples/rust/get_started/src/token.rs | 2 +- implementations/rust/ockam/ockam/src/error.rs | 1 + implementations/rust/ockam/ockam/src/lib.rs | 1 - implementations/rust/ockam/ockam/src/node.rs | 128 +- .../rust/ockam/ockam/tests/forwarder.rs | 12 +- .../src/attribute_access_control.rs | 16 +- .../ockam_abac/src/storage/lmdb_storage.rs | 2 +- .../rust/ockam/ockam_api/Cargo.toml | 11 +- .../rust/ockam/ockam_api/src/auth.rs | 8 +- .../ockam_api/src/authenticator/direct.rs | 73 +- .../src/authenticator/direct/types.rs | 8 +- .../src/bootstrapped_identities_store.rs | 71 +- .../ockam_api/src/cli_state/credentials.rs | 38 +- .../ockam_api/src/cli_state/identities.rs | 92 +- .../rust/ockam/ockam_api/src/cli_state/mod.rs | 30 +- .../ockam/ockam_api/src/cli_state/nodes.rs | 17 +- .../ockam/ockam_api/src/cli_state/projects.rs | 4 +- .../ockam/ockam_api/src/cli_state/vaults.rs | 23 +- .../rust/ockam/ockam_api/src/cloud/enroll.rs | 4 +- .../rust/ockam/ockam_api/src/cloud/mod.rs | 10 +- .../rust/ockam/ockam_api/src/cloud/project.rs | 42 +- .../ockam/ockam_api/src/cloud/share/create.rs | 18 +- .../ockam/ockam_api/src/cloud/share/mod.rs | 7 +- .../rust/ockam/ockam_api/src/config/cli.rs | 46 +- .../rust/ockam/ockam_api/src/config/lookup.rs | 17 +- .../ockam_api/src/enroll/oidc_service.rs | 4 +- .../src/identity/enrollment_ticket.rs | 2 +- .../src/identity/identity_service.rs | 75 +- .../ockam/ockam_api/src/identity/models.rs | 143 +- .../outlet_service/interceptor_listener.rs | 4 +- .../ockam_api/src/kafka/secure_channel_map.rs | 14 +- .../src/nodes/authority_node/authority.rs | 38 +- .../src/nodes/authority_node/configuration.rs | 35 +- .../ockam_api/src/nodes/connection/mod.rs | 6 +- .../ockam_api/src/nodes/connection/secure.rs | 6 +- .../ockam_api/src/nodes/models/forwarder.rs | 8 +- .../ockam_api/src/nodes/models/identity.rs | 48 - .../ockam/ockam_api/src/nodes/models/mod.rs | 1 - .../ockam_api/src/nodes/models/portal.rs | 8 +- .../src/nodes/models/secure_channel.rs | 6 +- .../ockam/ockam_api/src/nodes/registry.rs | 12 +- .../rust/ockam/ockam_api/src/nodes/service.rs | 45 +- .../src/nodes/service/credentials.rs | 4 +- .../ockam_api/src/nodes/service/forwarder.rs | 4 +- .../src/nodes/service/node_identities.rs | 67 +- .../src/nodes/service/node_services.rs | 29 +- .../ockam_api/src/nodes/service/portals.rs | 4 +- .../src/nodes/service/secure_channel.rs | 39 +- .../rust/ockam/ockam_api/src/okta/mod.rs | 23 +- .../rust/ockam/ockam_api/src/schema.cddl | 4 +- .../rust/ockam/ockam_api/src/util.rs | 66 +- .../rust/ockam/ockam_api/src/verifier.rs | 40 +- .../ockam/ockam_api/src/verifier/types.rs | 24 +- .../rust/ockam/ockam_api/static/controller.id | 2 +- .../rust/ockam/ockam_api/tests/auth_smoke.rs | 13 +- .../ockam_api/tests/credential_issuer.rs | 51 +- .../rust/ockam/ockam_api/tests/identity.rs | 73 - .../ockam_app/src/app/state/repository.rs | 2 +- .../ockam_app/src/invitations/commands.rs | 12 +- .../rust/ockam/ockam_command/Cargo.toml | 1 - .../ockam/ockam_command/src/authenticated.rs | 17 +- .../ockam_command/src/authority/create.rs | 28 +- .../ockam_command/src/credential/issue.rs | 48 +- .../ockam/ockam_command/src/credential/mod.rs | 62 +- .../ockam_command/src/credential/show.rs | 20 +- .../ockam_command/src/credential/store.rs | 39 +- .../ockam_command/src/credential/verify.rs | 26 +- .../ockam/ockam_command/src/enroll/command.rs | 6 +- .../ockam_command/src/identity/create.rs | 13 +- .../ockam/ockam_command/src/identity/show.rs | 49 +- .../ockam/ockam_command/src/output/output.rs | 327 ++++- .../ockam/ockam_command/src/policy/mod.rs | 5 +- .../ockam/ockam_command/src/project/enroll.rs | 5 +- .../ockam/ockam_command/src/project/ticket.rs | 4 +- .../ockam/ockam_command/src/project/util.rs | 6 +- .../ockam/ockam_command/src/relay/create.rs | 4 +- .../src/secure_channel/create.rs | 4 +- .../src/secure_channel/listener/create.rs | 6 +- .../ockam/ockam_command/src/service/config.rs | 4 +- .../ockam/ockam_command/src/share/service.rs | 7 +- .../rust/ockam/ockam_command/src/status.rs | 6 +- .../ockam_command/src/tcp/inlet/create.rs | 4 +- .../rust/ockam/ockam_command/src/util/api.rs | 4 +- .../rust/ockam/ockam_command/src/util/mod.rs | 2 +- .../src/util/orchestrator_api.rs | 7 +- .../ockam/ockam_command/src/util/parsers.rs | 7 +- .../ockam_command/src/vault/attach_key.rs | 65 +- .../ockam_command/tests/bats/authority.bats | 4 +- .../ockam_command/tests/bats/credentials.bats | 6 +- .../ockam_command/tests/bats/identity.bats | 8 +- .../ockam/ockam_command/tests/bats/nodes.bats | 4 +- .../ockam/ockam_command/tests/bats/vault.bats | 6 +- .../tests/fixtures/user.enrollment.ticket | 2 +- .../rust/ockam/ockam_ffi/src/vault.rs | 354 ++--- .../rust/ockam/ockam_identity/Cargo.toml | 3 +- .../src/credential/credential.rs | 161 --- .../src/credential/credential_builder.rs | 97 -- .../src/credential/credential_data.rs | 410 ------ .../ockam_identity/src/credential/mod.rs | 10 - .../src/credentials/authority_service.rs | 53 +- .../src/credentials/credentials.rs | 205 ++- .../src/credentials/credentials_creation.rs | 105 ++ .../src/credentials/credentials_issuer.rs | 105 +- .../src/credentials/credentials_retriever.rs | 33 +- .../src/credentials/credentials_server.rs | 46 +- .../credentials/credentials_server_worker.rs | 34 +- .../credentials/credentials_verification.rs | 201 +++ .../ockam_identity/src/credentials/mod.rs | 6 + .../one_time_code.rs | 0 .../src/credentials/trust_context.rs | 7 +- .../rust/ockam/ockam_identity/src/error.rs | 74 +- .../src/identities/identities.rs | 73 +- .../src/identities/identities_builder.rs | 58 +- .../src/identities/identities_creation.rs | 272 ++-- .../src/identities/identities_vault.rs | 131 -- .../src/identities/identity_builder.rs | 133 ++ .../src/identities/identity_keys.rs | 630 +++------ .../src/identities/identity_options.rs | 56 + .../ockam_identity/src/identities/mod.rs | 9 +- .../identities/storage/attributes_entry.rs | 32 +- .../storage/identities_repository.rs | 298 ---- .../storage/identities_repository_impl.rs | 10 +- .../storage/identities_repository_trait.rs | 4 +- .../src/identities/storage/lmdb_storage.rs | 147 -- .../src/identities/storage/mod.rs | 22 +- .../src/identities/storage/storage.rs | 91 -- .../ockam_identity/src/identities/tests.rs | 276 ---- .../src/{v2 => }/identity/constants.rs | 0 .../{v2 => }/identity/history_comparison.rs | 0 .../ockam_identity/src/identity/identity.rs | 314 ++++- .../identity_change/change_identifier.rs | 30 - .../identity/identity_change/create_key.rs | 55 - .../identity_change/identity_change.rs | 104 -- .../identity_change_constants.rs | 14 - .../identity_change/key_attributes.rs | 110 -- .../src/identity/identity_change/mod.rs | 16 - .../identity/identity_change/rotate_key.rs | 55 - .../src/identity/identity_change/signature.rs | 45 - .../src/identity/identity_change_history.rs | 238 ---- .../src/identity/identity_identifier.rs | 163 --- .../identity/identity_verification.rs | 128 +- .../ockam/ockam_identity/src/identity/mod.rs | 15 +- .../src/{v2 => }/identity/verified_change.rs | 38 +- .../rust/ockam/ockam_identity/src/lib.rs | 38 +- .../src/{v2 => }/models/change_history.rs | 31 +- .../src/{v2 => }/models/credential.rs | 5 +- .../models/credential_and_purpose_key.rs | 2 +- .../ockam_identity/src/models/identifiers.rs | 16 + .../ockam_identity/src/{v2 => }/models/mod.rs | 1 + .../ockam_identity/src/models/public_keys.rs | 11 + .../models/purpose_key_attestation.rs | 30 +- .../ockam_identity/src/models/signatures.rs | 7 + .../ockam_identity/src/models/timestamp.rs | 9 + .../src/models/utils/change_history.rs | 103 ++ .../src/models/utils/credentials.rs | 55 + .../models => models/utils}/identifiers.rs | 81 +- .../ockam_identity/src/models/utils/mod.rs | 14 + .../models => models/utils}/public_keys.rs | 38 +- .../models/utils/purpose_key_attestation.rs | 80 ++ .../{v2/models => models/utils}/signatures.rs | 22 +- .../src/models/utils/timestamp.rs | 31 + .../src/{v2 => }/models/versioned_data.rs | 0 .../src/{v2 => }/purpose_key/mod.rs | 0 .../src/{v2 => }/purpose_key/purpose.rs | 0 .../src/{v2 => }/purpose_key/purpose_key.rs | 25 +- .../ockam_identity/src/purpose_keys/mod.rs | 15 + .../src/purpose_keys/purpose_key_builder.rs | 168 +++ .../src/purpose_keys/purpose_key_options.rs | 75 + .../src/purpose_keys/purpose_keys.rs | 171 +++ .../src/purpose_keys/purpose_keys_creation.rs | 311 ++++ .../purpose_keys/purpose_keys_verification.rs | 123 ++ .../src/{v2 => }/purpose_keys/storage/mod.rs | 0 .../storage/purpose_keys_repository_impl.rs | 10 +- .../storage/purpose_keys_repository_trait.rs | 4 +- .../credential_access_control.rs | 11 +- .../access_control/identity_access_control.rs | 27 +- .../src/secure_channel/addresses.rs | 3 +- .../src/secure_channel/decryptor.rs | 38 +- .../src/secure_channel/encryptor.rs | 16 +- .../src/secure_channel/encryptor_worker.rs | 9 +- .../src/secure_channel/handshake/handshake.rs | 85 +- .../handshake/handshake_state_machine.rs | 185 ++- .../handshake/handshake_worker.rs | 48 +- .../handshake/initiator_state_machine.rs | 36 +- .../handshake/responder_state_machine.rs | 36 +- .../src/secure_channel/key_tracker.rs | 3 +- .../src/secure_channel/listener.rs | 32 +- .../src/secure_channel/local_info.rs | 11 +- .../ockam_identity/src/secure_channel/mod.rs | 19 +- .../src/secure_channel/options.rs | 25 +- .../src/secure_channel/registry.rs | 21 +- .../trust_policy/all_trust_policy.rs | 7 +- .../trust_policy/any_trust_policy.rs | 7 +- .../src/secure_channel/trust_policy/mod.rs | 2 - .../trust_policy/trust_everyone_policy.rs | 3 +- .../trust_policy/trust_identifier_policy.rs | 9 +- .../trust_multi_identifier_policy.rs | 19 +- .../trust_policy/trust_policy_type.rs | 8 +- .../trust_policy/trust_public_key_policy.rs | 50 - .../src/secure_channels/secure_channels.rs | 33 +- .../secure_channels_builder.rs | 53 +- .../src/{v2 => }/storage/lmdb_storage.rs | 19 +- .../src/{v2 => }/storage/memory.rs | 3 +- .../src/{v2 => }/storage/mod.rs | 0 .../storage/sqlite_storage.rs | 3 +- .../src/{v2 => }/storage/storage.rs | 0 .../ockam_identity/src/{v2 => }/utils.rs | 16 +- .../src/v2/credentials/authority_service.rs | 84 -- .../src/v2/credentials/credentials.rs | 301 ---- .../src/v2/credentials/credentials_issuer.rs | 159 --- .../v2/credentials/credentials_retriever.rs | 127 -- .../src/v2/credentials/credentials_server.rs | 165 --- .../credentials/credentials_server_worker.rs | 200 --- .../ockam_identity/src/v2/credentials/mod.rs | 17 - .../src/v2/credentials/one_time_code.rs | 134 -- .../src/v2/credentials/trust_context.rs | 40 - .../rust/ockam/ockam_identity/src/v2/error.rs | 61 - .../src/v2/identities/identities.rs | 95 -- .../src/v2/identities/identities_builder.rs | 70 - .../src/v2/identities/identities_creation.rs | 102 -- .../src/v2/identities/identities_vault.rs | 131 -- .../src/v2/identities/identity_keys.rs | 207 --- .../ockam_identity/src/v2/identities/mod.rs | 19 - .../v2/identities/storage/attributes_entry.rs | 57 - .../src/v2/identities/storage/mod.rs | 7 - .../ockam_identity/src/v2/identities/tests.rs | 201 --- .../src/v2/identity/identity.rs | 235 ---- .../ockam_identity/src/v2/identity/mod.rs | 12 - .../rust/ockam/ockam_identity/src/v2/mod.rs | 44 - .../ockam_identity/src/v2/models/timestamp.rs | 25 - .../ockam_identity/src/v2/purpose_keys/mod.rs | 7 - .../src/v2/purpose_keys/purpose_keys.rs | 332 ----- .../credential_access_control.rs | 71 - .../access_control/identity_access_control.rs | 77 - .../v2/secure_channel/access_control/mod.rs | 5 - .../src/v2/secure_channel/addresses.rs | 47 - .../src/v2/secure_channel/api.rs | 30 - .../src/v2/secure_channel/decryptor.rs | 193 --- .../src/v2/secure_channel/encryptor.rs | 95 -- .../src/v2/secure_channel/encryptor_worker.rs | 131 -- .../src/v2/secure_channel/handshake/error.rs | 42 - .../v2/secure_channel/handshake/handshake.rs | 764 ---------- .../handshake/handshake_state_machine.rs | 264 ---- .../handshake/handshake_worker.rs | 362 ----- .../handshake/initiator_state_machine.rs | 123 -- .../src/v2/secure_channel/handshake/mod.rs | 12 - .../handshake/responder_state_machine.rs | 126 -- .../src/v2/secure_channel/key_tracker.rs | 263 ---- .../src/v2/secure_channel/listener.rs | 131 -- .../src/v2/secure_channel/local_info.rs | 79 -- .../src/v2/secure_channel/mod.rs | 154 -- .../src/v2/secure_channel/nonce_tracker.rs | 83 -- .../src/v2/secure_channel/options.rs | 252 ---- .../src/v2/secure_channel/registry.rs | 157 --- .../src/v2/secure_channel/role.rs | 37 - .../trust_policy/all_trust_policy.rs | 75 - .../trust_policy/any_trust_policy.rs | 74 - .../src/v2/secure_channel/trust_policy/mod.rs | 13 - .../trust_policy/trust_everyone_policy.rs | 16 - .../trust_policy/trust_identifier_policy.rs | 26 - .../trust_multi_identifier_policy.rs | 49 - .../trust_policy/trust_policy_type.rs | 69 - .../src/v2/secure_channels/common.rs | 94 -- .../src/v2/secure_channels/mod.rs | 9 - .../src/v2/secure_channels/secure_channels.rs | 140 -- .../secure_channels_builder.rs | 84 -- .../src/v2/storage/sqlite_storage.rs | 192 --- .../rust/ockam/ockam_identity/src/vault.rs | 92 ++ .../rust/ockam/ockam_identity/tests/aws.rs | 126 ++ .../ockam/ockam_identity/tests/channel.rs | 355 +++-- .../tests/ciphertext_message_flow_auth.rs | 2 +- .../tests/common/crazy_vault.rs | 92 ++ .../mod.rs => common/message_flow_auth.rs} | 25 +- .../ockam/ockam_identity/tests/common/mod.rs | 171 +-- .../ockam/ockam_identity/tests/credentials.rs | 108 +- .../ockam_identity/tests/identity_creation.rs | 80 ++ .../tests/identity_verification.rs | 126 ++ .../tests/plaintext_message_flow_auth.rs | 12 +- .../tests/purpose_key_creation.rs | 192 +++ .../tests/purpose_key_verification.rs | 55 + .../rust/ockam/ockam_identity/tests/tests.rs | 162 --- .../ockam/ockam_identity/tests/v2_channel.rs | 1248 ----------------- .../tests/v2_ciphertext_message_flow_auth.rs | 102 -- .../ockam_identity/tests/v2_credentials.rs | 378 ----- .../tests/v2_plaintext_message_flow_auth.rs | 173 --- .../ockam/ockam_node/src/worker_builder.rs | 2 +- ...re-channel-over-ble-transport-initiator.rs | 2 +- .../rust/ockam/ockam_vault/Cargo.toml | 4 +- .../src/{vault/vault_error.rs => error.rs} | 55 +- .../rust/ockam/ockam_vault/src/lib.rs | 15 +- .../ockam/ockam_vault/src/software/mod.rs | 7 + .../src/software/secure_channel_vault/aes.rs | 93 ++ .../src/software/secure_channel_vault/mod.rs | 6 + .../secure_channel_vault.rs | 400 ++++++ .../ockam_vault/src/software/signing_vault.rs | 240 ++++ .../src/software/verifying_vault.rs | 133 ++ .../src/storage/persistent_storage.rs | 8 +- .../src/traits/asymmetric_vault.rs | 88 -- .../rust/ockam/ockam_vault/src/traits/mod.rs | 26 +- .../ockam_vault/src/traits/secrets_store.rs | 177 --- .../src/traits/secure_channel_vault.rs | 71 + .../ockam_vault/src/traits/security_module.rs | 43 - .../ockam/ockam_vault/src/traits/signer.rs | 53 - .../ockam_vault/src/traits/signing_vault.rs | 24 + .../ockam_vault/src/traits/symmetric_vault.rs | 56 - .../ockam_vault/src/traits/verifying_vault.rs | 17 + .../ockam/ockam_vault/src/types/constants.rs | 68 +- .../src/types/secret_attributes.rs | 40 +- .../ockam_vault/src/types/stored_secret.rs | 10 +- .../ockam_vault/src/vault/asymmetric_impl.rs | 138 -- .../rust/ockam/ockam_vault/src/vault/mod.rs | 26 - .../src/vault/secrets_store_impl.rs | 173 --- .../ockam_vault/src/vault/signer_impl.rs | 42 - .../ockam_vault/src/vault/symmetric_impl.rs | 119 -- .../rust/ockam/ockam_vault/src/vault/vault.rs | 295 ---- .../ockam_vault/src/vault/vault_builder.rs | 121 -- .../ockam_vault/src/vault/vault_kms/mod.rs | 4 - .../src/vault/vault_kms/vault_kms.rs | 338 ----- .../src/{vault => }/aws_kms_client.rs | 93 +- .../ockam_vault_aws/src/aws_signing_vault.rs | 137 ++ .../rust/ockam/ockam_vault_aws/src/error.rs | 39 + .../rust/ockam/ockam_vault_aws/src/lib.rs | 12 +- .../src/vault/aws_security_module.rs | 311 ---- .../ockam/ockam_vault_aws/src/vault/mod.rs | 5 - .../rust/ockam/ockam_vault_aws/tests/tests.rs | 54 + 332 files changed, 7244 insertions(+), 17324 deletions(-) delete mode 100644 implementations/rust/ockam/ockam_api/src/nodes/models/identity.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/credential/credential.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/credential/credential_builder.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/credential/credential_data.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/credential/mod.rs create mode 100644 implementations/rust/ockam/ockam_identity/src/credentials/credentials_creation.rs create mode 100644 implementations/rust/ockam/ockam_identity/src/credentials/credentials_verification.rs rename implementations/rust/ockam/ockam_identity/src/{credential => credentials}/one_time_code.rs (100%) delete mode 100644 implementations/rust/ockam/ockam_identity/src/identities/identities_vault.rs create mode 100644 implementations/rust/ockam/ockam_identity/src/identities/identity_builder.rs create mode 100644 implementations/rust/ockam/ockam_identity/src/identities/identity_options.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/identities/storage/identities_repository.rs rename implementations/rust/ockam/ockam_identity/src/{v2 => }/identities/storage/identities_repository_impl.rs (95%) rename implementations/rust/ockam/ockam_identity/src/{v2 => }/identities/storage/identities_repository_trait.rs (97%) delete mode 100644 implementations/rust/ockam/ockam_identity/src/identities/storage/lmdb_storage.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/identities/storage/storage.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/identities/tests.rs rename implementations/rust/ockam/ockam_identity/src/{v2 => }/identity/constants.rs (100%) rename implementations/rust/ockam/ockam_identity/src/{v2 => }/identity/history_comparison.rs (100%) delete mode 100644 implementations/rust/ockam/ockam_identity/src/identity/identity_change/change_identifier.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/identity/identity_change/create_key.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/identity/identity_change/identity_change.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/identity/identity_change/identity_change_constants.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/identity/identity_change/key_attributes.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/identity/identity_change/mod.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/identity/identity_change/rotate_key.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/identity/identity_change/signature.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/identity/identity_change_history.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/identity/identity_identifier.rs rename implementations/rust/ockam/ockam_identity/src/{v2 => }/identity/identity_verification.rs (53%) rename implementations/rust/ockam/ockam_identity/src/{v2 => }/identity/verified_change.rs (68%) rename implementations/rust/ockam/ockam_identity/src/{v2 => }/models/change_history.rs (75%) rename implementations/rust/ockam/ockam_identity/src/{v2 => }/models/credential.rs (96%) rename implementations/rust/ockam/ockam_identity/src/{v2 => }/models/credential_and_purpose_key.rs (89%) create mode 100644 implementations/rust/ockam/ockam_identity/src/models/identifiers.rs rename implementations/rust/ockam/ockam_identity/src/{v2 => }/models/mod.rs (97%) create mode 100644 implementations/rust/ockam/ockam_identity/src/models/public_keys.rs rename implementations/rust/ockam/ockam_identity/src/{v2 => }/models/purpose_key_attestation.rs (77%) create mode 100644 implementations/rust/ockam/ockam_identity/src/models/signatures.rs create mode 100644 implementations/rust/ockam/ockam_identity/src/models/timestamp.rs create mode 100644 implementations/rust/ockam/ockam_identity/src/models/utils/change_history.rs create mode 100644 implementations/rust/ockam/ockam_identity/src/models/utils/credentials.rs rename implementations/rust/ockam/ockam_identity/src/{v2/models => models/utils}/identifiers.rs (81%) create mode 100644 implementations/rust/ockam/ockam_identity/src/models/utils/mod.rs rename implementations/rust/ockam/ockam_identity/src/{v2/models => models/utils}/public_keys.rs (80%) create mode 100644 implementations/rust/ockam/ockam_identity/src/models/utils/purpose_key_attestation.rs rename implementations/rust/ockam/ockam_identity/src/{v2/models => models/utils}/signatures.rs (75%) create mode 100644 implementations/rust/ockam/ockam_identity/src/models/utils/timestamp.rs rename implementations/rust/ockam/ockam_identity/src/{v2 => }/models/versioned_data.rs (100%) rename implementations/rust/ockam/ockam_identity/src/{v2 => }/purpose_key/mod.rs (100%) rename implementations/rust/ockam/ockam_identity/src/{v2 => }/purpose_key/purpose.rs (100%) rename implementations/rust/ockam/ockam_identity/src/{v2 => }/purpose_key/purpose_key.rs (77%) create mode 100644 implementations/rust/ockam/ockam_identity/src/purpose_keys/mod.rs create mode 100644 implementations/rust/ockam/ockam_identity/src/purpose_keys/purpose_key_builder.rs create mode 100644 implementations/rust/ockam/ockam_identity/src/purpose_keys/purpose_key_options.rs create mode 100644 implementations/rust/ockam/ockam_identity/src/purpose_keys/purpose_keys.rs create mode 100644 implementations/rust/ockam/ockam_identity/src/purpose_keys/purpose_keys_creation.rs create mode 100644 implementations/rust/ockam/ockam_identity/src/purpose_keys/purpose_keys_verification.rs rename implementations/rust/ockam/ockam_identity/src/{v2 => }/purpose_keys/storage/mod.rs (100%) rename implementations/rust/ockam/ockam_identity/src/{v2 => }/purpose_keys/storage/purpose_keys_repository_impl.rs (88%) rename implementations/rust/ockam/ockam_identity/src/{v2 => }/purpose_keys/storage/purpose_keys_repository_trait.rs (95%) delete mode 100644 implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/trust_public_key_policy.rs rename implementations/rust/ockam/ockam_identity/src/{v2 => }/storage/lmdb_storage.rs (99%) rename implementations/rust/ockam/ockam_identity/src/{v2 => }/storage/memory.rs (98%) rename implementations/rust/ockam/ockam_identity/src/{v2 => }/storage/mod.rs (100%) rename implementations/rust/ockam/ockam_identity/src/{identities => }/storage/sqlite_storage.rs (99%) rename implementations/rust/ockam/ockam_identity/src/{v2 => }/storage/storage.rs (100%) rename implementations/rust/ockam/ockam_identity/src/{v2 => }/utils.rs (75%) delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/credentials/authority_service.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/credentials/credentials.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/credentials/credentials_issuer.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/credentials/credentials_retriever.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/credentials/credentials_server.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/credentials/credentials_server_worker.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/credentials/mod.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/credentials/one_time_code.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/credentials/trust_context.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/error.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/identities/identities.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/identities/identities_builder.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/identities/identities_creation.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/identities/identities_vault.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/identities/identity_keys.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/identities/mod.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/identities/storage/attributes_entry.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/identities/storage/mod.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/identities/tests.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/identity/identity.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/identity/mod.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/mod.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/models/timestamp.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/purpose_keys/mod.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/purpose_keys/purpose_keys.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/access_control/credential_access_control.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/access_control/identity_access_control.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/access_control/mod.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/addresses.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/api.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/decryptor.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/encryptor.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/encryptor_worker.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/error.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/handshake.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/handshake_state_machine.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/handshake_worker.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/initiator_state_machine.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/mod.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/responder_state_machine.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/key_tracker.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/listener.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/local_info.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/mod.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/nonce_tracker.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/options.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/registry.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/role.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/all_trust_policy.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/any_trust_policy.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/mod.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/trust_everyone_policy.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/trust_identifier_policy.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/trust_multi_identifier_policy.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/trust_policy_type.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channels/common.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channels/mod.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channels/secure_channels.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/secure_channels/secure_channels_builder.rs delete mode 100644 implementations/rust/ockam/ockam_identity/src/v2/storage/sqlite_storage.rs create mode 100644 implementations/rust/ockam/ockam_identity/src/vault.rs create mode 100644 implementations/rust/ockam/ockam_identity/tests/aws.rs create mode 100644 implementations/rust/ockam/ockam_identity/tests/common/crazy_vault.rs rename implementations/rust/ockam/ockam_identity/tests/{v2_common/mod.rs => common/message_flow_auth.rs} (87%) create mode 100644 implementations/rust/ockam/ockam_identity/tests/identity_creation.rs create mode 100644 implementations/rust/ockam/ockam_identity/tests/identity_verification.rs create mode 100644 implementations/rust/ockam/ockam_identity/tests/purpose_key_creation.rs create mode 100644 implementations/rust/ockam/ockam_identity/tests/purpose_key_verification.rs delete mode 100644 implementations/rust/ockam/ockam_identity/tests/tests.rs delete mode 100644 implementations/rust/ockam/ockam_identity/tests/v2_channel.rs delete mode 100644 implementations/rust/ockam/ockam_identity/tests/v2_ciphertext_message_flow_auth.rs delete mode 100644 implementations/rust/ockam/ockam_identity/tests/v2_credentials.rs delete mode 100644 implementations/rust/ockam/ockam_identity/tests/v2_plaintext_message_flow_auth.rs rename implementations/rust/ockam/ockam_vault/src/{vault/vault_error.rs => error.rs} (51%) create mode 100644 implementations/rust/ockam/ockam_vault/src/software/mod.rs create mode 100644 implementations/rust/ockam/ockam_vault/src/software/secure_channel_vault/aes.rs create mode 100644 implementations/rust/ockam/ockam_vault/src/software/secure_channel_vault/mod.rs create mode 100644 implementations/rust/ockam/ockam_vault/src/software/secure_channel_vault/secure_channel_vault.rs create mode 100644 implementations/rust/ockam/ockam_vault/src/software/signing_vault.rs create mode 100644 implementations/rust/ockam/ockam_vault/src/software/verifying_vault.rs delete mode 100644 implementations/rust/ockam/ockam_vault/src/traits/asymmetric_vault.rs delete mode 100644 implementations/rust/ockam/ockam_vault/src/traits/secrets_store.rs create mode 100644 implementations/rust/ockam/ockam_vault/src/traits/secure_channel_vault.rs delete mode 100644 implementations/rust/ockam/ockam_vault/src/traits/security_module.rs delete mode 100644 implementations/rust/ockam/ockam_vault/src/traits/signer.rs create mode 100644 implementations/rust/ockam/ockam_vault/src/traits/signing_vault.rs delete mode 100644 implementations/rust/ockam/ockam_vault/src/traits/symmetric_vault.rs create mode 100644 implementations/rust/ockam/ockam_vault/src/traits/verifying_vault.rs delete mode 100644 implementations/rust/ockam/ockam_vault/src/vault/asymmetric_impl.rs delete mode 100644 implementations/rust/ockam/ockam_vault/src/vault/mod.rs delete mode 100644 implementations/rust/ockam/ockam_vault/src/vault/secrets_store_impl.rs delete mode 100644 implementations/rust/ockam/ockam_vault/src/vault/signer_impl.rs delete mode 100644 implementations/rust/ockam/ockam_vault/src/vault/symmetric_impl.rs delete mode 100644 implementations/rust/ockam/ockam_vault/src/vault/vault.rs delete mode 100644 implementations/rust/ockam/ockam_vault/src/vault/vault_builder.rs delete mode 100644 implementations/rust/ockam/ockam_vault/src/vault/vault_kms/mod.rs delete mode 100644 implementations/rust/ockam/ockam_vault/src/vault/vault_kms/vault_kms.rs rename implementations/rust/ockam/ockam_vault_aws/src/{vault => }/aws_kms_client.rs (73%) create mode 100644 implementations/rust/ockam/ockam_vault_aws/src/aws_signing_vault.rs create mode 100644 implementations/rust/ockam/ockam_vault_aws/src/error.rs delete mode 100644 implementations/rust/ockam/ockam_vault_aws/src/vault/aws_security_module.rs delete mode 100644 implementations/rust/ockam/ockam_vault_aws/src/vault/mod.rs create mode 100644 implementations/rust/ockam/ockam_vault_aws/tests/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 18e707429c7..92b02d4852d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2239,6 +2239,7 @@ checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" dependencies = [ "curve25519-dalek", "ed25519", + "rand_core 0.6.4", "serde", "sha2", "zeroize", @@ -4705,7 +4706,6 @@ dependencies = [ "ockam_abac", "ockam_api", "ockam_core", - "ockam_identity", "ockam_macros", "ockam_multiaddr", "ockam_node", @@ -4798,7 +4798,6 @@ dependencies = [ "ockam_abac", "ockam_api", "ockam_core", - "ockam_identity", "ockam_macros", "ockam_multiaddr", "ockam_node", @@ -4898,6 +4897,7 @@ dependencies = [ "ockam_node", "ockam_transport_tcp", "ockam_vault", + "ockam_vault_aws", "quickcheck", "quickcheck_macros", "rand 0.8.5", @@ -5104,6 +5104,7 @@ dependencies = [ "serde_cbor", "serde_json", "sha2", + "static_assertions", "thiserror", "tokio", "tracing", diff --git a/examples/rust/get_started/examples/06-credentials-exchange-client.rs b/examples/rust/get_started/examples/06-credentials-exchange-client.rs index 25fe86e7d5b..d1856c3fe30 100644 --- a/examples/rust/get_started/examples/06-credentials-exchange-client.rs +++ b/examples/rust/get_started/examples/06-credentials-exchange-client.rs @@ -1,23 +1,37 @@ -use ockam::identity::{AuthorityService, CredentialsIssuerClient, SecureChannelOptions, TrustContext}; -use ockam::TcpTransportExtension; -use ockam::{node, route, Context, Result, TcpConnectionOptions}; +use ockam::identity::{AuthorityService, CredentialsIssuerClient, SecureChannelOptions, TrustContext, Vault}; +use ockam::vault::{Secret, SecretAttributes, SoftwareSigningVault}; +use ockam::{route, Context, Result, TcpConnectionOptions}; +use ockam::{Node, TcpTransportExtension}; #[ockam::node] async fn main(ctx: Context) -> Result<()> { - // Create a node with default implementations - let mut node = node(ctx); + let identity_vault = SoftwareSigningVault::create(); + // Import the signing secret key to the Vault + let secret = identity_vault + .import_key( + Secret::new(hex::decode("31FF4E1CD55F17735A633FBAB4B838CF88D1252D164735CB3185A6E315438C2C").unwrap()), + SecretAttributes::Ed25519, + ) + .await?; + + // Create a default Vault but use the signing vault with our secret in it + let mut vault = Vault::create(); + vault.identity_vault = identity_vault; + + let mut node = Node::builder().with_vault(vault).build(ctx).await?; // Initialize the TCP Transport let tcp = node.create_tcp_transport().await?; // Create an Identity representing the client // We preload the client vault with a change history and secret key corresponding to the identity identifier - // Pe92f183eb4c324804ef4d62962dea94cf095a265d4d28500c34e1a4e0d5ef638 + // I6342c580429b9a0733880bea4fa18f8055871130 // which is an identifier known to the credential issuer, with some preset attributes + // // We're hard coding this specific identity because its public identifier is known // to the credential issuer as a member of the production cluster. - let change_history = "01dcf392551f796ef1bcb368177e53f9a5875a962f67279259207d24a01e690721000547c93239ba3d818ec26c9cdadd2a35cbdf1fa3b6d1a731e06164b1079fb7b8084f434b414d5f524b03012000000020a0d205f09cab9a9467591fcee560429aab1215d8136e5c985a6b7dc729e6f08203010140b098463a727454c0e5292390d8f4cbd4dd0cae5db95606832f3d0a138936487e1da1489c40d8a0995fce71cc1948c6bcfd67186467cdd78eab7e95c080141505"; - let secret = "41b6873b20d95567bf958e6bab2808e9157720040882630b1bb37a72f4015cd2"; - let client = node.import_private_identity(change_history, secret).await?; + let change_history = hex::decode("81a201583ba20101025835a4028201815820530d1c2e9822433b679a66a60b9c2ed47c370cd0ce51cbe1a7ad847b5835a96303f4041a64dd4060051a77a94360028201815840042fff8f6c80603fb1cec4a3cf1ff169ee36889d3ed76184fe1dfbd4b692b02892df9525c61c2f1286b829586d13d5abf7d18973141f734d71c1840520d40a0e").unwrap(); + let client = node.import_private_identity(&change_history, &secret).await?; + println!("issuer identifier {}", client.identifier()); // Connect with the credential issuer and authenticate using the latest private // key of this program's hardcoded identity. @@ -28,7 +42,7 @@ async fn main(ctx: Context) -> Result<()> { let issuer_connection = tcp.connect("127.0.0.1:5000", TcpConnectionOptions::new()).await?; let issuer_channel = node .create_secure_channel( - &client.identifier(), + client.identifier(), route![issuer_connection, "secure"], SecureChannelOptions::new(), ) @@ -36,24 +50,23 @@ async fn main(ctx: Context) -> Result<()> { let issuer_client = CredentialsIssuerClient::new(route![issuer_channel, "issuer"], node.context()).await?; let credential = issuer_client.credential().await?; - println!("Credential:\n{credential}"); // Verify that the received credential has indeed be signed by the issuer. // The issuer identity must be provided out-of-band from a trusted source // and match the identity used to start the issuer node - let issuer_identity = "0180370b91c5d0aa4af34580a9ab4b8fb2a28351bed061525c96b4f07e75c0ee18000547c93239ba3d818ec26c9cdadd2a35cbdf1fa3b6d1a731e06164b1079fb7b8084f434b414d5f524b03012000000020236f79490d3f683e0c3bf458a7381c366c99a8f2b2ac406db1ef8c130111f12703010140b23fddceb11cea25602aa681b6ef6abda036722c27a6dee291f1d6b2234a127af21cc79de2252201f27e7e34e0bf5064adbf3d01eb355aff4bf5c90b8f1fd80a"; + let issuer_identity = "81a201583ba20101025835a4028201815820afbca9cf5d440147450f9f0d0a038a337b3fe5c17086163f2c54509558b62ef403f4041a64dd404a051a77a9434a0282018158407754214545cda6e7ff49136f67c9c7973ec309ca4087360a9f844aac961f8afe3f579a72c0c9530f3ff210f02b7c5f56e96ce12ee256b01d7628519800723805"; let issuer = node.import_identity_hex(issuer_identity).await?; node.credentials() - .verify_credential(&client.identifier(), &[issuer.clone()], credential.clone()) + .credentials_verification() + .verify_credential(Some(client.identifier()), &[issuer.identifier().clone()], &credential) .await?; // Create a trust context that will be used to authenticate credential exchanges let trust_context = TrustContext::new( "trust_context_id".to_string(), Some(AuthorityService::new( - node.identities().identities_reader(), node.credentials(), - issuer.identifier(), + issuer.identifier().clone(), None, )), ); @@ -62,7 +75,7 @@ async fn main(ctx: Context) -> Result<()> { let server_connection = tcp.connect("127.0.0.1:4000", TcpConnectionOptions::new()).await?; let channel = node .create_secure_channel( - &client.identifier(), + client.identifier(), route![server_connection, "secure"], SecureChannelOptions::new() .with_trust_context(trust_context) diff --git a/examples/rust/get_started/examples/06-credentials-exchange-issuer.rs b/examples/rust/get_started/examples/06-credentials-exchange-issuer.rs index dd462e6b1f2..5feb29042ac 100644 --- a/examples/rust/get_started/examples/06-credentials-exchange-issuer.rs +++ b/examples/rust/get_started/examples/06-credentials-exchange-issuer.rs @@ -1,25 +1,36 @@ use ockam::access_control::AllowAll; use ockam::access_control::IdentityIdAccessControl; -use ockam::identity::CredentialsIssuer; -use ockam::identity::SecureChannelListenerOptions; -use ockam::TcpTransportExtension; -use ockam::{node, Context, Result, TcpListenerOptions}; +use ockam::identity::{CredentialsIssuer, SecureChannelListenerOptions, Vault}; +use ockam::vault::{Secret, SecretAttributes, SoftwareSigningVault}; +use ockam::{Context, Result, TcpListenerOptions}; +use ockam::{Node, TcpTransportExtension}; #[ockam::node] async fn main(ctx: Context) -> Result<()> { - // Create a node with default implementations - let node = node(ctx); + let identity_vault = SoftwareSigningVault::create(); + // Import the signing secret key to the Vault + let secret = identity_vault + .import_key( + Secret::new(hex::decode("0127359911708ef4de9adaaf27c357501473c4a10a5326a69c1f7f874a0cd82e").unwrap()), + SecretAttributes::Ed25519, + ) + .await?; + + // Create a default Vault but use the signing vault with our secret in it + let mut vault = Vault::create(); + vault.identity_vault = identity_vault; + + let node = Node::builder().with_vault(vault).build(ctx).await?; - let issuer_identity = "0180370b91c5d0aa4af34580a9ab4b8fb2a28351bed061525c96b4f07e75c0ee18000547c93239ba3d818ec26c9cdadd2a35cbdf1fa3b6d1a731e06164b1079fb7b8084f434b414d5f524b03012000000020236f79490d3f683e0c3bf458a7381c366c99a8f2b2ac406db1ef8c130111f12703010140b23fddceb11cea25602aa681b6ef6abda036722c27a6dee291f1d6b2234a127af21cc79de2252201f27e7e34e0bf5064adbf3d01eb355aff4bf5c90b8f1fd80a"; - let secret = "9278735d525efceef16bfd9143d3534759f3d388e460e6002134b9541e06489f"; - let issuer = node.import_private_identity(issuer_identity, secret).await?; + let issuer_identity = hex::decode("81a201583ba20101025835a4028201815820afbca9cf5d440147450f9f0d0a038a337b3fe5c17086163f2c54509558b62ef403f4041a64dd404a051a77a9434a0282018158407754214545cda6e7ff49136f67c9c7973ec309ca4087360a9f844aac961f8afe3f579a72c0c9530f3ff210f02b7c5f56e96ce12ee256b01d7628519800723805").unwrap(); + let issuer = node.import_private_identity(&issuer_identity, &secret).await?; println!("issuer identifier {}", issuer.identifier()); // Tell the credential issuer about a set of public identifiers that are // known, in advance, to be members of the production cluster. let known_identifiers = vec![ - "Pe92f183eb4c324804ef4d62962dea94cf095a265d4d28500c34e1a4e0d5ef638".try_into()?, - "Pada09e0f96e56580f6a0cb54f55ecbde6c973db6732e30dfb39b178760aed041".try_into()?, + "I6342c580429b9a0733880bea4fa18f8055871130".try_into()?, // Client Identifier + "I2c3b0ef15c12fe43d405497fcfc46318da46d0f5".try_into()?, // Server Identifier ]; // Tell this credential issuer about the attributes to include in credentials @@ -32,12 +43,17 @@ async fn main(ctx: Context) -> Result<()> { // // For a different application this attested attribute set can be different and // distinct for each identifier, but for this example we'll keep things simple. - let credential_issuer = - CredentialsIssuer::new(node.identities(), issuer.identifier(), "trust_context".into()).await?; + let credential_issuer = CredentialsIssuer::new( + node.identities().repository(), + node.credentials(), + issuer.identifier(), + "trust_context".into(), + ) + .await?; for identifier in known_identifiers.iter() { node.identities() .repository() - .put_attribute_value(identifier, "cluster", "production") + .put_attribute_value(identifier, b"cluster".to_vec(), b"production".to_vec()) .await?; } @@ -49,7 +65,7 @@ async fn main(ctx: Context) -> Result<()> { // Start a secure channel listener that only allows channels where the identity // at the other end of the channel can authenticate with the latest private key // corresponding to one of the above known public identifiers. - node.create_secure_channel_listener(&issuer.identifier(), "secure", sc_listener_options) + node.create_secure_channel_listener(issuer.identifier(), "secure", sc_listener_options) .await?; // Start a credential issuer worker that will only accept incoming requests from diff --git a/examples/rust/get_started/examples/06-credentials-exchange-server.rs b/examples/rust/get_started/examples/06-credentials-exchange-server.rs index 935f525389d..bd35e6a20a0 100644 --- a/examples/rust/get_started/examples/06-credentials-exchange-server.rs +++ b/examples/rust/get_started/examples/06-credentials-exchange-server.rs @@ -4,27 +4,40 @@ use hello_ockam::Echoer; use ockam::abac::AbacAccessControl; use ockam::access_control::AllowAll; use ockam::identity::{ - AuthorityService, CredentialsIssuerClient, SecureChannelListenerOptions, SecureChannelOptions, TrustContext, + AuthorityService, CredentialsIssuerClient, SecureChannelListenerOptions, SecureChannelOptions, TrustContext, Vault, }; -use ockam::TcpTransportExtension; -use ockam::{node, route, Context, Result, TcpConnectionOptions, TcpListenerOptions}; +use ockam::vault::{Secret, SecretAttributes, SoftwareSigningVault}; +use ockam::{route, Context, Result, TcpConnectionOptions, TcpListenerOptions}; +use ockam::{Node, TcpTransportExtension}; #[ockam::node] async fn main(ctx: Context) -> Result<()> { - // Create a node with default implementations - let node = node(ctx); + let identity_vault = SoftwareSigningVault::create(); + // Import the signing secret key to the Vault + let secret = identity_vault + .import_key( + Secret::new(hex::decode("5FB3663DF8405379981462BABED7507E3D53A8D061188105E3ADBD70E0A74B8A").unwrap()), + SecretAttributes::Ed25519, + ) + .await?; + + // Create a default Vault but use the signing vault with our secret in it + let mut vault = Vault::create(); + vault.identity_vault = identity_vault; + + let node = Node::builder().with_vault(vault).build(ctx).await?; + // Initialize the TCP Transport let tcp = node.create_tcp_transport().await?; // Create an Identity representing the server // Load an identity corresponding to the following public identifier - // Pe92f183eb4c324804ef4d62962dea94cf095a265d4d28500c34e1a4e0d5ef638 + // I2c3b0ef15c12fe43d405497fcfc46318da46d0f5 // // We're hard coding this specific identity because its public identifier is known // to the credential issuer as a member of the production cluster. - let change_history = "01ed8a5b1303f975c1296c990d1bd3c1946cfef328de20531e3511ec5604ce0dd9000547c93239ba3d818ec26c9cdadd2a35cbdf1fa3b6d1a731e06164b1079fb7b8084f434b414d5f524b03012000000020e8c328bc0cc07a374762091d037e69c36fdd4d2e1a651abd4d43a1362d3f800503010140a349968063d7337d0c965969fa9c640824c01a6d37fe130d4ab963b0271b9d5bbf0923faa5e27f15359554f94f08676df01b99d997944e4feaf0caaa1189480e"; - let secret = "5b2b3f2abbd1787704d8f8b363529f8e2d8f423b6dd4b96a2c462e4f0e04ee18"; - let server = node.import_private_identity(change_history, secret).await?; + let change_history = hex::decode("81a201583ba20101025835a40282018158201d387ce453816d91159740a55e9a62ad3b58be9ecf7ef08760c42c0d885b6c2e03f4041a64dd4074051a77a9437402820181584053de69d82c9c4b12476c889b437be1d9d33bd0041655c4836a3a57ac5a67703e7f500af5bacaed291cfd6783d255fe0f0606638577d087a5612bfb4671f2b70a").unwrap(); + let server = node.import_private_identity(&change_history, &secret).await?; // Connect with the credential issuer and authenticate using the latest private // key of this program's hardcoded identity. @@ -35,7 +48,7 @@ async fn main(ctx: Context) -> Result<()> { let issuer_connection = tcp.connect("127.0.0.1:5000", TcpConnectionOptions::new()).await?; let issuer_channel = node .create_secure_channel( - &server.identifier(), + server.identifier(), route![issuer_connection, "secure"], SecureChannelOptions::new(), ) @@ -43,24 +56,23 @@ async fn main(ctx: Context) -> Result<()> { let issuer_client = CredentialsIssuerClient::new(route![issuer_channel, "issuer"], node.context()).await?; let credential = issuer_client.credential().await?; - println!("Credential:\n{credential}"); // Verify that the received credential has indeed be signed by the issuer. // The issuer identity must be provided out-of-band from a trusted source // and match the identity used to start the issuer node - let issuer_identity = "0180370b91c5d0aa4af34580a9ab4b8fb2a28351bed061525c96b4f07e75c0ee18000547c93239ba3d818ec26c9cdadd2a35cbdf1fa3b6d1a731e06164b1079fb7b8084f434b414d5f524b03012000000020236f79490d3f683e0c3bf458a7381c366c99a8f2b2ac406db1ef8c130111f12703010140b23fddceb11cea25602aa681b6ef6abda036722c27a6dee291f1d6b2234a127af21cc79de2252201f27e7e34e0bf5064adbf3d01eb355aff4bf5c90b8f1fd80a"; + let issuer_identity = "81a201583ba20101025835a4028201815820afbca9cf5d440147450f9f0d0a038a337b3fe5c17086163f2c54509558b62ef403f4041a64dd404a051a77a9434a0282018158407754214545cda6e7ff49136f67c9c7973ec309ca4087360a9f844aac961f8afe3f579a72c0c9530f3ff210f02b7c5f56e96ce12ee256b01d7628519800723805"; let issuer = node.import_identity_hex(issuer_identity).await?; node.credentials() - .verify_credential(&server.identifier(), &[issuer.clone()], credential.clone()) + .credentials_verification() + .verify_credential(Some(server.identifier()), &[issuer.identifier().clone()], &credential) .await?; // Create a trust context that will be used to authenticate credential exchanges let trust_context = TrustContext::new( "trust_context_id".to_string(), Some(AuthorityService::new( - node.identities().identities_reader(), node.credentials(), - issuer.identifier(), + issuer.identifier().clone(), None, )), ); @@ -77,13 +89,13 @@ async fn main(ctx: Context) -> Result<()> { node.flow_controls() .add_consumer("echoer", &sc_listener_options.spawner_flow_control_id()); - let allow_production = AbacAccessControl::create(node.repository(), "cluster", "production"); + let allow_production = AbacAccessControl::create(node.identities_repository(), "cluster", "production"); node.start_worker_with_access_control("echoer", Echoer, allow_production, AllowAll) .await?; // Start a secure channel listener that only allows channels with // authenticated identities. - node.create_secure_channel_listener(&server.identifier(), "secure", sc_listener_options) + node.create_secure_channel_listener(server.identifier(), "secure", sc_listener_options) .await?; // Create a TCP listener and wait for incoming connections diff --git a/examples/rust/get_started/examples/11-attribute-based-authentication-control-plane.rs b/examples/rust/get_started/examples/11-attribute-based-authentication-control-plane.rs index 83c12f76993..00958990da4 100644 --- a/examples/rust/get_started/examples/11-attribute-based-authentication-control-plane.rs +++ b/examples/rust/get_started/examples/11-attribute-based-authentication-control-plane.rs @@ -1,6 +1,6 @@ use hello_ockam::{create_token, import_project}; use ockam::abac::AbacAccessControl; -use ockam::identity::credential::OneTimeCode; +use ockam::identity::OneTimeCode; use ockam::identity::{ AuthorityService, RemoteCredentialsRetriever, RemoteCredentialsRetrieverInfo, SecureChannelListenerOptions, SecureChannelOptions, TrustContext, TrustMultiIdentifiersPolicy, @@ -86,7 +86,6 @@ async fn start_node(ctx: Context, project_information_path: &str, token: OneTime let trust_context = TrustContext::new( "trust_context_id".to_string(), Some(AuthorityService::new( - node.identities().identities_reader(), node.credentials(), project.authority_identifier(), Some(Arc::new(RemoteCredentialsRetriever::new( @@ -105,8 +104,6 @@ async fn start_node(ctx: Context, project_information_path: &str, token: OneTime .credential(node.context(), &control_plane) .await?; - println!("{credential}"); - // start a credential exchange worker which will be // later on to exchange credentials with the edge node node.credentials_server() @@ -120,7 +117,7 @@ async fn start_node(ctx: Context, project_information_path: &str, token: OneTime .await?; // 3. create an access control policy checking the value of the "component" attribute of the caller - let access_control = AbacAccessControl::create(node.repository(), "component", "edge"); + let access_control = AbacAccessControl::create(node.identities_repository(), "component", "edge"); // 4. create a tcp outlet with the above policy tcp.create_outlet( diff --git a/examples/rust/get_started/examples/11-attribute-based-authentication-edge-plane.rs b/examples/rust/get_started/examples/11-attribute-based-authentication-edge-plane.rs index 50c4a09fa57..8f349a31378 100644 --- a/examples/rust/get_started/examples/11-attribute-based-authentication-edge-plane.rs +++ b/examples/rust/get_started/examples/11-attribute-based-authentication-edge-plane.rs @@ -1,6 +1,6 @@ use hello_ockam::{create_token, import_project}; use ockam::abac::AbacAccessControl; -use ockam::identity::credential::OneTimeCode; +use ockam::identity::OneTimeCode; use ockam::identity::{ identities, AuthorityService, RemoteCredentialsRetriever, RemoteCredentialsRetrieverInfo, SecureChannelOptions, TrustContext, TrustMultiIdentifiersPolicy, @@ -85,7 +85,6 @@ async fn start_node(ctx: Context, project_information_path: &str, token: OneTime let trust_context = TrustContext::new( "trust_context_id".to_string(), Some(AuthorityService::new( - node.identities().identities_reader(), node.credentials(), project.authority_identifier(), Some(Arc::new(RemoteCredentialsRetriever::new( @@ -104,8 +103,6 @@ async fn start_node(ctx: Context, project_information_path: &str, token: OneTime .credential(node.context(), &edge_plane) .await?; - println!("{credential}"); - // start a credential exchange worker which will be // later on to exchange credentials with the control node node.credentials_server() @@ -159,7 +156,7 @@ async fn start_node(ctx: Context, project_information_path: &str, token: OneTime .present_credential_mutual( node.context(), route![secure_channel_to_control.clone(), "credential_exchange"], - &[project.authority_identity()], + &[project.authority_identifier()], credential, ) .await?; diff --git a/examples/rust/get_started/src/project.rs b/examples/rust/get_started/src/project.rs index 0b78479b8f7..e4f89b2756e 100644 --- a/examples/rust/get_started/src/project.rs +++ b/examples/rust/get_started/src/project.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use anyhow::anyhow; use serde_json::{Map, Value}; -use ockam::identity::{Identities, Identity, IdentityIdentifier}; +use ockam::identity::{Identifier, Identities, Identity}; use ockam_core::errcode::{Kind, Origin}; use ockam_core::{Error, Result}; use ockam_multiaddr::MultiAddr; @@ -14,7 +14,7 @@ use ockam_multiaddr::MultiAddr; /// This struct contains the json data exported /// when running `ockam project information > project.json` pub struct Project { - pub project_identifier: IdentityIdentifier, + pub project_identifier: Identifier, pub authority_identity: Identity, pub authority_route: MultiAddr, pub project_route: MultiAddr, @@ -23,7 +23,7 @@ pub struct Project { /// Accessors for a Project impl Project { /// Return the identity identifier of the project - pub fn identifier(&self) -> IdentityIdentifier { + pub fn identifier(&self) -> Identifier { self.project_identifier.clone() } @@ -33,8 +33,8 @@ impl Project { } /// Return the identifier of the authority - pub fn authority_identifier(&self) -> IdentityIdentifier { - self.authority_identity.identifier() + pub fn authority_identifier(&self) -> Identifier { + self.authority_identity.identifier().clone() } /// Return the authority route @@ -53,12 +53,12 @@ impl Project { pub async fn import_project(path: &str, identities: Arc) -> Result { match read_json(path)? { Value::Object(values) => { - let project_identifier = IdentityIdentifier::from_str(get_field_as_str(&values, "identity")?.as_str())?; + let project_identifier = Identifier::from_str(get_field_as_str(&values, "identity")?.as_str())?; let authority_identity = get_field_as_str(&values, "authority_identity")?; let identities_creation = identities.identities_creation(); let authority_public_identity = identities_creation - .decode_identity(&hex::decode(authority_identity).unwrap()) + .import(None, &hex::decode(authority_identity).unwrap()) .await?; let authority_access_route = get_field_as_str(&values, "authority_access_route")?; diff --git a/examples/rust/get_started/src/token.rs b/examples/rust/get_started/src/token.rs index 0898725c2b4..6219f6128c7 100644 --- a/examples/rust/get_started/src/token.rs +++ b/examples/rust/get_started/src/token.rs @@ -1,5 +1,5 @@ use anyhow::anyhow; -use ockam::identity::credential::OneTimeCode; +use ockam::identity::OneTimeCode; use ockam::Result; use ockam_api::identity::EnrollmentTicket; use ockam_core::errcode::{Kind, Origin}; diff --git a/implementations/rust/ockam/ockam/src/error.rs b/implementations/rust/ockam/ockam/src/error.rs index cae091b3d79..5a810cfe806 100644 --- a/implementations/rust/ockam/ockam/src/error.rs +++ b/implementations/rust/ockam/ockam/src/error.rs @@ -19,6 +19,7 @@ pub enum OckamError { SystemInvalidConfiguration, UnknownForwarderDestinationAddress, UnknownForwarderNextHopAddress, + InvalidHex, } impl ockam_core::compat::error::Error for OckamError {} diff --git a/implementations/rust/ockam/ockam/src/lib.rs b/implementations/rust/ockam/ockam/src/lib.rs index 0777f9a9c38..cdf404fd233 100644 --- a/implementations/rust/ockam/ockam/src/lib.rs +++ b/implementations/rust/ockam/ockam/src/lib.rs @@ -136,7 +136,6 @@ pub mod compat { #[cfg(feature = "ockam_vault")] pub mod vault { //! Types and traits relating to ockam vaults. - pub use ockam_vault::Vault; pub use ockam_vault::*; #[cfg(feature = "software_vault_storage")] diff --git a/implementations/rust/ockam/ockam/src/node.rs b/implementations/rust/ockam/ockam/src/node.rs index b4ef3c35a82..85585e74048 100644 --- a/implementations/rust/ockam/ockam/src/node.rs +++ b/implementations/rust/ockam/ockam/src/node.rs @@ -1,3 +1,11 @@ +use crate::identity::models::Identifier; +use crate::identity::storage::Storage; +use crate::identity::{ + secure_channels, Credentials, CredentialsServer, Identities, IdentitiesCreation, + IdentitiesKeys, IdentitiesRepository, Purpose, SecureChannel, SecureChannelListener, + SecureChannelRegistry, SecureChannels, SecureChannelsBuilder, +}; +use crate::identity::{Identity, SecureChannelListenerOptions, SecureChannelOptions}; use ockam_core::compat::string::String; use ockam_core::compat::sync::Arc; use ockam_core::flow_control::FlowControls; @@ -5,19 +13,13 @@ use ockam_core::{ Address, IncomingAccessControl, Message, OutgoingAccessControl, Processor, Result, Route, Routed, Worker, }; -use ockam_identity::{ - secure_channels, Credentials, CredentialsServer, Identities, IdentitiesCreation, - IdentitiesKeys, IdentitiesRepository, IdentityIdentifier, SecureChannel, SecureChannelListener, - SecureChannelRegistry, SecureChannels, SecureChannelsBuilder, Storage, -}; -use ockam_identity::{ - IdentitiesVault, Identity, SecureChannelListenerOptions, SecureChannelOptions, -}; +use ockam_identity::{PurposeKeys, Vault, VaultStorage}; use ockam_node::{Context, HasContext, MessageReceiveOptions, MessageSendReceiveOptions}; -use ockam_vault::VaultStorage; +use ockam_vault::KeyId; use crate::remote::{RemoteForwarder, RemoteForwarderInfo, RemoteForwarderOptions}; use crate::stream::Stream; +use crate::OckamError; /// This struct supports all the ockam services for managing identities /// and creating secure channels @@ -99,35 +101,53 @@ impl Node { } /// Create an Identity - pub async fn create_identity(&self) -> Result { + pub async fn create_identity(&self) -> Result { Ok(self .identities_creation() .create_identity() .await? - .identifier()) + .identifier() + .clone()) + } + + /// Create the [`SecureChannel`] [`PurposeKey`] + pub async fn create_secure_channel_key(&self, identifier: &Identifier) -> Result<()> { + let _ = self + .identities() + .purpose_keys() + .purpose_keys_creation() + .create_purpose_key(identifier, Purpose::SecureChannel) + .await?; + + Ok(()) } /// Import an Identity given its private key and change history /// Note: the data is not persisted! pub async fn import_private_identity( &self, - identity_history: &str, - secret: &str, + identity_change_history: &[u8], + key_id: &KeyId, ) -> Result { self.identities_creation() - .import_private_identity(identity_history, secret) + .import_private_identity(identity_change_history, key_id) .await } /// Import an Identity given that was exported as a hex-encoded string pub async fn import_identity_hex(&self, data: &str) -> Result { - self.identities_creation().decode_identity_hex(data).await + self.identities_creation() + .import( + None, + &hex::decode(data).map_err(|_| OckamError::InvalidHex)?, + ) + .await } /// Spawns a SecureChannel listener at given `Address` with given [`SecureChannelListenerOptions`] pub async fn create_secure_channel_listener( &self, - identifier: &IdentityIdentifier, + identifier: &Identifier, address: impl Into
, options: impl Into, ) -> Result { @@ -139,7 +159,7 @@ impl Node { /// Initiate a SecureChannel using `Route` to the SecureChannel listener and [`SecureChannelOptions`] pub async fn create_secure_channel( &self, - identifier: &IdentityIdentifier, + identifier: &Identifier, route: impl Into, options: impl Into, ) -> Result { @@ -256,42 +276,42 @@ impl Node { /// Return services to manage identities pub fn identities(&self) -> Arc { - self.secure_channels().identities() + self.secure_channels.identities() } /// Return services to create and import identities pub fn identities_creation(&self) -> Arc { - self.identities().identities_creation() + self.secure_channels.identities().identities_creation() } /// Return services to manage identities keys pub fn identities_keys(&self) -> Arc { - self.identities().identities_keys() + self.secure_channels.identities().identities_keys() } /// Return services to manage credentials - pub fn credentials(&self) -> Arc { - self.identities().credentials() + pub fn credentials(&self) -> Arc { + self.secure_channels.identities().credentials() } - /// Return services to serve credentials - pub fn credentials_server(&self) -> Arc { - self.identities().credentials_server() + /// Return the [`Vault`] + pub fn vault(&self) -> Vault { + self.secure_channels.vault() } - /// Return the repository used to store identities data - pub fn repository(&self) -> Arc { - self.identities().repository() + /// Return the vault used by secure channels + pub fn purpose_keys(&self) -> Arc { + self.secure_channels.identities().purpose_keys() } - /// Return the vault used by secure channels - pub fn secure_channels_vault(&self) -> Arc { - self.secure_channels().vault() + /// Return services to serve credentials + pub fn credentials_server(&self) -> Arc { + self.secure_channels.identities().credentials_server() } - /// Return the vault used by identities - pub fn identities_vault(&self) -> Arc { - self.identities().vault() + /// Return the repository used to store identities data + pub fn identities_repository(&self) -> Arc { + self.secure_channels.identities().repository() } /// Return a new builder for top-level services @@ -316,50 +336,44 @@ pub struct NodeBuilder { } impl NodeBuilder { - fn new() -> NodeBuilder { - NodeBuilder { + fn new() -> Self { + Self { builder: SecureChannels::builder(), } } - /// Set a specific vault storage for identities and secure channels - pub fn with_vault_storage(&mut self, storage: VaultStorage) -> NodeBuilder { - self.builder = self.builder.with_vault_storage(storage); - self.clone() + /// Set [`Vault`] + pub fn with_vault(mut self, vault: Vault) -> Self { + self.builder = self.builder.with_vault(vault); + self } - /// Set a specific identities vault - pub fn with_identities_vault(&mut self, vault: Arc) -> NodeBuilder { - self.builder = self.builder.with_identities_vault(vault); - self.clone() + /// With Software Vault with given Storage + pub fn with_vault_storage(mut self, storage: VaultStorage) -> Self { + self.builder = self.builder.with_vault_storage(storage); + self } /// Set a specific storage for identities - pub fn with_identities_storage(&mut self, storage: Arc) -> NodeBuilder { + pub fn with_identities_storage(mut self, storage: Arc) -> Self { self.builder = self.builder.with_identities_storage(storage); - self.clone() + self } /// Set a specific identities repository - pub fn with_identities_repository( - &mut self, - repository: Arc, - ) -> NodeBuilder { + pub fn with_identities_repository(mut self, repository: Arc) -> Self { self.builder = self.builder.with_identities_repository(repository); - self.clone() + self } /// Set a specific secure channels registry - pub fn with_secure_channels_registry( - &mut self, - registry: SecureChannelRegistry, - ) -> NodeBuilder { + pub fn with_secure_channels_registry(mut self, registry: SecureChannelRegistry) -> Self { self.builder = self.builder.with_secure_channels_registry(registry); - self.clone() + self } /// Build top level services - pub async fn build(&self, ctx: Context) -> Result { + pub async fn build(self, ctx: Context) -> Result { Ok(Node { context: ctx, secure_channels: self.builder.build(), diff --git a/implementations/rust/ockam/ockam/tests/forwarder.rs b/implementations/rust/ockam/ockam/tests/forwarder.rs index c011d51cb21..c4550d69789 100644 --- a/implementations/rust/ockam/ockam/tests/forwarder.rs +++ b/implementations/rust/ockam/ockam/tests/forwarder.rs @@ -1,8 +1,8 @@ +use ockam::identity::{secure_channels, SecureChannelListenerOptions, SecureChannelOptions}; use ockam::remote::{RemoteForwarder, RemoteForwarderOptions}; use ockam::workers::Echoer; use ockam::{ForwardingService, ForwardingServiceOptions}; use ockam_core::{route, AllowAll, Result}; -use ockam_identity::{secure_channels, SecureChannelListenerOptions, SecureChannelOptions}; use ockam_node::{Context, MessageReceiveOptions}; use ockam_transport_tcp::{TcpConnectionOptions, TcpListenerOptions, TcpTransport}; use std::time::Duration; @@ -172,7 +172,7 @@ async fn test4(ctx: &mut Context) -> Result<()> { secure_channels .create_secure_channel_listener( ctx, - &cloud_identity.identifier(), + cloud_identity.identifier(), "cloud_listener", cloud_secure_channel_listener_options, ) @@ -202,7 +202,7 @@ async fn test4(ctx: &mut Context) -> Result<()> { let cloud_server_channel = secure_channels .create_secure_channel( ctx, - &server_identity.identifier(), + server_identity.identifier(), route![cloud_server_connection, "cloud_listener"], server_secure_channel_options, ) @@ -210,7 +210,7 @@ async fn test4(ctx: &mut Context) -> Result<()> { secure_channels .create_secure_channel_listener( ctx, - &server_identity.identifier(), + server_identity.identifier(), "server_listener", server_secure_channel_listener_options, ) @@ -232,7 +232,7 @@ async fn test4(ctx: &mut Context) -> Result<()> { let cloud_client_channel = secure_channels .create_secure_channel( ctx, - &client_identity.identifier(), + client_identity.identifier(), route![cloud_client_connection, "cloud_listener"], SecureChannelOptions::new(), ) @@ -241,7 +241,7 @@ async fn test4(ctx: &mut Context) -> Result<()> { let tunnel_channel = secure_channels .create_secure_channel( ctx, - &client_identity.identifier(), + client_identity.identifier(), route![ cloud_client_channel, remote_info.remote_address(), diff --git a/implementations/rust/ockam/ockam_abac/src/attribute_access_control.rs b/implementations/rust/ockam/ockam_abac/src/attribute_access_control.rs index ce484dfafad..85dba10c91d 100644 --- a/implementations/rust/ockam/ockam_abac/src/attribute_access_control.rs +++ b/implementations/rust/ockam/ockam_abac/src/attribute_access_control.rs @@ -1,3 +1,4 @@ +use core::str::from_utf8; use ockam_core::async_trait; use ockam_core::compat::boxed::Box; use ockam_core::compat::fmt; @@ -15,7 +16,7 @@ use crate::{eval, Env, Expr}; use ockam_core::compat::format; use ockam_core::compat::string::ToString; use ockam_core::compat::sync::Arc; -use ockam_identity::{IdentitiesRepository, IdentityIdentifier, IdentitySecureChannelLocalInfo}; +use ockam_identity::{Identifier, IdentitiesRepository, IdentitySecureChannelLocalInfo}; /// This AccessControl uses a storage for authenticated attributes in order /// to verify if a policy expression is valid @@ -68,12 +69,23 @@ where { impl AbacAccessControl { /// Returns true if the identity is authorized - pub async fn is_identity_authorized(&self, id: IdentityIdentifier) -> Result { + pub async fn is_identity_authorized(&self, id: Identifier) -> Result { let mut environment = self.environment.clone(); // Get identity attributes and populate the environment: if let Some(attrs) = self.repository.get_attributes(&id).await? { for (key, value) in attrs.attrs() { + let key = match from_utf8(key) { + Ok(key) => key, + Err(_) => { + log::warn! { + policy = %self.expression, + id = %id, + "attribute key is not utf-8" + } + continue; + } + }; if key.find(|c: char| c.is_whitespace()).is_some() { log::warn! { policy = %self.expression, diff --git a/implementations/rust/ockam/ockam_abac/src/storage/lmdb_storage.rs b/implementations/rust/ockam/ockam_abac/src/storage/lmdb_storage.rs index 89b7c2ee2d0..fc456573519 100644 --- a/implementations/rust/ockam/ockam_abac/src/storage/lmdb_storage.rs +++ b/implementations/rust/ockam/ockam_abac/src/storage/lmdb_storage.rs @@ -7,7 +7,7 @@ use ockam_core::compat::boxed::Box; use ockam_core::compat::vec::Vec; use ockam_core::errcode::{Kind, Origin}; use ockam_core::{Error, Result}; -use ockam_identity::LmdbStorage; +use ockam_identity::storage::LmdbStorage; use std::borrow::Cow; use tracing as log; diff --git a/implementations/rust/ockam/ockam_api/Cargo.toml b/implementations/rust/ockam/ockam_api/Cargo.toml index b5013af2127..8deea23e41e 100644 --- a/implementations/rust/ockam/ockam_api/Cargo.toml +++ b/implementations/rust/ockam/ockam_api/Cargo.toml @@ -17,7 +17,7 @@ std = [ "minicbor/std", "ockam_core/std", "ockam_abac/std", - "ockam_identity/std", + "ockam/std", "ockam_multiaddr/std", "ockam_node/std", "ockam_vault/std", @@ -61,7 +61,6 @@ tokio-retry = "0.3.0" tracing = { version = "0.1", default-features = false } url = "2.4.1" -ockam = { path = "../ockam", version = "^0.92.0", features = ["software_vault"] } ockam_multiaddr = { path = "../ockam_multiaddr", version = "0.26.0", features = ["cbor", "serde"] } ockam_transport_tcp = { path = "../ockam_transport_tcp", version = "^0.86.0" } @@ -91,11 +90,11 @@ path = "../ockam_vault_aws" default-features = false features = ["std"] -[dependencies.ockam_identity] -version = "0.80.0" -path = "../ockam_identity" +[dependencies.ockam] +version = "^0.92.0" +path = "../ockam" default-features = false -features = ["std"] +features = ["ockam_transport_tcp", "software_vault_storage"] [dependencies.ockam_abac] version = "0.26.0" diff --git a/implementations/rust/ockam/ockam_api/src/auth.rs b/implementations/rust/ockam/ockam_api/src/auth.rs index 6b45c25b677..3b6566ef224 100644 --- a/implementations/rust/ockam/ockam_api/src/auth.rs +++ b/implementations/rust/ockam/ockam_api/src/auth.rs @@ -2,7 +2,7 @@ pub mod types; use core::fmt; use minicbor::Decoder; -use ockam::identity::{AttributesEntry, IdentityAttributesReader, IdentityIdentifier}; +use ockam::identity::{AttributesEntry, Identifier, IdentityAttributesReader}; use ockam_core::api::{decode_option, Error}; use ockam_core::api::{Method, Request, Response}; use ockam_core::compat::sync::Arc; @@ -55,7 +55,7 @@ impl Server { .body(self.store.list().await?) .to_vec()?, [id] => { - let identifier = IdentityIdentifier::try_from(id.to_string())?; + let identifier = Identifier::try_from(id.to_string())?; if let Some(a) = self.store.get_attributes(&identifier).await? { Response::ok(req.id()).body(a).to_vec()? } else { @@ -110,11 +110,11 @@ impl Client { self.buf = request(&self.ctx, label, None, self.route.clone(), req).await?; decode_option(label, "attribute", &self.buf) } - pub async fn list(&mut self) -> ockam_core::Result> { + pub async fn list(&mut self) -> ockam_core::Result> { let label = "list known identities"; let req = Request::get("/"); self.buf = request(&self.ctx, label, None, self.route.clone(), req).await?; - let a: Option> = + let a: Option> = decode_option(label, "attribute", &self.buf)?; Ok(a.unwrap()) } diff --git a/implementations/rust/ockam/ockam_api/src/authenticator/direct.rs b/implementations/rust/ockam/ockam_api/src/authenticator/direct.rs index 7068d834a7f..ee486c2e1db 100644 --- a/implementations/rust/ockam/ockam_api/src/authenticator/direct.rs +++ b/implementations/rust/ockam/ockam_api/src/authenticator/direct.rs @@ -3,14 +3,15 @@ pub mod types; use core::str; use lru::LruCache; use minicbor::Decoder; +use ockam::identity::utils::now; +use ockam::identity::OneTimeCode; +use ockam::identity::{secure_channel_required, TRUST_CONTEXT_ID}; use ockam::identity::{AttributesEntry, IdentityAttributesReader, IdentityAttributesWriter}; -use ockam::identity::{IdentityIdentifier, IdentitySecureChannelLocalInfo}; -use ockam::identity::{OneTimeCode, Timestamp}; +use ockam::identity::{Identifier, IdentitySecureChannelLocalInfo}; use ockam_core::api::{self, Method, Request, Response, Status}; use ockam_core::compat::sync::{Arc, RwLock}; use ockam_core::errcode::{Kind, Origin}; use ockam_core::{self, CowStr, Result, Routed, Worker}; -use ockam_identity::{secure_channel_required, LEGACY_ID, TRUST_CONTEXT_ID}; use ockam_node::{Context, RpcClient}; use std::collections::HashMap; use std::num::NonZeroUsize; @@ -167,37 +168,29 @@ impl DirectAuthenticator { async fn add_member<'a>( &self, - enroller: &IdentityIdentifier, - id: &IdentityIdentifier, + enroller: &Identifier, + id: &Identifier, attrs: &HashMap, CowStr<'a>>, ) -> Result<()> { let auth_attrs = attrs .iter() - .map(|(k, v)| (k.to_string(), v.as_bytes().to_vec())) + .map(|(k, v)| (k.as_bytes().to_vec(), v.as_bytes().to_vec())) .chain( - [ - (LEGACY_ID.to_owned(), self.trust_context.as_bytes().to_vec()), - ( - TRUST_CONTEXT_ID.to_owned(), - self.trust_context.as_bytes().to_vec(), - ), - ] + [( + TRUST_CONTEXT_ID.to_owned(), + self.trust_context.as_bytes().to_vec(), + )] .into_iter(), ) .collect(); - let entry = AttributesEntry::new( - auth_attrs, - Timestamp::now().unwrap(), - None, - Some(enroller.clone()), - ); + let entry = AttributesEntry::new(auth_attrs, now()?, None, Some(enroller.clone())); self.attributes_writer.put_attributes(id, entry).await } async fn list_members( &self, - enroller: &IdentityIdentifier, - ) -> Result> { + enroller: &Identifier, + ) -> Result> { // TODO: move filter to `list` function let all_attributes = self.attributes_reader.list().await?; let attested_by_me = all_attributes @@ -239,7 +232,7 @@ impl Worker for DirectAuthenticator { } (Some(Method::Get), ["member_ids"]) => { let entries = self.list_members(&from).await?; - let ids: Vec = entries.into_keys().collect(); + let ids: Vec = entries.into_keys().collect(); Response::ok(req.id()).body(ids).to_vec()? } // List members attested by our identity (enroller) @@ -250,7 +243,7 @@ impl Worker for DirectAuthenticator { } // Delete member if they were attested by our identity (enroller) (Some(Method::Delete), [id]) | (Some(Method::Delete), ["members", id]) => { - let identifier = IdentityIdentifier::try_from(id.to_string())?; + let identifier = Identifier::try_from(id.to_string())?; if let Some(entry) = self.attributes_reader.get_attributes(&identifier).await? { if entry.attested_by() == Some(from) { self.attributes_writer.delete(&identifier).await?; @@ -306,7 +299,7 @@ impl EnrollmentTokenAuthenticator { impl EnrollmentTokenIssuer { async fn issue_token( &self, - enroller: &IdentityIdentifier, + enroller: &Identifier, attrs: HashMap, token_duration: Option, ) -> Result { @@ -422,21 +415,11 @@ impl Worker for EnrollmentTokenAcceptor { let attrs = tkn .attrs .iter() - .map(|(k, v)| (k.to_string(), v.as_bytes().to_vec())) - .chain( - [ - (LEGACY_ID.to_owned(), trust_context.clone()), - (TRUST_CONTEXT_ID.to_owned(), trust_context), - ] - .into_iter(), - ) + .map(|(k, v)| (k.as_bytes().to_vec(), v.as_bytes().to_vec())) + .chain([(TRUST_CONTEXT_ID.to_owned(), trust_context)].into_iter()) .collect(); - let entry = AttributesEntry::new( - attrs, - Timestamp::now().unwrap(), - None, - Some(tkn.generated_by), - ); + let entry = + AttributesEntry::new(attrs, now()?, None, Some(tkn.generated_by)); self.1.put_attributes(&from, entry).await?; Response::ok(req.id()).to_vec()? } @@ -454,7 +437,7 @@ impl Worker for EnrollmentTokenAcceptor { struct Token { attrs: HashMap, - generated_by: IdentityIdentifier, + generated_by: Identifier, time: Instant, max_token_duration: Duration, } @@ -466,11 +449,7 @@ impl DirectAuthenticatorClient { DirectAuthenticatorClient(client) } - pub async fn add_member( - &self, - id: IdentityIdentifier, - attributes: HashMap<&str, &str>, - ) -> Result<()> { + pub async fn add_member(&self, id: Identifier, attributes: HashMap<&str, &str>) -> Result<()> { self.0 .request_no_resp_body( &Request::post("/").body(AddMember::new(id).with_attributes(attributes)), @@ -478,15 +457,15 @@ impl DirectAuthenticatorClient { .await } - pub async fn list_member_ids(&self) -> Result> { + pub async fn list_member_ids(&self) -> Result> { self.0.request(&Request::get("/member_ids")).await } - pub async fn list_members(&self) -> Result> { + pub async fn list_members(&self) -> Result> { self.0.request(&Request::get("/")).await } - pub async fn delete_member(&self, id: IdentityIdentifier) -> Result<()> { + pub async fn delete_member(&self, id: Identifier) -> Result<()> { self.0 .request_no_resp_body(&Request::delete(format!("/{id}"))) .await diff --git a/implementations/rust/ockam/ockam_api/src/authenticator/direct/types.rs b/implementations/rust/ockam/ockam_api/src/authenticator/direct/types.rs index 64ef3b119db..798a2e0eff1 100644 --- a/implementations/rust/ockam/ockam_api/src/authenticator/direct/types.rs +++ b/implementations/rust/ockam/ockam_api/src/authenticator/direct/types.rs @@ -1,5 +1,5 @@ use minicbor::{Decode, Encode}; -use ockam::identity::IdentityIdentifier; +use ockam::identity::Identifier; use ockam_core::CowStr; use std::collections::HashMap; use std::time::Duration; @@ -13,12 +13,12 @@ use ockam_core::TypeTag; pub struct AddMember<'a> { #[cfg(feature = "tag")] #[n(0)] tag: TypeTag<2820828>, - #[n(1)] member: IdentityIdentifier, + #[n(1)] member: Identifier, #[b(2)] attributes: HashMap, CowStr<'a>>, } impl<'a> AddMember<'a> { - pub fn new(member: IdentityIdentifier) -> Self { + pub fn new(member: Identifier) -> Self { AddMember { #[cfg(feature = "tag")] tag: TypeTag, @@ -35,7 +35,7 @@ impl<'a> AddMember<'a> { self } - pub fn member(&self) -> &IdentityIdentifier { + pub fn member(&self) -> &Identifier { &self.member } diff --git a/implementations/rust/ockam/ockam_api/src/bootstrapped_identities_store.rs b/implementations/rust/ockam/ockam_api/src/bootstrapped_identities_store.rs index dfa9c97c523..bc2baa8dab6 100644 --- a/implementations/rust/ockam/ockam_api/src/bootstrapped_identities_store.rs +++ b/implementations/rust/ockam/ockam_api/src/bootstrapped_identities_store.rs @@ -1,6 +1,8 @@ +use ockam::identity::models::ChangeHistory; +use ockam::identity::utils::now; use ockam::identity::{ - AttributesEntry, IdentitiesReader, IdentitiesRepository, IdentitiesWriter, Identity, - IdentityAttributesReader, IdentityAttributesWriter, IdentityIdentifier, Timestamp, + AttributesEntry, Identifier, IdentitiesReader, IdentitiesRepository, IdentitiesWriter, + IdentityAttributesReader, IdentityAttributesWriter, }; use ockam_core::async_trait; use ockam_core::compat::sync::Arc; @@ -32,10 +34,7 @@ impl BootstrapedIdentityStore { #[async_trait] impl IdentityAttributesReader for BootstrapedIdentityStore { - async fn get_attributes( - &self, - identity_id: &IdentityIdentifier, - ) -> Result> { + async fn get_attributes(&self, identity_id: &Identifier) -> Result> { trace! { target: "ockam_api::bootstrapped_identities_store", id = %identity_id, @@ -47,7 +46,7 @@ impl IdentityAttributesReader for BootstrapedIdentityStore { } } - async fn list(&self) -> Result> { + async fn list(&self) -> Result> { let mut l = self.repository.list().await?; let mut l2 = self.bootstrapped.list().await?; l.append(&mut l2); @@ -57,11 +56,7 @@ impl IdentityAttributesReader for BootstrapedIdentityStore { #[async_trait] impl IdentityAttributesWriter for BootstrapedIdentityStore { - async fn put_attributes( - &self, - sender: &IdentityIdentifier, - entry: AttributesEntry, - ) -> Result<()> { + async fn put_attributes(&self, sender: &Identifier, entry: AttributesEntry) -> Result<()> { trace! { target: "ockam_api::bootstrapped_identities_store", id = %sender, @@ -88,34 +83,40 @@ impl IdentityAttributesWriter for BootstrapedIdentityStore { async fn put_attribute_value( &self, - subject: &IdentityIdentifier, - attribute_name: &str, - attribute_value: &str, + subject: &Identifier, + attribute_name: Vec, + attribute_value: Vec, ) -> Result<()> { self.repository .put_attribute_value(subject, attribute_name, attribute_value) .await } - async fn delete(&self, identity: &IdentityIdentifier) -> Result<()> { + async fn delete(&self, identity: &Identifier) -> Result<()> { self.repository.delete(identity).await } } #[async_trait] impl IdentitiesReader for BootstrapedIdentityStore { - async fn retrieve_identity(&self, identifier: &IdentityIdentifier) -> Result> { + async fn retrieve_identity(&self, identifier: &Identifier) -> Result> { self.repository.retrieve_identity(identifier).await } - async fn get_identity(&self, identifier: &IdentityIdentifier) -> Result { + async fn get_identity(&self, identifier: &Identifier) -> Result { self.repository.get_identity(identifier).await } } #[async_trait] impl IdentitiesWriter for BootstrapedIdentityStore { - async fn update_identity(&self, identity: &Identity) -> Result<()> { - self.repository.update_identity(identity).await + async fn update_identity( + &self, + identifier: &Identifier, + change_history: &ChangeHistory, + ) -> Result<()> { + self.repository + .update_identity(identifier, change_history) + .await } } @@ -139,7 +140,7 @@ impl IdentitiesRepository for BootstrapedIdentityStore { #[derive(Clone, Debug, Serialize, Deserialize)] pub enum PreTrustedIdentities { - Fixed(HashMap), + Fixed(HashMap), ReloadFrom(PathBuf), } @@ -156,55 +157,51 @@ impl PreTrustedIdentities { Ok(Self::new_from_hashmap(Self::parse(entries)?)) } - pub fn new_from_hashmap(entries: HashMap) -> Self { + pub fn new_from_hashmap(entries: HashMap) -> Self { PreTrustedIdentities::Fixed(entries) } - pub fn get_trusted_identities(self) -> Result> { + pub fn get_trusted_identities(self) -> Result> { match self { PreTrustedIdentities::Fixed(identities) => Ok(identities), PreTrustedIdentities::ReloadFrom(path) => Self::parse_from_disk(&path), } } - fn parse_from_disk(path: &PathBuf) -> Result> { + fn parse_from_disk(path: &PathBuf) -> Result> { let contents = std::fs::read_to_string(path) .map_err(|e| ockam_core::Error::new(Origin::Other, Kind::Io, e))?; Self::parse(&contents) } - fn parse(entries: &str) -> Result> { - let raw_map = - json::from_str::>>(entries) - .map_err(|e| ockam_core::Error::new(Origin::Other, Kind::Invalid, e))?; + fn parse(entries: &str) -> Result> { + let raw_map = json::from_str::>>(entries) + .map_err(|e| ockam_core::Error::new(Origin::Other, Kind::Invalid, e))?; Ok(raw_map .into_iter() .map(|(identity_id, raw_attrs)| { let attrs = raw_attrs .into_iter() - .map(|(k, v)| (k, v.as_bytes().to_vec())) + .map(|(k, v)| (k.as_bytes().to_vec(), v.as_bytes().to_vec())) .collect(); ( identity_id, - AttributesEntry::new(attrs, Timestamp::now().unwrap(), None, None), + AttributesEntry::new(attrs, now().unwrap(), None, None), ) }) .collect()) } } -impl From> for PreTrustedIdentities { - fn from(h: HashMap) -> PreTrustedIdentities { +impl From> for PreTrustedIdentities { + fn from(h: HashMap) -> PreTrustedIdentities { PreTrustedIdentities::Fixed(h) } } #[async_trait] impl IdentityAttributesReader for PreTrustedIdentities { - async fn get_attributes( - &self, - identity_id: &IdentityIdentifier, - ) -> Result> { + async fn get_attributes(&self, identity_id: &Identifier) -> Result> { match self { PreTrustedIdentities::Fixed(trusted) => Ok(trusted.get(identity_id).cloned()), PreTrustedIdentities::ReloadFrom(path) => { @@ -213,7 +210,7 @@ impl IdentityAttributesReader for PreTrustedIdentities { } } - async fn list(&self) -> Result> { + async fn list(&self) -> Result> { match self { PreTrustedIdentities::Fixed(trusted) => Ok(trusted .into_iter() diff --git a/implementations/rust/ockam/ockam_api/src/cli_state/credentials.rs b/implementations/rust/ockam/ockam_api/src/cli_state/credentials.rs index 6937317e793..ebf5d23eece 100644 --- a/implementations/rust/ockam/ockam_api/src/cli_state/credentials.rs +++ b/implementations/rust/ockam/ockam_api/src/cli_state/credentials.rs @@ -1,6 +1,7 @@ use super::Result; use crate::cli_state::CliStateError; -use ockam_identity::{Credential, Identity, IdentityHistoryComparison}; +use ockam::identity::models::CredentialAndPurposeKey; +use ockam::identity::Identifier; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -22,35 +23,30 @@ impl CredentialState { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct CredentialConfig { - pub issuer: Identity, - pub encoded_credential: String, + pub issuer_identifier: Identifier, + // FIXME: Appear as array of number in JSON + pub encoded_issuer_change_history: Vec, + // FIXME: Appear as array of number in JSON + pub encoded_credential: Vec, } -impl PartialEq for CredentialConfig { - fn eq(&self, other: &Self) -> bool { - self.encoded_credential == other.encoded_credential - && self.issuer.compare(&other.issuer) == IdentityHistoryComparison::Equal - } -} - -impl Eq for CredentialConfig {} - impl CredentialConfig { - pub fn new(issuer: Identity, encoded_credential: String) -> Result { + pub fn new( + issuer_identifier: Identifier, + encoded_issuer_change_history: Vec, + encoded_credential: Vec, + ) -> Result { Ok(Self { - issuer, + issuer_identifier, + encoded_issuer_change_history, encoded_credential, }) } - pub fn credential(&self) -> Result { - let bytes = hex::decode(&self.encoded_credential).map_err(|e| { - error!(%e, "Unable to hex-decode credential"); - CliStateError::InvalidOperation("Unable to hex-decode credential".to_string()) - })?; - minicbor::decode::(&bytes).map_err(|e| { + pub fn credential(&self) -> Result { + minicbor::decode(&self.encoded_credential).map_err(|e| { error!(%e, "Unable to decode credential"); CliStateError::InvalidOperation("Unable to decode credential".to_string()) }) diff --git a/implementations/rust/ockam/ockam_api/src/cli_state/identities.rs b/implementations/rust/ockam/ockam_api/src/cli_state/identities.rs index 5917c2effac..761c1bd67cd 100644 --- a/implementations/rust/ockam/ockam_api/src/cli_state/identities.rs +++ b/implementations/rust/ockam/ockam_api/src/cli_state/identities.rs @@ -7,10 +7,8 @@ use serde::{Deserialize, Serialize}; use time::format_description::well_known::Iso8601; use time::OffsetDateTime; -use ockam_identity::{ - IdentitiesRepository, IdentitiesStorage, Identity, IdentityChangeHistory, IdentityIdentifier, - LmdbStorage, -}; +use ockam::identity::storage::LmdbStorage; +use ockam::identity::{Identifier, IdentitiesRepository, IdentitiesStorage}; use crate::cli_state::traits::{StateDirTrait, StateItemTrait}; use crate::cli_state::{CliStateError, DATA_DIR_NAME}; @@ -31,7 +29,7 @@ impl IdentitiesState { } } - pub fn get_by_identifier(&self, identifier: &IdentityIdentifier) -> Result { + pub fn get_by_identifier(&self, identifier: &Identifier) -> Result { self.list()? .into_iter() .find(|ident_state| &ident_state.config.identifier() == identifier) @@ -67,7 +65,7 @@ pub struct IdentityState { } impl IdentityState { - pub fn identifier(&self) -> IdentityIdentifier { + pub fn identifier(&self) -> Identifier { self.config.identifier() } @@ -119,7 +117,7 @@ impl Display for IdentityState { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct IdentityConfig { - pub identifier: IdentityIdentifier, + pub identifier: Identifier, pub enrollment_status: Option, } @@ -132,14 +130,14 @@ impl PartialEq for IdentityConfig { impl Eq for IdentityConfig {} impl IdentityConfig { - pub async fn new(identifier: &IdentityIdentifier) -> Self { + pub async fn new(identifier: &Identifier) -> Self { Self { identifier: identifier.clone(), enrollment_status: None, } } - pub fn identifier(&self) -> IdentityIdentifier { + pub fn identifier(&self) -> Identifier { self.identifier.clone() } } @@ -180,18 +178,25 @@ impl Display for EnrollmentStatus { } } +// TODO: No longer supported: consider deleting #[derive(Deserialize, Debug, Clone)] struct IdentityConfigV1 { - identifier: IdentityIdentifier, - #[allow(dead_code)] - change_history: IdentityChangeHistory, - enrollment_status: Option, + // Easiest way to fail deserialization + _non_existent_field: bool, } +// TODO: No longer supported: consider deleting #[derive(Deserialize, Debug, Clone)] struct IdentityConfigV2 { - identity: Identity, - enrollment_status: Option, + // Easiest way to fail deserialization + _non_existent_field: bool, +} + +// TODO: No longer supported: consider deleting +#[derive(Deserialize, Debug, Clone)] +struct IdentityConfigV3 { + // Easiest way to fail deserialization + _non_existent_field: bool, } #[derive(Deserialize, Debug, Clone)] @@ -199,7 +204,8 @@ struct IdentityConfigV2 { enum IdentityConfigs { V1(IdentityConfigV1), V2(IdentityConfigV2), - V3(IdentityConfig), + V3(IdentityConfigV3), + V4(IdentityConfig), } mod traits { @@ -252,31 +258,12 @@ mod traits { // old format we store the full identity in the shared identities repository before // writing the most recent configuration format match serde_json::from_str(&contents)? { - IdentityConfigs::V1(config) => { - let identifier = config.identifier.clone(); - let new_config = IdentityConfig { - identifier: identifier.clone(), - enrollment_status: config.enrollment_status, - }; - let identity = Identity::new(identifier, config.change_history); - self.identities_repository() - .await? - .update_identity(&identity) - .await?; - std::fs::write(path, serde_json::to_string(&new_config)?)?; + IdentityConfigs::V1(_) | IdentityConfigs::V2(_) | IdentityConfigs::V3(_) => { + return Err(CliStateError::InvalidVersion( + "Migration not supported for old Identities".to_string(), + )) } - IdentityConfigs::V2(config) => { - let new_config = IdentityConfig { - identifier: config.identity.identifier(), - enrollment_status: config.enrollment_status, - }; - self.identities_repository() - .await? - .update_identity(&config.identity) - .await?; - std::fs::write(path, serde_json::to_string(&new_config)?)?; - } - IdentityConfigs::V3(_) => (), + IdentityConfigs::V4(_) => {} } Ok(()) } @@ -325,7 +312,6 @@ mod traits { #[cfg(test)] mod tests { use super::*; - use ockam_identity::IdentityChangeHistory; #[test] fn test_serialize() { @@ -342,24 +328,10 @@ mod tests { assert_eq!(actual, expected) } - #[test] - fn test_deserialize_legacy() { - let json = create_identity_config_json_legacy(); - let actual: IdentityConfig = serde_json::from_str(json.as_str()).unwrap(); - let expected = create_identity_config(); - assert_eq!(actual, expected) - } - fn create_identity_config() -> IdentityConfig { - let data = hex::decode("0144c7eb72dd1e633f38e0d0521e9d5eb5072f6418176529eb1b00189e4d69ad2e000547c93239ba3d818ec26c9cdadd2a35cbdf1fa3b6d1a731e06164b1079fb7b8084f434b414d5f524b03012000000020c6c52380125d42b0b4da922b1cff8503a258c3497ec8ac0b4a3baa0d9ca7b3780301014075064b902bda9d16db81ab5f38fbcf226a0e904e517a8c087d379ea139df1f2d7fee484ac7e1c2b7ab2da75f85adef6af7ddb05e7fa8faf180820cb9e86def02").unwrap(); - let identity = Identity::new( - IdentityIdentifier::from_hex( - "fa804b7fca12a19eed206ae180b5b576860ae6512f196c189d90661bcc434b50", - ), - IdentityChangeHistory::import(data.to_vec().as_slice()).unwrap(), - ); + let identifier = Identifier::try_from("Ifa804b7fca12a19eed206ae180b5b576860ae651").unwrap(); IdentityConfig { - identifier: identity.identifier(), + identifier, enrollment_status: Some(EnrollmentStatus { is_enrolled: true, created_at: SystemTime::from(OffsetDateTime::from_unix_timestamp(0).unwrap()), @@ -368,10 +340,6 @@ mod tests { } fn create_identity_config_json() -> String { - r#"{"identifier":"Pfa804b7fca12a19eed206ae180b5b576860ae6512f196c189d90661bcc434b50","enrollment_status":{"is_enrolled":true,"created_at":{"secs_since_epoch":0,"nanos_since_epoch":0}}}"#.into() - } - - fn create_identity_config_json_legacy() -> String { - r#"{"identifier":"Pfa804b7fca12a19eed206ae180b5b576860ae6512f196c189d90661bcc434b50","change_history":[{"identifier":[68,199,235,114,221,30,99,63,56,224,208,82,30,157,94,181,7,47,100,24,23,101,41,235,27,0,24,158,77,105,173,46],"change":{"CreateKey":{"prev_change_id":[5,71,201,50,57,186,61,129,142,194,108,156,218,221,42,53,203,223,31,163,182,209,167,49,224,97,100,177,7,159,183,184],"key_attributes":{"label":"OCKAM_RK","secret_attributes":{"stype":"Ed25519","persistence":"Persistent","length":32}},"public_key":{"data":[198,197,35,128,18,93,66,176,180,218,146,43,28,255,133,3,162,88,195,73,126,200,172,11,74,59,170,13,156,167,179,120],"stype":"Ed25519"}}},"signatures":[{"stype":"SelfSign","data":[117,6,75,144,43,218,157,22,219,129,171,95,56,251,207,34,106,14,144,78,81,122,140,8,125,55,158,161,57,223,31,45,127,238,72,74,199,225,194,183,171,45,167,95,133,173,239,106,247,221,176,94,127,168,250,241,128,130,12,185,232,109,239,2]}]}],"enrollment_status":{"is_enrolled":true,"created_at":{"secs_since_epoch":0,"nanos_since_epoch":0}}}"#.into() + r#"{"identifier":"Ifa804b7fca12a19eed206ae180b5b576860ae651","enrollment_status":{"is_enrolled":true,"created_at":{"secs_since_epoch":0,"nanos_since_epoch":0}}}"#.into() } } diff --git a/implementations/rust/ockam/ockam_api/src/cli_state/mod.rs b/implementations/rust/ockam/ockam_api/src/cli_state/mod.rs index 237fda7ec72..6334142ccde 100644 --- a/implementations/rust/ockam/ockam_api/src/cli_state/mod.rs +++ b/implementations/rust/ockam/ockam_api/src/cli_state/mod.rs @@ -19,12 +19,12 @@ use crate::cli_state::user_info::UsersInfoState; pub use crate::cli_state::vaults::*; use crate::config::cli::LegacyCliConfig; use miette::Diagnostic; +use ockam::identity::Identifier; use ockam::identity::Identities; +use ockam::identity::Vault; use ockam_core::compat::sync::Arc; use ockam_core::env::get_env_with_default; -use ockam_identity::IdentityIdentifier; use ockam_node::Executor; -use ockam_vault::Vault; use rand::random; use std::path::{Path, PathBuf}; use thiserror::Error; @@ -256,7 +256,7 @@ impl CliState { pub async fn create_identity_state( &self, - identifier: &IdentityIdentifier, + identifier: &Identifier, identity_name: Option<&str>, ) -> Result { if let Ok(identity) = self.identities.get_or_default(identity_name) { @@ -268,7 +268,7 @@ impl CliState { async fn make_identity_state( &self, - identifier: &IdentityIdentifier, + identifier: &Identifier, name: Option<&str>, ) -> Result { let identity_config = IdentityConfig::new(identifier).await; @@ -278,16 +278,16 @@ impl CliState { self.identities.create(identity_name, identity_config) } - pub async fn get_identities(&self, vault: Arc) -> Result> { + pub async fn get_identities(&self, vault: Vault) -> Result> { Ok(Identities::builder() - .with_identities_vault(vault) + .with_vault(vault) .with_identities_repository(self.identities.identities_repository().await?) .build()) } pub async fn default_identities(&self) -> Result> { Ok(Identities::builder() - .with_identities_vault(self.vaults.default()?.identities_vault().await?) + .with_vault(self.vaults.default()?.vault().await?) .with_identities_repository(self.identities.identities_repository().await?) .build()) } @@ -392,14 +392,13 @@ mod tests { use crate::cloud::enroll::auth0::UserInfo; use crate::config::cli::TrustContextConfig; use crate::config::lookup::{ConfigLookup, LookupValue, ProjectLookup, SpaceLookup}; - use ockam_identity::IdentitiesVault; use ockam_multiaddr::MultiAddr; use std::str::FromStr; #[tokio::test] async fn test_create_default_identity_state() { let state = CliState::test().unwrap(); - let identifier = "Pe92f183eb4c324804ef4d62962dea94cf095a265d4d28500c34e1a4e0d5ef638" + let identifier = "Ie92f183eb4c324804ef4d62962dea94cf095a265" .try_into() .unwrap(); let identity1 = state @@ -422,7 +421,7 @@ mod tests { #[tokio::test] async fn test_create_named_identity_state() { let state = CliState::test().unwrap(); - let alice = "Pe92f183eb4c324804ef4d62962dea94cf095a265d4d28500c34e1a4e0d5ef638" + let alice = "Ie92f183eb4c324804ef4d62962dea94cf095a265" .try_into() .unwrap(); let identity1 = state @@ -461,10 +460,7 @@ mod tests { id: "pid".to_string(), name: "pname".to_string(), identity_id: Some( - IdentityIdentifier::from_str( - "Pbb37445cacb3ca7a20040a9b36469e321a57d2cdd8c9e24fd1002897a012a610", - ) - .unwrap(), + Identifier::from_str("Ibb37445cacb3ca7a20040a9b36469e321a57d2cd").unwrap(), ), authority: None, okta: None, @@ -527,9 +523,9 @@ mod tests { let identity_name = { let name = hex::encode(random::<[u8; 4]>()); let vault_state = sut.vaults.get(&vault_name).unwrap(); - let vault: Arc = vault_state.get().await.unwrap(); + let vault: Vault = vault_state.get().await.unwrap(); let identities = Identities::builder() - .with_identities_vault(vault) + .with_vault(vault) .with_identities_repository(sut.identities.identities_repository().await?) .build(); let identity = identities @@ -537,7 +533,7 @@ mod tests { .create_identity() .await .unwrap(); - let config = IdentityConfig::new(&identity.identifier()).await; + let config = IdentityConfig::new(identity.identifier()).await; let state = sut.identities.create(&name, config).unwrap(); let got = sut.identities.get(&name).unwrap(); diff --git a/implementations/rust/ockam/ockam_api/src/cli_state/nodes.rs b/implementations/rust/ockam/ockam_api/src/cli_state/nodes.rs index 0680bd8d836..2ae9a54f66b 100644 --- a/implementations/rust/ockam/ockam_api/src/cli_state/nodes.rs +++ b/implementations/rust/ockam/ockam_api/src/cli_state/nodes.rs @@ -8,10 +8,10 @@ use crate::nodes::models::transport::CreateTransportJson; use backwards_compatibility::*; use miette::{IntoDiagnostic, WrapErr}; use nix::errno::Errno; +use ockam::identity::Identifier; +use ockam::identity::Vault; +use ockam::LmdbStorage; use ockam_core::compat::collections::HashSet; -use ockam_core::compat::sync::Arc; -use ockam_identity::{IdentityIdentifier, LmdbStorage}; -use ockam_vault::Vault; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; use std::path::{Path, PathBuf}; @@ -195,7 +195,7 @@ impl NodeConfig { Ok(std::fs::canonicalize(&self.default_vault)?) } - pub async fn vault(&self) -> Result> { + pub async fn vault(&self) -> Result { let state = VaultState::load(self.vault_path()?)?; state.get().await } @@ -205,7 +205,7 @@ impl NodeConfig { Ok(serde_json::from_str(&std::fs::read_to_string(path)?)?) } - pub fn identifier(&self) -> Result { + pub fn identifier(&self) -> Result { let state_path = std::fs::canonicalize(&self.default_identity)?; let state = IdentityState::load(state_path)?; Ok(state.identifier()) @@ -590,7 +590,7 @@ pub async fn init_node_state( .wrap_err("Failed to create identity")?; let identity_state = cli_state - .create_identity_state(&identity.identifier(), identity_name) + .create_identity_state(identity.identifier(), identity_name) .await?; // Create the node with the given vault and identity @@ -643,10 +643,7 @@ pub async fn add_project_info_to_node_state( } } -pub async fn update_enrolled_identity( - cli_state: &CliState, - node_name: &str, -) -> Result { +pub async fn update_enrolled_identity(cli_state: &CliState, node_name: &str) -> Result { let identities = cli_state.identities.list()?; let node_state = cli_state.nodes.get(node_name)?; diff --git a/implementations/rust/ockam/ockam_api/src/cli_state/projects.rs b/implementations/rust/ockam/ockam_api/src/cli_state/projects.rs index af2cbb4832e..53f8f24f713 100644 --- a/implementations/rust/ockam/ockam_api/src/cli_state/projects.rs +++ b/implementations/rust/ockam/ockam_api/src/cli_state/projects.rs @@ -2,7 +2,7 @@ use super::Result; use crate::cloud::project::{OktaConfig, Project}; use crate::config::lookup::ProjectLookup; use crate::error::ApiError; -use ockam_identity::IdentityIdentifier; +use ockam::identity::Identifier; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -61,7 +61,7 @@ impl From for Project { pub struct ProjectConfigCompact { pub id: String, pub name: String, - pub identity: Option, + pub identity: Option, pub access_route: String, pub authority_access_route: Option, pub authority_identity: Option, diff --git a/implementations/rust/ockam/ockam_api/src/cli_state/vaults.rs b/implementations/rust/ockam/ockam_api/src/cli_state/vaults.rs index 733597373e6..bb257f37088 100644 --- a/implementations/rust/ockam/ockam_api/src/cli_state/vaults.rs +++ b/implementations/rust/ockam/ockam_api/src/cli_state/vaults.rs @@ -4,9 +4,8 @@ use std::sync::Arc; use serde::{Deserialize, Serialize}; -use ockam_identity::IdentitiesVault; -use ockam_vault::Vault; -use ockam_vault_aws::{AwsKmsConfig, AwsSecurityModule}; +use ockam::identity::Vault; +use ockam_vault_aws::AwsSigningVault; use crate::cli_state::traits::StateItemTrait; use crate::cli_state::{CliStateError, StateDirTrait, DATA_DIR_NAME}; @@ -45,16 +44,14 @@ pub struct VaultState { } impl VaultState { - pub async fn get(&self) -> Result> { + pub async fn get(&self) -> Result { if self.config.aws_kms { - let config = AwsKmsConfig::default().await?; - Ok(Vault::create_with_security_module( - AwsSecurityModule::create_with_storage_path( - config, - self.vault_file_path().as_path(), - ) - .await?, - )) + let mut vault = Vault::create(); + let aws_vault = Arc::new(AwsSigningVault::create().await?); + vault.identity_vault = aws_vault.clone(); + vault.credential_vault = aws_vault; + + Ok(vault) } else { let vault = Vault::create_with_persistent_storage_path(self.vault_file_path().as_path()) @@ -74,7 +71,7 @@ impl VaultState { &self.data_path } - pub async fn identities_vault(&self) -> Result> { + pub async fn vault(&self) -> Result { let path = self.vault_file_path().clone(); let vault = Vault::create_with_persistent_storage_path(path.as_path()).await?; Ok(vault) diff --git a/implementations/rust/ockam/ockam_api/src/cloud/enroll.rs b/implementations/rust/ockam/ockam_api/src/cloud/enroll.rs index 974ec260cc3..2af151829b9 100644 --- a/implementations/rust/ockam/ockam_api/src/cloud/enroll.rs +++ b/implementations/rust/ockam/ockam_api/src/cloud/enroll.rs @@ -24,7 +24,7 @@ mod node { use minicbor::Decoder; use tracing::trace; - use ockam::identity::credential::Attributes; + use ockam::identity::Attributes; use ockam_core::api::Request; use ockam_core::{self, Result}; use ockam_multiaddr::MultiAddr; @@ -230,7 +230,7 @@ pub mod auth0 { pub mod enrollment_token { use serde::Serialize; - use ockam::identity::credential::Attributes; + use ockam::identity::Attributes; use super::*; diff --git a/implementations/rust/ockam/ockam_api/src/cloud/mod.rs b/implementations/rust/ockam/ockam_api/src/cloud/mod.rs index c31197f5b38..b6d2ad13c99 100644 --- a/implementations/rust/ockam/ockam_api/src/cloud/mod.rs +++ b/implementations/rust/ockam/ockam_api/src/cloud/mod.rs @@ -95,7 +95,7 @@ mod node { use minicbor::Encode; - use ockam::identity::{IdentityIdentifier, SecureChannelOptions, TrustIdentifierPolicy}; + use ockam::identity::{Identifier, SecureChannelOptions, TrustIdentifierPolicy}; use ockam_core::api::RequestBuilder; use ockam_core::compat::str::FromStr; use ockam_core::env::get_env; @@ -113,16 +113,16 @@ mod node { /// /// If the env var `OCKAM_CONTROLLER_IDENTITY_ID` is set, that will be used to /// load the identifier instead of the file. - pub fn load_controller_identifier() -> Result { - if let Ok(Some(idt)) = get_env::(OCKAM_CONTROLLER_IDENTITY_ID) { + pub fn load_controller_identifier() -> Result { + if let Ok(Some(idt)) = get_env::(OCKAM_CONTROLLER_IDENTITY_ID) { trace!(idt = %idt, "Read controller identifier from env"); return Ok(idt); } - IdentityIdentifier::from_str(include_str!("../../static/controller.id")) + Identifier::from_str(include_str!("../../static/controller.id")) } /// Return controller identity's identifier. - pub fn controller_identifier(&self) -> IdentityIdentifier { + pub fn controller_identifier(&self) -> Identifier { self.controller_identity_id.clone() } diff --git a/implementations/rust/ockam/ockam_api/src/cloud/project.rs b/implementations/rust/ockam/ockam_api/src/cloud/project.rs index d0a12e9d707..fcb5d33da1a 100644 --- a/implementations/rust/ockam/ockam_api/src/cloud/project.rs +++ b/implementations/rust/ockam/ockam_api/src/cloud/project.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use minicbor::{Decode, Encode}; use serde::{Deserialize, Serialize}; -use ockam::identity::IdentityIdentifier; +use ockam::identity::Identifier; use ockam_core::Result; #[cfg(feature = "tag")] use ockam_core::TypeTag; @@ -44,7 +44,7 @@ pub struct Project { pub space_id: String, #[cbor(n(8))] - pub identity: Option, + pub identity: Option, #[cbor(n(9))] pub authority_access_route: Option, @@ -582,6 +582,7 @@ mod node { #[cfg(test)] mod tests { + use ockam::identity::models::IDENTIFIER_LEN; use quickcheck::{Arbitrary, Gen}; use super::*; @@ -599,12 +600,12 @@ mod tests { } } - #[derive(Debug, Clone)] - struct Pr(Project); - - impl Arbitrary for Pr { + impl Arbitrary for Project { fn arbitrary(g: &mut Gen) -> Self { - Pr(Project { + let identifier = [0u8; IDENTIFIER_LEN]; + identifier.map(|_| ::arbitrary(g)); + + Project { #[cfg(feature = "tag")] tag: Default::default(), id: String::arbitrary(g), @@ -613,8 +614,7 @@ mod tests { access_route: String::arbitrary(g), users: vec![String::arbitrary(g), String::arbitrary(g)], space_id: String::arbitrary(g), - identity: bool::arbitrary(g) - .then(|| IdentityIdentifier::from_hex(&String::arbitrary(g))), + identity: bool::arbitrary(g).then_some(Identifier(identifier)), authority_access_route: bool::arbitrary(g).then(|| String::arbitrary(g)), authority_identity: bool::arbitrary(g) .then(|| hex::encode(>::arbitrary(g))), @@ -624,21 +624,18 @@ mod tests { running: bool::arbitrary(g).then(|| bool::arbitrary(g)), operation_id: bool::arbitrary(g).then(|| String::arbitrary(g)), user_roles: vec![], - }) + } } } - #[derive(Debug, Clone)] - struct CPr(CreateProject); - - impl Arbitrary for CPr { + impl Arbitrary for CreateProject { fn arbitrary(g: &mut Gen) -> Self { - CPr(CreateProject { + CreateProject { #[cfg(feature = "tag")] tag: Default::default(), name: String::arbitrary(g), users: vec![String::arbitrary(g), String::arbitrary(g)], - }) + } } } @@ -651,15 +648,15 @@ mod tests { use super::*; quickcheck! { - fn project(o: Pr) -> TestResult { - let cbor = minicbor::to_vec(o.0).unwrap(); + fn project(o: Project) -> TestResult { + let cbor = minicbor::to_vec(o).unwrap(); if let Err(e) = validate_cbor_bytes("project", SCHEMA, &cbor) { return TestResult::error(e.to_string()) } TestResult::passed() } - fn projects(o: Vec) -> TestResult { + fn projects(o: Vec) -> TestResult { let empty: Vec = vec![]; let cbor = minicbor::to_vec(empty).unwrap(); if let Err(e) = validate_cbor_bytes("projects", SCHEMA, &cbor) { @@ -667,7 +664,6 @@ mod tests { } TestResult::passed(); - let o: Vec = o.into_iter().map(|p| p.0).collect(); let cbor = minicbor::to_vec(o).unwrap(); if let Err(e) = validate_cbor_bytes("projects", SCHEMA, &cbor) { return TestResult::error(e.to_string()) @@ -675,8 +671,8 @@ mod tests { TestResult::passed() } - fn create_project(o: CPr) -> TestResult { - let cbor = minicbor::to_vec(o.0).unwrap(); + fn create_project(o: CreateProject) -> TestResult { + let cbor = minicbor::to_vec(o).unwrap(); if let Err(e) = validate_cbor_bytes("create_project", SCHEMA, &cbor) { return TestResult::error(e.to_string()) } @@ -688,7 +684,7 @@ mod tests { #[test] fn convert_access_route_to_socket_addr() { let mut g = Gen::new(100); - let mut p = Pr::arbitrary(&mut g).0; + let mut p = Project::arbitrary(&mut g); p.access_route = "/dnsaddr/node.dnsaddr.com/tcp/4000/service/api".into(); let socket_addr = p.access_route_socket_addr().unwrap(); diff --git a/implementations/rust/ockam/ockam_api/src/cloud/share/create.rs b/implementations/rust/ockam/ockam_api/src/cloud/share/create.rs index 1e134c12b41..fb713c992d2 100644 --- a/implementations/rust/ockam/ockam_api/src/cloud/share/create.rs +++ b/implementations/rust/ockam/ockam_api/src/cloud/share/create.rs @@ -1,11 +1,11 @@ use minicbor::{Decode, Encode}; use ockam_core::Result; -use ockam_identity::identities; use serde::{Deserialize, Serialize}; use crate::cli_state::{CliState, StateDirTrait, StateItemTrait}; use crate::error::ApiError; use crate::identity::EnrollmentTicket; +use ockam::identity::{identities, Identifier}; use super::{RoleInShare, SentInvitation, ShareScope}; @@ -30,11 +30,11 @@ pub struct CreateServiceInvitation { #[n(3)] pub recipient_email: String, // TODO: Should route be a MultiAddr? - #[n(4)] pub project_identity: String, + #[n(4)] pub project_identity: Identifier, #[n(5)] pub project_route: String, - #[n(6)] pub project_authority_identity: String, + #[n(6)] pub project_authority_identity: Identifier, #[n(7)] pub project_authority_route: String, - #[n(8)] pub shared_node_identity: String, + #[n(8)] pub shared_node_identity: Identifier, #[n(9)] pub shared_node_route: String, #[n(10)] pub enrollment_ticket: String, } @@ -63,9 +63,10 @@ impl CreateServiceInvitation { })?; identities() .identities_creation() - .decode_identity(&as_hex) + .import(None, &as_hex) .await? .identifier() + .clone() }; // see also: ockam_command::project::ticket let enrollment_ticket = hex::encode( @@ -79,12 +80,11 @@ impl CreateServiceInvitation { recipient_email: recipient_email.as_ref().to_string(), project_identity: project .identity - .ok_or(ApiError::core("Project identity is missing"))? - .to_string(), + .ok_or(ApiError::core("Project identity is missing"))?, project_route: project.access_route, - project_authority_identity: project_authority_identifier.to_string(), + project_authority_identity: project_authority_identifier, project_authority_route, - shared_node_identity: node_identifier.to_string(), + shared_node_identity: node_identifier, shared_node_route: service_route.as_ref().to_string(), }) } diff --git a/implementations/rust/ockam/ockam_api/src/cloud/share/mod.rs b/implementations/rust/ockam/ockam_api/src/cloud/share/mod.rs index 76de8ad9571..31a2507f93b 100644 --- a/implementations/rust/ockam/ockam_api/src/cloud/share/mod.rs +++ b/implementations/rust/ockam/ockam_api/src/cloud/share/mod.rs @@ -1,6 +1,7 @@ use std::{fmt::Display, str::FromStr}; use minicbor::{Decode, Encode}; +use ockam::identity::Identifier; use serde::{Deserialize, Serialize}; use time::format_description::well_known::iso8601::Iso8601; use time::OffsetDateTime; @@ -154,11 +155,11 @@ fn is_expired(date: &str) -> ockam_core::Result { #[cbor(map)] #[rustfmt::skip] pub struct ServiceAccessDetails { - #[n(1)] pub project_identity: String, + #[n(1)] pub project_identity: Identifier, #[n(2)] pub project_route: String, - #[n(3)] pub project_authority_identity: String, + #[n(3)] pub project_authority_identity: Identifier, #[n(4)] pub project_authority_route: String, - #[n(5)] pub shared_node_identity: String, + #[n(5)] pub shared_node_identity: Identifier, #[n(6)] pub shared_node_route: String, #[n(7)] pub enrollment_ticket: String, // hex-encoded as with CLI output/input } diff --git a/implementations/rust/ockam/ockam_api/src/config/cli.rs b/implementations/rust/ockam/ockam_api/src/config/cli.rs index 92ac41dd185..525c7cc7692 100644 --- a/implementations/rust/ockam/ockam_api/src/config/cli.rs +++ b/implementations/rust/ockam/ockam_api/src/config/cli.rs @@ -5,14 +5,13 @@ use crate::cloud::project::Project; use crate::config::{lookup::ConfigLookup, ConfigValues}; use crate::error::ApiError; use crate::{cli_state, multiaddr_to_transport_route, DefaultAddress, HexByteVec}; -use ockam_core::compat::sync::Arc; -use ockam_core::{Result, Route}; -use ockam_identity::credential::Credential; -use ockam_identity::{ - identities, AuthorityService, CredentialsMemoryRetriever, CredentialsRetriever, Identities, - Identity, IdentityIdentifier, RemoteCredentialsRetriever, RemoteCredentialsRetrieverInfo, +use ockam::identity::{ + identities, AuthorityService, CredentialsMemoryRetriever, CredentialsRetriever, Identifier, + Identities, Identity, RemoteCredentialsRetriever, RemoteCredentialsRetrieverInfo, SecureChannels, TrustContext, }; +use ockam_core::compat::sync::Arc; +use ockam_core::{Result, Route}; use ockam_multiaddr::MultiAddr; use ockam_transport_tcp::TcpTransport; use serde::{Deserialize, Serialize}; @@ -129,16 +128,15 @@ impl TrustContextConfig { }; Some(AuthorityService::new( - secure_channels.identities().identities_reader(), secure_channels.identities().credentials(), - identity.identifier(), + identity.identifier().clone(), credential_retriever, )) } else { None }; - Ok(TrustContext::new(self.id.clone(), authority)) + Ok(TrustContext::new(self.id.to_string(), authority)) } pub fn from_authority_identity( @@ -162,14 +160,11 @@ impl TryFrom for TrustContextConfig { type Error = CliStateError; fn try_from(state: CredentialState) -> std::result::Result { - let issuer = state.config().issuer.clone(); - let identity = issuer.export_hex()?; + let issuer = hex::encode(&state.config().encoded_issuer_change_history); + let identifier = state.config().issuer_identifier.clone().to_string(); let retriever = CredentialRetrieverConfig::FromPath(state); - let authority = TrustAuthorityConfig::new(identity, Some(retriever)); - Ok(TrustContextConfig::new( - issuer.identifier().to_string(), - Some(authority), - )) + let authority = TrustAuthorityConfig::new(issuer, Some(retriever)); + Ok(TrustContextConfig::new(identifier, Some(authority))) } } @@ -244,7 +239,8 @@ impl TrustAuthorityConfig { pub async fn identity(&self) -> Result { identities() .identities_creation() - .decode_identity( + .import( + None, &hex::decode(&self.identity) .map_err(|_| ApiError::core("unable to decode authority identity"))?, ) @@ -262,7 +258,7 @@ impl TrustAuthorityConfig { #[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] pub enum CredentialRetrieverConfig { /// Credential is stored in memory - FromMemory(Credential), + FromMemory(Vec), /// Path to credential file FromPath(CredentialState), /// MultiAddr to Credential Issuer @@ -277,7 +273,7 @@ impl CredentialRetrieverConfig { ) -> Result> { match self { CredentialRetrieverConfig::FromMemory(credential) => Ok(Arc::new( - CredentialsMemoryRetriever::new(credential.clone()), + CredentialsMemoryRetriever::new(minicbor::decode(credential)?), )), CredentialRetrieverConfig::FromPath(state) => Ok(Arc::new( CredentialsMemoryRetriever::new(state.config().credential()?), @@ -285,7 +281,7 @@ impl CredentialRetrieverConfig { CredentialRetrieverConfig::FromCredentialIssuer(issuer_config) => { let _ = tcp_transport.ok_or_else(|| ApiError::core("TCP Transport was not provided when credential retriever was defined as an issuer."))?; let credential_issuer_info = RemoteCredentialsRetrieverInfo::new( - issuer_config.resolve_identity().await?.identifier(), + issuer_config.resolve_identity().await?.identifier().clone(), issuer_config.resolve_route().await?, DefaultAddress::CREDENTIAL_ISSUER.into(), ); @@ -301,15 +297,15 @@ impl CredentialRetrieverConfig { #[derive(Debug, Default, Clone, Serialize, Deserialize)] pub struct AuthoritiesConfig { - authorities: BTreeMap, + authorities: BTreeMap, } impl AuthoritiesConfig { - pub fn add_authority(&mut self, i: IdentityIdentifier, a: Authority) { + pub fn add_authority(&mut self, i: Identifier, a: Authority) { self.authorities.insert(i, a); } - pub fn authorities(&self) -> impl Iterator { + pub fn authorities(&self) -> impl Iterator { self.authorities.iter() } @@ -319,7 +315,7 @@ impl AuthoritiesConfig { v.push( identities .identities_creation() - .decode_identity(a.identity.as_slice()) + .import(None, a.identity.as_slice()) .await?, ) } @@ -384,7 +380,7 @@ impl CredentialIssuerConfig { hex::decode(&self.identity).map_err(|_| ApiError::core("Invalid project authority"))?; identities() .identities_creation() - .decode_identity(&encoded) + .import(None, &encoded) .await } } diff --git a/implementations/rust/ockam/ockam_api/src/config/lookup.rs b/implementations/rust/ockam/ockam_api/src/config/lookup.rs index 21ab79c5968..c6b847f3a8f 100644 --- a/implementations/rust/ockam/ockam_api/src/config/lookup.rs +++ b/implementations/rust/ockam/ockam_api/src/config/lookup.rs @@ -3,7 +3,7 @@ use crate::cloud::project::{OktaAuth0, Project}; use crate::error::ApiError; use bytes::Bytes; use miette::WrapErr; -use ockam::identity::{identities, IdentityIdentifier}; +use ockam::identity::{identities, Identifier}; use ockam_core::compat::collections::VecDeque; use ockam_multiaddr::MultiAddr; use serde::{Deserialize, Serialize}; @@ -219,7 +219,7 @@ pub struct ProjectLookup { /// Name of this project within pub name: String, /// Identifier of the IDENTITY of the project (for secure-channel) - pub identity_id: Option, + pub identity_id: Option, /// Project authority information. pub authority: Option, /// OktaAuth0 information. @@ -268,13 +268,13 @@ impl ProjectLookup { #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] pub struct ProjectAuthority { - id: IdentityIdentifier, + id: Identifier, address: MultiAddr, identity: Bytes, } impl ProjectAuthority { - pub fn new(id: IdentityIdentifier, addr: MultiAddr, identity: Vec) -> Self { + pub fn new(id: Identifier, addr: MultiAddr, identity: Vec) -> Self { Self { id, address: addr, @@ -294,11 +294,8 @@ impl ProjectAuthority { .to_string(); let a = hex::decode(a.as_str()) .map_err(|_| ApiError::message("Invalid project authority"))?; - let p = identities() - .identities_creation() - .decode_identity(&a) - .await?; - Ok(Some(ProjectAuthority::new(p.identifier(), rte, a))) + let p = identities().identities_creation().import(None, &a).await?; + Ok(Some(ProjectAuthority::new(p.identifier().clone(), rte, a))) } else { Ok(None) } @@ -308,7 +305,7 @@ impl ProjectAuthority { &self.identity } - pub fn identity_id(&self) -> &IdentityIdentifier { + pub fn identity_id(&self) -> &Identifier { &self.id } diff --git a/implementations/rust/ockam/ockam_api/src/enroll/oidc_service.rs b/implementations/rust/ockam/ockam_api/src/enroll/oidc_service.rs index 947956741b6..a03df6d7635 100644 --- a/implementations/rust/ockam/ockam_api/src/enroll/oidc_service.rs +++ b/implementations/rust/ockam/ockam_api/src/enroll/oidc_service.rs @@ -17,7 +17,7 @@ use ockam::compat::fmt::Debug; use ockam_core::compat::rand::{thread_rng, RngCore}; use ockam_core::Result; use ockam_node::callback::{new_callback, CallbackSender}; -use ockam_vault::Vault; +use ockam_vault::SoftwareVerifyingVault; /// This service supports various flows of authentication with an OIDC Provider /// @@ -84,7 +84,7 @@ impl OidcService { async fn authorization_code(&self, code_verifier: &str) -> Result { // Hash and base64 encode the random bytes // to obtain a code challenge - let hashed = Vault::sha256(code_verifier.as_bytes()); + let hashed = SoftwareVerifyingVault::compute_sha256(code_verifier.as_bytes())?; let code_challenge = base64_url::encode(&hashed); // Start a local server to get back the authorization code after redirect diff --git a/implementations/rust/ockam/ockam_api/src/identity/enrollment_ticket.rs b/implementations/rust/ockam/ockam_api/src/identity/enrollment_ticket.rs index 8e18cdc1dc3..6867170fc10 100644 --- a/implementations/rust/ockam/ockam_api/src/identity/enrollment_ticket.rs +++ b/implementations/rust/ockam/ockam_api/src/identity/enrollment_ticket.rs @@ -1,5 +1,5 @@ +use ockam::identity::OneTimeCode; use ockam_core::Result; -use ockam_identity::credential::OneTimeCode; use serde::{Deserialize, Serialize}; use crate::config::{cli::TrustContextConfig, lookup::ProjectLookup}; diff --git a/implementations/rust/ockam/ockam_api/src/identity/identity_service.rs b/implementations/rust/ockam/ockam_api/src/identity/identity_service.rs index 2844a9cee25..ffa01a8609d 100644 --- a/implementations/rust/ockam/ockam_api/src/identity/identity_service.rs +++ b/implementations/rust/ockam/ockam_api/src/identity/identity_service.rs @@ -2,15 +2,17 @@ use core::convert::Infallible; use minicbor::encode::Write; use minicbor::{Decoder, Encode}; +use ockam::identity::IdentityHistoryComparison; use tracing::trace; -use ockam::identity::IdentityHistoryComparison; +use crate::identity::models::{ + CompareIdentityChangeHistoryRequest, CreateResponse, ValidateIdentityChangeHistoryRequest, + ValidateIdentityChangeHistoryResponse, +}; use ockam_core::api::{Error, Id, Method, Request, Response, Status}; use ockam_core::{Result, Routed, Worker}; use ockam_node::Context; -use ockam_vault::Signature; -use crate::identity::models::*; use crate::nodes::service::NodeIdentities; /// Vault Service Worker @@ -109,7 +111,7 @@ impl IdentityService { Some(identity) => { let body = CreateResponse::new( identity.export()?, - identity.identifier().to_string(), + identity.identifier().clone(), ); Self::ok_response(req, Some(body), enc) } @@ -127,7 +129,7 @@ impl IdentityService { .create_identity() .await?; let body = - CreateResponse::new(identity.export()?, identity.identifier().to_string()); + CreateResponse::new(identity.export()?, identity.identifier().clone()); Self::ok_response(req, Some(body), enc) } @@ -141,63 +143,10 @@ impl IdentityService { .node_identities .get_default_identities_creation() .await?; - let identity = identities_creation.decode_identity(args.identity()).await?; - - let body = ValidateIdentityChangeHistoryResponse::new(String::from( - identity.identifier(), - )); - - Self::ok_response(req, Some(body), enc) - } - ["actions", "create_signature"] => { - if !req.has_body() { - return Self::response_for_bad_request(req, "empty body", enc); - } - - let args = dec.decode::()?; - let identities_creation = self - .node_identities - .get_identities_creation(args.vault_name()) - .await?; - let identity = identities_creation.decode_identity(args.identity()).await?; - let identities_keys = self - .node_identities - .get_identities_keys(args.vault_name()) - .await?; - let signature = identities_keys - .create_signature(&identity, args.data(), None) - .await?; - - let body = CreateSignatureResponse::new(signature.as_ref()); - - Self::ok_response(req, Some(body), enc) - } - ["actions", "verify_signature"] => { - if !req.has_body() { - return Self::response_for_bad_request(req, "empty body", enc); - } - - let args = dec.decode::()?; - let identities_creation = self - .node_identities - .get_default_identities_creation() - .await?; - let peer_identity = identities_creation - .decode_identity(args.signer_identity()) - .await?; - - let identities_keys = - self.node_identities.get_default_identities_keys().await?; - let verified = identities_keys - .verify_signature( - &peer_identity, - &Signature::new(args.signature().to_vec()), - args.data(), - None, - ) - .await?; + let identity = identities_creation.import(None, args.identity()).await?; - let body = VerifySignatureResponse::new(verified); + let body = + ValidateIdentityChangeHistoryResponse::new(identity.identifier().clone()); Self::ok_response(req, Some(body), enc) } @@ -214,14 +163,14 @@ impl IdentityService { .await?; let current_identity = identities_creation - .decode_identity(args.current_identity()) + .import(None, args.current_identity()) .await?; let body = if args.known_identity().is_empty() { IdentityHistoryComparison::Newer } else { let known_identity = identities_creation - .decode_identity(args.known_identity()) + .import(Some(current_identity.identifier()), args.known_identity()) .await?; current_identity.compare(&known_identity) }; diff --git a/implementations/rust/ockam/ockam_api/src/identity/models.rs b/implementations/rust/ockam/ockam_api/src/identity/models.rs index 8e77b86034b..402577612f4 100644 --- a/implementations/rust/ockam/ockam_api/src/identity/models.rs +++ b/implementations/rust/ockam/ockam_api/src/identity/models.rs @@ -1,8 +1,9 @@ #![allow(missing_docs)] -use ockam_core::{CowBytes, CowStr}; +use ockam_core::CowBytes; use minicbor::{Decode, Encode}; +use ockam::identity::Identifier; #[cfg(feature = "tag")] use ockam_core::TypeTag; @@ -10,26 +11,26 @@ use ockam_core::TypeTag; #[derive(Debug, Clone, Encode, Decode)] #[rustfmt::skip] #[cbor(map)] -pub struct CreateResponse<'a> { +pub struct CreateResponse { #[cfg(feature = "tag")] #[n(0)] tag: TypeTag<3500430>, - #[b(1)] identity: CowBytes<'a>, - #[b(2)] identity_id: CowStr<'a>, + #[n(1)] identity: Vec, + #[n(2)] identity_id: Identifier, } -impl<'a> CreateResponse<'a> { - pub fn new(identity: impl Into>, identity_id: impl Into>) -> Self { +impl CreateResponse { + pub fn new(identity: Vec, identity_id: Identifier) -> Self { Self { #[cfg(feature = "tag")] tag: TypeTag, - identity: identity.into(), - identity_id: identity_id.into(), + identity, + identity_id, } } pub fn identity(&self) -> &[u8] { &self.identity } - pub fn identity_id(&self) -> &str { + pub fn identity_id(&self) -> &Identifier { &self.identity_id } } @@ -59,21 +60,21 @@ impl<'a> ValidateIdentityChangeHistoryRequest<'a> { #[derive(Debug, Clone, Encode, Decode)] #[rustfmt::skip] #[cbor(map)] -pub struct ValidateIdentityChangeHistoryResponse<'a> { +pub struct ValidateIdentityChangeHistoryResponse { #[cfg(feature = "tag")] #[n(0)] tag: TypeTag<4245404>, - #[b(1)] identity_id: CowStr<'a>, + #[n(1)] identity_id: Identifier, } -impl<'a> ValidateIdentityChangeHistoryResponse<'a> { - pub fn new(identity_id: impl Into>) -> Self { +impl ValidateIdentityChangeHistoryResponse { + pub fn new(identity_id: Identifier) -> Self { Self { #[cfg(feature = "tag")] tag: TypeTag, - identity_id: identity_id.into(), + identity_id, } } - pub fn identity_id(&self) -> &str { + pub fn identity_id(&self) -> &Identifier { &self.identity_id } } @@ -107,115 +108,3 @@ impl<'a> CompareIdentityChangeHistoryRequest<'a> { &self.known_identity } } - -#[derive(Debug, Clone, Encode, Decode)] -#[rustfmt::skip] -#[cbor(map)] -pub struct CreateSignatureRequest<'a> { - #[cfg(feature = "tag")] - #[n(0)] tag: TypeTag<1019956>, - #[b(1)] identity: CowBytes<'a>, - #[b(2)] data: CowBytes<'a>, - #[b(3)] vault_name: Option>, -} - -impl<'a> CreateSignatureRequest<'a> { - pub fn new(identity: impl Into>, data: impl Into>) -> Self { - Self { - #[cfg(feature = "tag")] - tag: TypeTag, - identity: identity.into(), - data: data.into(), - vault_name: None, - } - } - pub fn identity(&self) -> &[u8] { - &self.identity - } - pub fn data(&self) -> &[u8] { - &self.data - } - pub fn vault_name(&self) -> Option { - self.vault_name.as_ref().map(|x| x.to_string()) - } -} - -#[derive(Debug, Clone, Encode, Decode)] -#[rustfmt::skip] -#[cbor(map)] -pub struct CreateSignatureResponse<'a> { - #[cfg(feature = "tag")] - #[n(0)] tag: TypeTag<2592832>, - #[b(1)] signature: CowBytes<'a>, -} - -impl<'a> CreateSignatureResponse<'a> { - pub fn new(signature: impl Into>) -> Self { - Self { - #[cfg(feature = "tag")] - tag: TypeTag, - signature: signature.into(), - } - } - pub fn signature(&self) -> &[u8] { - &self.signature - } -} - -#[derive(Debug, Clone, Encode, Decode)] -#[rustfmt::skip] -#[cbor(map)] -pub struct VerifySignatureRequest<'a> { - #[cfg(feature = "tag")] - #[n(0)] tag: TypeTag<7550780>, - #[b(1)] signer_identity: CowBytes<'a>, - #[b(2)] data: CowBytes<'a>, - #[b(3)] signature: CowBytes<'a>, -} - -impl<'a> VerifySignatureRequest<'a> { - pub fn new( - signer_identity: impl Into>, - data: impl Into>, - signature: impl Into>, - ) -> Self { - Self { - #[cfg(feature = "tag")] - tag: TypeTag, - signer_identity: signer_identity.into(), - data: data.into(), - signature: signature.into(), - } - } - pub fn signer_identity(&self) -> &[u8] { - &self.signer_identity - } - pub fn data(&self) -> &[u8] { - &self.data - } - pub fn signature(&self) -> &[u8] { - &self.signature - } -} - -#[derive(Debug, Clone, Encode, Decode)] -#[rustfmt::skip] -#[cbor(map)] -pub struct VerifySignatureResponse { - #[cfg(feature = "tag")] - #[n(0)] tag: TypeTag<1236745>, - #[n(1)] verified: bool, -} - -impl VerifySignatureResponse { - pub fn new(verified: bool) -> Self { - Self { - #[cfg(feature = "tag")] - tag: TypeTag, - verified, - } - } - pub fn verified(&self) -> bool { - self.verified - } -} diff --git a/implementations/rust/ockam/ockam_api/src/kafka/outlet_service/interceptor_listener.rs b/implementations/rust/ockam/ockam_api/src/kafka/outlet_service/interceptor_listener.rs index 9bef652ccc7..833ad0a5a58 100644 --- a/implementations/rust/ockam/ockam_api/src/kafka/outlet_service/interceptor_listener.rs +++ b/implementations/rust/ockam/ockam_api/src/kafka/outlet_service/interceptor_listener.rs @@ -2,11 +2,11 @@ use crate::kafka::outlet_controller::KafkaOutletController; use crate::kafka::portal_worker::KafkaPortalWorker; use crate::kafka::protocol_aware::OutletInterceptorImpl; use crate::kafka::{KAFKA_OUTLET_BOOTSTRAP_ADDRESS, KAFKA_OUTLET_INTERCEPTOR_ADDRESS}; +use ockam::identity::{SecureChannels, TRUST_CONTEXT_ID_UTF8}; use ockam::{Any, Context, Result, Routed, Worker}; use ockam_abac::AbacAccessControl; use ockam_core::flow_control::{FlowControlId, FlowControlOutgoingAccessControl, FlowControls}; use ockam_core::Address; -use ockam_identity::{SecureChannels, TRUST_CONTEXT_ID}; use ockam_node::WorkerBuilder; use std::sync::Arc; @@ -49,7 +49,7 @@ impl OutletManagerService { outlet_controller: KafkaOutletController::new(), incoming_access_control: Arc::new(AbacAccessControl::create( secure_channels.identities().repository(), - TRUST_CONTEXT_ID, + TRUST_CONTEXT_ID_UTF8, trust_context_id, )), flow_control_id: flow_control_id.clone(), diff --git a/implementations/rust/ockam/ockam_api/src/kafka/secure_channel_map.rs b/implementations/rust/ockam/ockam_api/src/kafka/secure_channel_map.rs index 490547d60f7..86c0a776859 100644 --- a/implementations/rust/ockam/ockam_api/src/kafka/secure_channel_map.rs +++ b/implementations/rust/ockam/ockam_api/src/kafka/secure_channel_map.rs @@ -7,16 +7,16 @@ use crate::nodes::models::secure_channel::{ use crate::nodes::NODEMANAGER_ADDR; use crate::DefaultAddress; use minicbor::Decoder; +use ockam::identity::{ + DecryptionRequest, DecryptionResponse, EncryptionRequest, EncryptionResponse, + SecureChannelRegistryEntry, SecureChannels, TRUST_CONTEXT_ID_UTF8, +}; use ockam_abac::AbacAccessControl; use ockam_core::api::{Request, Response, Status}; use ockam_core::compat::collections::{HashMap, HashSet}; use ockam_core::compat::sync::Arc; use ockam_core::errcode::{Kind, Origin}; use ockam_core::{async_trait, route, Address, Error, Result}; -use ockam_identity::{ - DecryptionRequest, DecryptionResponse, EncryptionRequest, EncryptionResponse, - SecureChannelRegistryEntry, SecureChannels, TRUST_CONTEXT_ID, -}; use ockam_multiaddr::proto::Service; use ockam_multiaddr::MultiAddr; use ockam_node::compat::tokio::sync::Mutex; @@ -212,7 +212,7 @@ impl KafkaSecureChannelControllerImpl { ) -> KafkaSecureChannelControllerImpl { let access_control = AbacAccessControl::create( secure_channels.identities().repository(), - TRUST_CONTEXT_ID, + TRUST_CONTEXT_ID_UTF8, &trust_context_id, ); @@ -413,7 +413,7 @@ impl KafkaSecureChannelControllerImpl { if let Some(entry) = record { let authorized = inner .access_control - .is_identity_authorized(entry.their_id()) + .is_identity_authorized(entry.their_id().clone()) .await?; if authorized { @@ -457,7 +457,7 @@ impl KafkaSecureChannelControllerImpl { let authorized = inner .access_control - .is_identity_authorized(entry.their_id()) + .is_identity_authorized(entry.their_id().clone()) .await?; if authorized { diff --git a/implementations/rust/ockam/ockam_api/src/nodes/authority_node/authority.rs b/implementations/rust/ockam/ockam_api/src/nodes/authority_node/authority.rs index 8753cee9222..72adef1cfb8 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/authority_node/authority.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/authority_node/authority.rs @@ -2,9 +2,12 @@ use std::path::Path; use tracing::info; +use ockam::identity::storage::LmdbStorage; +use ockam::identity::Vault; use ockam::identity::{ - Identities, IdentitiesRepository, IdentitiesStorage, IdentitiesVault, IdentityAttributesReader, - IdentityAttributesWriter, SecureChannelListenerOptions, SecureChannels, TrustEveryonePolicy, + CredentialsIssuer, Identifier, Identities, IdentitiesRepository, IdentitiesStorage, + IdentityAttributesReader, IdentityAttributesWriter, SecureChannelListenerOptions, + SecureChannels, TrustEveryonePolicy, }; use ockam_abac::expr::{and, eq, ident, str}; use ockam_abac::{AbacAccessControl, Env}; @@ -12,10 +15,8 @@ use ockam_core::compat::sync::Arc; use ockam_core::errcode::{Kind, Origin}; use ockam_core::flow_control::FlowControlId; use ockam_core::{Error, Result, Worker}; -use ockam_identity::{CredentialsIssuer, IdentityIdentifier, LmdbStorage}; use ockam_node::{Context, WorkerBuilder}; use ockam_transport_tcp::{TcpListenerOptions, TcpTransport}; -use ockam_vault::Vault; use crate::authenticator::direct::EnrollmentTokenAuthenticator; use crate::bootstrapped_identities_store::BootstrapedIdentityStore; @@ -32,7 +33,7 @@ use crate::{actions, DefaultAddress}; // - an enrollment token issuer // - an enrollment token acceptor pub struct Authority { - identifier: IdentityIdentifier, + identifier: Identifier, secure_channels: Arc, } @@ -41,7 +42,7 @@ pub struct Authority { /// - start services impl Authority { /// Return the identity identifier for this authority - pub fn identifier(&self) -> IdentityIdentifier { + pub fn identifier(&self) -> Identifier { self.identifier.clone() } @@ -53,7 +54,7 @@ impl Authority { let vault = Self::create_secure_channels_vault(configuration).await?; let repository = Self::create_identities_repository(configuration).await?; let secure_channels = SecureChannels::builder() - .with_identities_vault(vault) + .with_vault(vault) .with_identities_repository(repository) .build(); @@ -186,8 +187,9 @@ impl Authority { ) -> Result<()> { // create and start a credential issuer worker let issuer = CredentialsIssuer::new( - self.identities(), - self.identifier(), + self.secure_channels.identities().repository(), + self.secure_channels.identities().credentials(), + &self.identifier, configuration.trust_context_identifier(), ) .await?; @@ -265,9 +267,7 @@ impl Authority { } /// Create an identity vault backed by a FileStorage - async fn create_secure_channels_vault( - configuration: &Configuration, - ) -> Result> { + async fn create_secure_channels_vault(configuration: &Configuration) -> Result { let vault_path = &configuration.vault_path; Self::create_ockam_directory_if_necessary(vault_path)?; let vault = Vault::create_with_persistent_storage_path(vault_path).await?; @@ -345,7 +345,6 @@ impl Authority { // the same project id as the authority let rule = if enroller_check == EnrollerOnly { and([ - eq([ident("resource.project_id"), ident("subject.project_id")]), // TODO: DEPRECATE - Removing PROJECT_ID attribute in favor of TRUST_CONTEXT_ID eq([ ident("resource.trust_context_id"), ident("subject.trust_context_id"), @@ -353,21 +352,14 @@ impl Authority { eq([ident("subject.ockam-role"), str("enroller")]), ]) } else { - and([ - eq([ident("resource.project_id"), ident("subject.project_id")]), // TODO: DEPRECATE - Removing PROJECT_ID attribute in favor of TRUST_CONTEXT_ID - eq([ - ident("resource.trust_context_id"), - ident("subject.trust_context_id"), - ]), + eq([ + ident("resource.trust_context_id"), + ident("subject.trust_context_id"), ]) }; let mut env = Env::new(); env.put("resource.id", str(address.as_str())); env.put("action.id", str(actions::HANDLE_MESSAGE.as_str())); - env.put( - "resource.project_id", - str(configuration.clone().project_identifier), - ); env.put( "resource.trust_context_id", str(configuration.clone().trust_context_identifier), diff --git a/implementations/rust/ockam/ockam_api/src/nodes/authority_node/configuration.rs b/implementations/rust/ockam/ockam_api/src/nodes/authority_node/configuration.rs index e90bcfb801a..ab8e1171fb8 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/authority_node/configuration.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/authority_node/configuration.rs @@ -1,7 +1,8 @@ use crate::bootstrapped_identities_store::PreTrustedIdentities; use crate::DefaultAddress; -use ockam::identity::credential::Timestamp; -use ockam::identity::{AttributesEntry, IdentityIdentifier}; + +use ockam::identity::utils::now; +use ockam::identity::{AttributesEntry, Identifier, TRUST_CONTEXT_ID}; use ockam_core::compat::collections::HashMap; use ockam_core::compat::fmt; use ockam_core::compat::fmt::{Display, Formatter}; @@ -13,7 +14,7 @@ use std::path::PathBuf; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Configuration { /// Authority identity or identity associated with the newly created node - pub identifier: IdentityIdentifier, + pub identifier: Identifier, /// path where the storage for identity attributes should be persisted pub storage_path: PathBuf, @@ -54,7 +55,7 @@ pub struct Configuration { /// Local and private functions for the authority configuration impl Configuration { /// Return the authority identity identifier - pub(crate) fn identifier(&self) -> IdentityIdentifier { + pub(crate) fn identifier(&self) -> Identifier { self.identifier.clone() } @@ -120,7 +121,7 @@ impl OktaConfiguration { /// as having all its attributes fully authenticated #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] pub struct TrustedIdentity { - identifier: IdentityIdentifier, + identifier: Identifier, attributes: HashMap, } @@ -135,44 +136,34 @@ impl Display for TrustedIdentity { } impl TrustedIdentity { - pub fn new( - identifier: &IdentityIdentifier, - attributes: &HashMap, - ) -> TrustedIdentity { + pub fn new(identifier: &Identifier, attributes: &HashMap) -> TrustedIdentity { TrustedIdentity { identifier: identifier.clone(), attributes: attributes.clone(), } } - pub fn identifier(&self) -> IdentityIdentifier { + pub fn identifier(&self) -> Identifier { self.identifier.clone() } pub fn attributes_entry( &self, project_identifier: String, - authority_identifier: &IdentityIdentifier, + authority_identifier: &Identifier, ) -> AttributesEntry { - let mut map: BTreeMap> = BTreeMap::new(); + let mut map: BTreeMap, Vec> = BTreeMap::new(); for (name, value) in self.attributes.clone().iter() { - map.insert(name.clone(), value.as_bytes().to_vec()); + map.insert(name.as_bytes().to_vec(), value.as_bytes().to_vec()); } - // Since the authority node is started for a given project - // add the project_id attribute to the trusted identities - map.insert( - "project_id".to_string(), // TODO: DEPRECATE - Removing PROJECT_ID attribute in favor of TRUST_CONTEXT_ID - project_identifier.as_bytes().to_vec(), - ); - map.insert( - "trust_context_id".to_string(), + TRUST_CONTEXT_ID.to_vec(), project_identifier.as_bytes().to_vec(), ); AttributesEntry::new( map, - Timestamp::now().unwrap(), + now().unwrap(), None, Some(authority_identifier.clone()), ) diff --git a/implementations/rust/ockam/ockam_api/src/nodes/connection/mod.rs b/implementations/rust/ockam/ockam_api/src/nodes/connection/mod.rs index 53856bff131..c1a8a4d0808 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/connection/mod.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/connection/mod.rs @@ -2,10 +2,10 @@ mod plain_tcp; mod project; mod secure; +use ockam::identity::Identifier; use ockam_core::errcode::{Kind, Origin}; use ockam_core::flow_control::FlowControlId; use ockam_core::{async_trait, route, Address, CowStr, Route, LOCAL}; -use ockam_identity::IdentityIdentifier; use ockam_multiaddr::proto::Service; use ockam_multiaddr::{Match, MultiAddr, Protocol}; use ockam_node::Context; @@ -22,7 +22,7 @@ pub struct Connection<'a> { pub addr: &'a MultiAddr, pub identity_name: Option>, pub credential_name: Option>, - pub authorized_identities: Option>, + pub authorized_identities: Option>, pub timeout: Option, pub add_default_consumers: bool, } @@ -46,7 +46,7 @@ impl<'a> Connection<'a> { self } - pub fn with_authorized_identity>>( + pub fn with_authorized_identity>>( mut self, authorized_identity: T, ) -> Self { diff --git a/implementations/rust/ockam/ockam_api/src/nodes/connection/secure.rs b/implementations/rust/ockam/ockam_api/src/nodes/connection/secure.rs index aa41c27040b..15172ed14c2 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/connection/secure.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/connection/secure.rs @@ -5,8 +5,8 @@ use crate::nodes::NodeManager; use crate::{local_multiaddr_to_route, try_address_to_multiaddr}; use ockam::compat::tokio::sync::RwLock; +use ockam::identity::Identifier; use ockam_core::{async_trait, route, Error}; -use ockam_identity::IdentityIdentifier; use ockam_multiaddr::proto::Secure; use ockam_multiaddr::{Match, Protocol}; use ockam_node::Context; @@ -19,7 +19,7 @@ pub(crate) struct SecureChannelInstantiator { node_manager: Arc>, timeout: Option, context: Arc, - authorized_identities: Option>, + authorized_identities: Option>, } impl SecureChannelInstantiator { @@ -27,7 +27,7 @@ impl SecureChannelInstantiator { context: Arc, node_manager: Arc>, timeout: Option, - authorized_identities: Option>, + authorized_identities: Option>, ) -> Self { Self { authorized_identities, diff --git a/implementations/rust/ockam/ockam_api/src/nodes/models/forwarder.rs b/implementations/rust/ockam/ockam_api/src/nodes/models/forwarder.rs index e2d53500245..4a0c44c889c 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/models/forwarder.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/models/forwarder.rs @@ -1,6 +1,6 @@ use minicbor::{Decode, Encode}; -use ockam::identity::IdentityIdentifier; +use ockam::identity::Identifier; use ockam::remote::RemoteForwarderInfo; use ockam::route; use ockam_core::flow_control::FlowControlId; @@ -27,7 +27,7 @@ pub struct CreateForwarder { /// An authorised identity for secure channels. /// Only set for non-project addresses as for projects the project's /// authorised identity will be used. - #[n(4)] authorized: Option, + #[n(4)] authorized: Option, } impl CreateForwarder { @@ -46,7 +46,7 @@ impl CreateForwarder { address: MultiAddr, alias: Option, at_rust_node: bool, - auth: Option, + auth: Option, ) -> Self { Self { #[cfg(feature = "tag")] @@ -70,7 +70,7 @@ impl CreateForwarder { self.at_rust_node } - pub fn authorized(&self) -> Option { + pub fn authorized(&self) -> Option { self.authorized.clone() } } diff --git a/implementations/rust/ockam/ockam_api/src/nodes/models/identity.rs b/implementations/rust/ockam/ockam_api/src/nodes/models/identity.rs deleted file mode 100644 index c4e891899a8..00000000000 --- a/implementations/rust/ockam/ockam_api/src/nodes/models/identity.rs +++ /dev/null @@ -1,48 +0,0 @@ -use minicbor::{Decode, Encode}; -use ockam_core::compat::borrow::Cow; -use serde::Serialize; - -use ockam_core::CowBytes; - -#[cfg(feature = "tag")] -use ockam_core::TypeTag; - -#[derive(Debug, Clone, Decode, Encode, Serialize)] -#[rustfmt::skip] -#[cbor(map)] -pub struct LongIdentityResponse<'a> { - #[cfg(feature = "tag")] - #[serde(skip)] - #[n(0)] tag: TypeTag<7961643>, - #[b(1)] pub identity: CowBytes<'a>, -} - -impl<'a> LongIdentityResponse<'a> { - pub fn new(identity: impl Into>) -> Self { - Self { - #[cfg(feature = "tag")] - tag: TypeTag, - identity: CowBytes(identity.into()), - } - } -} - -#[derive(Debug, Clone, Decode, Encode, Serialize)] -#[rustfmt::skip] -#[cbor(map)] -pub struct ShortIdentityResponse<'a> { - #[cfg(feature = "tag")] - #[serde(skip)] - #[n(0)] tag: TypeTag<5773131>, - #[b(1)] pub identity_id: Cow<'a, str>, -} - -impl<'a> ShortIdentityResponse<'a> { - pub fn new(identity_id: impl Into>) -> Self { - Self { - #[cfg(feature = "tag")] - tag: TypeTag, - identity_id: identity_id.into(), - } - } -} diff --git a/implementations/rust/ockam/ockam_api/src/nodes/models/mod.rs b/implementations/rust/ockam/ockam_api/src/nodes/models/mod.rs index 48ed9ce187e..dfc653d72f5 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/models/mod.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/models/mod.rs @@ -6,7 +6,6 @@ pub mod base; pub mod credentials; pub mod flow_controls; pub mod forwarder; -pub mod identity; pub mod policy; pub mod portal; pub mod secure_channel; diff --git a/implementations/rust/ockam/ockam_api/src/nodes/models/portal.rs b/implementations/rust/ockam/ockam_api/src/nodes/models/portal.rs index ce75751da66..4b5bfb446ea 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/models/portal.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/models/portal.rs @@ -4,12 +4,12 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::time::Duration; use minicbor::{Decode, Encode}; +use ockam::identity::Identifier; use ockam::route; use ockam_core::compat::borrow::Cow; #[cfg(feature = "tag")] use ockam_core::TypeTag; use ockam_core::{Address, CowStr, Route}; -use ockam_identity::IdentityIdentifier; use ockam_multiaddr::MultiAddr; use serde::{Deserialize, Serialize}; @@ -34,7 +34,7 @@ pub struct CreateInlet<'a> { /// An authorised identity for secure channels. /// Only set for non-project addresses as for projects the project's /// authorised identity will be used. - #[n(4)] authorized: Option, + #[n(4)] authorized: Option, /// A prefix route that will be applied before outlet_addr, and won't be used /// to monitor the state of the connection #[n(5)] prefix_route: Route, @@ -70,7 +70,7 @@ impl<'a> CreateInlet<'a> { to: MultiAddr, prefix_route: Route, suffix_route: Route, - auth: Option, + auth: Option, ) -> Self { Self { #[cfg(feature = "tag")] @@ -101,7 +101,7 @@ impl<'a> CreateInlet<'a> { &self.outlet_addr } - pub fn authorized(&self) -> Option { + pub fn authorized(&self) -> Option { self.authorized.clone() } diff --git a/implementations/rust/ockam/ockam_api/src/nodes/models/secure_channel.rs b/implementations/rust/ockam/ockam_api/src/nodes/models/secure_channel.rs index ff8bef2fd47..9033a048e50 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/models/secure_channel.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/models/secure_channel.rs @@ -3,7 +3,7 @@ use std::time::Duration; use minicbor::{Decode, Encode}; use serde::Serialize; -use ockam::identity::IdentityIdentifier; +use ockam::identity::Identifier; use ockam_core::flow_control::FlowControlId; #[cfg(feature = "tag")] use ockam_core::TypeTag; @@ -41,7 +41,7 @@ pub struct CreateSecureChannelRequest { impl CreateSecureChannelRequest { pub fn new( addr: &MultiAddr, - authorized_identifiers: Option>, + authorized_identifiers: Option>, credential_exchange_mode: CredentialExchangeMode, identity_name: Option, credential_name: Option, @@ -107,7 +107,7 @@ pub struct CreateSecureChannelListenerRequest { impl CreateSecureChannelListenerRequest { pub fn new( addr: &Address, - authorized_identifiers: Option>, + authorized_identifiers: Option>, vault: Option, identity: Option, ) -> Self { diff --git a/implementations/rust/ockam/ockam_api/src/nodes/registry.rs b/implementations/rust/ockam/ockam_api/src/nodes/registry.rs index d89823d7311..ff0004c5daf 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/registry.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/registry.rs @@ -1,9 +1,9 @@ use crate::nodes::service::Alias; -use ockam::identity::IdentityIdentifier; +use ockam::identity::Identifier; +use ockam::identity::{SecureChannel, SecureChannelListener}; use ockam::remote::RemoteForwarderInfo; use ockam_core::compat::collections::BTreeMap; use ockam_core::{Address, Route}; -use ockam_identity::{SecureChannel, SecureChannelListener}; use std::fmt::Display; use std::net::SocketAddr; @@ -23,7 +23,7 @@ impl SecureChannelRegistry { &mut self, route: Route, sc: SecureChannel, - authorized_identifiers: Option>, + authorized_identifiers: Option>, ) { self.channels .push(SecureChannelInfo::new(route, sc, authorized_identifiers)) @@ -43,14 +43,14 @@ pub struct SecureChannelInfo { // Target route of the channel route: Route, sc: SecureChannel, - authorized_identifiers: Option>, + authorized_identifiers: Option>, } impl SecureChannelInfo { pub fn new( route: Route, sc: SecureChannel, - authorized_identifiers: Option>, + authorized_identifiers: Option>, ) -> Self { Self { route, @@ -67,7 +67,7 @@ impl SecureChannelInfo { &self.sc } - pub fn authorized_identifiers(&self) -> Option<&Vec> { + pub fn authorized_identifiers(&self) -> Option<&Vec> { self.authorized_identifiers.as_ref() } } diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service.rs b/implementations/rust/ockam/ockam_api/src/nodes/service.rs index b11aef1600c..a5543d200c6 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service.rs @@ -8,23 +8,24 @@ use std::path::PathBuf; use minicbor::{Decoder, Encode}; pub use node_identities::*; +use ockam::identity::TrustContext; +use ockam::identity::Vault; use ockam::identity::{ Credentials, CredentialsServer, CredentialsServerModule, Identities, IdentitiesRepository, - IdentitiesVault, IdentityAttributesReader, IdentityAttributesWriter, + IdentityAttributesReader, IdentityAttributesWriter, }; -use ockam::identity::{IdentityIdentifier, SecureChannels}; +use ockam::identity::{Identifier, SecureChannels}; use ockam::{ Address, Context, ForwardingService, ForwardingServiceOptions, Result, Routed, TcpTransport, Worker, }; -use ockam_abac::expr::{and, eq, ident, str}; +use ockam_abac::expr::{eq, ident, str}; use ockam_abac::{Action, Env, Expr, PolicyAccessControl, PolicyStorage, Resource}; use ockam_core::api::{Error, Method, Request, Response, ResponseBuilder, Status}; use ockam_core::compat::{string::String, sync::Arc}; use ockam_core::flow_control::FlowControlId; use ockam_core::IncomingAccessControl; use ockam_core::{AllowAll, AsyncTryClone}; -use ockam_identity::TrustContext; use ockam_multiaddr::MultiAddr; use ockam_node::compat::asynchronous::RwLock; @@ -89,10 +90,10 @@ pub struct NodeManager { node_name: String, api_transport_flow_control_id: FlowControlId, pub(crate) tcp_transport: TcpTransport, - pub(crate) controller_identity_id: IdentityIdentifier, + pub(crate) controller_identity_id: Identifier, skip_defaults: bool, enable_credential_checks: bool, - identifier: IdentityIdentifier, + identifier: Identifier, pub(crate) secure_channels: Arc, trust_context: Option, pub(crate) registry: Registry, @@ -101,7 +102,7 @@ pub struct NodeManager { } impl NodeManager { - pub(super) fn identifier(&self) -> IdentityIdentifier { + pub(super) fn identifier(&self) -> Identifier { self.identifier.clone() } @@ -121,7 +122,7 @@ impl NodeManager { self.identities_repository().as_attributes_reader() } - pub(super) fn credentials(&self) -> Arc { + pub(super) fn credentials(&self) -> Arc { self.identities().credentials() } @@ -129,8 +130,8 @@ impl NodeManager { Arc::new(CredentialsServerModule::new(self.credentials())) } - pub(super) fn secure_channels_vault(&self) -> Arc { - self.secure_channels.vault().clone() + pub(super) fn secure_channels_vault(&self) -> Vault { + self.secure_channels.identities().vault() } pub(super) fn list_outlets(&self) -> OutletList { @@ -191,7 +192,6 @@ impl NodeManager { let mut env = Env::new(); env.put("resource.id", str(r.as_str())); env.put("action.id", str(a.as_str())); - env.put("resource.project_id", str(tcid.to_string())); env.put("resource.trust_context_id", str(tcid)); // Check if a policy exists for (resource, action) and if not, then @@ -199,17 +199,9 @@ impl NodeManager { if self.policies.get_policy(r, a).await?.is_none() { let fallback = match custom_default { Some(e) => e.clone(), - None => and([ - eq([ident("resource.project_id"), ident("subject.project_id")]), // TODO: DEPRECATE - Removing PROJECT_ID attribute in favor of TRUST_CONTEXT_ID - /* - * TODO: replace the project_id check for trust_context_id. For now the - * existing authority deployed doesn't know about trust_context so this is to - * be done after updating deployed authorities. - eq([ - ident("resource.trust_context_id"), - ident("subject.trust_context_id"), - ]), - */ + None => eq([ + ident("resource.trust_context_id"), + ident("subject.trust_context_id"), ]), }; self.policies.set_policy(r, a, &fallback).await? @@ -325,7 +317,7 @@ impl NodeManager { //TODO: fix this. Either don't require it to be a bootstrappedidentitystore (and use the //trait instead), or pass it from the general_options always. - let vault: Arc = node_state.config().vault().await?; + let vault: Vault = node_state.config().vault().await?; let identities_repository: Arc = Arc::new(match general_options.pre_trusted_identities { None => BootstrapedIdentityStore::new( @@ -337,7 +329,7 @@ impl NodeManager { debug!("create the secure channels service"); let secure_channels = SecureChannels::builder() - .with_identities_vault(vault) + .with_vault(vault) .with_identities_repository(identities_repository.clone()) .build(); @@ -486,10 +478,7 @@ impl NodeManager { Ok(connection_instance) } - pub(crate) async fn resolve_project( - &self, - name: &str, - ) -> Result<(MultiAddr, IdentityIdentifier)> { + pub(crate) async fn resolve_project(&self, name: &str) -> Result<(MultiAddr, Identifier)> { let projects = ProjectLookup::from_state(self.cli_state.projects.list()?) .await .map_err(|e| ApiError::core(format!("Cannot load projects: {:?}", e)))?; diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service/credentials.rs b/implementations/rust/ockam/ockam_api/src/nodes/service/credentials.rs index 60f505b18dd..35c7f53bde1 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service/credentials.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service/credentials.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use either::Either; use minicbor::Decoder; -use ockam::identity::Credential; +use ockam::identity::models::CredentialAndPurposeKey; use ockam::Result; use ockam_core::api::{Error, Request, Response, ResponseBuilder}; use ockam_multiaddr::MultiAddr; @@ -22,7 +22,7 @@ impl NodeManagerWorker { req: &Request, dec: &mut Decoder<'_>, ctx: &Context, - ) -> Result, ResponseBuilder>> { + ) -> Result, ResponseBuilder>> { let node_manager = self.node_manager.write().await; let request: GetCredentialRequest = dec.decode()?; diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service/forwarder.rs b/implementations/rust/ockam/ockam_api/src/nodes/service/forwarder.rs index 75247b8b482..aeb2e79bcb4 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service/forwarder.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service/forwarder.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use minicbor::Decoder; use ockam::compat::asynchronous::RwLock; use ockam::compat::sync::Mutex; -use ockam::identity::IdentityIdentifier; +use ockam::identity::Identifier; use ockam::remote::{RemoteForwarder, RemoteForwarderOptions}; use ockam::Result; use ockam_core::api::{Error, Request, Response, ResponseBuilder}; @@ -216,7 +216,7 @@ fn replacer( connection_instance: ConnectionInstance, addr: MultiAddr, alias: Option, - auth: Option, + auth: Option, ) -> Replacer { let connection_instance_arc = Arc::new(Mutex::new(connection_instance)); Box::new(move |prev_route| { diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service/node_identities.rs b/implementations/rust/ockam/ockam_api/src/nodes/service/node_identities.rs index e49521d589b..88971f3e808 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service/node_identities.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service/node_identities.rs @@ -1,8 +1,9 @@ use ockam::compat::sync::Arc; -use ockam::identity::{Identities, IdentitiesCreation, IdentitiesKeys}; -use ockam::identity::{IdentitiesVault, Identity}; +use ockam::identity::Identity; +use ockam::identity::Vault; +use ockam::identity::{Identifier, IdentitiesRepository}; +use ockam::identity::{Identities, IdentitiesCreation}; use ockam::Result; -use ockam_identity::{IdentitiesRepository, IdentityIdentifier}; use crate::cli_state::traits::StateDirTrait; use crate::cli_state::CliState; @@ -22,7 +23,7 @@ impl NodeIdentities { } } - pub(super) fn identities_vault(&self) -> Arc { + pub(super) fn identities_vault(&self) -> Vault { self.identities.vault() } @@ -33,14 +34,31 @@ impl NodeIdentities { /// Return an identity if it has been created with that name before pub(crate) async fn get_identity(&self, identity_name: String) -> Result> { let repository = self.identities_repository(); - if let Ok(idt_state) = self.cli_state.identities.get(identity_name.as_str()) { - Ok(repository.get_identity(&idt_state.identifier()).await.ok()) + + let idt_state = if let Ok(idt_state) = self.cli_state.identities.get(identity_name.as_str()) + { + idt_state } else { - Ok(None) - } + return Ok(None); + }; + + let change_history = + if let Ok(change_history) = repository.get_identity(&idt_state.identifier()).await { + change_history + } else { + return Ok(None); + }; + + let identity = self + .identities + .identities_creation() + .import_from_change_history(Some(&idt_state.identifier()), change_history) + .await?; + + Ok(Some(identity)) } - pub(crate) async fn get_identifier(&self, identity_name: String) -> Result { + pub(crate) async fn get_identifier(&self, identity_name: String) -> Result { let identity_state = self.cli_state.identities.get(identity_name.as_str())?; Ok(identity_state.identifier()) } @@ -50,14 +68,6 @@ impl NodeIdentities { Ok(Arc::new(self.get_identities_creation(None).await?)) } - /// Return an identities keys service backed up by the default vault - pub(crate) async fn get_default_identities_keys(&self) -> Result> { - Ok(Identities::builder() - .with_identities_vault(self.identities_vault()) - .build() - .identities_keys()) - } - /// Return an identities service, possibly backed by a specific vault pub(crate) async fn get_identities( &self, @@ -66,7 +76,7 @@ impl NodeIdentities { let vault = self.get_identities_vault(vault_name).await?; let repository = self.cli_state.identities.identities_repository().await?; Ok(Identities::builder() - .with_identities_vault(vault) + .with_vault(vault) .with_identities_repository(repository) .build()) } @@ -77,14 +87,15 @@ impl NodeIdentities { vault_name: Option, ) -> Result { let vault = self.get_identities_vault(vault_name).await?; - Ok(IdentitiesCreation::new(self.identities_repository(), vault)) + Ok(IdentitiesCreation::new( + self.identities_repository(), + vault.identity_vault, + vault.verifying_vault, + )) } /// Return either the default vault or a specific one - pub(crate) async fn get_identities_vault( - &self, - vault_name: Option, - ) -> Result> { + pub(crate) async fn get_identities_vault(&self, vault_name: Option) -> Result { if let Some(vault) = vault_name { let existing_vault = self.cli_state.vaults.get(vault.as_str())?.get().await?; Ok(existing_vault) @@ -92,14 +103,4 @@ impl NodeIdentities { Ok(self.identities_vault()) } } - - /// Return a service to perform key operations - pub(crate) async fn get_identities_keys( - &self, - vault_name: Option, - ) -> Result> { - Ok(Arc::new(IdentitiesKeys::new( - self.get_identities_vault(vault_name).await?, - ))) - } } diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service/node_services.rs b/implementations/rust/ockam/ockam_api/src/nodes/service/node_services.rs index dba3977559c..77ba84e0db3 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service/node_services.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service/node_services.rs @@ -2,6 +2,7 @@ use std::net::IpAddr; use minicbor::Decoder; +use ockam::identity::{identities, AuthorityService, CredentialsIssuer, TrustContext}; use ockam::{Address, Context, Result}; use ockam_abac::expr::{and, eq, ident, str}; use ockam_abac::{Action, Env, Expr, PolicyAccessControl, Resource}; @@ -9,7 +10,6 @@ use ockam_core::api::{Error, Request, Response, ResponseBuilder}; use ockam_core::compat::net::SocketAddr; use ockam_core::compat::sync::Arc; use ockam_core::{route, IncomingAccessControl}; -use ockam_identity::{identities, AuthorityService, CredentialsIssuer, TrustContext}; use ockam_multiaddr::MultiAddr; use ockam_node::WorkerBuilder; @@ -193,7 +193,7 @@ impl NodeManager { let mut env = Env::new(); env.put("resource.id", str(r.as_str())); env.put("action.id", str(a.as_str())); - env.put("resource.project_id", str(project_id)); + env.put("resource.trust_context_id", str(project_id)); // Check if a policy exists for (resource, action) and if not, then // create a default entry: if self.policies.get_policy(r, a).await?.is_none() { @@ -219,11 +219,20 @@ impl NodeManager { } let action = actions::HANDLE_MESSAGE; let resource = Resource::new(&addr.to_string()); - let rule = eq([ident("resource.project_id"), ident("subject.project_id")]); + let rule = eq([ + ident("resource.trust_context_id"), + ident("subject.trust_context_id"), + ]); let abac = self .build_access_control(&resource, &action, project.as_str(), &rule) .await?; - let issuer = CredentialsIssuer::new(self.identities(), self.identifier(), project).await?; + let issuer = CredentialsIssuer::new( + self.identities().repository(), + self.identities().credentials(), + &self.identifier, + project, + ) + .await?; WorkerBuilder::new(issuer) .with_address(addr.clone()) .with_incoming_access_control_arc(abac) @@ -305,7 +314,10 @@ impl NodeManager { self.attributes_writer(), ); let rule = and([ - eq([ident("resource.project_id"), ident("subject.project_id")]), + eq([ + ident("resource.trust_context_id"), + ident("subject.trust_context_id"), + ]), eq([ident("subject.ockam-role"), str("enroller")]), ]); let abac = self @@ -538,15 +550,14 @@ impl NodeManagerWorker { &hex::decode(encoded_identity).map_err(|_| ApiError::core("Unable to decode trust context's public identity when starting credential service."))?; let i = identities() .identities_creation() - .decode_identity(decoded_identity) + .import(None, decoded_identity) .await?; let trust_context = TrustContext::new( encoded_identity.to_string(), Some(AuthorityService::new( - node_manager.identities().identities_reader(), - node_manager.credentials(), - i.identifier(), + node_manager.identities().credentials(), + i.identifier().clone(), None, )), ); diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service/portals.rs b/implementations/rust/ockam/ockam_api/src/nodes/service/portals.rs index 47395b328e9..1e0b4e6a2df 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service/portals.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service/portals.rs @@ -5,7 +5,7 @@ use std::time::Duration; use minicbor::Decoder; use ockam::compat::tokio::time::timeout; -use ockam::identity::IdentityIdentifier; +use ockam::identity::Identifier; use ockam::{Address, AsyncTryClone, Result}; use ockam_abac::Resource; use ockam_core::api::{Error, Id, Request, Response, ResponseBuilder}; @@ -541,7 +541,7 @@ fn replacer( addr: MultiAddr, prefix_route: Route, suffix_route: Route, - auth: Option, + auth: Option, access: Arc, ctx: Arc, ) -> Replacer { diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service/secure_channel.rs b/implementations/rust/ockam/ockam_api/src/nodes/service/secure_channel.rs index 7318438a479..fbc6ab4b9d1 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service/secure_channel.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service/secure_channel.rs @@ -3,16 +3,17 @@ use std::time::Duration; use minicbor::Decoder; +use ockam::identity::models::CredentialAndPurposeKey; use ockam::identity::TrustEveryonePolicy; +use ockam::identity::Vault; use ockam::identity::{ - Identities, IdentitiesVault, IdentityIdentifier, SecureChannelListenerOptions, - SecureChannelOptions, SecureChannels, TrustMultiIdentifiersPolicy, + Identifier, Identities, SecureChannelListenerOptions, SecureChannelOptions, SecureChannels, + TrustMultiIdentifiersPolicy, }; +use ockam::identity::{SecureChannel, SecureChannelListener}; use ockam::{Address, Result, Route}; use ockam_core::api::{Error, Request, Response, ResponseBuilder}; use ockam_core::compat::sync::Arc; -use ockam_identity::Credential; -use ockam_identity::{SecureChannel, SecureChannelListener}; use ockam_multiaddr::MultiAddr; use ockam_node::Context; @@ -37,12 +38,12 @@ use super::NodeManagerWorker; impl NodeManager { pub(crate) async fn create_secure_channel_internal( &mut self, - identifier: &IdentityIdentifier, + identifier: &Identifier, ctx: &Context, sc_route: Route, - authorized_identifiers: Option>, + authorized_identifiers: Option>, timeout: Option, - credential: Option, + credential: Option, ) -> Result { debug!(%sc_route, "Creating secure channel"); let options = SecureChannelOptions::new(); @@ -87,7 +88,7 @@ impl NodeManager { pub(crate) async fn create_secure_channel_impl( &mut self, sc_route: Route, - authorized_identifiers: Option>, + authorized_identifiers: Option>, credential_exchange_mode: CredentialExchangeMode, timeout: Option, identity_name: Option, @@ -154,7 +155,7 @@ impl NodeManager { pub(super) async fn create_secure_channel_listener_impl( &mut self, address: Address, - authorized_identifiers: Option>, + authorized_identifiers: Option>, vault_name: Option, identity_name: Option, ctx: &Context, @@ -221,7 +222,7 @@ impl NodeManager { let identities = self.get_identities(vault_name).await?; let registry = self.secure_channels.secure_channel_registry(); Ok(SecureChannels::builder() - .with_identities_vault(vault) + .with_vault(vault) .with_identities(identities) .with_secure_channels_registry(registry) .build()) @@ -231,10 +232,7 @@ impl NodeManager { NodeIdentities::new(self.identities(), self.cli_state.clone()) } - pub(crate) async fn get_identifier( - &self, - identity_name: Option, - ) -> Result { + pub(crate) async fn get_identifier(&self, identity_name: Option) -> Result { if let Some(name) = identity_name { self.node_identities().get_identifier(name.clone()).await } else { @@ -246,10 +244,7 @@ impl NodeManager { self.node_identities().get_identities(vault_name).await } - async fn get_secure_channels_vault( - &mut self, - vault_name: Option, - ) -> Result> { + async fn get_secure_channels_vault(&mut self, vault_name: Option) -> Result { if let Some(vault) = vault_name { let existing_vault = self.cli_state.vaults.get(vault.as_str())?.get().await?; Ok(existing_vault) @@ -333,8 +328,8 @@ impl NodeManagerWorker { Some(ids) => { let ids = ids .into_iter() - .map(IdentityIdentifier::try_from) - .collect::>>()?; + .map(Identifier::try_from) + .collect::>>()?; Some(ids) } @@ -444,8 +439,8 @@ impl NodeManagerWorker { Some(ids) => { let ids = ids .into_iter() - .map(IdentityIdentifier::try_from) - .collect::>>()?; + .map(Identifier::try_from) + .collect::>>()?; Some(ids) } diff --git a/implementations/rust/ockam/ockam_api/src/okta/mod.rs b/implementations/rust/ockam/ockam_api/src/okta/mod.rs index 68f901ba612..64693281ab7 100644 --- a/implementations/rust/ockam/ockam_api/src/okta/mod.rs +++ b/implementations/rust/ockam/ockam_api/src/okta/mod.rs @@ -1,15 +1,15 @@ use crate::error::ApiError; use core::str; use minicbor::Decoder; -use ockam::identity::credential::Timestamp; +use ockam::identity::utils::now; +use ockam::identity::TRUST_CONTEXT_ID; use ockam::identity::{ - AttributesEntry, IdentityAttributesWriter, IdentityIdentifier, IdentitySecureChannelLocalInfo, + AttributesEntry, Identifier, IdentityAttributesWriter, IdentitySecureChannelLocalInfo, }; use ockam_core::api; use ockam_core::api::{Method, Request, Response}; use ockam_core::compat::sync::Arc; use ockam_core::{self, Result, Routed, Worker}; -use ockam_identity::{LEGACY_ID, TRUST_CONTEXT_ID}; use ockam_node::Context; use reqwest::StatusCode; use std::collections::HashMap; @@ -60,7 +60,7 @@ impl Server { }) } - async fn on_request(&mut self, from: &IdentityIdentifier, data: &[u8]) -> Result> { + async fn on_request(&mut self, from: &Identifier, data: &[u8]) -> Result> { let mut dec = Decoder::new(data); let req: Request = dec.decode()?; @@ -90,19 +90,16 @@ impl Server { let entry = AttributesEntry::new( attrs .into_iter() - .map(|(k, v)| (k, v.as_bytes().to_vec())) + .map(|(k, v)| (k.as_bytes().to_vec(), v.as_bytes().to_vec())) .chain( - [ - (LEGACY_ID.to_owned(), self.project.as_bytes().to_vec()), - ( - TRUST_CONTEXT_ID.to_owned(), - self.project.as_bytes().to_vec(), - ), - ] + [( + TRUST_CONTEXT_ID.to_owned(), + self.project.as_bytes().to_vec(), + )] .into_iter(), ) .collect(), - Timestamp::now().unwrap(), + now().unwrap(), None, None, ); diff --git a/implementations/rust/ockam/ockam_api/src/schema.cddl b/implementations/rust/ockam/ockam_api/src/schema.cddl index 1f5a36fdf0e..14954c54f4d 100644 --- a/implementations/rust/ockam/ockam_api/src/schema.cddl +++ b/implementations/rust/ockam/ockam_api/src/schema.cddl @@ -161,9 +161,9 @@ identity = bytes current_identity = bytes known_identity = bytes signer_identity = bytes -identity_id = text +identity_id = bytes signature = bytes -peer_identity_id = text +peer_identity_id = bytes data = bytes verified = bool diff --git a/implementations/rust/ockam/ockam_api/src/util.rs b/implementations/rust/ockam/ockam_api/src/util.rs index 24833279af1..db91deacf91 100644 --- a/implementations/rust/ockam/ockam_api/src/util.rs +++ b/implementations/rust/ockam/ockam_api/src/util.rs @@ -345,18 +345,17 @@ pub fn local_worker(code: &Code) -> Result { #[cfg(test)] pub mod test_utils { - use ockam::identity::SecureChannels; + use ockam::identity::storage::InMemoryStorage; + use ockam::identity::utils::AttributesBuilder; + use ockam::identity::{Identifier, Identity, MAX_CREDENTIAL_VALIDITY}; + use ockam::identity::{SecureChannels, PROJECT_MEMBER_SCHEMA, TRUST_CONTEXT_ID}; use ockam::Result; use ockam_core::compat::sync::Arc; use ockam_core::flow_control::FlowControls; use ockam_core::AsyncTryClone; - use ockam_identity::{ - CredentialData, Credentials, Identity, IdentityIdentifier, InMemoryStorage, KeyAttributes, - }; use ockam_node::compat::asynchronous::RwLock; - use ockam_node::{Context, InMemoryKeyValueStorage}; + use ockam_node::Context; use ockam_transport_tcp::TcpTransport; - use ockam_vault::{Secret, SecretAttributes}; use crate::cli_state::{traits::*, CliState, IdentityConfig, NodeConfig, VaultConfig}; use crate::config::cli::{CredentialRetrieverConfig, TrustAuthorityConfig, TrustContextConfig}; @@ -375,7 +374,7 @@ pub mod test_utils { pub node_manager: Arc>, pub tcp: TcpTransport, pub secure_channels: Arc, - pub identifier: IdentityIdentifier, + pub identifier: Identifier, } impl Drop for NodeManagerHandle { @@ -410,29 +409,35 @@ pub mod test_utils { // export the identity and credentials,then import in the LMDB after secure-channel // has been re-created let secure_channels = SecureChannels::builder() - .with_identities_vault(vault) + .with_vault(vault) .with_identities_repository(cli_state.identities.identities_repository().await?) .with_identities_storage(InMemoryStorage::create()) - .with_vault_storage(InMemoryKeyValueStorage::create()) .build(); - let identity = create_identity_zero(&secure_channels).await?; + let identity = create_random_identity(&secure_channels).await?; + + let exported_identity = identity.export()?; + + let attributes = AttributesBuilder::with_schema(PROJECT_MEMBER_SCHEMA) + .with_attribute(TRUST_CONTEXT_ID.to_vec(), b"test_trust_context_id".to_vec()) + .build(); let credential = secure_channels .identities() + .credentials() + .credentials_creation() .issue_credential( - &identity.identifier(), - CredentialData::builder(identity.identifier(), identity.identifier()) - .with_attribute("trust_context_id", b"test_trust_context_id") - .build() - .unwrap(), + identity.identifier(), + identity.identifier(), + attributes, + MAX_CREDENTIAL_VALIDITY, ) .await .unwrap(); drop(secure_channels); - let config = IdentityConfig::new(&identity.identifier()).await; + let config = IdentityConfig::new(identity.identifier()).await; cli_state.identities.create(&identity_name, config).unwrap(); let node_name = hex::encode(rand::random::<[u8; 4]>()); @@ -449,8 +454,10 @@ pub mod test_utils { NodeManagerTrustOptions::new(Some(TrustContextConfig::new( "test_trust_context".to_string(), Some(TrustAuthorityConfig::new( - identity.export_hex().unwrap(), - Some(CredentialRetrieverConfig::FromMemory(credential)), + hex::encode(&identity.export().unwrap()), + Some(CredentialRetrieverConfig::FromMemory(minicbor::to_vec( + &credential, + )?)), )), ))), ) @@ -460,8 +467,12 @@ pub mod test_utils { let node_manager = node_manager_worker.inner().clone(); let secure_channels = node_manager.read().await.secure_channels.clone(); - // since we re-created secure-channels, we rewrite the identity in the LMDB storage - create_identity_zero(&secure_channels).await?; + // Import identity, since it doesn't exist in the LMDB storage + let _ = secure_channels + .identities() + .identities_creation() + .import(Some(identity.identifier()), &exported_identity) + .await?; context .start_worker(NODEMANAGER_ADDR, node_manager_worker) @@ -472,25 +483,18 @@ pub mod test_utils { node_manager, tcp: tcp.async_try_clone().await?, secure_channels: secure_channels.clone(), - identifier: identity.identifier(), + identifier: identity.identifier().clone(), }) } - async fn create_identity_zero(secure_channels: &Arc) -> Result { - let identity_key_id = secure_channels - .vault() - .import_ephemeral_secret(Secret::new([0u8; 32].to_vec()), SecretAttributes::Ed25519) - .await?; - + async fn create_random_identity(secure_channels: &Arc) -> Result { let identity = secure_channels .identities() .identities_creation() - .create_identity_with_existing_key( - &identity_key_id, - KeyAttributes::new("OCKAM_RK".to_string(), SecretAttributes::Ed25519), - ) + .create_identity() .await .unwrap(); + Ok(identity) } } diff --git a/implementations/rust/ockam/ockam_api/src/verifier.rs b/implementations/rust/ockam/ockam_api/src/verifier.rs index 94485d5dea0..70e0a4220df 100644 --- a/implementations/rust/ockam/ockam_api/src/verifier.rs +++ b/implementations/rust/ockam/ockam_api/src/verifier.rs @@ -1,18 +1,17 @@ pub mod types; +use crate::verifier::types::{VerifyRequest, VerifyResponse}; use either::Either; use minicbor::Decoder; -use ockam::identity::credential::{Credential, CredentialData, Verified}; -use ockam::identity::Identities; -use ockam_core::api::{self, Id, ResponseBuilder}; -use ockam_core::api::{Error, Method, Request, Response}; +use ockam::identity::models::{CredentialAndPurposeKey, PurposeKeyAttestationData, VersionedData}; +use ockam::identity::{CredentialAndPurposeKeyData, Identities}; +use ockam_core::api::{Error, Request, Response, ResponseBuilder}; +use ockam_core::api::{Id, Method}; use ockam_core::compat::sync::Arc; -use ockam_core::{self, Result, Routed, Worker}; +use ockam_core::{self, api, Result, Routed, Worker}; use ockam_node::Context; use tracing::trace; -use self::types::{VerifyRequest, VerifyResponse}; - pub struct Verifier { identities: Arc, } @@ -57,13 +56,16 @@ impl Verifier { Some(Method::Post) => match req.path_segments::<2>().as_slice() { ["verify"] => { let vr: VerifyRequest = dec.decode()?; - let cr: Credential = minicbor::decode(vr.credential())?; + let cr: CredentialAndPurposeKey = minicbor::decode(vr.credential())?; match self.verify(req.id(), &vr, &cr).await { Ok(Either::Left(err)) => err.to_vec()?, Ok(Either::Right(dat)) => { - let exp = dat.expires_at(); + let exp = dat.credential_data.expires_at; Response::ok(req.id()) - .body(VerifyResponse::new(dat.into_attributes(), exp)) + .body(VerifyResponse::new( + dat.credential_data.subject_attributes, + exp, + )) .to_vec()? } Err(err) => { @@ -85,15 +87,20 @@ impl Verifier { &self, id: Id, req: &'a VerifyRequest<'a>, - cre: &Credential, - ) -> Result, CredentialData>> { - let data = CredentialData::try_from(cre.data.as_slice())?; + cre: &CredentialAndPurposeKey, + ) -> Result, CredentialAndPurposeKeyData>> { + let versioned_data: VersionedData = + minicbor::decode(cre.purpose_key_attestation.data.as_slice())?; + let data: PurposeKeyAttestationData = minicbor::decode(&versioned_data.data)?; - let authority = if let Some(ident) = req.authority(data.unverified_issuer()) { + let authority = if let Some(ident) = req.authority(&data.subject) { + // FIXME: Put Authority separately to avoid decoding the data manually here self.identities .identities_creation() - .decode_identity(ident) + .import(Some(&data.subject), ident) .await? + .identifier() + .clone() } else { let err = Error::new("/verify").with_message("unauthorised issuer"); return Ok(Either::Left(Response::unauthorized(id).body(err))); @@ -102,7 +109,8 @@ impl Verifier { let data = match self .identities .credentials() - .verify_credential(req.subject(), &[authority], cre.clone()) + .credentials_verification() + .verify_credential(Some(req.subject()), &[authority], cre) .await { Ok(data) => data, diff --git a/implementations/rust/ockam/ockam_api/src/verifier/types.rs b/implementations/rust/ockam/ockam_api/src/verifier/types.rs index 434c8277a18..d2d0a46b59f 100644 --- a/implementations/rust/ockam/ockam_api/src/verifier/types.rs +++ b/implementations/rust/ockam/ockam_api/src/verifier/types.rs @@ -1,6 +1,6 @@ use minicbor::{Decode, Encode}; -use ockam::identity::credential::{Attributes, Timestamp}; -use ockam::identity::IdentityIdentifier; +use ockam::identity::Identifier; +use ockam::identity::{Attributes, TimestampInSeconds}; use ockam_core::compat::borrow::Cow; use ockam_core::CowBytes; use std::collections::BTreeMap; @@ -15,8 +15,8 @@ pub struct VerifyRequest<'a> { #[cfg(feature = "tag")] #[n(0)] tag: TypeTag<4592146>, #[b(1)] cred: CowBytes<'a>, - #[n(2)] subj: IdentityIdentifier, - #[b(3)] auth: BTreeMap> + #[n(2)] subj: Identifier, + #[b(3)] auth: BTreeMap> } #[derive(Debug, Decode, Encode)] @@ -26,11 +26,11 @@ pub struct VerifyResponse { #[cfg(feature = "tag")] #[n(0)] tag: TypeTag<6845123>, #[b(1)] attrs: Attributes, - #[n(2)] expires: Timestamp + #[n(2)] expires: TimestampInSeconds } impl<'a> VerifyRequest<'a> { - pub fn new>>(cred: C, subj: IdentityIdentifier) -> Self { + pub fn new>>(cred: C, subj: Identifier) -> Self { Self { #[cfg(feature = "tag")] tag: TypeTag, @@ -40,7 +40,7 @@ impl<'a> VerifyRequest<'a> { } } - pub fn with_authority(mut self, id: IdentityIdentifier, identity: T) -> Self + pub fn with_authority(mut self, id: Identifier, identity: T) -> Self where T: Into>, { @@ -52,21 +52,21 @@ impl<'a> VerifyRequest<'a> { &self.cred } - pub fn subject(&self) -> &IdentityIdentifier { + pub fn subject(&self) -> &Identifier { &self.subj } - pub fn authorities(&self) -> &BTreeMap> { + pub fn authorities(&self) -> &BTreeMap> { &self.auth } - pub fn authority(&self, id: &IdentityIdentifier) -> Option<&CowBytes<'a>> { + pub fn authority(&self, id: &Identifier) -> Option<&CowBytes<'a>> { self.auth.get(id) } } impl VerifyResponse { - pub fn new(attrs: Attributes, expires: Timestamp) -> Self { + pub fn new(attrs: Attributes, expires: TimestampInSeconds) -> Self { Self { #[cfg(feature = "tag")] tag: TypeTag, @@ -79,7 +79,7 @@ impl VerifyResponse { &self.attrs } - pub fn expires_at(&self) -> Timestamp { + pub fn expires_at(&self) -> TimestampInSeconds { self.expires } } diff --git a/implementations/rust/ockam/ockam_api/static/controller.id b/implementations/rust/ockam/ockam_api/static/controller.id index 0fd3da7cd0a..3b72ab3c770 100644 --- a/implementations/rust/ockam/ockam_api/static/controller.id +++ b/implementations/rust/ockam/ockam_api/static/controller.id @@ -1 +1 @@ -Pd14c9e0a57cd41382c69092987cc7ce912a8f7ec7de26cd44115a42561b1a4de +Id14c9e0a57cd41382c69092987cc7ce912a8f7ec diff --git a/implementations/rust/ockam/ockam_api/tests/auth_smoke.rs b/implementations/rust/ockam/ockam_api/tests/auth_smoke.rs index d24f8e9b0cb..e9521f5b3b7 100644 --- a/implementations/rust/ockam/ockam_api/tests/auth_smoke.rs +++ b/implementations/rust/ockam/ockam_api/tests/auth_smoke.rs @@ -8,8 +8,8 @@ use std::sync::Arc; #[ockam_macros::test] async fn auth_smoke(ctx: &mut Context) -> Result<()> { let s = PreTrustedIdentities::new_from_string( - r#"{"P624ed0b2e5a2be82e267ead6b3279f683616b66de9537a23e45343c95cbb357a":{"attr":"value"}, - "P624ed0b2e5a2be82e267ead6b3279f683616b66de9537a23e45343c95cbb357b":{"attr":"value2"} + r#"{"I124ed0b2e5a2be82e267ead6b3279f683616b66d":{"attr":"value"}, + "I224ed0b2e5a2be82e267ead6b3279f683616b66d":{"attr":"value2"} }"#, )?; let s: Arc = Arc::new(s); @@ -19,10 +19,13 @@ async fn auth_smoke(ctx: &mut Context) -> Result<()> { // Retrieve an existing one let entry = client - .get("P624ed0b2e5a2be82e267ead6b3279f683616b66de9537a23e45343c95cbb357a") + .get("I124ed0b2e5a2be82e267ead6b3279f683616b66d") .await? .expect("found"); - assert_eq!(Some(&b"value"[..].to_vec()), entry.attrs().get("attr")); + assert_eq!( + Some(&b"value"[..].to_vec()), + entry.attrs().get("attr".as_bytes()) + ); assert_eq!(None, entry.attested_by()); assert_eq!(None, entry.expires()); @@ -30,7 +33,7 @@ async fn auth_smoke(ctx: &mut Context) -> Result<()> { assert_eq!( None, client - .get("P111ed0b2e5a2be82e267ead6b3279f683616b66de9537a23e45343c95cbb357b") + .get("I324ed0b2e5a2be82e267ead6b3279f683616b66d") .await? ); diff --git a/implementations/rust/ockam/ockam_api/tests/credential_issuer.rs b/implementations/rust/ockam/ockam_api/tests/credential_issuer.rs index b8563497c34..bebfdd213f0 100644 --- a/implementations/rust/ockam/ockam_api/tests/credential_issuer.rs +++ b/implementations/rust/ockam/ockam_api/tests/credential_issuer.rs @@ -1,14 +1,15 @@ -use ockam::identity::credential::Timestamp; +use minicbor::bytes::ByteSlice; +use ockam::identity::utils::now; use ockam::identity::{identities, AttributesEntry}; +use ockam::identity::{ + CredentialsIssuer, CredentialsIssuerClient, Identities, SecureChannelListenerOptions, + SecureChannelOptions, SecureChannels, +}; use ockam::route; use ockam_api::bootstrapped_identities_store::{BootstrapedIdentityStore, PreTrustedIdentities}; use ockam_core::compat::collections::{BTreeMap, HashMap}; use ockam_core::compat::sync::Arc; use ockam_core::{Address, Result}; -use ockam_identity::{ - CredentialsIssuer, CredentialsIssuerClient, Identities, SecureChannelListenerOptions, - SecureChannelOptions, SecureChannels, -}; use ockam_node::Context; #[ockam_macros::test] @@ -21,12 +22,12 @@ async fn credential(ctx: &mut Context) -> Result<()> { let auth_identity = identities.identities_creation().create_identity().await?; let member_identity = identities.identities_creation().create_identity().await?; - let now = Timestamp::now().unwrap(); + let now = now().unwrap(); let pre_trusted = HashMap::from([( - member_identity.identifier(), + member_identity.identifier().clone(), AttributesEntry::new( - BTreeMap::from([("attr".to_string(), "value".as_bytes().to_vec())]), + BTreeMap::from([(b"attr".to_vec(), b"value".to_vec())]), now, None, None, @@ -43,7 +44,8 @@ async fn credential(ctx: &mut Context) -> Result<()> { // and the repository containing the trusted identities let identities = Identities::builder() .with_identities_repository(Arc::new(bootstrapped)) - .with_identities_vault(identities.clone().vault()) + .with_vault(identities.vault()) + .with_purpose_keys_repository(identities.purpose_keys_repository()) .build(); let secure_channels = SecureChannels::builder() .with_identities(identities.clone()) @@ -56,7 +58,7 @@ async fn credential(ctx: &mut Context) -> Result<()> { secure_channels .create_secure_channel_listener( ctx, - &auth_identity.identifier(), + auth_identity.identifier(), api_worker_addr.clone(), options, ) @@ -64,7 +66,8 @@ async fn credential(ctx: &mut Context) -> Result<()> { ctx.flow_controls() .add_consumer(auth_worker_addr.clone(), &sc_flow_control_id); let auth = CredentialsIssuer::new( - identities.clone(), + identities.repository(), + identities.credentials(), auth_identity.identifier(), "project42".into(), ) @@ -75,7 +78,7 @@ async fn credential(ctx: &mut Context) -> Result<()> { let e2a = secure_channels .create_secure_channel( ctx, - &member_identity.identifier(), + member_identity.identifier(), api_worker_addr, SecureChannelOptions::new(), ) @@ -87,17 +90,31 @@ async fn credential(ctx: &mut Context) -> Result<()> { let exported = member_identity.export()?; let imported = identities_creation - .decode_identity(&exported) + .import(Some(member_identity.identifier()), &exported) .await .unwrap(); let data = identities .credentials() - .verify_credential(&imported.identifier(), &[auth_identity], credential) + .credentials_verification() + .verify_credential( + Some(imported.identifier()), + &[auth_identity.identifier().clone()], + &credential, + ) .await?; assert_eq!( - Some(b"project42".as_slice()), - data.attributes().get("project_id") + Some(&b"project42".to_vec().into()), + data.credential_data + .subject_attributes + .map + .get::(b"trust_context_id".as_slice().into()) + ); + assert_eq!( + Some(&b"value".to_vec().into()), + data.credential_data + .subject_attributes + .map + .get::(b"attr".as_slice().into()) ); - assert_eq!(Some(b"value".as_slice()), data.attributes().get("attr")); ctx.stop().await } diff --git a/implementations/rust/ockam/ockam_api/tests/identity.rs b/implementations/rust/ockam/ockam_api/tests/identity.rs index d0cb600a48d..281650bba6c 100644 --- a/implementations/rust/ockam/ockam_api/tests/identity.rs +++ b/implementations/rust/ockam/ockam_api/tests/identity.rs @@ -7,7 +7,6 @@ use ockam_api::identity::models::*; use ockam_api::identity::IdentityService; use ockam_api::nodes::service::NodeIdentities; use ockam_core::api::{Request, Response, Status}; -use ockam_core::compat::rand::random; use ockam_core::errcode::{Kind, Origin}; use ockam_core::{route, AsyncTryClone, Error, Result}; use ockam_node::Context; @@ -93,67 +92,6 @@ async fn compare_identity_change_history( Ok(res) } -async fn create_signature( - ctx: &mut Context, - identity: &[u8], - data: &[u8], - service_address: &str, -) -> Result> { - let body = CreateSignatureRequest::new(identity, data); - let req = Request::post("actions/create_signature") - .body(body) - .to_vec()?; - - let receiving_buf: Vec = ctx.send_and_receive(route![service_address], req).await?; - let mut dec = Decoder::new(&receiving_buf); - - let res: Response = dec.decode()?; - - if let Some(Status::Ok) = res.status() { - } else { - return Err(Error::new( - Origin::Identity, - Kind::Other, - "consistency error", - )); - } - - let res: CreateSignatureResponse = dec.decode()?; - - Ok(res.signature().to_vec()) -} - -async fn verify_signature( - ctx: &mut Context, - signer_identity: &[u8], - data: &[u8], - signature: &[u8], - service_address: &str, -) -> Result { - let body = VerifySignatureRequest::new(signer_identity, data, signature); - let req = Request::post("actions/verify_signature") - .body(body) - .to_vec()?; - - let receiving_buf: Vec = ctx.send_and_receive(route![service_address], req).await?; - let mut dec = Decoder::new(&receiving_buf); - - let res: Response = dec.decode()?; - - if let Some(Status::Ok) = res.status() { - } else { - return Err(Error::new( - Origin::Identity, - Kind::Other, - "consistency error", - )); - } - - let res: VerifySignatureResponse = dec.decode()?; - - Ok(res.verified()) -} - #[ockam_macros::test] async fn full_flow(ctx: &mut Context) -> Result<()> { let cli_state = CliState::test().unwrap(); @@ -185,17 +123,6 @@ async fn full_flow(ctx: &mut Context) -> Result<()> { assert_eq!(comparison1, IdentityHistoryComparison::Newer); assert_eq!(comparison2, IdentityHistoryComparison::Newer); - let state: [u8; 32] = random(); - - let proof1 = create_signature(ctx, &identity1, &state, "1").await?; - let proof2 = create_signature(ctx, &identity2, &state, "2").await?; - - let verified1 = verify_signature(ctx, &identity1, &state, &proof1, "2").await?; - let verified2 = verify_signature(ctx, &identity2, &state, &proof2, "1").await?; - - assert!(verified1); - assert!(verified2); - ctx.stop().await?; Ok(()) diff --git a/implementations/rust/ockam/ockam_app/src/app/state/repository.rs b/implementations/rust/ockam/ockam_app/src/app/state/repository.rs index 4f763670800..c6c46f108b4 100644 --- a/implementations/rust/ockam/ockam_app/src/app/state/repository.rs +++ b/implementations/rust/ockam/ockam_app/src/app/state/repository.rs @@ -3,7 +3,7 @@ use std::path::Path; use miette::miette; use crate::app::state::model::ModelState; -use ockam::identity::Storage; +use ockam::identity::storage::Storage; use ockam::LmdbStorage; use ockam_core::async_trait; diff --git a/implementations/rust/ockam/ockam_app/src/invitations/commands.rs b/implementations/rust/ockam/ockam_app/src/invitations/commands.rs index 683827ed1c4..5e17c6acaae 100644 --- a/implementations/rust/ockam/ockam_app/src/invitations/commands.rs +++ b/implementations/rust/ockam/ockam_app/src/invitations/commands.rs @@ -488,11 +488,17 @@ mod tests { ); invitation.service_access_details = Some(ServiceAccessDetails { - project_identity: "project_identity".to_string(), + project_identity: "I1234561234561234561234561234561234561234" + .try_into() + .unwrap(), project_route: "project_route".to_string(), - project_authority_identity: "project_authority_identity".to_string(), + project_authority_identity: "Iabcdefabcdefabcdefabcdefabcdefabcdefabcd" + .try_into() + .unwrap(), project_authority_route: "project_authority_route".to_string(), - shared_node_identity: "shared_node_identity".to_string(), + shared_node_identity: "I12ab34cd56ef12ab34cd56ef12ab34cd56ef12ab" + .try_into() + .unwrap(), shared_node_route: "shared_node_route".to_string(), enrollment_ticket: EnrollmentTicket::new( OneTimeCode::new(), diff --git a/implementations/rust/ockam/ockam_command/Cargo.toml b/implementations/rust/ockam/ockam_command/Cargo.toml index b53c678cb5b..4152756b323 100644 --- a/implementations/rust/ockam/ockam_command/Cargo.toml +++ b/implementations/rust/ockam/ockam_command/Cargo.toml @@ -80,7 +80,6 @@ ockam = { path = "../ockam", version = "^0.92.0", features = ["software_vault"] ockam_abac = { path = "../ockam_abac", version = "0.26.0", features = ["std"] } ockam_api = { path = "../ockam_api", version = "0.35.0", features = ["std", "authenticators"] } ockam_core = { path = "../ockam_core", version = "^0.85.0" } -ockam_identity = { path = "../ockam_identity", version = "^0.80.0" } ockam_multiaddr = { path = "../ockam_multiaddr", version = "0.26.0", features = ["std"] } ockam_node = { path = "../ockam_node", version = "^0.88.0" } ockam_transport_tcp = { path = "../ockam_transport_tcp", version = "^0.86.0" } diff --git a/implementations/rust/ockam/ockam_command/src/authenticated.rs b/implementations/rust/ockam/ockam_command/src/authenticated.rs index 5edd74e168c..39cc13dc107 100644 --- a/implementations/rust/ockam/ockam_command/src/authenticated.rs +++ b/implementations/rust/ockam/ockam_command/src/authenticated.rs @@ -6,7 +6,7 @@ use clap::{Args, Subcommand}; use miette::Context as _; use miette::{miette, IntoDiagnostic}; use ockam::compat::collections::HashMap; -use ockam::identity::{AttributesEntry, IdentityIdentifier}; +use ockam::identity::{AttributesEntry, Identifier}; use ockam::{Context, TcpTransport}; use ockam_api::auth; use ockam_api::is_local_node; @@ -69,7 +69,7 @@ async fn run_impl(ctx: Context, cmd: AuthenticatedSubcommand) -> miette::Result< is_local_node(addr).context("The address must point to a local node")?; let mut c = client(&ctx, &tcp, addr).await?; if let Some(entry) = c.get(id).await.into_diagnostic()? { - print_entries(&[(IdentityIdentifier::try_from(id.to_string()).unwrap(), entry)]); + print_entries(&[(Identifier::try_from(id.to_string()).unwrap(), entry)]); } else { println!("Not found"); } @@ -84,7 +84,7 @@ async fn run_impl(ctx: Context, cmd: AuthenticatedSubcommand) -> miette::Result< Ok(()) } -fn print_entries(entries: &[(IdentityIdentifier, AttributesEntry)]) { +fn print_entries(entries: &[(Identifier, AttributesEntry)]) { let template = TextTemplate::from(LIST_VIEW); let model: Vec<_> = entries .iter() @@ -92,15 +92,20 @@ fn print_entries(entries: &[(IdentityIdentifier, AttributesEntry)]) { let attrs: HashMap = entry .attrs() .iter() - .map(|(k, v)| (k.to_string(), String::from_utf8(v.clone()).unwrap())) + .map(|(k, v)| { + ( + String::from_utf8(k.clone()).unwrap(), + String::from_utf8(v.clone()).unwrap(), + ) + }) .collect(); ( String::from(identifier), serde_json::to_string(&attrs).unwrap(), - format!("{:?}", entry.added().unix_time()), + format!("{:?}", entry.added()), entry .expires() - .map_or("-".to_string(), |t| format!("{:?}", t.unix_time())), + .map_or("-".to_string(), |t| format!("{:?}", t)), entry.attested_by().map_or("-".to_string(), String::from), ) }) diff --git a/implementations/rust/ockam/ockam_command/src/authority/create.rs b/implementations/rust/ockam/ockam_command/src/authority/create.rs index c250717c12e..d1136df9622 100644 --- a/implementations/rust/ockam/ockam_command/src/authority/create.rs +++ b/implementations/rust/ockam/ockam_command/src/authority/create.rs @@ -5,6 +5,7 @@ use crate::{docs, identity, CommandGlobalOpts, Result}; use clap::{ArgGroup, Args}; use miette::Context as _; use miette::{miette, IntoDiagnostic}; +use ockam::identity::{AttributesEntry, Identifier}; use ockam::Context; use ockam_api::bootstrapped_identities_store::PreTrustedIdentities; use ockam_api::cli_state::init_node_state; @@ -15,7 +16,6 @@ use ockam_api::nodes::models::transport::{CreateTransportJson, TransportMode, Tr use ockam_api::DefaultAddress; use ockam_core::compat::collections::HashMap; use ockam_core::compat::fmt; -use ockam_identity::{AttributesEntry, IdentityIdentifier}; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; use std::path::PathBuf; @@ -209,7 +209,7 @@ impl CreateCommand { /// or an explicit list of identities passed on the command line pub(crate) fn trusted_identities( &self, - authority_identifier: &IdentityIdentifier, + authority_identifier: &Identifier, ) -> Result { match ( &self.reload_from_trusted_identities_file, @@ -364,41 +364,35 @@ fn parse_trusted_identities(values: &str) -> Result { #[cfg(test)] mod tests { use super::*; + use ockam::identity::Identifier; use ockam_core::compat::collections::HashMap; - use ockam_identity::IdentityIdentifier; #[test] fn test_parse_trusted_identities() { - let identity1 = IdentityIdentifier::from_hex( - "e86be15e83d1c93e24dd1967010b01b6df491b459725fd9ae0bebfd7c1bf8ea3", - ); - let identity2 = IdentityIdentifier::from_hex( - "6c20e814b56579306f55c64e8747e6c1b4a53d9a3f4ca83c252cc2fbfc72fa94", - ); + let identity1 = Identifier::try_from("Ie86be15e83d1c93e24dd1967010b01b6df491b45").unwrap(); + let identity2 = Identifier::try_from("I6c20e814b56579306f55c64e8747e6c1b4a53d9a").unwrap(); - let trusted = format!("{{\"{identity1}\": {{\"name\": \"value\", \"project_id\": \"1\", \"trust_context_id\": \"1\"}}, \"{identity2}\": {{\"project_id\" : \"1\", \"trust_context_id\" : \"1\", \"ockam-role\" : \"enroller\"}}}}"); + let trusted = format!("{{\"{identity1}\": {{\"name\": \"value\", \"trust_context_id\": \"1\"}}, \"{identity2}\": {{\"trust_context_id\" : \"1\", \"ockam-role\" : \"enroller\"}}}}"); let actual = parse_trusted_identities(trusted.as_str()).unwrap(); let attributes1 = HashMap::from([ ("name".into(), "value".into()), - ("project_id".into(), "1".into()), ("trust_context_id".into(), "1".into()), ]); let attributes2 = HashMap::from([ - ("project_id".into(), "1".into()), ("trust_context_id".into(), "1".into()), ("ockam-role".into(), "enroller".into()), ]); let expected = vec![ - TrustedIdentity::new(&identity1, &attributes1), TrustedIdentity::new(&identity2, &attributes2), + TrustedIdentity::new(&identity1, &attributes1), ]; assert_eq!(actual.trusted_identities(), expected); } } #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -struct TrustedIdentities(HashMap>); +struct TrustedIdentities(HashMap>); impl TrustedIdentities { pub fn trusted_identities(&self) -> Vec { @@ -408,14 +402,14 @@ impl TrustedIdentities { .collect() } - /// Return a map from IdentityIdentifier to AttributesEntry and: + /// Return a map from Identifier to AttributesEntry and: /// - add the project identifier as an attribute /// - use the authority identifier an the attributes issuer pub(crate) fn to_map( &self, project_identifier: String, - authority_identifier: &IdentityIdentifier, - ) -> HashMap { + authority_identifier: &Identifier, + ) -> HashMap { HashMap::from_iter(self.trusted_identities().iter().map(|t| { ( t.identifier(), diff --git a/implementations/rust/ockam/ockam_command/src/credential/issue.rs b/implementations/rust/ockam/ockam_command/src/credential/issue.rs index 9f4196bc4e6..27b66a41d45 100644 --- a/implementations/rust/ockam/ockam_command/src/credential/issue.rs +++ b/implementations/rust/ockam/ockam_command/src/credential/issue.rs @@ -8,12 +8,13 @@ use crate::{ }; use clap::Args; -use crate::output::EncodeFormat; +use crate::output::{CredentialAndPurposeKeyDisplay, EncodeFormat}; use miette::{miette, IntoDiagnostic}; -use ockam::identity::CredentialData; +use ockam::identity::utils::AttributesBuilder; +use ockam::identity::Identifier; +use ockam::identity::{MAX_CREDENTIAL_VALIDITY, PROJECT_MEMBER_SCHEMA, TRUST_CONTEXT_ID}; use ockam::Context; use ockam_api::cli_state::traits::{StateDirTrait, StateItemTrait}; -use ockam_identity::IdentityIdentifier; #[derive(Clone, Debug, Args)] pub struct IssueCommand { @@ -21,7 +22,7 @@ pub struct IssueCommand { pub as_identity: Option, #[arg(long = "for", value_name = "IDENTIFIER", value_parser = identity_identifier_parser)] - pub identity_identifier: IdentityIdentifier, + pub identity_identifier: Identifier, /// Attributes in `key=value` format to be attached to the member #[arg(short, long = "attribute", value_name = "ATTRIBUTE")] @@ -52,8 +53,8 @@ impl IssueCommand { Ok(attributes) } - pub fn identity_identifier(&self) -> IdentityIdentifier { - self.identity_identifier.clone() + pub fn identity_identifier(&self) -> &Identifier { + &self.identity_identifier } } @@ -65,16 +66,6 @@ async fn run_impl( let ident_state = opts.state.identities.get(&identity_name)?; let auth_identity_identifier = ident_state.config().identifier().clone(); - let mut attrs = cmd.attributes()?; - attrs.insert( - "project_id".to_string(), // TODO: DEPRECATE - Removing PROJECT_ID attribute in favor of TRUST_CONTEXT_ID - auth_identity_identifier.to_string(), - ); - attrs.insert( - "trust_context_id".to_string(), - auth_identity_identifier.to_string(), - ); - let vault_name = cmd .vault .clone() @@ -83,15 +74,30 @@ async fn run_impl( let identities = opts.state.get_identities(vault).await?; let issuer = ident_state.identifier(); - let credential_data = - CredentialData::from_attributes(cmd.identity_identifier(), issuer.clone(), attrs) - .into_diagnostic()?; + let mut attributes_builder = AttributesBuilder::with_schema(PROJECT_MEMBER_SCHEMA) + .with_attribute( + TRUST_CONTEXT_ID.to_vec(), + auth_identity_identifier.to_string(), + ); + for (key, value) in cmd.attributes()? { + attributes_builder = + attributes_builder.with_attribute(key.as_bytes().to_vec(), value.as_bytes().to_vec()); + } + let credential = identities .credentials() - .issue_credential(&issuer, credential_data) + .credentials_creation() + .issue_credential( + &issuer, + cmd.identity_identifier(), + attributes_builder.build(), + MAX_CREDENTIAL_VALIDITY, + ) .await .into_diagnostic()?; - cmd.encode_format.println_value(&credential)?; + cmd.encode_format + .println_value(&CredentialAndPurposeKeyDisplay(credential))?; + Ok(()) } diff --git a/implementations/rust/ockam/ockam_command/src/credential/mod.rs b/implementations/rust/ockam/ockam_command/src/credential/mod.rs index 36b2a14011e..8b46bf70687 100644 --- a/implementations/rust/ockam/ockam_command/src/credential/mod.rs +++ b/implementations/rust/ockam/ockam_command/src/credential/mod.rs @@ -10,18 +10,19 @@ use colorful::Colorful; pub(crate) use get::GetCommand; pub(crate) use issue::IssueCommand; pub(crate) use list::ListCommand; -use miette::miette; -use ockam::identity::credential::{Credential, CredentialData, Unverified}; -use ockam::identity::IdentityIdentifier; +use ockam::identity::{Identifier, Identities, Identity}; use ockam_api::cli_state::{CredentialState, StateItemTrait}; pub(crate) use present::PresentCommand; pub(crate) use show::ShowCommand; +use std::sync::Arc; pub(crate) use store::StoreCommand; pub(crate) use verify::VerifyCommand; -use crate::output::Output; +use crate::output::{CredentialAndPurposeKeyDisplay, Output}; use crate::{CommandGlobalOpts, Result}; use clap::{Args, Subcommand}; +use miette::IntoDiagnostic; +use ockam::identity::models::CredentialAndPurposeKey; use ockam_api::cli_state::traits::StateDirTrait; /// Manage Credentials @@ -58,26 +59,35 @@ impl CredentialCommand { } } -pub async fn validate_encoded_cred( - encoded_cred: &str, - issuer: &IdentityIdentifier, - vault: &str, - opts: &CommandGlobalOpts, -) -> Result<()> { - let vault = opts.state.vaults.get(vault)?.get().await?; +pub async fn identities(vault_name: &str, opts: &CommandGlobalOpts) -> Result> { + let vault = opts.state.vaults.get(vault_name)?.get().await?; let identities = opts.state.get_identities(vault).await?; - let identity = identities.repository().get_identity(issuer).await?; - let bytes = match hex::decode(encoded_cred) { - Ok(b) => b, - Err(e) => return Err(miette!(e).into()), - }; - let cred: Credential = minicbor::decode(&bytes)?; - let cred_data: CredentialData = minicbor::decode(cred.unverified_data())?; + Ok(identities) +} + +pub async fn identity(identity: &str, identities: Arc) -> Result { + let identity_as_bytes = hex::decode(identity)?; + + let identity = identities + .identities_creation() + .import(None, &identity_as_bytes) + .await?; + + Ok(identity) +} + +pub async fn validate_encoded_cred( + encoded_cred: &[u8], + identities: Arc, + issuer: &Identifier, +) -> Result<()> { + let cred: CredentialAndPurposeKey = minicbor::decode(encoded_cred)?; identities .credentials() - .verify_credential(cred_data.unverified_subject(), &[identity], cred) + .credentials_verification() + .verify_credential(None, &[issuer.clone()], &cred) .await?; Ok(()) @@ -96,19 +106,23 @@ impl CredentialOutput { vault_name: &str, ) -> Result { let config = state.config(); + + let identities = identities(vault_name, opts).await.into_diagnostic()?; + let is_verified = validate_encoded_cred( &config.encoded_credential, - &config.issuer.identifier(), - vault_name, - opts, + identities, + &config.issuer_identifier, ) .await .is_ok(); + let credential = config.credential()?; + let credential = format!("{}", CredentialAndPurposeKeyDisplay(credential)); + let output = Self { name: state.name().to_string(), - - credential: config.credential()?.to_string(), + credential, is_verified, }; diff --git a/implementations/rust/ockam/ockam_command/src/credential/show.rs b/implementations/rust/ockam/ockam_command/src/credential/show.rs index c305893024f..f7ecdd4354a 100644 --- a/implementations/rust/ockam/ockam_command/src/credential/show.rs +++ b/implementations/rust/ockam/ockam_command/src/credential/show.rs @@ -1,8 +1,11 @@ use clap::{arg, Args}; use colorful::Colorful; +use miette::IntoDiagnostic; use ockam::Context; use ockam_api::cli_state::{StateDirTrait, StateItemTrait}; +use crate::credential::identities; +use crate::output::CredentialAndPurposeKeyDisplay; use crate::{ credential::validate_encoded_cred, util::node_rpc, vault::default_vault_name, CommandGlobalOpts, }; @@ -41,11 +44,20 @@ pub(crate) async fn display_credential( let cred = opts.state.credentials.get(cred_name)?; let cred_config = cred.config(); + let identities = identities(vault_name, opts).await?; + identities + .identities_creation() + .import( + Some(&cred_config.issuer_identifier), + &cred_config.encoded_issuer_change_history, + ) + .await + .into_diagnostic()?; + let is_verified = match validate_encoded_cred( &cred_config.encoded_credential, - &cred_config.issuer.identifier(), - vault_name, - opts, + identities, + &cred_config.issuer_identifier, ) .await { @@ -55,7 +67,7 @@ pub(crate) async fn display_credential( let cred = cred_config.credential()?; println!("Credential: {cred_name} {is_verified}"); - println!("{cred}"); + println!("{}", CredentialAndPurposeKeyDisplay(cred)); Ok(()) } diff --git a/implementations/rust/ockam/ockam_command/src/credential/store.rs b/implementations/rust/ockam/ockam_command/src/credential/store.rs index 69fdfe9f34f..83920e42765 100644 --- a/implementations/rust/ockam/ockam_command/src/credential/store.rs +++ b/implementations/rust/ockam/ockam_command/src/credential/store.rs @@ -6,15 +6,15 @@ use crate::{ terminal::OckamColor, util::{node_rpc, random_name}, vault::default_vault_name, - CommandGlobalOpts, Result, + CommandGlobalOpts, }; use colorful::Colorful; use miette::miette; +use crate::credential::{identities, identity}; use clap::Args; use ockam::Context; use ockam_api::cli_state::{CredentialConfig, StateDirTrait}; -use ockam_identity::{identities, Identity}; use tokio::{sync::Mutex, try_join}; #[derive(Clone, Debug, Args)] @@ -39,18 +39,6 @@ impl StoreCommand { pub fn run(self, opts: CommandGlobalOpts) { node_rpc(run_impl, (opts, self)); } - - pub async fn identity(&self) -> Result { - let identity_as_bytes = match hex::decode(&self.issuer) { - Ok(b) => b, - Err(e) => return Err(miette!(e).into()), - }; - let identity = identities() - .identities_creation() - .decode_identity(&identity_as_bytes) - .await?; - Ok(identity) - } } async fn run_impl( @@ -66,7 +54,10 @@ async fn run_impl( let send_req = async { let cred_as_str = match (&cmd.credential, &cmd.credential_path) { - (_, Some(credential_path)) => tokio::fs::read_to_string(credential_path).await?, + (_, Some(credential_path)) => tokio::fs::read_to_string(credential_path) + .await? + .trim() + .to_string(), (Some(credential), _) => credential.to_string(), _ => { *is_finished.lock().await = true; @@ -81,16 +72,24 @@ async fn run_impl( .clone() .unwrap_or_else(|| default_vault_name(&opts.state)); - let issuer = match &cmd.identity().await { + let identities = match identities(&vault_name, &opts).await { + Ok(i) => i, + Err(_) => { + *is_finished.lock().await = true; + return Err(miette!("Invalid state").into()); + } + }; + + let issuer = match identity(&cmd.issuer, identities.clone()).await { Ok(i) => i, Err(_) => { *is_finished.lock().await = true; return Err(miette!("Issuer is invalid").into()); } - } - .identifier(); + }; - if let Err(e) = validate_encoded_cred(&cred_as_str, &issuer, &vault_name, &opts).await { + let cred = hex::decode(&cred_as_str)?; + if let Err(e) = validate_encoded_cred(&cred, identities, issuer.identifier()).await { *is_finished.lock().await = true; return Err(miette!("Credential is invalid\n{}", e).into()); } @@ -98,7 +97,7 @@ async fn run_impl( // store opts.state.credentials.create( &cmd.credential_name, - CredentialConfig::new(cmd.identity().await?, cred_as_str.clone())?, + CredentialConfig::new(issuer.identifier().clone(), issuer.export()?, cred)?, )?; *is_finished.lock().await = true; diff --git a/implementations/rust/ockam/ockam_command/src/credential/verify.rs b/implementations/rust/ockam/ockam_command/src/credential/verify.rs index 0f3414e97ca..29127f01729 100644 --- a/implementations/rust/ockam/ockam_command/src/credential/verify.rs +++ b/implementations/rust/ockam/ockam_command/src/credential/verify.rs @@ -5,10 +5,11 @@ use crate::{ }; use miette::miette; +use crate::credential::identities; use clap::Args; use colorful::Colorful; +use ockam::identity::Identifier; use ockam::Context; -use ockam_identity::IdentityIdentifier; use tokio::{sync::Mutex, try_join}; use crate::util::parsers::identity_identifier_parser; @@ -18,7 +19,7 @@ use super::validate_encoded_cred; #[derive(Clone, Debug, Args)] pub struct VerifyCommand { #[arg(long = "issuer", value_name = "IDENTIFIER", value_parser = identity_identifier_parser)] - pub issuer: IdentityIdentifier, + pub issuer: Identifier, #[arg(group = "credential_value", value_name = "CREDENTIAL_STRING", long)] pub credential: Option, @@ -35,8 +36,8 @@ impl VerifyCommand { node_rpc(run_impl, (opts, self)); } - pub fn issuer(&self) -> IdentityIdentifier { - self.issuer.clone() + pub fn issuer(&self) -> &Identifier { + &self.issuer } } @@ -51,7 +52,10 @@ async fn run_impl( let send_req = async { let cred_as_str = match (&cmd.credential, &cmd.credential_path) { - (_, Some(credential_path)) => tokio::fs::read_to_string(credential_path).await?, + (_, Some(credential_path)) => tokio::fs::read_to_string(credential_path) + .await? + .trim() + .to_string(), (Some(credential), _) => credential.clone(), _ => { *is_finished.lock().await = true; @@ -68,8 +72,16 @@ async fn run_impl( let issuer = cmd.issuer(); - let is_valid = match validate_encoded_cred(&cred_as_str, &issuer, &vault_name, &opts).await - { + let identities = match identities(&vault_name, &opts).await { + Ok(i) => i, + Err(_) => { + *is_finished.lock().await = true; + return Err(miette!("Invalid state").into()); + } + }; + + let cred = hex::decode(&cred_as_str)?; + let is_valid = match validate_encoded_cred(&cred, identities, issuer).await { Ok(_) => (true, String::new()), Err(e) => (false, e.to_string()), }; diff --git a/implementations/rust/ockam/ockam_command/src/enroll/command.rs b/implementations/rust/ockam/ockam_command/src/enroll/command.rs index 80256556c3e..ffaaf612b55 100644 --- a/implementations/rust/ockam/ockam_command/src/enroll/command.rs +++ b/implementations/rust/ockam/ockam_command/src/enroll/command.rs @@ -6,6 +6,7 @@ use tokio::try_join; use tracing::info; use tracing::log::warn; +use ockam::identity::Identifier; use ockam::Context; use ockam_api::cli_state::traits::StateDirTrait; use ockam_api::cli_state::{update_enrolled_identity, SpaceConfig}; @@ -14,7 +15,6 @@ use ockam_api::cloud::project::Project; use ockam_api::cloud::space::Space; use ockam_api::enroll::oidc_service::OidcService; use ockam_core::api::Status; -use ockam_identity::IdentityIdentifier; use ockam_multiaddr::MultiAddr; use crate::enroll::OidcServiceExt; @@ -103,7 +103,7 @@ async fn run_impl( pub async fn retrieve_user_project<'a>( opts: &CommandGlobalOpts, rpc: &mut Rpc, -) -> Result { +) -> Result { let space = default_space(opts, rpc) .await .wrap_err("Unable to retrieve and set a space as default")?; @@ -127,7 +127,7 @@ pub async fn retrieve_user_project<'a>( .to_string() .color(OckamColor::PrimaryResource.color()) ))?; - info!("Enrolled a user with the IdentityIdentifier {}", identifier); + info!("Enrolled a user with the Identifier {}", identifier); Ok(identifier) } diff --git a/implementations/rust/ockam/ockam_command/src/identity/create.rs b/implementations/rust/ockam/ockam_command/src/identity/create.rs index bc089dabab2..8dc7019184c 100644 --- a/implementations/rust/ockam/ockam_command/src/identity/create.rs +++ b/implementations/rust/ockam/ockam_command/src/identity/create.rs @@ -3,9 +3,9 @@ use crate::util::node_rpc; use crate::{docs, fmt_log, fmt_ok, CommandGlobalOpts}; use clap::Args; use colorful::Colorful; +use ockam::identity::Identifier; use ockam::Context; use ockam_api::cli_state::traits::StateDirTrait; -use ockam_identity::IdentityIdentifier; use rand::prelude::random; use tokio::sync::Mutex; use tokio::try_join; @@ -44,10 +44,7 @@ impl CreateCommand { cmd.create_identity(options).await.map(|_| ()) } - pub async fn create_identity( - &self, - opts: CommandGlobalOpts, - ) -> miette::Result { + pub async fn create_identity(&self, opts: CommandGlobalOpts) -> miette::Result { opts.terminal.write_line(&fmt_log!( "Creating identity {}...\n", &self @@ -83,10 +80,10 @@ impl CreateCommand { .await?; opts.state - .create_identity_state(&identity.identifier(), Some(&self.name)) + .create_identity_state(identity.identifier(), Some(&self.name)) .await?; - let identifier = identity.identifier(); + let identifier = identity.identifier().clone(); *is_finished.lock().await = true; Ok(identifier) @@ -119,6 +116,6 @@ impl CreateCommand { .machine(identifier.clone()) .json(serde_json::json!({ "identity": { "identifier": &identifier } })) .write_line()?; - Ok(identifier) + Ok(identifier.clone()) } } diff --git a/implementations/rust/ockam/ockam_command/src/identity/show.rs b/implementations/rust/ockam/ockam_command/src/identity/show.rs index 2eb8463f635..96e1f0764bd 100644 --- a/implementations/rust/ockam/ockam_command/src/identity/show.rs +++ b/implementations/rust/ockam/ockam_command/src/identity/show.rs @@ -1,13 +1,11 @@ use crate::identity::{get_identity_name, initialize_identity_if_default}; -use crate::output::{EncodeFormat, Output}; +use crate::output::{EncodeFormat, IdentifierDisplay, IdentityDisplay}; use crate::util::node_rpc; -use crate::{docs, CommandGlobalOpts, Result}; +use crate::{docs, CommandGlobalOpts}; use clap::Args; -use core::fmt::Write; use miette::IntoDiagnostic; -use ockam::identity::identity::IdentityChangeHistory; +use ockam::identity::{Identity, Vault}; use ockam_api::cli_state::traits::{StateDirTrait, StateItemTrait}; -use ockam_api::nodes::models::identity::{LongIdentityResponse, ShortIdentityResponse}; use ockam_node::Context; const LONG_ABOUT: &str = include_str!("./static/show/long_about.txt"); @@ -49,46 +47,35 @@ impl ShowCommand { let (opts, cmd) = options; let name = get_identity_name(&opts.state, &cmd.name); let state = opts.state.identities.get(&name)?; + let identifier = state.config().identifier(); if cmd.full { - let identifier = state.config().identifier(); - let identity = opts + let change_history = opts .state .identities .identities_repository() .await? .get_identity(&identifier) .await - .into_diagnostic()? - .export() .into_diagnostic()?; if Some(EncodeFormat::Hex) == cmd.encoding { - opts.println(&identity)?; + opts.println(&hex::encode(change_history.export().into_diagnostic()?))?; } else { - let output = LongIdentityResponse::new(identity); - opts.println(&output)?; + let identity = Identity::import_from_change_history( + Some(&identifier), + change_history, + Vault::create_verifying_vault(), + ) + .await + .into_diagnostic()?; + + let identity_display = IdentityDisplay(identity); + opts.println(&identity_display)?; } } else { - let output = ShortIdentityResponse::new(state.config().identifier().to_string()); - opts.println(&output)?; + let identifier_display = IdentifierDisplay(identifier); + opts.println(&identifier_display)?; } Ok(()) } } - -impl Output for LongIdentityResponse<'_> { - fn output(&self) -> Result { - let mut w = String::new(); - let id: IdentityChangeHistory = serde_bare::from_slice(self.identity.0.as_ref())?; - write!(w, "{id}")?; - Ok(w) - } -} - -impl Output for ShortIdentityResponse<'_> { - fn output(&self) -> Result { - let mut w = String::new(); - write!(w, "{}", self.identity_id)?; - Ok(w) - } -} diff --git a/implementations/rust/ockam/ockam_command/src/output/output.rs b/implementations/rust/ockam/ockam_command/src/output/output.rs index 85002b59d08..8fe30262081 100644 --- a/implementations/rust/ockam/ockam_command/src/output/output.rs +++ b/implementations/rust/ockam/ockam_command/src/output/output.rs @@ -1,11 +1,20 @@ +use core::fmt; use core::fmt::Write; +use std::fmt::Formatter; use cli_table::{Cell, Style, Table}; use colorful::Colorful; use miette::miette; use miette::IntoDiagnostic; +use minicbor::Encode; +use ockam::identity::models::{ + CredentialAndPurposeKey, CredentialData, CredentialSigningKey, Ed25519PublicKey, + P256ECDSAPublicKey, PurposeKeyAttestation, PurposeKeyAttestationData, PurposePublicKey, + X25519PublicKey, +}; +use ockam::identity::{Credential, Identifier, Identity, TimestampInSeconds}; +use serde::{Serialize, Serializer}; -use ockam::identity::credential::Credential; use ockam_api::cli_state::{ProjectConfigCompact, StateItemTrait, VaultState}; use ockam_api::cloud::project::Project; use ockam_api::cloud::space::Space; @@ -58,6 +67,18 @@ impl Output for &O { } } +impl Output for String { + fn output(&self) -> Result { + Ok(self.clone()) + } +} + +impl Output for &str { + fn output(&self) -> Result { + Ok(self.to_string()) + } +} + impl Output for Space { fn output(&self) -> Result { let mut w = String::new(); @@ -282,12 +303,6 @@ From {} to {}"#, } } -impl Output for Credential { - fn output(&self) -> Result { - Ok(self.to_string()) - } -} - impl Output for Vec { fn output(&self) -> Result { Ok(hex::encode(self)) @@ -378,3 +393,301 @@ impl Output for VaultState { Ok(output) } } + +fn human_readable_time(time: TimestampInSeconds) -> String { + use time::format_description::well_known::iso8601::*; + use time::Error::Format; + use time::OffsetDateTime; + + match OffsetDateTime::from_unix_timestamp(*time as i64) { + Ok(time) => { + match time.format( + &Iso8601::< + { + Config::DEFAULT + .set_time_precision(TimePrecision::Second { + decimal_digits: None, + }) + .encode() + }, + >, + ) { + Ok(now_iso) => now_iso, + Err(_) => { + Format(time::error::Format::InvalidComponent("timestamp error")).to_string() + } + } + } + Err(_) => Format(time::error::Format::InvalidComponent( + "unix time is invalid", + )) + .to_string(), + } +} + +pub struct X25519PublicKeyDisplay(pub X25519PublicKey); + +impl fmt::Display for X25519PublicKeyDisplay { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "X25519: {}", hex::encode(self.0 .0)) + } +} + +pub struct Ed25519PublicKeyDisplay(pub Ed25519PublicKey); + +impl fmt::Display for Ed25519PublicKeyDisplay { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "Ed25519: {}", hex::encode(self.0 .0)) + } +} + +pub struct P256PublicKeyDisplay(pub P256ECDSAPublicKey); + +impl fmt::Display for P256PublicKeyDisplay { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "P256: {}", hex::encode(self.0 .0)) + } +} + +pub struct PurposePublicKeyDisplay(pub PurposePublicKey); + +impl fmt::Display for PurposePublicKeyDisplay { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match &self.0 { + PurposePublicKey::SecureChannelStaticKey(key) => { + writeln!( + f, + "Secure Channel Key -> {}", + X25519PublicKeyDisplay(key.clone()) + )?; + } + PurposePublicKey::CredentialSigningKey(key) => match key { + CredentialSigningKey::Ed25519PublicKey(key) => { + writeln!( + f, + "Credentials Key -> {}", + Ed25519PublicKeyDisplay(key.clone()) + )?; + } + CredentialSigningKey::P256ECDSAPublicKey(key) => { + writeln!( + f, + "Credentials Key -> {}", + P256PublicKeyDisplay(key.clone()) + )?; + } + }, + } + + Ok(()) + } +} + +pub struct CredentialDisplay(pub Credential); + +impl fmt::Display for CredentialDisplay { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let versioned_data = match self.0.get_versioned_data() { + Ok(versioned_data) => versioned_data, + Err(_) => { + writeln!(f, "Invalid VersionedData")?; + return Ok(()); + } + }; + + writeln!(f, "Version: {}", versioned_data.version)?; + + let credential_data = match CredentialData::get_data(&versioned_data) { + Ok(credential_data) => credential_data, + Err(_) => { + writeln!(f, "Invalid CredentialData")?; + return Ok(()); + } + }; + + if let Some(subject) = &credential_data.subject { + writeln!(f, "Subject: {}", subject)?; + } + + if let Some(subject_latest_change_hash) = &credential_data.subject_latest_change_hash { + writeln!( + f, + "Subject Latest Change Hash: {}", + subject_latest_change_hash + )?; + } + + writeln!( + f, + "Created: {}", + human_readable_time(credential_data.created_at) + )?; + writeln!( + f, + "Expires: {}", + human_readable_time(credential_data.expires_at) + )?; + + writeln!(f, "Attributes: ")?; + + write!( + f, + " Schema: {}; ", + credential_data.subject_attributes.schema.0 + )?; + + f.debug_map() + .entries(credential_data.subject_attributes.map.iter().map(|(k, v)| { + ( + std::str::from_utf8(k).unwrap_or("**binary**"), + std::str::from_utf8(v).unwrap_or("**binary**"), + ) + })) + .finish()?; + + Ok(()) + } +} + +pub struct PurposeKeyDisplay(pub PurposeKeyAttestation); + +impl fmt::Display for PurposeKeyDisplay { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let versioned_data = match self.0.get_versioned_data() { + Ok(versioned_data) => versioned_data, + Err(_) => { + writeln!(f, "Invalid VersionedData")?; + return Ok(()); + } + }; + + writeln!(f, "Version: {}", versioned_data.version)?; + + let purpose_key_attestation_data = + match PurposeKeyAttestationData::get_data(&versioned_data) { + Ok(purpose_key_attestation_data) => purpose_key_attestation_data, + Err(_) => { + writeln!(f, "Invalid PurposeKeyAttestationData")?; + return Ok(()); + } + }; + + writeln!( + f, + "Subject: {}", + purpose_key_attestation_data.subject + )?; + + writeln!( + f, + "Subject Latest Change Hash: {}", + purpose_key_attestation_data.subject_latest_change_hash + )?; + + writeln!( + f, + "Created: {}", + human_readable_time(purpose_key_attestation_data.created_at) + )?; + writeln!( + f, + "Expires: {}", + human_readable_time(purpose_key_attestation_data.expires_at) + )?; + + writeln!( + f, + "Public Key -> {}", + PurposePublicKeyDisplay(purpose_key_attestation_data.public_key.clone()) + )?; + + Ok(()) + } +} + +#[derive(Encode)] +#[cbor(transparent)] +pub struct CredentialAndPurposeKeyDisplay(#[n(0)] pub CredentialAndPurposeKey); + +impl Output for CredentialAndPurposeKeyDisplay { + fn output(&self) -> Result { + Ok(format!("{}", self)) + } +} + +impl fmt::Display for CredentialAndPurposeKeyDisplay { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + // TODO: Could borrow using a lifetime + writeln!(f, "Credential:")?; + writeln!(f, "{}", CredentialDisplay(self.0.credential.clone()))?; + writeln!(f)?; + writeln!(f, "Purpose key:")?; + writeln!( + f, + "{}", + PurposeKeyDisplay(self.0.purpose_key_attestation.clone()) + )?; + + Ok(()) + } +} + +#[derive(Serialize)] +#[serde(transparent)] +pub struct IdentifierDisplay(pub Identifier); + +impl fmt::Display for IdentifierDisplay { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl Output for IdentifierDisplay { + fn output(&self) -> Result { + Ok(self.to_string()) + } +} + +pub struct IdentityDisplay(pub Identity); + +impl Serialize for IdentityDisplay { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + use serde::ser::Error; + serializer.serialize_bytes(&self.0.export().map_err(Error::custom)?) + } +} + +impl fmt::Display for IdentityDisplay { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln!(f, "Identifier: {}", self.0.identifier())?; + for (i_num, change) in self.0.changes().iter().enumerate() { + writeln!(f, " Change[{}]:", i_num)?; + writeln!( + f, + " identifier: {}", + hex::encode(change.change_hash()) + )?; + writeln!( + f, + " primary_public_key: {}", + change.primary_public_key() + )?; + writeln!( + f, + " revoke_all_purpose_keys: {}", + change.data().revoke_all_purpose_keys + )?; + } + + Ok(()) + } +} + +impl Output for IdentityDisplay { + fn output(&self) -> Result { + Ok(format!("{}", self)) + } +} diff --git a/implementations/rust/ockam/ockam_command/src/policy/mod.rs b/implementations/rust/ockam/ockam_command/src/policy/mod.rs index eadd607e2fb..60e8e8f0349 100644 --- a/implementations/rust/ockam/ockam_command/src/policy/mod.rs +++ b/implementations/rust/ockam/ockam_command/src/policy/mod.rs @@ -67,7 +67,10 @@ pub(crate) async fn add_default_project_policy( project: ProjectLookup, resource: &Resource, ) -> miette::Result<()> { - let expr = eq([ident("subject.project_id"), str(project.id.to_string())]); + let expr = eq([ + ident("subject.trust_context_id"), + str(project.id.to_string()), + ]); let bdy = Policy::new(expr); let req = Request::post(policy_path(resource, &Action::new("handle_message"))).body(bdy); diff --git a/implementations/rust/ockam/ockam_command/src/project/enroll.rs b/implementations/rust/ockam/ockam_command/src/project/enroll.rs index c8ac3f19878..aefdcccb224 100644 --- a/implementations/rust/ockam/ockam_command/src/project/enroll.rs +++ b/implementations/rust/ockam/ockam_command/src/project/enroll.rs @@ -4,6 +4,7 @@ use clap::Args; use miette::Context as _; use miette::{miette, IntoDiagnostic}; +use ockam::identity::CredentialsIssuerClient; use ockam::Context; use ockam_api::authenticator::direct::TokenAcceptorClient; use ockam_api::cli_state::{ProjectConfigCompact, StateDirTrait, StateItemTrait}; @@ -14,7 +15,6 @@ use ockam_api::enroll::okta_oidc_provider::OktaOidcProvider; use ockam_api::identity::EnrollmentTicket; use ockam_api::DefaultAddress; use ockam_core::route; -use ockam_identity::CredentialsIssuerClient; use ockam_multiaddr::proto::Service; use ockam_multiaddr::MultiAddr; use ockam_node::RpcClient; @@ -22,6 +22,7 @@ use ockam_node::RpcClient; use crate::enroll::{enroll_with_node, OidcServiceExt}; use crate::identity::{get_identity_name, initialize_identity_if_default}; use crate::node::util::delete_embedded_node; +use crate::output::CredentialAndPurposeKeyDisplay; use crate::project::util::create_secure_channel_to_authority; use crate::util::api::{CloudOpts, TrustContextOpts}; use crate::util::{node_rpc, Rpc}; @@ -218,7 +219,7 @@ pub async fn project_enroll<'a>( opts.terminal .clone() .stdout() - .plain(credential) + .plain(CredentialAndPurposeKeyDisplay(credential)) .write_line()?; Ok(project.name) } diff --git a/implementations/rust/ockam/ockam_command/src/project/ticket.rs b/implementations/rust/ockam/ockam_command/src/project/ticket.rs index d101e6e2e38..8a92658e4c8 100644 --- a/implementations/rust/ockam/ockam_command/src/project/ticket.rs +++ b/implementations/rust/ockam/ockam_command/src/project/ticket.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use std::time::Duration; use miette::{miette, IntoDiagnostic}; -use ockam::identity::IdentityIdentifier; +use ockam::identity::Identifier; use ockam::Context; use ockam_api::authenticator::direct::{DirectAuthenticatorClient, TokenIssuerClient}; use ockam_api::cli_state::{CliState, StateDirTrait, StateItemTrait}; @@ -42,7 +42,7 @@ pub struct TicketCommand { trust_opts: TrustContextOpts, #[arg(long, short, conflicts_with = "expires_in")] - member: Option, + member: Option, #[arg(long, short, default_value = "/project/default")] to: MultiAddr, diff --git a/implementations/rust/ockam/ockam_command/src/project/util.rs b/implementations/rust/ockam/ockam_command/src/project/util.rs index 6965c65336a..6b817d3f8f8 100644 --- a/implementations/rust/ockam/ockam_command/src/project/util.rs +++ b/implementations/rust/ockam/ockam_command/src/project/util.rs @@ -4,7 +4,7 @@ use tokio_retry::strategy::FixedInterval; use tokio_retry::Retry; use tracing::debug; -use ockam::identity::IdentityIdentifier; +use ockam::identity::Identifier; use ockam::AsyncTryClone; use ockam_api::cli_state::{StateDirTrait, StateItemTrait}; use ockam_api::cloud::project::Project; @@ -100,7 +100,7 @@ pub async fn create_secure_channel_to_project<'a>( credential_exchange_mode: CredentialExchangeMode, identity: Option, ) -> crate::Result { - let authorized_identifier = vec![IdentityIdentifier::from_str(project_identity)?]; + let authorized_identifier = vec![Identifier::from_str(project_identity)?]; let payload = models::secure_channel::CreateSecureChannelRequest::new( project_access_route, Some(authorized_identifier), @@ -115,7 +115,7 @@ pub async fn create_secure_channel_to_project<'a>( pub async fn create_secure_channel_to_authority<'a>( rpc: &mut Rpc, - authority: IdentityIdentifier, + authority: Identifier, addr: &MultiAddr, identity: Option, ) -> crate::Result { diff --git a/implementations/rust/ockam/ockam_command/src/relay/create.rs b/implementations/rust/ockam/ockam_command/src/relay/create.rs index 73ecc1a3c9b..af56206cb24 100644 --- a/implementations/rust/ockam/ockam_command/src/relay/create.rs +++ b/implementations/rust/ockam/ockam_command/src/relay/create.rs @@ -7,7 +7,7 @@ use miette::{miette, IntoDiagnostic}; use tokio::sync::Mutex; use tokio::try_join; -use ockam::identity::IdentityIdentifier; +use ockam::identity::Identifier; use ockam::Context; use ockam_api::address::extract_address_value; use ockam_api::is_local_node; @@ -46,7 +46,7 @@ pub struct CreateCommand { /// Authorized identity for secure channel connection #[arg(long, id = "AUTHORIZED", display_order = 900)] - authorized: Option, + authorized: Option, } impl CreateCommand { diff --git a/implementations/rust/ockam/ockam_command/src/secure_channel/create.rs b/implementations/rust/ockam/ockam_command/src/secure_channel/create.rs index 80df532842d..221130c2979 100644 --- a/implementations/rust/ockam/ockam_command/src/secure_channel/create.rs +++ b/implementations/rust/ockam/ockam_command/src/secure_channel/create.rs @@ -4,7 +4,7 @@ use miette::{miette, IntoDiagnostic, WrapErr}; use serde_json::json; use tokio::{sync::Mutex, try_join}; -use ockam::{identity::IdentityIdentifier, route, Context}; +use ockam::{identity::Identifier, route, Context}; use ockam_api::address::extract_address_value; use ockam_api::nodes::models; use ockam_api::nodes::models::secure_channel::{ @@ -47,7 +47,7 @@ pub struct CreateCommand { /// Identifiers authorized to be presented by the listener #[arg(value_name = "IDENTIFIER", long, short, display_order = 801)] - pub authorized: Option>, + pub authorized: Option>, /// Orchestrator address to resolve projects present in the `at` argument #[command(flatten)] diff --git a/implementations/rust/ockam/ockam_command/src/secure_channel/listener/create.rs b/implementations/rust/ockam/ockam_command/src/secure_channel/listener/create.rs index 516f2e90683..8476a243e74 100644 --- a/implementations/rust/ockam/ockam_command/src/secure_channel/listener/create.rs +++ b/implementations/rust/ockam/ockam_command/src/secure_channel/listener/create.rs @@ -2,7 +2,7 @@ use clap::Args; use colorful::Colorful; use miette::{miette, IntoDiagnostic, WrapErr}; -use ockam::identity::IdentityIdentifier; +use ockam::identity::Identifier; use ockam::Context; use ockam_api::nodes::models::secure_channel::CreateSecureChannelListenerRequest; use ockam_api::nodes::NODEMANAGER_ADDR; @@ -32,7 +32,7 @@ pub struct CreateCommand { /// Authorized Identifiers of secure channel initiators #[arg(short, long, value_name = "IDENTIFIERS")] - authorized: Option>, + authorized: Option>, #[arg(value_name = "VAULT", long, requires = "identity")] vault: Option, @@ -99,7 +99,7 @@ async fn run_impl( pub async fn create_listener( ctx: &Context, addr: Address, - authorized_identifiers: Option>, + authorized_identifiers: Option>, identity: Option, mut base_route: Route, ) -> miette::Result<()> { diff --git a/implementations/rust/ockam/ockam_command/src/service/config.rs b/implementations/rust/ockam/ockam_command/src/service/config.rs index c23b953d8bc..70f46519c33 100644 --- a/implementations/rust/ockam/ockam_command/src/service/config.rs +++ b/implementations/rust/ockam/ockam_command/src/service/config.rs @@ -3,7 +3,7 @@ use std::path::Path; use miette::{Context as _, IntoDiagnostic}; use serde::{Deserialize, Serialize}; -use ockam::identity::IdentityIdentifier; +use ockam::identity::Identifier; use ockam_api::DefaultAddress; use crate::Result; @@ -23,7 +23,7 @@ pub struct SecureChannelListenerConfig { pub(crate) address: String, #[serde(default)] - pub(crate) authorized_identifiers: Option>, + pub(crate) authorized_identifiers: Option>, #[serde(default)] pub(crate) disabled: bool, diff --git a/implementations/rust/ockam/ockam_command/src/share/service.rs b/implementations/rust/ockam/ockam_command/src/share/service.rs index 540f7762e6c..b579e26a8c4 100644 --- a/implementations/rust/ockam/ockam_command/src/share/service.rs +++ b/implementations/rust/ockam/ockam_command/src/share/service.rs @@ -5,6 +5,7 @@ use tokio::sync::Mutex; use tokio::try_join; use tracing::debug; +use ockam::identity::Identifier; use ockam::Context; use ockam_api::cloud::share::{CreateServiceInvitation, SentInvitation}; @@ -25,11 +26,11 @@ pub struct ServiceCreateCommand { pub project_id: String, pub recipient_email: String, - pub project_identity: String, + pub project_identity: Identifier, pub project_route: String, - pub project_authority_identity: String, + pub project_authority_identity: Identifier, pub project_authority_route: String, - pub shared_node_identity: String, + pub shared_node_identity: Identifier, pub shared_node_route: String, pub enrollment_ticket: String, diff --git a/implementations/rust/ockam/ockam_command/src/status.rs b/implementations/rust/ockam/ockam_command/src/status.rs index 840b789c151..7a163b32739 100644 --- a/implementations/rust/ockam/ockam_command/src/status.rs +++ b/implementations/rust/ockam/ockam_command/src/status.rs @@ -5,6 +5,7 @@ use clap::Args; use miette::miette; use minicbor::{Decode, Decoder, Encode}; +use ockam::identity::{Identifier, SecureChannelOptions, TrustIdentifierPolicy}; use ockam::{Context, Node, TcpConnectionOptions, TcpTransport}; use ockam_api::cli_state::identities::IdentityState; use ockam_api::cli_state::traits::{StateDirTrait, StateItemTrait}; @@ -13,7 +14,6 @@ use ockam_api::nodes::models::base::NodeStatus as NodeStatusModel; use ockam_api::nodes::NodeManager; use ockam_core::api::{Request, Response, Status}; use ockam_core::route; -use ockam_identity::{IdentityIdentifier, SecureChannelOptions, TrustIdentifierPolicy}; use ockam_node::MessageSendReceiveOptions; use crate::util::api::CloudOpts; @@ -120,7 +120,7 @@ async fn get_orchestrator_version( let identities_vault = opts.state.vaults.default()?.get().await?; let identities_repository = opts.state.identities.identities_repository().await?; Node::builder() - .with_identities_vault(identities_vault) + .with_vault(identities_vault) .with_identities_repository(identities_repository) .build(ctx) .await? @@ -270,7 +270,7 @@ struct NodeStatus { } struct NodeDetails { - identifier: IdentityIdentifier, + identifier: Identifier, state: NodeState, status: String, } diff --git a/implementations/rust/ockam/ockam_command/src/tcp/inlet/create.rs b/implementations/rust/ockam/ockam_command/src/tcp/inlet/create.rs index 603cf348ea8..b3fc74445a8 100644 --- a/implementations/rust/ockam/ockam_command/src/tcp/inlet/create.rs +++ b/implementations/rust/ockam/ockam_command/src/tcp/inlet/create.rs @@ -10,7 +10,7 @@ use tokio::sync::Mutex; use tokio::try_join; use tracing::log::trace; -use ockam::identity::IdentityIdentifier; +use ockam::identity::Identifier; use ockam::Context; use ockam_abac::Resource; use ockam_api::cli_state::{StateDirTrait, StateItemTrait}; @@ -54,7 +54,7 @@ pub struct CreateCommand { /// Authorized identity for secure channel connection #[arg(long, name = "AUTHORIZED", display_order = 900)] - authorized: Option, + authorized: Option, /// Assign a name to this inlet. #[arg(long, display_order = 900, id = "ALIAS", value_parser = alias_parser)] diff --git a/implementations/rust/ockam/ockam_command/src/util/api.rs b/implementations/rust/ockam/ockam_command/src/util/api.rs index fe3ef55c010..c2546b6a120 100644 --- a/implementations/rust/ockam/ockam_command/src/util/api.rs +++ b/implementations/rust/ockam/ockam_command/src/util/api.rs @@ -8,7 +8,7 @@ use miette::miette; use minicbor::Decoder; use regex::Regex; -use ockam::identity::IdentityIdentifier; +use ockam::identity::Identifier; use ockam_api::address::controller_route; use ockam_api::cli_state::CliState; use ockam_api::cloud::{BareCloudRequestWrapper, CloudRequestWrapper}; @@ -93,7 +93,7 @@ pub(crate) fn show_secure_channel( /// Construct a request to create Secure Channel Listeners pub(crate) fn create_secure_channel_listener( addr: &Address, - authorized_identifiers: Option>, + authorized_identifiers: Option>, identity: Option, ) -> Result> { let payload = models::secure_channel::CreateSecureChannelListenerRequest::new( diff --git a/implementations/rust/ockam/ockam_command/src/util/mod.rs b/implementations/rust/ockam/ockam_command/src/util/mod.rs index b1ab3620a48..4b4d39f352c 100644 --- a/implementations/rust/ockam/ockam_command/src/util/mod.rs +++ b/implementations/rust/ockam/ockam_command/src/util/mod.rs @@ -586,7 +586,7 @@ mod tests { .identities_creation() .create_identity() .await?; - let idt_config = IdentityConfig::new(&idt.identifier()).await; + let idt_config = IdentityConfig::new(idt.identifier()).await; cli_state .identities .create(cli_state::random_name(), idt_config)?; diff --git a/implementations/rust/ockam/ockam_command/src/util/orchestrator_api.rs b/implementations/rust/ockam/ockam_command/src/util/orchestrator_api.rs index 406a2428596..551fcfea979 100644 --- a/implementations/rust/ockam/ockam_command/src/util/orchestrator_api.rs +++ b/implementations/rust/ockam/ockam_command/src/util/orchestrator_api.rs @@ -5,7 +5,9 @@ use miette::miette; use minicbor::{Decode, Encode}; use tracing::info; -use ockam::identity::credential::{Credential, OneTimeCode}; +use ockam::identity::models::CredentialAndPurposeKey; +use ockam::identity::CredentialsIssuerClient; +use ockam::identity::OneTimeCode; use ockam::Context; use ockam_api::cli_state::{ProjectConfigCompact, StateDirTrait, StateItemTrait}; use ockam_api::{ @@ -14,7 +16,6 @@ use ockam_api::{ }; use ockam_core::api::RequestBuilder; use ockam_core::route; -use ockam_identity::CredentialsIssuerClient; use ockam_multiaddr::proto::Service; use ockam_multiaddr::MultiAddr; @@ -144,7 +145,7 @@ impl<'a> OrchestratorApiBuilder<'a> { self } - pub async fn authenticate(&self) -> Result { + pub async fn authenticate(&self) -> Result { let sc_addr = self .secure_channel_to(&OrchestratorEndpoint::Authenticator) .await?; diff --git a/implementations/rust/ockam/ockam_command/src/util/parsers.rs b/implementations/rust/ockam/ockam_command/src/util/parsers.rs index 2d7668a504c..06b7fa3840d 100644 --- a/implementations/rust/ockam/ockam_command/src/util/parsers.rs +++ b/implementations/rust/ockam/ockam_command/src/util/parsers.rs @@ -1,6 +1,6 @@ use crate::Result; use miette::miette; -use ockam_identity::IdentityIdentifier; +use ockam::identity::Identifier; use ockam_transport_tcp::resolve_peer; use std::net::SocketAddr; use std::str::FromStr; @@ -23,9 +23,8 @@ pub(crate) fn socket_addr_parser(input: &str) -> Result { /// Helper fn for parsing an identity from user input by using /// [`ockam_identity::IdentityIdentifier::from_str()`] -pub(crate) fn identity_identifier_parser(input: &str) -> Result { - IdentityIdentifier::from_str(input) - .map_err(|_| miette!("Invalid identity identifier: {input}").into()) +pub(crate) fn identity_identifier_parser(input: &str) -> Result { + Identifier::from_str(input).map_err(|_| miette!("Invalid identity identifier: {input}").into()) } #[cfg(test)] diff --git a/implementations/rust/ockam/ockam_command/src/vault/attach_key.rs b/implementations/rust/ockam/ockam_command/src/vault/attach_key.rs index bf16c11c777..3cf8045982f 100644 --- a/implementations/rust/ockam/ockam_command/src/vault/attach_key.rs +++ b/implementations/rust/ockam/ockam_command/src/vault/attach_key.rs @@ -6,9 +6,6 @@ use ockam_api::cli_state; use ockam_api::cli_state::identities::IdentityConfig; use ockam_api::cli_state::traits::{StateDirTrait, StateItemTrait}; -use ockam_identity::{IdentityChangeConstants, KeyAttributes}; -use ockam_vault::SecretAttributes; - use crate::util::node_rpc; use crate::CommandGlobalOpts; @@ -44,60 +41,28 @@ async fn run_impl(opts: CommandGlobalOpts, cmd: AttachKeyCommand) -> miette::Res } let vault = v_state.get().await?; let idt = { - let attrs = SecretAttributes::NistP256; - let key_attrs = KeyAttributes::new(IdentityChangeConstants::ROOT_LABEL.to_string(), attrs); - opts.state + let identities_creation = opts + .state .get_identities(vault) .await? - .identities_creation() - .create_identity_with_existing_key(&cmd.key_id, key_attrs) + .identities_creation(); + + let public_key = identities_creation + .identity_vault() + .get_public_key(&cmd.key_id) + .await + .into_diagnostic()?; + + identities_creation + .identity_builder() + .with_existing_key(cmd.key_id, public_key.stype()) + .build() .await .into_diagnostic()? }; let idt_name = cli_state::random_name(); - let idt_config = IdentityConfig::new(&idt.identifier()).await; + let idt_config = IdentityConfig::new(idt.identifier()).await; opts.state.identities.create(&idt_name, idt_config)?; println!("Identity attached to vault: {idt_name}"); Ok(()) } - -#[cfg(test)] -mod tests { - use super::*; - use ockam_core::Result; - use ockam_identity::Identities; - use ockam_vault::{PersistentSecretsStore, Vault}; - use ockam_vault_aws::AwsSecurityModule; - use std::sync::Arc; - - /// This test needs to be executed with the following environment variables - /// AWS_REGION - /// AWS_ACCESS_KEY_ID - /// AWS_SECRET_ACCESS_KEY - #[tokio::test] - #[ignore] - async fn test_create_identity_with_external_key_id() -> Result<()> { - let vault = - Vault::create_with_security_module(Arc::new(AwsSecurityModule::default().await?)); - let identities = Identities::builder() - .with_identities_vault(vault.clone()) - .build(); - - // create a secret key using the AWS KMS - let key_id = vault - .create_persistent_secret(SecretAttributes::NistP256) - .await?; - let key_attrs = KeyAttributes::new( - IdentityChangeConstants::ROOT_LABEL.to_string(), - SecretAttributes::NistP256, - ); - - let identity = identities - .identities_creation() - .create_identity_with_existing_key(&key_id, key_attrs) - .await; - assert!(identity.is_ok()); - - Ok(()) - } -} diff --git a/implementations/rust/ockam/ockam_command/tests/bats/authority.bats b/implementations/rust/ockam/ockam_command/tests/bats/authority.bats index 089858d8014..0c90895e970 100644 --- a/implementations/rust/ockam/ockam_command/tests/bats/authority.bats +++ b/implementations/rust/ockam/ockam_command/tests/bats/authority.bats @@ -40,7 +40,7 @@ teardown() { PROJECT_NAME="default" echo "{\"id\": \"1\", \"name\" : \"$PROJECT_NAME\", - \"identity\" : \"P6c20e814b56579306f55c64e8747e6c1b4a53d9a3f4ca83c252cc2fbfc72fa94\", + \"identity\" : \"I6c20e814b56579306f55c64e8747e6c1b4a53d9a\", \"access_route\" : \"/dnsaddr/127.0.0.1/tcp/4000/service/api\", \"authority_access_route\" : \"/dnsaddr/127.0.0.1/tcp/$port/service/api\", \"authority_identity\" : \"$authority_identity_full\"}" >"$PROJECT_JSON_PATH" @@ -90,7 +90,7 @@ teardown() { PROJECT_JSON_PATH="$OCKAM_HOME/project-authority.json" echo "{\"id\": \"1\", \"name\" : \"default\", - \"identity\" : \"P6c20e814b56579306f55c64e8747e6c1b4a53d9a3f4ca83c252cc2fbfc72fa94\", + \"identity\" : \"I6c20e814b56579306f55c64e8747e6c1b4a53d9a\", \"access_route\" : \"/dnsaddr/127.0.0.1/tcp/4000/service/api\", \"authority_access_route\" : \"/dnsaddr/127.0.0.1/tcp/$port/service/api\", \"authority_identity\" : \"$authority_identity_full\"}" >"$PROJECT_JSON_PATH" diff --git a/implementations/rust/ockam/ockam_command/tests/bats/credentials.bats b/implementations/rust/ockam/ockam_command/tests/bats/credentials.bats index b169c59d2cd..deb6ac1a7f4 100644 --- a/implementations/rust/ockam/ockam_command/tests/bats/credentials.bats +++ b/implementations/rust/ockam/ockam_command/tests/bats/credentials.bats @@ -33,11 +33,11 @@ teardown() { run_success "$OCKAM" credential show smart_nyc_cred assert_output --partial "Credential: smart_nyc_cred" - assert_output --partial "Attributes: {\"application\": \"Smart Factory\", \"city\": \"New York\"" + assert_output --partial "{\"application\": \"Smart Factory\", \"city\": \"New York\"" run_success "$OCKAM" credential list assert_output --partial "Credential: smart_nyc_cred" - assert_output --partial "Attributes: {\"application\": \"Smart Factory\", \"city\": \"New York\"" + assert_output --partial "{\"application\": \"Smart Factory\", \"city\": \"New York\"" } @test "credential - verify and store reject invalid credentials" { @@ -46,7 +46,7 @@ teardown() { idt1_short=$($OCKAM identity show i1) # create an invalid credential - echo "FOOBAR" >"$OCKAM_HOME/bad_credential" + echo "aabbcc" >"$OCKAM_HOME/bad_credential" run_success "$OCKAM" credential verify --issuer "$idt1_short" --credential-path "$OCKAM_HOME/bad_credential" assert_output --partial "false" diff --git a/implementations/rust/ockam/ockam_command/tests/bats/identity.bats b/implementations/rust/ockam/ockam_command/tests/bats/identity.bats index 4a2c60207ab..a9af0ca7b42 100644 --- a/implementations/rust/ockam/ockam_command/tests/bats/identity.bats +++ b/implementations/rust/ockam/ockam_command/tests/bats/identity.bats @@ -18,12 +18,12 @@ teardown() { i=$(random_str) run_success "$OCKAM" identity create "${i}" run_success "$OCKAM" identity show "${i}" - assert_output --regexp '^P' + assert_output --regexp '^I' run_success "$OCKAM" identity show "${i}" --full - assert_output --partial "Change History" - assert_output --partial "identifier" - assert_output --partial "signatures" + assert_output --partial "Change[0]:" + assert_output --partial "Identifier: " + assert_output --partial "primary_public_key: " } @test "identity - CRUD" { diff --git a/implementations/rust/ockam/ockam_command/tests/bats/nodes.bats b/implementations/rust/ockam/ockam_command/tests/bats/nodes.bats index 328b6f3032f..5ad200586e8 100644 --- a/implementations/rust/ockam/ockam_command/tests/bats/nodes.bats +++ b/implementations/rust/ockam/ockam_command/tests/bats/nodes.bats @@ -63,8 +63,8 @@ force_kill_node() { run_failure "$OCKAM" service start verifier --addr my_verifier --at n1 # Check we can start service, but only once with the same name - run_success "$OCKAM" service start credentials --addr my_credentials --at n1 --identity 0134dabe4f886af3bd5d2b3ab50891a6dfe90c99099668ce8cb680888cac7d67db000547c93239ba3d818ec26c9cdadd2a35cbdf1fa3b6d1a731e06164b1079fb7b8084f434b414d5f524b03012000000020e1acf2670f5bfc34c466910949618c68a53183976e8e57d5fc07b6a3d02d22a3030101407e6332d0deeccf8d12de9972e31b54200f1597db2a195d08b15b251d6293c180611c66acc26913a16d5ea5536227c8baefb4fa95bd709212fdc1ca4fc3370e02 - run_failure "$OCKAM" service start credentials --addr my_credentials --at n1 --identity 0134dabe4f886af3bd5d2b3ab50891a6dfe90c99099668ce8cb680888cac7d67db000547c93239ba3d818ec26c9cdadd2a35cbdf1fa3b6d1a731e06164b1079fb7b8084f434b414d5f524b03012000000020e1acf2670f5bfc34c466910949618c68a53183976e8e57d5fc07b6a3d02d22a3030101407e6332d0deeccf8d12de9972e31b54200f1597db2a195d08b15b251d6293c180611c66acc26913a16d5ea5536227c8baefb4fa95bd709212fdc1ca4fc3370e02 + run_success "$OCKAM" service start credentials --addr my_credentials --at n1 --identity 81a201583ba20101025835a4028201815820984249b1a11c6933002d02019f408ec0bdb7f3058068227a472986ea588ec67003f4041a64e49a5e051a77b09d5e02820181584002c2cc20acf3d7d59d67c420c3c29d4ebb1ebe483bfaba7fb046f59de96284ebfb570d17539e5d4989b74f22af12261b9c1d5eecf731e2d19907b092f6c47d04 + run_failure "$OCKAM" service start credentials --addr my_credentials --at n1 --identity 81a201583ba20101025835a4028201815820984249b1a11c6933002d02019f408ec0bdb7f3058068227a472986ea588ec67003f4041a64e49a5e051a77b09d5e02820181584002c2cc20acf3d7d59d67c420c3c29d4ebb1ebe483bfaba7fb046f59de96284ebfb570d17539e5d4989b74f22af12261b9c1d5eecf731e2d19907b092f6c47d04 } @test "node - is restarted with default services" { diff --git a/implementations/rust/ockam/ockam_command/tests/bats/vault.bats b/implementations/rust/ockam/ockam_command/tests/bats/vault.bats index b85c82bdd6c..7527fb801d8 100644 --- a/implementations/rust/ockam/ockam_command/tests/bats/vault.bats +++ b/implementations/rust/ockam/ockam_command/tests/bats/vault.bats @@ -23,17 +23,17 @@ teardown() { assert_output --partial "Type: OCKAM" v2=$(random_str) - run_success "$OCKAM" vault create "${v2}" --aws-kms + run_success "$OCKAM" vault create "${v2}" run_success "$OCKAM" vault show "${v2}" assert_output --partial "Name: ${v2}" - assert_output --partial "Type: AWS KMS" + assert_output --partial "Type: OCKAM" run_success "$OCKAM" vault list assert_output --partial "Vault ${v1}" assert_output --partial "Type OCKAM" assert_output --partial "Vault ${v2}" - assert_output --partial "Type AWS KMS" + assert_output --partial "Type OCKAM" } @test "vault - CRUD" { diff --git a/implementations/rust/ockam/ockam_command/tests/fixtures/user.enrollment.ticket b/implementations/rust/ockam/ockam_command/tests/fixtures/user.enrollment.ticket index 39c9754bdf6..8933d082b2d 100644 --- a/implementations/rust/ockam/ockam_command/tests/fixtures/user.enrollment.ticket +++ b/implementations/rust/ockam/ockam_command/tests/fixtures/user.enrollment.ticket @@ -1 +1 @@ -7b226f6e655f74696d655f636f6465223a2263306633656163373862383238633665346564316263393935626261626431653432373436653332343137373332373564303230313165326137326263623133222c2270726f6a656374223a7b226e6f64655f726f757465223a222f646e73616464722f6b38732d6875626465762d6e67696e78696e672d383536373330343661622d363630656539343138303064346131312e656c622e75732d776573742d312e616d617a6f6e6177732e636f6d2f7463702f343031312f736572766963652f617069222c226964223a2232646163363536342d656537382d346261622d623938322d643432306238646261643966222c226e616d65223a2264656661756c74222c226964656e746974795f6964223a225030373461353735633839376163363534333431376235373935663836326434373836383932386639396634386438326165613661643230303637396532326131222c22617574686f72697479223a7b226964223a225035626265373235633934346133336439343566376530643363663235633036333066393232653030356563373538623931653063633838653565633030636131222c2261646472657373223a222f646e73616464722f6b38732d6875626465762d6e67696e78696e672d383536373330343661622d363630656539343138303064346131312e656c622e75732d776573742d312e616d617a6f6e6177732e636f6d2f7463702f343031322f736572766963652f617069222c226964656e74697479223a5b312c3136302c31362c3139352c37352c3137332c3231362c322c39392c3131322c3232312c32382c33382c312c3133312c3234332c32322c3133372c3132382c3139362c3136302c3231332c362c37352c3233302c3131342c3233382c3134362c34362c35372c3232322c3135332c3231362c302c352c37312c3230312c35302c35372c3138362c36312c3132392c3134322c3139342c3130382c3135362c3231382c3232312c34322c35332c3230332c3232332c33312c3136332c3138322c3230392c3136372c34392c3232342c39372c3130302c3137372c372c3135392c3138332c3138342c382c37392c36372c37352c36352c37372c39352c38322c37352c332c312c33322c302c302c302c33322c3131342c3232302c3139392c32312c38392c3232332c392c3134382c32382c352c36392c31342c32322c3235302c38332c38342c36312c3234392c31392c3130342c302c3134302c3135322c32372c3131312c3132312c36362c39312c39362c35332c3133322c3234352c332c312c312c36342c372c3233342c3138392c38302c392c35342c302c3132302c3137352c3136302c3233302c3138312c3133372c37392c3132312c3133362c3131392c3230352c352c31372c3138322c3139312c3134352c3130302c3235322c3231332c3133312c37372c3230302c3234342c32372c3134372c382c34332c3132312c35382c38382c31392c3131342c3139322c34342c3137322c362c37312c382c3134342c35312c3235312c3230352c3234392c39312c35302c3231362c3131332c3139312c31362c31302c3130352c36362c33362c3135352c3133362c332c31355d7d2c226f6b7461223a6e756c6c7d2c2274727573745f636f6e74657874223a6e756c6c7d +7b226f6e655f74696d655f636f6465223a2263306633656163373862383238633665346564316263393935626261626431653432373436653332343137373332373564303230313165326137326263623133222c2270726f6a656374223a7b226e6f64655f726f757465223a222f646e73616464722f6b38732d6875626465762d6e67696e78696e672d383536373330343661622d363630656539343138303064346131312e656c622e75732d776573742d312e616d617a6f6e6177732e636f6d2f7463702f343031312f736572766963652f617069222c226964223a2232646163363536342d656537382d346261622d623938322d643432306238646261643966222c226e616d65223a2264656661756c74222c226964656e746974795f6964223a224939393535313030383963336632343831326265633337356438333065303130373631666539666432222c22617574686f72697479223a7b226964223a224936656439666465326439613333646539336166366132643666616265653765383865353630323537222c2261646472657373223a222f646e73616464722f6b38732d6875626465762d6e67696e78696e672d383536373330343661622d363630656539343138303064346131312e656c622e75732d776573742d312e616d617a6f6e6177732e636f6d2f7463702f343031322f736572766963652f617069222c226964656e74697479223a2238316132303135383362613230313031303235383335613430323832303138313538323062633633666661316337343834636533386464333430373933626166613861333831626637633736646534386138363766643832623266353562393962383061303366343034316136346462383839643035316137376137386239643032383230313831353834306131336332386539366563393732343539383738343666663032363639313464643339383739383234643234363532393631646131366633363930663963303736306139643439303836633130373730343664643139393138393062656130653935373764613436643865323633316238636263316466383762356164373061227d2c226f6b7461223a6e756c6c7d2c2274727573745f636f6e74657874223a6e756c6c7d diff --git a/implementations/rust/ockam/ockam_ffi/src/vault.rs b/implementations/rust/ockam/ockam_ffi/src/vault.rs index 30f28678017..fb8601ba023 100644 --- a/implementations/rust/ockam/ockam_ffi/src/vault.rs +++ b/implementations/rust/ockam/ockam_ffi/src/vault.rs @@ -1,15 +1,18 @@ use crate::vault_types::{FfiSecretAttributes, SecretKeyHandle}; use crate::{check_buffer, FfiError, FfiOckamError}; use crate::{FfiVaultFatPointer, FfiVaultType}; -use core::{future::Future, result::Result as StdResult, slice}; -use futures::future::join_all; -use lazy_static::lazy_static; + use ockam_core::compat::collections::BTreeMap; -use ockam_core::compat::sync::Arc; +use ockam_core::compat::sync::{Arc, RwLock}; use ockam_core::{Error, Result}; -use ockam_vault::{AsymmetricVault, KeyId, PublicKey, Secret, SecretAttributes, SymmetricVault}; -use ockam_vault::{EphemeralSecretsStore, SecretsStoreReader, Vault}; -use tokio::{runtime::Runtime, sync::RwLock, task}; +use ockam_vault::{ + KeyId, PublicKey, Secret, SecretAttributes, SecureChannelVault, SoftwareSecureChannelVault, + SoftwareVerifyingVault, +}; + +use core::{future::Future, result::Result as StdResult, slice}; +use lazy_static::lazy_static; +use tokio::{runtime::Runtime, task}; #[derive(Default)] struct SecretsMapping { @@ -39,23 +42,35 @@ impl SecretsMapping { } } -#[derive(Clone, Default)] +#[derive(Clone)] struct VaultEntry { - vault: Vault, + secure_channel_vault: Arc, + // TODO: Remove sha256 from FFI and remove that dependency + verifying_vault: Arc, secrets_mapping: Arc>, } +impl Default for VaultEntry { + fn default() -> Self { + Self { + secure_channel_vault: SoftwareSecureChannelVault::create(), + verifying_vault: SoftwareVerifyingVault::create(), + secrets_mapping: Default::default(), + } + } +} + impl VaultEntry { - async fn insert(&self, key_id: KeyId) -> u64 { - self.secrets_mapping.write().await.insert(key_id) + fn insert(&self, key_id: KeyId) -> u64 { + self.secrets_mapping.write().unwrap().insert(key_id) } - async fn get(&self, index: u64) -> Result { - self.secrets_mapping.read().await.get(index) + fn get(&self, index: u64) -> Result { + self.secrets_mapping.read().unwrap().get(index) } - async fn take(&self, index: u64) -> Result { - self.secrets_mapping.write().await.take(index) + fn take(&self, index: u64) -> Result { + self.secrets_mapping.write().unwrap().take(index) } } @@ -79,12 +94,12 @@ where }) } -async fn get_vault_entry(context: FfiVaultFatPointer) -> Result { +fn get_vault_entry(context: FfiVaultFatPointer) -> Result { match context.vault_type() { FfiVaultType::Software => { let item = SOFTWARE_VAULTS .read() - .await + .unwrap() .get(context.handle() as usize) .ok_or(FfiError::VaultNotFound)? .clone(); @@ -99,11 +114,11 @@ async fn get_vault_entry(context: FfiVaultFatPointer) -> Result { pub extern "C" fn ockam_vault_default_init(context: &mut FfiVaultFatPointer) -> FfiOckamError { handle_panics(|| { // TODO: handle logging - let handle = block_future(async move { - let mut write_lock = SOFTWARE_VAULTS.write().await; - write_lock.push(Default::default()); - write_lock.len() - 1 - }); + + let mut write_lock = SOFTWARE_VAULTS.write().unwrap(); + write_lock.push(Default::default()); + let handle = write_lock.len() - 1; + drop(write_lock); *context = FfiVaultFatPointer::new(handle as u64, FfiVaultType::Software); @@ -126,10 +141,9 @@ pub extern "C" fn ockam_vault_sha256( let input = unsafe { core::slice::from_raw_parts(input, input_length as usize) }; - let res = block_future(async move { - let entry = get_vault_entry(context).await?; - Ok::<[u8; 32], Error>(entry.vault.compute_sha256(input)) - })?; + let entry = get_vault_entry(context)?; + + let res = entry.verifying_vault.sha256_sync(input)?; unsafe { std::ptr::copy_nonoverlapping(res.as_ptr(), digest, res.len()); @@ -147,15 +161,19 @@ pub extern "C" fn ockam_vault_secret_generate( attributes: FfiSecretAttributes, ) -> FfiOckamError { handle_panics(|| { - *secret = block_future(async move { - let entry = get_vault_entry(context).await?; - let atts = attributes.try_into()?; - let key_id = entry.vault.create_ephemeral_secret(atts).await?; + let entry = get_vault_entry(context)?; + let atts = attributes.try_into()?; + + let entry_clone = entry.clone(); + let key_id = block_future(async move { + entry_clone + .secure_channel_vault + .generate_ephemeral_secret(atts) + .await + })?; - let index = entry.insert(key_id).await; + *secret = entry.insert(key_id); - Ok::(index) - })?; Ok(()) }) } @@ -171,19 +189,21 @@ pub extern "C" fn ockam_vault_secret_import( ) -> FfiOckamError { handle_panics(|| { check_buffer!(input, input_length); - *secret = block_future(async move { - let entry = get_vault_entry(context).await?; - let atts = attributes.try_into()?; - - let secret_data = unsafe { core::slice::from_raw_parts(input, input_length as usize) }; - - let secret = Secret::new(secret_data.to_vec()); - let key_id = entry.vault.import_ephemeral_secret(secret, atts).await?; + let entry = get_vault_entry(context)?; + let atts = attributes.try_into()?; + let secret_data = unsafe { core::slice::from_raw_parts(input, input_length as usize) }; + let secret_data = Secret::new(secret_data.to_vec()); + + let entry_clone = entry.clone(); + let key_id = block_future(async move { + entry_clone + .secure_channel_vault + .import_ephemeral_secret(secret_data, atts) + .await + })?; - let index = entry.insert(key_id).await; + *secret = entry.insert(key_id); - Ok::(index) - })?; Ok(()) }) } @@ -199,24 +219,19 @@ pub extern "C" fn ockam_vault_secret_export( ) -> FfiOckamError { *output_buffer_length = 0; handle_panics(|| { - block_future(async move { - let entry = get_vault_entry(context).await?; - let key_id = entry.get(secret).await?; - let key = entry - .vault - .get_ephemeral_secret(&key_id, "secret from ffi") - .await?; - let key = key.secret().as_ref(); - if output_buffer_size < key.len() as u32 { - return Err(FfiError::BufferTooSmall.into()); - } - *output_buffer_length = key.len() as u32; + let entry = get_vault_entry(context)?; + let key_id = entry.get(secret)?; + let key = entry.secure_channel_vault.get_ephemeral_secret(&key_id)?; + let key = key.secret().as_ref(); + if output_buffer_size < key.len() as u32 { + return Err(FfiError::BufferTooSmall.into()); + } + *output_buffer_length = key.len() as u32; + + unsafe { + std::ptr::copy_nonoverlapping(key.as_ptr(), output_buffer, key.len()); + }; - unsafe { - std::ptr::copy_nonoverlapping(key.as_ptr(), output_buffer, key.len()); - }; - Ok::<(), Error>(()) - })?; Ok(()) }) } @@ -232,20 +247,21 @@ pub extern "C" fn ockam_vault_secret_publickey_get( ) -> FfiOckamError { *output_buffer_length = 0; handle_panics(|| { - block_future(async move { - let entry = get_vault_entry(context).await?; - let key_id = entry.get(secret).await?; - let key = entry.vault.get_public_key(&key_id).await?; - if output_buffer_size < key.data().len() as u32 { - return Err(FfiError::BufferTooSmall.into()); - } - *output_buffer_length = key.data().len() as u32; + let entry = get_vault_entry(context)?; + let key_id = entry.get(secret)?; + + let key = + block_future(async move { entry.secure_channel_vault.get_public_key(&key_id).await })?; + + if output_buffer_size < key.data().len() as u32 { + return Err(FfiError::BufferTooSmall.into()); + } + *output_buffer_length = key.data().len() as u32; + + unsafe { + std::ptr::copy_nonoverlapping(key.data().as_ptr(), output_buffer, key.data().len()); + }; - unsafe { - std::ptr::copy_nonoverlapping(key.data().as_ptr(), output_buffer, key.data().len()); - }; - Ok::<(), Error>(()) - })?; Ok(()) }) } @@ -258,12 +274,17 @@ pub extern "C" fn ockam_vault_secret_attributes_get( attributes: &mut FfiSecretAttributes, ) -> FfiOckamError { handle_panics(|| { + let entry = get_vault_entry(context)?; + let key_id = entry.get(secret)?; + *attributes = block_future(async move { - let entry = get_vault_entry(context).await?; - let key_id = entry.get(secret).await?; - let atts = entry.vault.get_secret_attributes(&key_id).await?; - Ok::(atts.into()) - })?; + entry + .secure_channel_vault + .get_secret_attributes(&key_id) + .await + })? + .into(); + Ok(()) }) } @@ -274,15 +295,14 @@ pub extern "C" fn ockam_vault_secret_destroy( context: FfiVaultFatPointer, secret: SecretKeyHandle, ) -> FfiOckamError { - match block_future(async move { - let entry = get_vault_entry(context).await?; - let key_id = entry.take(secret).await?; - entry.vault.delete_ephemeral_secret(key_id).await?; - Ok::<(), Error>(()) - }) { - Ok(_) => FfiOckamError::none(), - Err(err) => err.into(), - } + handle_panics(|| { + let entry = get_vault_entry(context)?; + let key_id = entry.take(secret)?; + + block_future(async move { entry.secure_channel_vault.delete_secret(key_id).await })?; + + Ok(()) + }) } /// Perform an ECDH operation on the supplied Ockam Vault `secret` and `peer_publickey`. The result @@ -300,14 +320,20 @@ pub extern "C" fn ockam_vault_ecdh( let peer_publickey = unsafe { core::slice::from_raw_parts(peer_publickey, peer_publickey_length as usize) }; + let entry = get_vault_entry(context)?; + let key_id = entry.get(secret)?; *shared_secret = block_future(async move { - let entry = get_vault_entry(context).await?; - let key_id = entry.get(secret).await?; - let atts = entry.vault.get_secret_attributes(&key_id).await?; + let atts = entry + .secure_channel_vault + .get_secret_attributes(&key_id) + .await?; let pubkey = PublicKey::new(peer_publickey.to_vec(), atts.secret_type()); - let shared_ctx = entry.vault.ec_diffie_hellman(&key_id, &pubkey).await?; - let index = entry.insert(shared_ctx).await; + let shared_ctx = entry + .secure_channel_vault + .ec_diffie_hellman(&key_id, &pubkey) + .await?; + let index = entry.insert(shared_ctx); Ok::(index) })?; Ok(()) @@ -328,25 +354,26 @@ pub extern "C" fn ockam_vault_hkdf_sha256( handle_panics(|| { let derived_outputs_count = derived_outputs_count as usize; - block_future(async move { - let entry = get_vault_entry(context).await?; - let salt_key_id = entry.get(salt).await?; - let ikm_key_id = if input_key_material.is_null() { - None - } else { - let ctx = unsafe { entry.get(*input_key_material).await? }; - Some(ctx) - }; - let ikm_key_id = ikm_key_id.as_ref(); - - let array: &[FfiSecretAttributes] = - unsafe { slice::from_raw_parts(derived_outputs_attributes, derived_outputs_count) }; - - let mut output_attributes = Vec::::with_capacity(array.len()); - for x in array.iter() { - output_attributes.push(SecretAttributes::try_from(*x)?); - } + let entry = get_vault_entry(context)?; + let salt_key_id = entry.get(salt)?; + let ikm_key_id = if input_key_material.is_null() { + None + } else { + let ctx = unsafe { entry.get(*input_key_material)? }; + Some(ctx) + }; + let ikm_key_id = ikm_key_id.as_ref(); + let array: &[FfiSecretAttributes] = + unsafe { slice::from_raw_parts(derived_outputs_attributes, derived_outputs_count) }; + + let mut output_attributes = Vec::::with_capacity(array.len()); + for x in array.iter() { + output_attributes.push(SecretAttributes::try_from(*x)?); + } + + let entry_clone = entry.clone(); + let hkdf_output = block_future(async move { // TODO: Hardcoded to be empty for now because any changes // to the C layer requires an API change. // This change was necessary to implement Enrollment since the info string is not @@ -358,23 +385,23 @@ pub extern "C" fn ockam_vault_hkdf_sha256( // I don't like the idea of yelling at consumers through comments. // Instead the vault could be encapsulated in channels and key exchanges. // Either way, I don't want to change the API until this decision is finalized. - let hkdf_output = entry - .vault + entry_clone + .secure_channel_vault .hkdf_sha256(&salt_key_id, b"", ikm_key_id, output_attributes) - .await?; - - let hkdf_output: Vec = - join_all(hkdf_output.into_iter().map(|x| entry.insert(x))).await; - - unsafe { - std::ptr::copy_nonoverlapping( - hkdf_output.as_ptr(), - derived_outputs, - derived_outputs_count, - ) - }; - Ok::<(), Error>(()) + .await })?; + + let hkdf_output: Vec = + hkdf_output.into_iter().map(|x| entry.insert(x)).collect(); + + unsafe { + std::ptr::copy_nonoverlapping( + hkdf_output.as_ptr(), + derived_outputs, + derived_outputs_count, + ) + }; + Ok(()) }) } @@ -405,30 +432,27 @@ pub extern "C" fn ockam_vault_aead_aes_gcm_encrypt( let plaintext = unsafe { core::slice::from_raw_parts(plaintext, plaintext_length as usize) }; - block_future(async move { - let entry = get_vault_entry(context).await?; - let key_id = entry.get(secret).await?; - let mut nonce_vec = vec![0; 12 - 8]; - nonce_vec.extend_from_slice(&nonce.to_be_bytes()); - let ciphertext = entry - .vault - .aead_aes_gcm_encrypt(&key_id, plaintext, &nonce_vec, additional_data) - .await?; + let entry = get_vault_entry(context)?; + let key_id = entry.get(secret)?; + let mut nonce_vec = vec![0; 12 - 8]; + nonce_vec.extend_from_slice(&nonce.to_be_bytes()); - if ciphertext_and_tag_size < ciphertext.len() as u32 { - return Err(FfiError::BufferTooSmall.into()); - } - *ciphertext_and_tag_length = ciphertext.len() as u32; - - unsafe { - std::ptr::copy_nonoverlapping( - ciphertext.as_ptr(), - ciphertext_and_tag, - ciphertext.len(), - ) - }; - Ok::<(), Error>(()) + let ciphertext = block_future(async move { + entry + .secure_channel_vault + .aead_aes_gcm_encrypt(&key_id, plaintext, &nonce_vec, additional_data) + .await })?; + + if ciphertext_and_tag_size < ciphertext.len() as u32 { + return Err(FfiError::BufferTooSmall.into()); + } + *ciphertext_and_tag_length = ciphertext.len() as u32; + + unsafe { + std::ptr::copy_nonoverlapping(ciphertext.as_ptr(), ciphertext_and_tag, ciphertext.len()) + }; + Ok(()) }) } @@ -460,23 +484,25 @@ pub extern "C" fn ockam_vault_aead_aes_gcm_decrypt( core::slice::from_raw_parts(ciphertext_and_tag, ciphertext_and_tag_length as usize) }; - block_future(async move { - let entry = get_vault_entry(context).await?; - let key_id = entry.get(secret).await?; - let mut nonce_vec = vec![0; 12 - 8]; - nonce_vec.extend_from_slice(&nonce.to_be_bytes()); - let plain = entry - .vault - .aead_aes_gcm_decrypt(&key_id, ciphertext_and_tag, &nonce_vec, additional_data) - .await?; - if plaintext_size < plain.len() as u32 { - return Err(FfiError::BufferTooSmall.into()); - } - *plaintext_length = plain.len() as u32; + let entry = get_vault_entry(context)?; + let key_id = entry.get(secret)?; + let mut nonce_vec = vec![0; 12 - 8]; + nonce_vec.extend_from_slice(&nonce.to_be_bytes()); - unsafe { std::ptr::copy_nonoverlapping(plain.as_ptr(), plaintext, plain.len()) }; - Ok::<(), Error>(()) + let plain = block_future(async move { + entry + .secure_channel_vault + .aead_aes_gcm_decrypt(&key_id, ciphertext_and_tag, &nonce_vec, additional_data) + .await })?; + + if plaintext_size < plain.len() as u32 { + return Err(FfiError::BufferTooSmall.into()); + } + *plaintext_length = plain.len() as u32; + + unsafe { std::ptr::copy_nonoverlapping(plain.as_ptr(), plaintext, plain.len()) }; + Ok(()) }) } @@ -489,7 +515,7 @@ pub extern "C" fn ockam_vault_deinit(context: FfiVaultFatPointer) -> FfiOckamErr match context.vault_type() { FfiVaultType::Software => { let handle = context.handle() as usize; - let mut v = SOFTWARE_VAULTS.write().await; + let mut v = SOFTWARE_VAULTS.write().unwrap(); if handle < v.len() { v.remove(handle); Ok(()) diff --git a/implementations/rust/ockam/ockam_identity/Cargo.toml b/implementations/rust/ockam/ockam_identity/Cargo.toml index f9b643f2bf7..27f6e234a3b 100644 --- a/implementations/rust/ockam/ockam_identity/Cargo.toml +++ b/implementations/rust/ockam/ockam_identity/Cargo.toml @@ -89,7 +89,8 @@ tracing = { version = "0.1", default_features = false } [dev-dependencies] ockam_transport_tcp = { path = "../ockam_transport_tcp" } -ockam_vault = { path = "../ockam_vault", version = "^0.81.0" } +ockam_vault = { path = "../ockam_vault" } +ockam_vault_aws = { path = "../ockam_vault_aws" } quickcheck = "1.0.3" quickcheck_macros = "1.0.0" rand_xorshift = "0" diff --git a/implementations/rust/ockam/ockam_identity/src/credential/credential.rs b/implementations/rust/ockam/ockam_identity/src/credential/credential.rs deleted file mode 100644 index 58d3b141541..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/credential/credential.rs +++ /dev/null @@ -1,161 +0,0 @@ -use crate::CredentialData; -use core::fmt; -use minicbor::{Decode, Encode}; -use ockam_core::compat::{string::String, vec::Vec}; -use ockam_core::Result; -use serde::de::Error; -use serde::{Deserialize, Deserializer}; -use serde::{Serialize, Serializer}; -#[cfg(feature = "std")] -use std::ops::Deref; - -#[cfg(feature = "tag")] -use crate::TypeTag; -use crate::Unverified; - -/// Credential data + signature for that data -#[derive(Clone, Debug, Decode, Encode, PartialEq, Eq)] -#[rustfmt::skip] -#[cbor(map)] -pub struct Credential { - #[cfg(feature = "tag")] - #[n(0)] tag: TypeTag<3796735>, - /// CBOR-encoded [`CredentialData`]. - #[cbor(with = "minicbor::bytes")] - #[n(1)] pub data: Vec, - /// Cryptographic signature of attributes data. - #[cbor(with = "minicbor::bytes")] - #[n(2)] pub signature: Vec, -} - -impl Credential { - /// Return the signature of a credential - pub fn signature(&self) -> &[u8] { - &self.signature - } - - /// Return the serialized data of a credential - pub fn unverified_data(&self) -> &[u8] { - &self.data - } - - pub(crate) fn new(data: Vec, signature: Vec) -> Self { - Credential { - #[cfg(feature = "tag")] - tag: TypeTag, - data, - signature, - } - } -} - -impl fmt::Display for Credential { - #[cfg(feature = "std")] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let data = CredentialData::::try_from(self) - .map_err(|_| fmt::Error)? - .into_verified(); - write!(f, "{}", data)?; - writeln!(f, "Signature: {}", hex::encode(self.signature.deref())) - } - - #[cfg(not(feature = "std"))] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Credential {{ ... }}") - } -} - -impl Serialize for Credential { - fn serialize(&self, ser: S) -> Result { - let bytes = minicbor::to_vec(self).expect("encoding credential to vec never errors"); - if ser.is_human_readable() { - ser.serialize_str(&hex::encode(&bytes)) - } else { - ser.serialize_bytes(&bytes) - } - } -} - -impl<'a> Deserialize<'a> for Credential { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'a>, - { - let bytes: Vec = if deserializer.is_human_readable() { - let s = String::deserialize(deserializer)?; - hex::decode(s).map_err(D::Error::custom)? - } else { - >::deserialize(deserializer)? - }; - minicbor::decode(&bytes).map_err(D::Error::custom) - } -} - -impl TryFrom<&Credential> for CredentialData { - type Error = minicbor::decode::Error; - - fn try_from(value: &Credential) -> Result { - minicbor::decode(value.clone().data.as_slice()) - } -} - -#[cfg(test)] -mod test { - use super::*; - use quickcheck::{Arbitrary, Gen}; - use quickcheck_macros::quickcheck; - use serde_json; - - #[quickcheck] - fn test_serialization_roundtrip(credential: Credential) -> bool { - let serialized = serde_bare::to_vec(&credential).unwrap(); - let actual: Credential = serde_bare::from_slice(serialized.as_slice()).unwrap(); - actual == credential - } - - #[test] - fn test_serialization() { - // this test makes sure that we are using the minicbor Bytes encoder - // for the Credential fields - let credential = Credential::new(vec![1, 2, 3], vec![5, 6, 7]); - let serialized = serde_bare::to_vec(&credential).unwrap(); - let expected: Vec = vec![11, 162, 1, 67, 1, 2, 3, 2, 67, 5, 6, 7]; - assert_eq!(serialized, expected) - } - - #[quickcheck] - fn test_serialization_roundtrip_human_readable(credential: Credential) -> bool { - let serialized = serde_json::to_string(&credential).unwrap(); - let actual: Credential = serde_json::from_str(serialized.as_str()).unwrap(); - actual == credential - } - - #[test] - fn test_display_credential() { - let credential_data = super::super::credential_data::test::make_credential_data(); - let data = minicbor::to_vec(credential_data).unwrap(); - let credential = Credential::new(data, vec![1, 2, 3]); - - let actual = format!("{credential}"); - let expected = r#"Schema: 1 -Subject: P6474cfdbf547240b6d716bff89c976810859bc3f47be8ea620df12a392ea6cb7 -Issuer: P0db4fec87ff764485f1311e68d6f474e786f1fdbafcd249a5eb73dd681fd1d5d (OCKAM_RK) -Created: 1970-01-01T00:02:00Z -Expires: 1970-01-01T00:03:20Z -Attributes: {"name": "value"} -Signature: 010203 -"#; - assert_eq!(actual, expected); - } - - impl Arbitrary for Credential { - fn arbitrary(g: &mut Gen) -> Self { - Credential::new(>::arbitrary(g), >::arbitrary(g)) - } - - /// there is no meaningful shrinking in general for a credential - fn shrink(&self) -> Box> { - Box::new(std::iter::empty()) - } - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/credential/credential_builder.rs b/implementations/rust/ockam/ockam_identity/src/credential/credential_builder.rs deleted file mode 100644 index 2c10240ab27..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/credential/credential_builder.rs +++ /dev/null @@ -1,97 +0,0 @@ -use core::marker::PhantomData; -use core::time::Duration; -use ockam_core::compat::collections::HashMap; -use ockam_core::compat::string::String; -use ockam_core::errcode::{Kind, Origin}; -use ockam_core::{Error, Result}; - -use crate::credential::{ - Attributes, CredentialData, SchemaId, Timestamp, Verified, MAX_CREDENTIAL_VALIDITY, -}; -use crate::identity::identity_change::IdentityChangeConstants; -use crate::identity::IdentityIdentifier; - -#[cfg(feature = "tag")] -use crate::TypeTag; - -/// Convenience structure to create [`Credential`](crate::Credential)s. -pub struct CredentialBuilder { - pub(crate) schema: Option, - pub(crate) attrs: Attributes, - pub(crate) subject: IdentityIdentifier, - pub(crate) issuer: IdentityIdentifier, - pub(crate) validity: Duration, -} - -impl CredentialBuilder { - pub(super) fn new( - subject: IdentityIdentifier, - issuer: IdentityIdentifier, - ) -> CredentialBuilder { - Self { - schema: None, - attrs: Attributes::default(), - subject, - issuer, - validity: MAX_CREDENTIAL_VALIDITY, - } - } - - /// - pub fn from_attributes( - subject: IdentityIdentifier, - issuer: IdentityIdentifier, - attrs: HashMap, - ) -> Self { - attrs - .iter() - .fold(CredentialData::builder(subject, issuer), |crd, (k, v)| { - crd.with_attribute(k, v.as_bytes()) - }) - } - - /// Add some key-value pair as credential attribute. - pub fn with_attribute(mut self, k: &str, v: &[u8]) -> Self { - self.attrs.put(k, v); - self - } - - /// Set the schema identifier of the credential. - pub fn with_schema(mut self, s: SchemaId) -> Self { - self.schema = Some(s); - self - } - - /// Set validity duration of the credential. - /// - /// # Panics - /// - /// If the given validity exceeds [`MAX_CREDENTIAL_VALIDITY`]. - pub fn valid_for(mut self, val: Duration) -> Self { - assert! { - val <= MAX_CREDENTIAL_VALIDITY, - "validity exceeds allowed maximum" - } - self.validity = val; - self - } - - /// Return a verified credential data, with a created timestamp - pub fn build(self) -> Result> { - let key_label = IdentityChangeConstants::ROOT_LABEL; - let now = Timestamp::now() - .ok_or_else(|| Error::new(Origin::Core, Kind::Internal, "invalid system time"))?; - let exp = Timestamp::add_seconds(&now, self.validity.as_secs()); - - Ok(CredentialData { - schema: self.schema, - attributes: self.attrs, - subject: self.subject, - issuer: self.issuer, - issuer_key_label: key_label.into(), - created: now, - expires: exp, - status: None::>, - }) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/credential/credential_data.rs b/implementations/rust/ockam/ockam_identity/src/credential/credential_data.rs deleted file mode 100644 index d27c68bef9d..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/credential/credential_data.rs +++ /dev/null @@ -1,410 +0,0 @@ -use crate::alloc::string::ToString; -use crate::identity::identity_change::IdentityChangeConstants; -use crate::identity::IdentityIdentifier; -use crate::CredentialBuilder; -use core::marker::PhantomData; -use core::time::Duration; -use minicbor::bytes::ByteVec; -use minicbor::{Decode, Encode}; -use ockam_core::compat::collections::HashMap; -use ockam_core::compat::{collections::BTreeMap, fmt, string::String, vec::Vec}; -use ockam_core::errcode::{Kind, Origin}; -use ockam_core::{Error, Result}; -use serde::{Deserialize, Serialize}; -#[cfg(feature = "std")] -use time::format_description::well_known::iso8601::{Iso8601, TimePrecision}; -#[cfg(feature = "std")] -use time::{Error::Format, OffsetDateTime}; - -#[cfg(feature = "tag")] -use crate::TypeTag; - -/// Identifier for the schema of a project credential -pub const PROJECT_MEMBER_SCHEMA: SchemaId = SchemaId(1); - -/// Set of attributes associated to a given identity issued by another identity -#[derive(Debug, Decode, Encode, Clone)] -#[rustfmt::skip] -#[cbor(map)] -pub struct CredentialData { - /// A schema identifier to allow distinguishing sets of attributes. - #[n(1)] pub(crate) schema: Option, - /// User-defined key-value pairs. - #[b(2)] pub(crate) attributes: Attributes, - /// The subject this credential is issued for. - #[n(3)] pub(crate) subject: IdentityIdentifier, - /// The entity that signed this credential. - #[n(4)] pub(crate) issuer: IdentityIdentifier, - /// The label of the issuer's public key. - #[b(5)] pub(crate) issuer_key_label: String, - /// The time when this credential was created. - #[n(6)] pub(crate) created: Timestamp, - /// The time this credential expires. - #[n(7)] pub(crate) expires: Timestamp, - /// Term to represent the verification status type. - #[n(8)] pub(crate) status: Option>, -} - -impl CredentialData { - /// Create a builder for a subject and an issuer, all other fields are optional and - /// can be set with the builder methods - pub fn builder(subject: IdentityIdentifier, issuer: IdentityIdentifier) -> CredentialBuilder { - CredentialBuilder::new(subject, issuer) - } - - /// Return a credential data struct with a fixed set of attributes - pub fn from_attributes( - subject: IdentityIdentifier, - issuer: IdentityIdentifier, - attrs: HashMap, - ) -> Result> { - CredentialBuilder::from_attributes(subject, issuer, attrs).build() - } -} - -impl CredentialData { - pub(crate) fn verify( - &self, - subject: &IdentityIdentifier, - issuer: &IdentityIdentifier, - now: Timestamp, - ) -> Result<()> { - if self.issuer_key_label != IdentityChangeConstants::ROOT_LABEL { - return Err(Error::new( - Origin::Application, - Kind::Invalid, - "invalid signing key", - )); - } - - if &self.issuer != issuer { - return Err(Error::new( - Origin::Application, - Kind::Invalid, - "unknown authority", - )); - } - - if &self.subject != subject { - return Err(Error::new( - Origin::Application, - Kind::Invalid, - "unknown subject", - )); - } - - if self.expires <= now { - return Err(Error::new( - Origin::Application, - Kind::Invalid, - "expired credential", - )); - } - - Ok(()) - } - - pub(crate) fn into_verified(self) -> CredentialData { - CredentialData { - schema: self.schema, - attributes: self.attributes, - subject: self.subject, - issuer: self.issuer, - issuer_key_label: self.issuer_key_label, - created: self.created, - expires: self.expires, - status: None::>, - } - } -} - -impl fmt::Display for CredentialData { - #[cfg(feature = "std")] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use time::format_description::well_known::iso8601; - - if let Some(schema_id) = self.schema { - writeln!(f, "Schema: {schema_id}")?; - } - writeln!(f, "Subject: {}", self.subject)?; - writeln!(f, "Issuer: {} ({})", self.issuer, self.issuer_key_label)?; - - let human_readable_time = - |time: Timestamp| match OffsetDateTime::from_unix_timestamp(u64::from(time) as i64) { - Ok(time) => { - match time.format( - &Iso8601::< - { - iso8601::Config::DEFAULT - .set_time_precision(TimePrecision::Second { - decimal_digits: None, - }) - .encode() - }, - >, - ) { - Ok(now_iso) => now_iso, - Err(_) => Format(time::error::Format::InvalidComponent("timestamp error")) - .to_string(), - } - } - Err(_) => Format(time::error::Format::InvalidComponent( - "unix time is invalid", - )) - .to_string(), - }; - writeln!(f, "Created: {}", human_readable_time(self.created))?; - writeln!(f, "Expires: {}", human_readable_time(self.expires))?; - write!(f, "Attributes: ")?; - f.debug_map() - .entries( - self.attributes - .iter() - .map(|(k, v)| (k, std::str::from_utf8(v).unwrap_or("**binary**"))), - ) - .finish()?; - writeln!(f) - } - - #[cfg(not(feature = "std"))] - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "{:?}", self) - } -} - -impl fmt::Display for SchemaId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -/// Maximum duration for a valid credential in seconds (30 days) -pub const MAX_CREDENTIAL_VALIDITY: Duration = Duration::from_secs(30 * 24 * 3600); - -/// Type to represent data of verified credential. -#[derive(Debug, Encode, Clone)] -pub enum Verified {} - -/// Type to represent data of unverified credential. -#[derive(Debug, Decode, Clone)] -pub enum Unverified {} - -impl CredentialData { - /// Return the credential schema - pub fn schema(&self) -> Option { - self.schema - } - - /// Return the credential subject - pub fn subject(&self) -> &IdentityIdentifier { - &self.subject - } - - /// Return the credential issuer - pub fn issuer(&self) -> &IdentityIdentifier { - &self.issuer - } - - /// Return the credential issuer - pub fn issuer_key_label(&self) -> &str { - &self.issuer_key_label - } - - /// Return the credential creation date - pub fn created_at(&self) -> Timestamp { - self.created - } - - /// Return the credential expiration date - pub fn expires_at(&self) -> Timestamp { - self.expires - } - - /// Return the identity attributes as a reference - pub fn attributes(&self) -> &Attributes { - &self.attributes - } - - /// Return the identity attributes - pub fn into_attributes(self) -> Attributes { - self.attributes - } -} - -impl CredentialData { - /// Return the issuer of a credential data when unverified - pub fn unverified_issuer(&self) -> &IdentityIdentifier { - &self.issuer - } - - /// Return the issuer key label of a credential data when unverified - pub fn unverified_key_label(&self) -> &str { - &self.issuer_key_label - } - - /// Return the subject of a credential data when unverified - pub fn unverified_subject(&self) -> &IdentityIdentifier { - &self.subject - } -} - -impl TryFrom<&[u8]> for CredentialData { - type Error = minicbor::decode::Error; - - fn try_from(value: &[u8]) -> Result { - minicbor::decode(value) - } -} - -/// User-defined key-value pairs. -#[derive(Debug, Clone, Default, Encode, Decode, PartialEq, Eq)] -#[rustfmt::skip] -#[cbor(map)] -pub struct Attributes { - #[cfg(feature = "tag")] - #[n(0)] tag: TypeTag<4724285>, - #[b(1)] attrs: BTreeMap, -} - -impl Attributes { - /// Create a new empty attribute set. - pub fn new() -> Self { - Attributes { - #[cfg(feature = "tag")] - tag: TypeTag, - attrs: BTreeMap::new(), - } - } - - /// Return true if this set of key / value is empty - pub fn is_empty(&self) -> bool { - self.attrs.is_empty() - } - - /// Return the number of key / values - pub fn len(&self) -> usize { - self.attrs.len() - } - - /// Add a key-value pair to the attribute set. - /// - /// If an entry with the same key exists it is replaced with the new value. - pub fn put(&mut self, k: &str, v: &[u8]) -> &mut Self { - self.attrs.insert(k.into(), v.to_vec().into()); - self - } - - /// Return the value associated to a given key - pub fn get(&self, k: &str) -> Option<&[u8]> { - self.attrs.get(k).map(|s| &***s) - } - - /// Return an iterator on the list of key / values - pub fn iter(&self) -> impl Iterator { - self.attrs.iter() - } - - //TODO: review the credential' attributes types. They are references and has lifetimes, - //etc, but in reality this is always just deserizalided (either from wire or from - //storage), so imho all that just add to the complexity without gaining much - pub(crate) fn as_map_vec_u8(&self) -> BTreeMap> { - self.attrs - .iter() - .map(|(k, v)| (k.to_string(), v.to_vec())) - .collect() - } -} - -/// A Unix timestamp (seconds since 1970-01-01 00:00:00 UTC) -#[derive( - Debug, Clone, Copy, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, -)] -#[cbor(transparent)] -pub struct Timestamp(#[n(0)] u64); - -impl Timestamp { - /// Create a new timestamp using the system time - #[cfg(feature = "std")] - pub fn now() -> Option { - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .ok() - .map(|d| Timestamp(d.as_secs())) - } - - pub(crate) fn add_seconds(&self, seconds: u64) -> Self { - Timestamp(self.0.saturating_add(seconds)) - } - - /// Create a new timestamp using the system time - #[cfg(not(feature = "std"))] - pub fn now() -> Option { - None - } - - /// Return the time elapsed between this timestamp and a previous one - pub fn elapsed(&self, since: Timestamp) -> Option { - (self.0 >= since.0).then(|| Duration::from_secs(self.0 - since.0)) - } - - /// Return the timestamp value as a number of seconds since the UNIX epoch time - pub fn unix_time(&self) -> u64 { - self.0 - } -} - -impl From for u64 { - fn from(t: Timestamp) -> Self { - t.0 - } -} - -/// A schema identifier allows discriminate sets of credential attributes. -#[derive(Debug, Clone, Copy, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[cbor(transparent)] -pub struct SchemaId(#[n(0)] pub u64); - -impl From for u64 { - fn from(s: SchemaId) -> Self { - s.0 - } -} - -#[cfg(test)] -pub(crate) mod test { - use super::*; - - #[test] - fn test_display_credential_data() { - let credential_data = make_credential_data(); - let actual = format!("{credential_data}"); - let expected = r#"Schema: 1 -Subject: P6474cfdbf547240b6d716bff89c976810859bc3f47be8ea620df12a392ea6cb7 -Issuer: P0db4fec87ff764485f1311e68d6f474e786f1fdbafcd249a5eb73dd681fd1d5d (OCKAM_RK) -Created: 1970-01-01T00:02:00Z -Expires: 1970-01-01T00:03:20Z -Attributes: {"name": "value"} -"#; - assert_eq!(actual, expected); - } - - pub(crate) fn make_credential_data() -> CredentialData { - let mut attributes = Attributes::new(); - attributes.put("name", "value".as_bytes()); - - CredentialData { - schema: Some(SchemaId(1)), - subject: IdentityIdentifier::from_hex( - "6474cfdbf547240b6d716bff89c976810859bc3f47be8ea620df12a392ea6cb7", - ), - issuer: IdentityIdentifier::from_hex( - "0db4fec87ff764485f1311e68d6f474e786f1fdbafcd249a5eb73dd681fd1d5d", - ), - attributes, - issuer_key_label: "OCKAM_RK".into(), - created: Timestamp(120), - expires: Timestamp(200), - status: None::>, - } - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/credential/mod.rs b/implementations/rust/ockam/ockam_identity/src/credential/mod.rs deleted file mode 100644 index ccfab67668a..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/credential/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -#[allow(clippy::module_inception)] -mod credential; -mod credential_builder; -mod credential_data; -mod one_time_code; - -pub use credential::*; -pub use credential_builder::*; -pub use credential_data::*; -pub use one_time_code::*; diff --git a/implementations/rust/ockam/ockam_identity/src/credentials/authority_service.rs b/implementations/rust/ockam/ockam_identity/src/credentials/authority_service.rs index 397f95c9574..6755877d203 100644 --- a/implementations/rust/ockam/ockam_identity/src/credentials/authority_service.rs +++ b/implementations/rust/ockam/ockam_identity/src/credentials/authority_service.rs @@ -1,40 +1,36 @@ use crate::credentials::credentials_retriever::CredentialsRetriever; -use crate::{ - Credential, Credentials, IdentitiesReader, Identity, IdentityError, IdentityIdentifier, - Timestamp, -}; +use crate::models::{CredentialAndPurposeKey, Identifier, TimestampInSeconds}; +use crate::utils::{add_seconds, now}; +use crate::{Credentials, IdentityError}; + use ockam_core::compat::sync::Arc; use ockam_core::compat::sync::RwLock; -use ockam_core::errcode::{Kind, Origin}; -use ockam_core::{Error, Result}; +use ockam_core::Result; use ockam_node::Context; /// An AuthorityService represents an authority which issued credentials #[derive(Clone)] pub struct AuthorityService { - identities_reader: Arc, - credentials: Arc, - identifier: IdentityIdentifier, + credentials: Arc, + identifier: Identifier, own_credential: Option>, inner_cache: Arc>>, } #[derive(Clone)] struct CachedCredential { - credential: Credential, - valid_until: Timestamp, + credential: CredentialAndPurposeKey, + valid_until: TimestampInSeconds, } impl AuthorityService { /// Create a new authority service pub fn new( - identities_reader: Arc, - credentials: Arc, - identifier: IdentityIdentifier, + credentials: Arc, + identifier: Identifier, own_credential: Option>, ) -> Self { Self { - identities_reader, credentials, identifier, own_credential, @@ -42,26 +38,19 @@ impl AuthorityService { } } - /// Return the Public Identity of the Authority - pub async fn identity(&self) -> Result { - self.identities_reader.get_identity(&self.identifier).await - } - /// Retrieve the credential for an identity within this authority pub async fn credential( &self, ctx: &Context, - for_identity: &IdentityIdentifier, - ) -> Result { + subject: &Identifier, + ) -> Result { { // check if we have a valid cached credential let guard = self.inner_cache.read().unwrap(); - let now = Timestamp::now().ok_or_else(|| { - Error::new(Origin::Application, Kind::Invalid, "invalid system time") - })?; + let now = now()?; if let Some(cache) = guard.as_ref() { // add an extra minute to have a bit of leeway for clock skew - if cache.valid_until > now.add_seconds(60) { + if cache.valid_until > add_seconds(&now, 60) { return Ok(cache.credential.clone()); } } @@ -72,19 +61,25 @@ impl AuthorityService { .own_credential .clone() .ok_or(IdentityError::UnknownAuthority)?; - let credential = retriever.retrieve(ctx, for_identity).await?; + let credential = retriever.retrieve(ctx, subject).await?; let credential_data = self .credentials - .verify_credential(for_identity, &[self.identity().await?], credential.clone()) + .credentials_verification() + .verify_credential(Some(subject), &[self.identifier.clone()], &credential) .await?; let mut guard = self.inner_cache.write().unwrap(); *guard = Some(CachedCredential { credential: credential.clone(), - valid_until: credential_data.expires, + valid_until: credential_data.credential_data.expires_at, }); Ok(credential) } + + /// Issuer [`Identifier`] + pub fn identifier(&self) -> &Identifier { + &self.identifier + } } diff --git a/implementations/rust/ockam/ockam_identity/src/credentials/credentials.rs b/implementations/rust/ockam/ockam_identity/src/credentials/credentials.rs index 5b2cf488d57..e066b12e533 100644 --- a/implementations/rust/ockam/ockam_identity/src/credentials/credentials.rs +++ b/implementations/rust/ockam/ockam_identity/src/credentials/credentials.rs @@ -1,118 +1,117 @@ -use crate::credential::{Credential, CredentialData, Timestamp, Verified}; -use crate::identities::{AttributesEntry, Identities}; -use crate::identity::{Identity, IdentityIdentifier}; -use crate::IdentityError; -use async_trait::async_trait; -use ockam_core::compat::boxed::Box; -use ockam_core::errcode::{Kind, Origin}; -use ockam_core::{Error, Result}; -use ockam_vault::SignatureVec; - -/// This trait provides functions to issue, accept and verify credentials -#[async_trait] -pub trait Credentials: Send + Sync + 'static { - /// Issue a credential for by having the issuer sign the serialized credential data - async fn issue_credential( - &self, - issuer: &IdentityIdentifier, - credential_data: CredentialData, - ) -> Result; - - /// Verify that a credential has been signed by one of the authorities - async fn verify_credential( - &self, - subject: &IdentityIdentifier, - authorities: &[Identity], - credential: Credential, - ) -> Result>; - - /// Verify and store a credential sent by a specific identity - async fn receive_presented_credential( - &self, - sender: &IdentityIdentifier, - authorities: &[Identity], - credential: Credential, - ) -> Result<()>; +use crate::models::{CredentialData, PurposeKeyAttestationData}; +use crate::{CredentialsCreation, CredentialsVerification, IdentitiesRepository, PurposeKeys}; + +use ockam_core::compat::sync::Arc; +use ockam_vault::{SigningVault, VerifyingVault}; + +/// Structure with both [`CredentialData`] and [`PurposeKeyAttestationData`] that we get +/// after parsing and verifying corresponding [`Credential`] and [`super::super::models::PurposeKeyAttestation`] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CredentialAndPurposeKeyData { + /// [`CredentialData`] + pub credential_data: CredentialData, + /// [`PurposeKeyAttestationData`] + pub purpose_key_data: PurposeKeyAttestationData, } -#[async_trait] -impl Credentials for Identities { - async fn verify_credential( - &self, - subject: &IdentityIdentifier, - authorities: &[Identity], - credential: Credential, - ) -> Result> { - let credential_data = CredentialData::try_from(credential.data.as_slice())?; - - let issuer = authorities - .iter() - .find(|&x| x.identifier() == credential_data.issuer); - let issuer = match issuer { - Some(i) => i, - None => return Err(IdentityError::UnknownAuthority.into()), - }; +/// Service for managing [`Credential`]s +pub struct Credentials { + credential_vault: Arc, + verifying_vault: Arc, + purpose_keys: Arc, + identities_repository: Arc, +} - let now = Timestamp::now() - .ok_or_else(|| Error::new(Origin::Application, Kind::Invalid, "invalid system time"))?; +impl Credentials { + ///Constructor + pub fn new( + credential_vault: Arc, + verifying_vault: Arc, + purpose_keys: Arc, + identities_repository: Arc, + ) -> Self { + Self { + credential_vault, + verifying_vault, + purpose_keys, + identities_repository, + } + } - credential_data.verify(subject, &issuer.identifier(), now)?; + /// [`PurposeKeys`] + pub fn purpose_keys(&self) -> Arc { + self.purpose_keys.clone() + } - let sig = ockam_vault::Signature::new(credential.signature().to_vec()); + /// [`IdentitiesRepository`] + pub fn identities_repository(&self) -> Arc { + self.identities_repository.clone() + } - if !self - .identities_keys() - .verify_signature( - issuer, - &sig, - credential.unverified_data(), - Some(credential_data.clone().unverified_key_label()), - ) - .await? - { - return Err(Error::new( - Origin::Application, - Kind::Invalid, - "invalid signature", - )); - } - Ok(credential_data.into_verified()) + /// Return [`CredentialsCreation`] + pub fn credentials_creation(&self) -> Arc { + Arc::new(CredentialsCreation::new( + self.purpose_keys.purpose_keys_creation(), + self.credential_vault.clone(), + self.verifying_vault.clone(), + self.identities_repository.clone(), + )) } - /// Create a signed credential based on the given values. - async fn issue_credential( - &self, - issuer: &IdentityIdentifier, - credential_data: CredentialData, - ) -> Result { - let bytes = minicbor::to_vec(credential_data)?; - let issuer_identity = self.repository().get_identity(issuer).await?; - let sig = self - .identities_keys() - .create_signature(&issuer_identity, &bytes, None) - .await?; - Ok(Credential::new(bytes, SignatureVec::from(sig))) + /// Return [`CredentialsVerification`] + pub fn credentials_verification(&self) -> Arc { + Arc::new(CredentialsVerification::new( + self.purpose_keys.purpose_keys_verification(), + self.verifying_vault.clone(), + self.identities_repository.clone(), + )) } +} + +#[cfg(test)] +mod tests { + use crate::identities::identities; + use crate::models::SchemaId; + use crate::Attributes; + use minicbor::bytes::ByteVec; + use ockam_core::compat::collections::BTreeMap; + use ockam_core::Result; + use std::time::Duration; + + #[tokio::test] + async fn test_issue_credential() -> Result<()> { + let identities = identities(); + let creation = identities.identities_creation(); + + let issuer = creation.create_identity().await?; + let subject = creation.create_identity().await?; + let credentials = identities.credentials(); - async fn receive_presented_credential( - &self, - sender: &IdentityIdentifier, - authorities: &[Identity], - credential: Credential, - ) -> Result<()> { - let credential_data = self - .verify_credential(sender, authorities, credential) + let mut map: BTreeMap = Default::default(); + map.insert(b"key".to_vec().into(), b"value".to_vec().into()); + let subject_attributes = Attributes { + schema: SchemaId(1), + map, + }; + + let credential = credentials + .credentials_creation() + .issue_credential( + issuer.identifier(), + subject.identifier(), + subject_attributes, + Duration::from_secs(60), + ) .await?; - self.identities_repository - .put_attributes( - sender, - AttributesEntry::new( - credential_data.attributes.as_map_vec_u8(), - Timestamp::now().unwrap(), - Some(credential_data.expires), - Some(credential_data.issuer), - ), + println!("{}", hex::encode(minicbor::to_vec(&credential)?)); + + let _res = credentials + .credentials_verification() + .verify_credential( + Some(subject.identifier()), + &[issuer.identifier().clone()], + &credential, ) .await?; diff --git a/implementations/rust/ockam/ockam_identity/src/credentials/credentials_creation.rs b/implementations/rust/ockam/ockam_identity/src/credentials/credentials_creation.rs new file mode 100644 index 00000000000..7176d27016a --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/src/credentials/credentials_creation.rs @@ -0,0 +1,105 @@ +use crate::models::{ + Attributes, Credential, CredentialAndPurposeKey, CredentialData, CredentialSignature, + Identifier, VersionedData, +}; +use crate::utils::{add_seconds, now}; +use crate::{IdentitiesRepository, Identity, Purpose, PurposeKeysCreation}; + +use core::time::Duration; +use ockam_core::compat::sync::Arc; +use ockam_core::Result; +use ockam_vault::{SigningVault, VerifyingVault}; + +/// Service for managing [`Credential`]s +pub struct CredentialsCreation { + purpose_keys_creation: Arc, + credential_vault: Arc, + verifying_vault: Arc, + identities_repository: Arc, +} + +impl CredentialsCreation { + ///Constructor + pub fn new( + purpose_keys_creation: Arc, + credential_vault: Arc, + verifying_vault: Arc, + identities_repository: Arc, + ) -> Self { + Self { + purpose_keys_creation, + verifying_vault, + credential_vault, + identities_repository, + } + } + + /// [`IdentitiesRepository`] + pub fn identities_repository(&self) -> Arc { + self.identities_repository.clone() + } +} + +impl CredentialsCreation { + /// Issue a [`Credential`] + pub async fn issue_credential( + &self, + issuer: &Identifier, + subject: &Identifier, + subject_attributes: Attributes, + ttl: Duration, + ) -> Result { + // TODO: Allow manual PurposeKey management + let issuer_purpose_key = self + .purpose_keys_creation + .get_or_create_purpose_key(issuer, Purpose::Credentials) + .await?; + + let subject_change_history = self.identities_repository.get_identity(subject).await?; + let subject_identity = Identity::import_from_change_history( + Some(subject), + subject_change_history, + self.verifying_vault.clone(), + ) + .await?; + + let created_at = now()?; + let expires_at = add_seconds(&created_at, ttl.as_secs()); + + let credential_data = CredentialData { + subject: Some(subject.clone()), + subject_latest_change_hash: Some(subject_identity.latest_change_hash()?.clone()), + subject_attributes, + created_at, + expires_at, + }; + let credential_data = minicbor::to_vec(credential_data)?; + + let versioned_data = VersionedData { + version: 1, + data: credential_data, + }; + let versioned_data = minicbor::to_vec(&versioned_data)?; + + let versioned_data_hash = self.verifying_vault.sha256(&versioned_data).await?; + + let signature = self + .credential_vault + .sign(issuer_purpose_key.key_id(), &versioned_data_hash) + .await?; + let signature = + CredentialSignature::try_from_signature(signature, issuer_purpose_key.stype())?; + + let credential = Credential { + data: versioned_data, + signature, + }; + + let res = CredentialAndPurposeKey { + credential, + purpose_key_attestation: issuer_purpose_key.attestation().clone(), + }; + + Ok(res) + } +} diff --git a/implementations/rust/ockam/ockam_identity/src/credentials/credentials_issuer.rs b/implementations/rust/ockam/ockam_identity/src/credentials/credentials_issuer.rs index 76227f7e836..7035d23caaf 100644 --- a/implementations/rust/ockam/ockam_identity/src/credentials/credentials_issuer.rs +++ b/implementations/rust/ockam/ockam_identity/src/credentials/credentials_issuer.rs @@ -1,75 +1,94 @@ -use minicbor::Decoder; -use tracing::trace; +use crate::models::{Attributes, CredentialAndPurposeKey, Identifier, SchemaId}; +use crate::utils::AttributesBuilder; +use crate::{Credentials, IdentitiesRepository, IdentitySecureChannelLocalInfo}; use ockam_core::api::{Method, Request, Response}; use ockam_core::compat::boxed::Box; use ockam_core::compat::string::String; +use ockam_core::compat::string::ToString; use ockam_core::compat::sync::Arc; use ockam_core::compat::vec::Vec; use ockam_core::{api, Result, Route, Routed, Worker}; use ockam_node::{Context, RpcClient}; -use crate::alloc::string::ToString; -use crate::credential::Credential; -use crate::identity::IdentityIdentifier; -use crate::{CredentialData, Identities, IdentitySecureChannelLocalInfo, PROJECT_MEMBER_SCHEMA}; +use core::time::Duration; +use minicbor::Decoder; +use tracing::trace; -/// Legacy id for a trust context, it used to be 'project_id', not it is the more general 'trust_context_id' -/// TODO: DEPRECATE - Removing PROJECT_ID attribute in favor of TRUST_CONTEXT_ID -pub const LEGACY_ID: &str = "project_id"; /// Name of the attribute identifying the trust context for that attribute, meaning /// from which set of trusted authorities the attribute comes from -pub const TRUST_CONTEXT_ID: &str = "trust_context_id"; +pub const TRUST_CONTEXT_ID: &[u8] = b"trust_context_id"; + +/// The same as above but in string format +pub const TRUST_CONTEXT_ID_UTF8: &str = "trust_context_id"; + +/// Identifier for the schema of a project credential +pub const PROJECT_MEMBER_SCHEMA: SchemaId = SchemaId(1); + +/// Maximum duration for a valid credential in seconds (30 days) +pub const MAX_CREDENTIAL_VALIDITY: Duration = Duration::from_secs(30 * 24 * 3600); /// This struct runs as a Worker to issue credentials based on a request/response protocol pub struct CredentialsIssuer { - identities: Arc, - issuer: IdentityIdentifier, - trust_context: String, + identities_repository: Arc, + credentials: Arc, + issuer: Identifier, + subject_attributes: Attributes, } impl CredentialsIssuer { /// Create a new credentials issuer pub async fn new( - identities: Arc, - issuer: IdentityIdentifier, + identities_repository: Arc, + credentials: Arc, + issuer: &Identifier, trust_context: String, ) -> Result { + let subject_attributes = AttributesBuilder::with_schema(PROJECT_MEMBER_SCHEMA) + .with_attribute(TRUST_CONTEXT_ID.to_vec(), trust_context.as_bytes().to_vec()) + .build(); + Ok(Self { - identities, - issuer, - trust_context, + identities_repository, + credentials, + issuer: issuer.clone(), + subject_attributes, }) } - async fn issue_credential(&self, from: &IdentityIdentifier) -> Result> { - match self - .identities - .repository() + async fn issue_credential( + &self, + subject: &Identifier, + ) -> Result> { + let entry = match self + .identities_repository .as_attributes_reader() - .get_attributes(from) + .get_attributes(subject) .await? { - Some(entry) => { - let crd = entry - .attrs() - .iter() - .fold( - CredentialData::builder(from.clone(), self.issuer.clone()) - .with_schema(PROJECT_MEMBER_SCHEMA), - |crd, (a, v)| crd.with_attribute(a, v), - ) - .with_attribute(LEGACY_ID, self.trust_context.as_bytes()) // TODO: DEPRECATE - Removing PROJECT_ID attribute in favor of TRUST_CONTEXT_ID - .with_attribute(TRUST_CONTEXT_ID, self.trust_context.as_bytes()); - Ok(Some( - self.identities - .credentials() - .issue_credential(&self.issuer, crd.build()?) - .await?, - )) - } - None => Ok(None), + Some(entry) => entry, + None => return Ok(None), + }; + + let mut subject_attributes = self.subject_attributes.clone(); + for (key, value) in entry.attrs().iter() { + subject_attributes + .map + .insert(key.clone().into(), value.clone().into()); } + + let credential = self + .credentials + .credentials_creation() + .issue_credential( + &self.issuer, + subject, + subject_attributes, + MAX_CREDENTIAL_VALIDITY, + ) + .await?; + + Ok(Some(credential)) } } @@ -140,7 +159,7 @@ impl CredentialsIssuerClient { } /// Return a credential for the identity which initiated the secure channel - pub async fn credential(&self) -> Result { + pub async fn credential(&self) -> Result { self.client.request(&Request::post("/")).await } } diff --git a/implementations/rust/ockam/ockam_identity/src/credentials/credentials_retriever.rs b/implementations/rust/ockam/ockam_identity/src/credentials/credentials_retriever.rs index 90317ca1470..945ef930695 100644 --- a/implementations/rust/ockam/ockam_identity/src/credentials/credentials_retriever.rs +++ b/implementations/rust/ockam/ockam_identity/src/credentials/credentials_retriever.rs @@ -6,10 +6,9 @@ use ockam_core::compat::sync::Arc; use ockam_core::{async_trait, route, Address, Result, Route}; use ockam_node::Context; -use crate::{ - Credential, CredentialsIssuerClient, IdentityIdentifier, SecureChannelOptions, SecureChannels, - TrustMultiIdentifiersPolicy, -}; +use crate::models::{CredentialAndPurposeKey, Identifier}; +use crate::CredentialsIssuerClient; +use crate::{SecureChannelOptions, SecureChannels, TrustMultiIdentifiersPolicy}; /// Trait for retrieving a credential for a given identity #[async_trait] @@ -18,19 +17,21 @@ pub trait CredentialsRetriever: Send + Sync + 'static { async fn retrieve( &self, ctx: &Context, - for_identity: &IdentityIdentifier, - ) -> Result; + for_identity: &Identifier, + ) -> Result; } /// Credentials retriever that retrieves a credential from memory pub struct CredentialsMemoryRetriever { - credential: Credential, + credential_and_purpose_key: CredentialAndPurposeKey, } impl CredentialsMemoryRetriever { /// Create a new CredentialsMemoryRetriever - pub fn new(credential: Credential) -> Self { - Self { credential } + pub fn new(credential_and_purpose_key: CredentialAndPurposeKey) -> Self { + Self { + credential_and_purpose_key, + } } } @@ -40,9 +41,9 @@ impl CredentialsRetriever for CredentialsMemoryRetriever { async fn retrieve( &self, _ctx: &Context, - _for_identity: &IdentityIdentifier, - ) -> Result { - Ok(self.credential.clone()) + _for_identity: &Identifier, + ) -> Result { + Ok(self.credential_and_purpose_key.clone()) } } @@ -70,8 +71,8 @@ impl CredentialsRetriever for RemoteCredentialsRetriever { async fn retrieve( &self, ctx: &Context, - for_identity: &IdentityIdentifier, - ) -> Result { + for_identity: &Identifier, + ) -> Result { debug!("Getting credential from : {}", &self.issuer.route); let resolved_route = ctx .resolve_transport_route(self.issuer.route.clone()) @@ -107,7 +108,7 @@ impl CredentialsRetriever for RemoteCredentialsRetriever { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RemoteCredentialsRetrieverInfo { /// Issuer identity, used to validate retrieved credentials - pub identifier: IdentityIdentifier, + pub identifier: Identifier, /// Route used to establish a secure channel to the remote node pub route: Route, /// Address of the credentials service on the remote node @@ -116,7 +117,7 @@ pub struct RemoteCredentialsRetrieverInfo { impl RemoteCredentialsRetrieverInfo { /// Create new information for a credential retriever - pub fn new(identifier: IdentityIdentifier, route: Route, service_address: Address) -> Self { + pub fn new(identifier: Identifier, route: Route, service_address: Address) -> Self { Self { identifier, route, diff --git a/implementations/rust/ockam/ockam_identity/src/credentials/credentials_server.rs b/implementations/rust/ockam/ockam_identity/src/credentials/credentials_server.rs index db3e642bb33..6f796dfc13a 100644 --- a/implementations/rust/ockam/ockam_identity/src/credentials/credentials_server.rs +++ b/implementations/rust/ockam/ockam_identity/src/credentials/credentials_server.rs @@ -1,11 +1,3 @@ -use crate::credential::Credential; -use crate::credentials::credentials_server_worker::CredentialsServerWorker; -use crate::credentials::Credentials; -use crate::identity::Identity; -use crate::secure_channel::IdentitySecureChannelLocalInfo; -use crate::{IdentityIdentifier, TrustContext}; -use async_trait::async_trait; -use minicbor::Decoder; use ockam_core::api::{Request, Response, Status}; use ockam_core::compat::boxed::Box; use ockam_core::compat::sync::Arc; @@ -14,6 +6,14 @@ use ockam_core::{Address, Error, Result, Route}; use ockam_node::api::{request, request_with_local_info}; use ockam_node::{Context, WorkerBuilder}; +use crate::credentials::credentials_server_worker::CredentialsServerWorker; +use crate::credentials::Credentials; +use crate::models::{CredentialAndPurposeKey, Identifier}; +use crate::{IdentitySecureChannelLocalInfo, TrustContext}; + +use async_trait::async_trait; +use minicbor::Decoder; + /// This trait allows an identity to send its credential to another identity /// located at the end of a secure channel route #[async_trait] @@ -25,8 +25,8 @@ pub trait CredentialsServer: Send + Sync { &self, ctx: &Context, route: Route, - authorities: &[Identity], - credential: Credential, + authorities: &[Identifier], + credential: CredentialAndPurposeKey, ) -> Result<()>; /// Present credential to other party, route shall use secure channel @@ -34,7 +34,7 @@ pub trait CredentialsServer: Send + Sync { &self, ctx: &Context, route: Route, - credential: Credential, + credential: CredentialAndPurposeKey, ) -> Result<()>; /// Start this service as a worker @@ -42,7 +42,7 @@ pub trait CredentialsServer: Send + Sync { &self, ctx: &Context, trust_context: TrustContext, - identifier: IdentityIdentifier, + identifier: Identifier, address: Address, present_back: bool, ) -> Result<()>; @@ -50,7 +50,7 @@ pub trait CredentialsServer: Send + Sync { /// Implementation of the CredentialsService pub struct CredentialsServerModule { - credentials: Arc, + credentials: Arc, } #[async_trait] @@ -61,8 +61,8 @@ impl CredentialsServer for CredentialsServerModule { &self, ctx: &Context, route: Route, - authorities: &[Identity], - credential: Credential, + authorities: &[Identifier], + credential: CredentialAndPurposeKey, ) -> Result<()> { let path = "actions/present_mutual"; let (buf, local_info) = request_with_local_info( @@ -74,9 +74,8 @@ impl CredentialsServer for CredentialsServerModule { ) .await?; - let their_id = IdentitySecureChannelLocalInfo::find_info_from_list(&local_info)? - .their_identity_id() - .clone(); + let their_id = + IdentitySecureChannelLocalInfo::find_info_from_list(&local_info)?.their_identity_id(); let mut dec = Decoder::new(&buf); let res: Response = dec.decode()?; @@ -98,9 +97,10 @@ impl CredentialsServer for CredentialsServerModule { } } - let credential: Credential = dec.decode()?; + let credential_and_purpose_key: CredentialAndPurposeKey = dec.decode()?; self.credentials - .receive_presented_credential(&their_id, authorities, credential) + .credentials_verification() + .receive_presented_credential(&their_id, authorities, &credential_and_purpose_key) .await?; Ok(()) @@ -111,7 +111,7 @@ impl CredentialsServer for CredentialsServerModule { &self, ctx: &Context, route: Route, - credential: Credential, + credential: CredentialAndPurposeKey, ) -> Result<()> { let buf = request( ctx, @@ -139,7 +139,7 @@ impl CredentialsServer for CredentialsServerModule { &self, ctx: &Context, trust_context: TrustContext, - identifier: IdentityIdentifier, + identifier: Identifier, address: Address, present_back: bool, ) -> Result<()> { @@ -161,7 +161,7 @@ impl CredentialsServer for CredentialsServerModule { impl CredentialsServerModule { /// Create a CredentialsService. It is simply backed by the Credentials interface - pub fn new(credentials: Arc) -> Self { + pub fn new(credentials: Arc) -> Self { Self { credentials } } } diff --git a/implementations/rust/ockam/ockam_identity/src/credentials/credentials_server_worker.rs b/implementations/rust/ockam/ockam_identity/src/credentials/credentials_server_worker.rs index 6a7e3af0ffb..588294f5904 100644 --- a/implementations/rust/ockam/ockam_identity/src/credentials/credentials_server_worker.rs +++ b/implementations/rust/ockam/ockam_identity/src/credentials/credentials_server_worker.rs @@ -1,6 +1,3 @@ -use minicbor::Decoder; -use tracing::{debug, error, info, trace, warn}; - use ockam_core::api::{Error, Id, Request, Response, ResponseBuilder, Status}; use ockam_core::async_trait; use ockam_core::compat::boxed::Box; @@ -8,27 +5,28 @@ use ockam_core::compat::{string::ToString, sync::Arc, vec::Vec}; use ockam_core::{Result, Routed, Worker}; use ockam_node::Context; -use crate::credential::Credential; use crate::credentials::Credentials; -use crate::identity::IdentityIdentifier; -use crate::secure_channel::IdentitySecureChannelLocalInfo; -use crate::TrustContext; +use crate::models::{CredentialAndPurposeKey, Identifier}; +use crate::{IdentitySecureChannelLocalInfo, TrustContext}; + +use minicbor::Decoder; +use tracing::{debug, error, info, trace, warn}; const TARGET: &str = "ockam::credential_exchange_worker::service"; /// Worker responsible for receiving and verifying other party's credential pub struct CredentialsServerWorker { - credentials: Arc, + credentials: Arc, trust_context: TrustContext, - identifier: IdentityIdentifier, + identifier: Identifier, present_back: bool, } impl CredentialsServerWorker { pub fn new( - credentials: Arc, + credentials: Arc, trust_context: TrustContext, - identifier: IdentityIdentifier, + identifier: Identifier, present_back: bool, ) -> Self { Self { @@ -45,7 +43,7 @@ impl CredentialsServerWorker { &mut self, ctx: &mut Context, req: &Request, - sender: IdentityIdentifier, + sender: Identifier, dec: &mut Decoder<'_>, ) -> Result> { trace! { @@ -75,14 +73,15 @@ impl CredentialsServerWorker { "Received one-way credential presentation request from {}", sender ); - let credential: Credential = dec.decode()?; + let credential_and_purpose_key: CredentialAndPurposeKey = dec.decode()?; let res = self .credentials + .credentials_verification() .receive_presented_credential( &sender, self.trust_context.authorities().await?.as_slice(), - credential, + &credential_and_purpose_key, ) .await; @@ -105,15 +104,16 @@ impl CredentialsServerWorker { "Received mutual credential presentation request from {}", sender ); - let credential: Credential = dec.decode()?; + let credential_and_purpose_key: CredentialAndPurposeKey = dec.decode()?; - info!("presented credential {}", credential); + // FIXME info!("presented credential {}", credential); let res = self .credentials + .credentials_verification() .receive_presented_credential( &sender, self.trust_context.authorities().await?.as_slice(), - credential, + &credential_and_purpose_key, ) .await; diff --git a/implementations/rust/ockam/ockam_identity/src/credentials/credentials_verification.rs b/implementations/rust/ockam/ockam_identity/src/credentials/credentials_verification.rs new file mode 100644 index 00000000000..62d25c43143 --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/src/credentials/credentials_verification.rs @@ -0,0 +1,201 @@ +use crate::identities::AttributesEntry; +use crate::models::{CredentialAndPurposeKey, CredentialData, Identifier, PurposePublicKey}; +use crate::utils::now; +use crate::{ + CredentialAndPurposeKeyData, IdentitiesRepository, IdentityError, PurposeKeysVerification, + TimestampInSeconds, +}; + +use ockam_core::compat::collections::BTreeMap; +use ockam_core::compat::sync::Arc; +use ockam_core::compat::vec::Vec; +use ockam_core::Result; +use ockam_vault::VerifyingVault; + +/// We allow Credentials to be created in the future related to this machine's time due to +/// possible time dyssynchronization +const MAX_ALLOWED_TIME_DRIFT: TimestampInSeconds = TimestampInSeconds(5); + +/// Service for managing [`Credential`]s +pub struct CredentialsVerification { + purpose_keys_verification: Arc, + verifying_vault: Arc, + identities_repository: Arc, +} + +impl CredentialsVerification { + ///Constructor + pub fn new( + purpose_keys_verification: Arc, + verifying_vault: Arc, + identities_repository: Arc, + ) -> Self { + Self { + purpose_keys_verification, + verifying_vault, + identities_repository, + } + } + + /// [`IdentitiesRepository`] + pub fn identities_repository(&self) -> Arc { + self.identities_repository.clone() + } +} + +impl CredentialsVerification { + /// Verify a [`Credential`] + // TODO: Move to CredentialsVerification + pub async fn verify_credential( + &self, + expected_subject: Option<&Identifier>, + authorities: &[Identifier], + credential_and_purpose_key: &CredentialAndPurposeKey, + ) -> Result { + let purpose_key_data = self + .purpose_keys_verification + .verify_purpose_key_attestation( + None, + &credential_and_purpose_key.purpose_key_attestation, + ) + .await?; + + if !authorities.contains(&purpose_key_data.subject) { + return Err(IdentityError::UnknownAuthority.into()); + } + + let public_key = match purpose_key_data.public_key.clone() { + PurposePublicKey::SecureChannelStaticKey(_) => { + return Err(IdentityError::InvalidKeyType.into()) + } + + PurposePublicKey::CredentialSigningKey(public_key) => public_key, + }; + + let public_key = public_key.into(); + + let versioned_data_hash = self + .verifying_vault + .sha256(&credential_and_purpose_key.credential.data) + .await?; + + let signature = credential_and_purpose_key + .credential + .signature + .clone() + .into(); + + if !self + .verifying_vault + .verify(&public_key, &versioned_data_hash, &signature) + .await? + { + return Err(IdentityError::CredentialVerificationFailed.into()); + } + + let versioned_data = credential_and_purpose_key.credential.get_versioned_data()?; + if versioned_data.version != 1 { + return Err(IdentityError::UnknownCredentialVersion.into()); + } + + let credential_data = CredentialData::get_data(&versioned_data)?; + + if credential_data.subject.is_none() { + // Currently unsupported + return Err(IdentityError::CredentialVerificationFailed.into()); + } + + if credential_data.subject.is_none() && credential_data.subject_latest_change_hash.is_none() + { + // At least one should be always present, otherwise it's unclear who this credential belongs to + return Err(IdentityError::CredentialVerificationFailed.into()); + } + + if expected_subject.is_some() && credential_data.subject.as_ref() != expected_subject { + // We expected credential that belongs to someone else + return Err(IdentityError::CredentialVerificationFailed.into()); + } + + if credential_data.created_at < purpose_key_data.created_at { + // Credential validity time range should be inside the purpose key validity time range + return Err(IdentityError::CredentialVerificationFailed.into()); + } + + if credential_data.expires_at > purpose_key_data.expires_at { + // Credential validity time range should be inside the purpose key validity time range + return Err(IdentityError::CredentialVerificationFailed.into()); + } + + let now = now()?; + + if credential_data.created_at > now + && credential_data.created_at - now > MAX_ALLOWED_TIME_DRIFT + { + // Credential can't be created in the future + return Err(IdentityError::CredentialVerificationFailed.into()); + } + + if credential_data.expires_at < now { + // Credential expired + return Err(IdentityError::CredentialVerificationFailed.into()); + } + + if let Some(_subject_latest_change_hash) = &credential_data.subject_latest_change_hash { + // TODO: Check how that aligns with the ChangeHistory of the subject that we have in the storage + // For example, if we just established a secure channel with that subject, + // latest_change_hash MUST be equal to the one in present ChangeHistory. + // If credential_data.subject_latest_change_hash equals to some older value from the + // subject's ChangeHistory, that means that subject hasn't updated its Credentials + // after the Identity Key rotation, which is suspicious, such Credential should be rejected + // If credential_data.subject_latest_change_hash equals to some future value that we haven't yet + // observed, than subject should had presented its newer Changes as well. We should + // reject such Credential, unless we have cases where subject may not had an opportunity + // to present its newer Changes (e.g., if we receive its Credential from someone else). + // In such cases some limited tolerance may be introduced. + } + + // FIXME: Verify if given authority is allowed to issue credentials with given Schema <-- Should be handled somewhere in the TrustContext + // FIXME: Verify if Schema aligns with Attributes <-- Should be handled somewhere in the TrustContext + + Ok(CredentialAndPurposeKeyData { + credential_data, + purpose_key_data, + }) + } + + /// Receive someone's [`Credential`]: verify and put attributes from it to the storage + pub async fn receive_presented_credential( + &self, + subject: &Identifier, + authorities: &[Identifier], + credential_and_purpose_key_attestation: &CredentialAndPurposeKey, + ) -> Result<()> { + let credential_data = self + .verify_credential( + Some(subject), + authorities, + credential_and_purpose_key_attestation, + ) + .await?; + + let map = credential_data.credential_data.subject_attributes.map; + let map: BTreeMap<_, _> = map + .into_iter() + .map(|(k, v)| (Vec::::from(k), Vec::::from(v))) + .collect(); + + self.identities_repository + .put_attributes( + subject, + AttributesEntry::new( + map, + now()?, + Some(credential_data.credential_data.expires_at), + Some(credential_data.purpose_key_data.subject), + ), + ) + .await?; + + Ok(()) + } +} diff --git a/implementations/rust/ockam/ockam_identity/src/credentials/mod.rs b/implementations/rust/ockam/ockam_identity/src/credentials/mod.rs index 4dfba9322a4..b3ea4ecc89c 100644 --- a/implementations/rust/ockam/ockam_identity/src/credentials/mod.rs +++ b/implementations/rust/ockam/ockam_identity/src/credentials/mod.rs @@ -1,15 +1,21 @@ mod authority_service; #[allow(clippy::module_inception)] mod credentials; +mod credentials_creation; mod credentials_issuer; mod credentials_retriever; mod credentials_server; mod credentials_server_worker; +mod credentials_verification; +mod one_time_code; mod trust_context; pub use authority_service::*; pub use credentials::*; +pub use credentials_creation::*; pub use credentials_issuer::*; pub use credentials_retriever::*; pub use credentials_server::*; +pub use credentials_verification::*; +pub use one_time_code::*; pub use trust_context::*; diff --git a/implementations/rust/ockam/ockam_identity/src/credential/one_time_code.rs b/implementations/rust/ockam/ockam_identity/src/credentials/one_time_code.rs similarity index 100% rename from implementations/rust/ockam/ockam_identity/src/credential/one_time_code.rs rename to implementations/rust/ockam/ockam_identity/src/credentials/one_time_code.rs diff --git a/implementations/rust/ockam/ockam_identity/src/credentials/trust_context.rs b/implementations/rust/ockam/ockam_identity/src/credentials/trust_context.rs index 6af20cd7122..53b50a779c4 100644 --- a/implementations/rust/ockam/ockam_identity/src/credentials/trust_context.rs +++ b/implementations/rust/ockam/ockam_identity/src/credentials/trust_context.rs @@ -2,7 +2,8 @@ use ockam_core::compat::string::String; use ockam_core::compat::vec::Vec; use ockam_core::Result; -use crate::{AuthorityService, Identity, IdentityError}; +use crate::models::Identifier; +use crate::{AuthorityService, IdentityError}; /// A trust context defines which authorities are trusted to attest to which attributes, within a context. /// Our first implementation assumes that there is only one authority and it is trusted to attest to all attributes within this context. @@ -33,7 +34,7 @@ impl TrustContext { } /// Return the authority identities attached to this trust context - pub async fn authorities(&self) -> Result> { - Ok(vec![self.authority()?.identity().await?]) + pub async fn authorities(&self) -> Result> { + Ok(vec![self.authority()?.identifier().clone()]) } } diff --git a/implementations/rust/ockam/ockam_identity/src/error.rs b/implementations/rust/ockam/ockam_identity/src/error.rs index 62aece9eccd..180aea7e4ec 100644 --- a/implementations/rust/ockam/ockam_identity/src/error.rs +++ b/implementations/rust/ockam/ockam_identity/src/error.rs @@ -6,32 +6,66 @@ use ockam_core::{ /// Identity crate error #[derive(Clone, Copy, Debug)] pub enum IdentityError { - /// Bare serialization error - BareError = 1, - /// Invalid internal state of the `Identity` - InvalidInternalState, - /// Consistency check failed - ConsistencyError, - /// SecureChannel signature check failed during Identity authentication - SecureChannelVerificationFailed, - /// SecureChannel `TrustPolicy` check failed - SecureChannelTrustCheckFailed, - /// Unknown channel message destination - UnknownChannelMsgDestination, - /// Invalid `LocalInfo` type - InvalidLocalInfoType, - /// `Identity` verification error + /// Invalid key type + InvalidKeyType = 1, + /// Invalid Key Data + InvalidKeyData, + /// Invalid Signature Data + InvalidSignatureData, + /// Invalid Identifier format + InvalidIdentifier, + /// Identity Change History is empty + EmptyIdentity, + /// Identity Verification Failed IdentityVerificationFailed, - /// Invalid `IdentityIdentifier` format - InvalidIdentityId, + /// PurposeKeyAttestation Verification Failed + PurposeKeyAttestationVerificationFailed, + /// Credential Verification Failed + CredentialVerificationFailed, + /// Error occurred while getting current UTC Timestamp + UnknownTimestamp, + /// Attributes were already set + AttributesAlreadySet, + /// Attributes hasn't been set + AttributesNotSet, + /// Schema was not yet set + SchemaNotSet, + /// Maximum time for credential validity exceeded + CredentialTtlExceeded, + /// Credential ttl wasn't set + CredentialTtlNotSet, /// Unknown Authority UnknownAuthority, - /// SecureChannel with this address already exists - DuplicateSecureChannel, - /// Invalid nonce format + /// Unknown version of the Credential + UnknownCredentialVersion, + /// Unknown version of the PurposeKeyAttestation + UnknownPurposeKeyVersion, + /// Unknown version of the Identity + UnknownIdentityVersion, + /// SecureChannelVerificationFailed + SecureChannelVerificationFailed, + /// SecureChannelTrustCheckFailed + SecureChannelTrustCheckFailed, + /// Invalid Nonce value InvalidNonce, /// Nonce overflow NonceOverflow, + /// Unknown message destination + UnknownChannelMsgDestination, + /// Invalid LocalInfo type + InvalidLocalInfoType, + /// Duplicate Secure Channel + DuplicateSecureChannel, + /// Consistency Error + ConsistencyError, + /// Invalid Hex + InvalidHex, + /// Secret Key doesn't correspond to the Identity + WrongSecretKey, + /// Expected Secret Key, got Public Key + ExpectedSecretKeyInsteadOfPublic, + /// Expected Public Key, got Secret Key + ExpectedPublicKeyInsteadOfSecret, } impl ockam_core::compat::error::Error for IdentityError {} diff --git a/implementations/rust/ockam/ockam_identity/src/identities/identities.rs b/implementations/rust/ockam/ockam_identity/src/identities/identities.rs index 103dfdb646b..bb9579924e7 100644 --- a/implementations/rust/ockam/ockam_identity/src/identities/identities.rs +++ b/implementations/rust/ockam/ockam_identity/src/identities/identities.rs @@ -1,21 +1,25 @@ -use crate::identities::{IdentitiesKeys, IdentitiesRepository, IdentitiesVault}; +use crate::identities::{IdentitiesKeys, IdentitiesRepository}; +use crate::purpose_keys::storage::{PurposeKeysRepository, PurposeKeysStorage}; use crate::{ - Credentials, CredentialsServer, CredentialsServerModule, IdentitiesBuilder, IdentitiesCreation, - IdentitiesReader, IdentitiesStorage, + Credentials, CredentialsServer, CredentialsServerModule, Identifier, IdentitiesBuilder, + IdentitiesCreation, IdentitiesReader, IdentitiesStorage, Identity, PurposeKeys, Vault, }; + use ockam_core::compat::sync::Arc; -use ockam_vault::Vault; +use ockam_core::compat::vec::Vec; +use ockam_core::Result; /// This struct supports all the services related to identities #[derive(Clone)] pub struct Identities { - pub(crate) vault: Arc, - pub(crate) identities_repository: Arc, + vault: Vault, + identities_repository: Arc, + purpose_keys_repository: Arc, } impl Identities { - /// Return the identities vault - pub fn vault(&self) -> Arc { + /// Vault + pub fn vault(&self) -> Vault { self.vault.clone() } @@ -24,16 +28,51 @@ impl Identities { self.identities_repository.clone() } + /// Return the purpose keys repository + pub fn purpose_keys_repository(&self) -> Arc { + self.purpose_keys_repository.clone() + } + + /// Get an [`Identity`] from the repository + pub async fn get_identity(&self, identifier: &Identifier) -> Result { + let change_history = self.identities_repository.get_identity(identifier).await?; + Identity::import_from_change_history( + Some(identifier), + change_history, + self.vault.verifying_vault.clone(), + ) + .await + } + + /// Export an [`Identity`] from the repository + pub async fn export_identity(&self, identifier: &Identifier) -> Result> { + self.get_identity(identifier).await?.export() + } + + /// Return the [`PurposeKeys`] instance + pub fn purpose_keys(&self) -> Arc { + Arc::new(PurposeKeys::new( + self.vault.clone(), + self.identities_repository.as_identities_reader(), + self.identities_keys(), + self.purpose_keys_repository.clone(), + )) + } + /// Return the identities keys management service pub fn identities_keys(&self) -> Arc { - Arc::new(IdentitiesKeys::new(self.vault.clone())) + Arc::new(IdentitiesKeys::new( + self.vault.identity_vault.clone(), + self.vault.verifying_vault.clone(), + )) } /// Return the identities creation service pub fn identities_creation(&self) -> Arc { Arc::new(IdentitiesCreation::new( self.repository(), - self.vault.clone(), + self.vault.identity_vault.clone(), + self.vault.verifying_vault.clone(), )) } @@ -43,8 +82,13 @@ impl Identities { } /// Return the identities credentials service - pub fn credentials(&self) -> Arc { - Arc::new(self.clone()) + pub fn credentials(&self) -> Arc { + Arc::new(Credentials::new( + self.vault.credential_vault.clone(), + self.vault.verifying_vault.clone(), + self.purpose_keys(), + self.identities_repository.clone(), + )) } /// Return the identities credentials server @@ -56,12 +100,14 @@ impl Identities { impl Identities { /// Create a new identities module pub(crate) fn new( - vault: Arc, + vault: Vault, identities_repository: Arc, + purpose_keys_repository: Arc, ) -> Identities { Identities { vault, identities_repository, + purpose_keys_repository, } } @@ -70,6 +116,7 @@ impl Identities { IdentitiesBuilder { vault: Vault::create(), repository: IdentitiesStorage::create(), + purpose_keys_repository: PurposeKeysStorage::create(), } } } diff --git a/implementations/rust/ockam/ockam_identity/src/identities/identities_builder.rs b/implementations/rust/ockam/ockam_identity/src/identities/identities_builder.rs index 5da65fa2b45..4cc6e9252a6 100644 --- a/implementations/rust/ockam/ockam_identity/src/identities/identities_builder.rs +++ b/implementations/rust/ockam/ockam_identity/src/identities/identities_builder.rs @@ -1,14 +1,16 @@ -use crate::identities::{ - Identities, IdentitiesRepository, IdentitiesStorage, IdentitiesVault, Storage, -}; +use crate::identities::{Identities, IdentitiesRepository, IdentitiesStorage}; +use crate::purpose_keys::storage::{PurposeKeysRepository, PurposeKeysStorage}; +use crate::storage::Storage; +use crate::{Vault, VaultStorage}; + use ockam_core::compat::sync::Arc; -use ockam_vault::{Vault, VaultStorage}; /// Builder for Identities services #[derive(Clone)] pub struct IdentitiesBuilder { - pub(crate) vault: Arc, + pub(crate) vault: Vault, pub(crate) repository: Arc, + pub(crate) purpose_keys_repository: Arc, } /// Return a default identities @@ -17,41 +19,49 @@ pub fn identities() -> Arc { } impl IdentitiesBuilder { - /// Set a specific storage for the identities vault - pub fn with_vault_storage(&mut self, storage: VaultStorage) -> IdentitiesBuilder { - self.with_identities_vault(Vault::create_with_persistent_storage(storage)) + /// With Software Vault with given Storage + pub fn with_vault_storage(mut self, storage: VaultStorage) -> Self { + self.vault = Vault::create_with_persistent_storage(storage); + self } - /// Set a specific identities vault - pub fn with_identities_vault(&mut self, vault: Arc) -> IdentitiesBuilder { + /// Set a Vault + pub fn with_vault(mut self, vault: Vault) -> Self { self.vault = vault; - self.clone() + self } /// Set a specific storage for identities - pub fn with_identities_storage(&mut self, storage: Arc) -> IdentitiesBuilder { + pub fn with_identities_storage(self, storage: Arc) -> Self { self.with_identities_repository(Arc::new(IdentitiesStorage::new(storage))) } - /// Set a specific repository - pub fn with_identities_repository( - &mut self, - repository: Arc, - ) -> IdentitiesBuilder { + /// Set a specific repository for identities + pub fn with_identities_repository(mut self, repository: Arc) -> Self { self.repository = repository; - self.clone() + self } - fn vault(&self) -> Arc { - self.vault.clone() + /// Set a specific storage for Purpose Keys + pub fn with_purpose_keys_storage(self, storage: Arc) -> Self { + self.with_purpose_keys_repository(Arc::new(PurposeKeysStorage::new(storage))) } - fn repository(&self) -> Arc { - self.repository.clone() + /// Set a specific repository for Purpose Keys + pub fn with_purpose_keys_repository( + mut self, + repository: Arc, + ) -> Self { + self.purpose_keys_repository = repository; + self } /// Build identities - pub fn build(&self) -> Arc { - Arc::new(Identities::new(self.vault(), self.repository())) + pub fn build(self) -> Arc { + Arc::new(Identities::new( + self.vault, + self.repository, + self.purpose_keys_repository, + )) } } diff --git a/implementations/rust/ockam/ockam_identity/src/identities/identities_creation.rs b/implementations/rust/ockam/ockam_identity/src/identities/identities_creation.rs index ff7c085512c..cbdc847eece 100644 --- a/implementations/rust/ockam/ockam_identity/src/identities/identities_creation.rs +++ b/implementations/rust/ockam/ockam_identity/src/identities/identities_creation.rs @@ -1,159 +1,199 @@ use ockam_core::compat::sync::Arc; -use ockam_core::compat::vec::Vec; use ockam_core::Result; -use ockam_vault::{KeyId, Secret, SecretAttributes}; +use ockam_vault::{KeyId, SigningVault, VerifyingVault}; -use crate::alloc::string::ToString; -use crate::IdentityError; -use crate::{ - IdentitiesKeys, IdentitiesRepository, IdentitiesVault, Identity, IdentityChangeConstants, - IdentityChangeHistory, IdentityIdentifier, KeyAttributes, -}; +use crate::identities::identity_builder::IdentityBuilder; +use crate::models::{ChangeHistory, Identifier}; +use crate::{IdentitiesKeys, IdentitiesRepository, Identity, IdentityError}; +use crate::{IdentityHistoryComparison, IdentityOptions}; /// This struct supports functions for the creation and import of identities using an IdentityVault pub struct IdentitiesCreation { - repository: Arc, - vault: Arc, + pub(super) repository: Arc, + pub(super) identity_vault: Arc, + pub(super) verifying_vault: Arc, } impl IdentitiesCreation { /// Create a new identities import module pub fn new( repository: Arc, - vault: Arc, - ) -> IdentitiesCreation { - IdentitiesCreation { repository, vault } + identity_vault: Arc, + verifying_vault: Arc, + ) -> Self { + Self { + repository, + identity_vault, + verifying_vault, + } } - /// Import and verify an `Identity` from its change history in a hex format - pub async fn decode_identity_hex(&self, data: &str) -> Result { - self.decode_identity( - hex::decode(data) - .map_err(|_| IdentityError::ConsistencyError)? - .as_slice(), - ) - .await + /// Return the identities keys management service + pub fn identities_keys(&self) -> Arc { + Arc::new(IdentitiesKeys::new( + self.identity_vault.clone(), + self.verifying_vault.clone(), + )) } - /// Import and verify an `Identity` from its change history in a binary format - pub async fn decode_identity(&self, data: &[u8]) -> Result { - let change_history = IdentityChangeHistory::import(data)?; - let identity_keys = IdentitiesKeys::new(self.vault.clone()); - identity_keys - .verify_all_existing_changes(&change_history) - .await?; + /// Import and verify identity from its binary format + /// This action persists the Identity in the storage, use `Identity::import` to avoid that + pub async fn import( + &self, + expected_identifier: Option<&Identifier>, + data: &[u8], + ) -> Result { + let identity = + Identity::import(expected_identifier, data, self.verifying_vault.clone()).await?; + + self.update_identity(&identity).await?; - let identifier = self.compute_identity_identifier(&change_history).await?; - Ok(Identity::new(identifier, change_history)) + Ok(identity) } - /// Create an identity with a vault initialized with a secret key - /// encoded as a hex string. - /// Such a key can be obtained by running vault.secret_export and then encoding - /// the exported secret as a hex string - /// Note: the data is not persisted! - pub async fn import_private_identity( + /// Import and verify identity from its Change History + /// This action persists the Identity in the storage, use `Identity::import` to avoid that + pub async fn import_from_change_history( &self, - identity_history: &str, - secret: &str, + expected_identifier: Option<&Identifier>, + change_history: ChangeHistory, ) -> Result { - let secret = Secret::new(hex::decode(secret).unwrap()); - let key_attributes = KeyAttributes::default_with_label(IdentityChangeConstants::ROOT_LABEL); - self.vault - .import_ephemeral_secret(secret, key_attributes.secret_attributes()) - .await?; - let identity_history_data: Vec = - hex::decode(identity_history).map_err(|_| IdentityError::InvalidInternalState)?; - let identity = self - .decode_identity(identity_history_data.as_slice()) + let identity = Identity::import_from_change_history( + expected_identifier, + change_history, + self.verifying_vault.clone(), + ) + .await?; + + self.update_identity(&identity).await?; + + Ok(identity) + } + + /// Get an instance of [`IdentityBuilder`] + pub fn identity_builder(&self) -> IdentityBuilder { + IdentityBuilder::new(Arc::new(Self::new( + self.repository.clone(), + self.identity_vault.clone(), + self.verifying_vault.clone(), + ))) + } + + /// Create an `Identity` and store it + pub async fn create_identity(&self) -> Result { + let builder = self.identity_builder(); + builder.build().await + } + + /// Create an `Identity` and store it + pub async fn create_identity_with_options(&self, options: IdentityOptions) -> Result { + let identity = self.identities_keys().create_initial_key(options).await?; + self.repository + .update_identity(identity.identifier(), identity.change_history()) .await?; - self.repository.update_identity(&identity).await?; Ok(identity) } - /// Cryptographically compute `IdentityIdentifier` - pub(super) async fn compute_identity_identifier( - &self, - change_history: &IdentityChangeHistory, - ) -> Result { - let root_public_key = change_history.get_first_root_public_key()?; - Ok(IdentityIdentifier::from_public_key(&root_public_key)) + /// Rotate an existing `Identity` and update the stored version + pub async fn rotate_identity(&self, identifier: &Identifier) -> Result<()> { + let builder = self.identity_builder(); + let options = builder.build_options().await?; + + self.rotate_identity_with_options(identifier, options).await } - /// Create an `Identity` with a key previously created in the Vault. Extended version - pub async fn create_identity_with_existing_key( + /// Rotate an existing `Identity` and update the stored version + pub async fn rotate_identity_with_options( &self, - kid: &KeyId, - attrs: KeyAttributes, - ) -> Result { - self.make_and_persist_identity(Some(kid), attrs).await - } + identifier: &Identifier, + options: IdentityOptions, + ) -> Result<()> { + let change_history = self.repository.get_identity(identifier).await?; + + let identity = Identity::import_from_change_history( + Some(identifier), + change_history, + self.verifying_vault.clone(), + ) + .await?; - /// Create an Identity - pub async fn create_identity(&self) -> Result { - let attrs = KeyAttributes::new( - IdentityChangeConstants::ROOT_LABEL.to_string(), - SecretAttributes::Ed25519, - ); - self.make_and_persist_identity(None, attrs).await + let identity = self + .identities_keys() + .rotate_key_with_options(identity, options) + .await?; + + self.repository + .update_identity(identity.identifier(), identity.change_history()) + .await?; + + Ok(()) } -} -impl IdentitiesCreation { - /// Make a new identity with its key and attributes - /// and persist it - async fn make_and_persist_identity( + /// Import an existing Identity from its binary format + /// Its secret is expected to exist in the Vault (either generated there, or some Vault + /// implementations may allow importing a secret) + pub async fn import_private_identity( &self, - key_id: Option<&KeyId>, - key_attributes: KeyAttributes, + identity_change_history: &[u8], + key_id: &KeyId, ) -> Result { - let identity_keys = IdentitiesKeys::new(self.vault.clone()); - let change_history = identity_keys - .create_initial_key(key_id, key_attributes.clone()) + let identity = self.import(None, identity_change_history).await?; + if identity.get_latest_public_key()? != self.identity_vault.get_public_key(key_id).await? { + return Err(IdentityError::WrongSecretKey.into()); + } + + self.repository + .update_identity(identity.identifier(), identity.change_history()) .await?; - let identifier = self.compute_identity_identifier(&change_history).await?; - let identity = Identity::new(identifier, change_history); - self.repository.update_identity(&identity).await?; Ok(identity) } + + /// [`SigningVault`] + pub fn identity_vault(&self) -> Arc { + self.identity_vault.clone() + } + + /// [`VerifyingVault`] + pub fn verifying_vault(&self) -> Arc { + self.verifying_vault.clone() + } } -#[cfg(test)] -mod tests { - use super::*; - use crate::identities; - - #[tokio::test] - async fn test_identity_creation() -> Result<()> { - let identities = identities(); - let creation = identities.identities_creation(); - let repository = identities.repository(); - let keys = identities.identities_keys(); - - let identity = creation.create_identity().await?; - let actual = repository.get_identity(&identity.identifier()).await?; - assert_eq!( - actual, - identity.clone(), - "the identity can be retrieved from the repository" - ); - - let actual = repository.retrieve_identity(&identity.identifier()).await?; - assert_eq!( - actual, - Some(identity.clone()), - "the identity can be retrieved from the repository as an Option" - ); - - let missing = repository - .retrieve_identity(&IdentityIdentifier::from_hex( - "e92f183eb4c324804ef4d62962dea94cf095a265d4d28500c34e1a4e0d5ef638", - )) +impl IdentitiesCreation { + /// Compare Identity that was received by any side-channel (e.g., Secure Channel) to the + /// version we have observed and stored before. + /// - Do nothing if they're equal + /// - Throw an error if the received version has conflict or is older that previously observed + /// - Update stored Identity if the received version is newer + pub async fn update_identity(&self, identity: &Identity) -> Result<()> { + if let Some(known_identity) = self + .repository + .retrieve_identity(identity.identifier()) + .await? + { + let known_identity = Identity::import_from_change_history( + Some(identity.identifier()), + known_identity, + self.verifying_vault.clone(), + ) .await?; - assert_eq!(missing, None, "a missing identity returns None"); - let root_key = keys.get_secret_key(&identity, None).await; - assert!(root_key.is_ok(), "there is a key for the created identity"); + match identity.compare(&known_identity) { + IdentityHistoryComparison::Conflict | IdentityHistoryComparison::Older => { + return Err(IdentityError::ConsistencyError.into()); + } + IdentityHistoryComparison::Newer => { + self.repository + .update_identity(identity.identifier(), identity.change_history()) + .await?; + } + IdentityHistoryComparison::Equal => {} + } + } else { + self.repository + .update_identity(identity.identifier(), identity.change_history()) + .await?; + } Ok(()) } diff --git a/implementations/rust/ockam/ockam_identity/src/identities/identities_vault.rs b/implementations/rust/ockam/ockam_identity/src/identities/identities_vault.rs deleted file mode 100644 index 7b2aebc0c3d..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/identities/identities_vault.rs +++ /dev/null @@ -1,131 +0,0 @@ -use ockam_core::compat::boxed::Box; -use ockam_core::compat::sync::Arc; -use ockam_core::compat::vec::Vec; -use ockam_core::{async_trait, Result}; -use ockam_vault::{ - AsymmetricVault, EphemeralSecretsStore, Implementation, KeyId, PersistentSecretsStore, - SecretsStore, SecretsStoreReader, SymmetricVault, -}; -use ockam_vault::{PublicKey, Secret, SecretAttributes}; -use ockam_vault::{Signature, Signer, StoredSecret}; - -/// Traits required for a Vault implementation suitable for use in an Identity -/// Vault with XX required functionality -pub trait IdentitiesVault: XXVault + PersistentSecretsStore + Signer {} - -impl IdentitiesVault for D where D: XXVault + PersistentSecretsStore + Signer {} - -/// Vault with XX required functionality -pub trait XXVault: SecretsStore + AsymmetricVault + SymmetricVault + Send + Sync + 'static {} - -impl XXVault for D where - D: SecretsStore + AsymmetricVault + SymmetricVault + Send + Sync + 'static -{ -} - -/// Vault with required functionalities after XX key exchange -pub trait XXInitializedVault: SecretsStore + SymmetricVault + Send + Sync + 'static {} - -impl XXInitializedVault for D where D: SecretsStore + SymmetricVault + Send + Sync + 'static {} - -/// This struct is used to compensate for the lack of non-experimental trait upcasting in Rust -/// We encapsulate an IdentitiesVault and delegate the implementation of all the functions of -/// the various traits inherited by IdentitiesVault: SymmetricVault, SecretVault, etc... -struct CoercedIdentitiesVault { - vault: Arc, -} - -impl Implementation for CoercedIdentitiesVault {} - -#[async_trait] -impl EphemeralSecretsStore for CoercedIdentitiesVault { - async fn create_ephemeral_secret(&self, attributes: SecretAttributes) -> Result { - self.vault.create_ephemeral_secret(attributes).await - } - - async fn import_ephemeral_secret( - &self, - secret: Secret, - attributes: SecretAttributes, - ) -> Result { - self.vault.import_ephemeral_secret(secret, attributes).await - } - - async fn get_ephemeral_secret( - &self, - key_id: &KeyId, - description: &str, - ) -> Result { - self.vault.get_ephemeral_secret(key_id, description).await - } - - async fn delete_ephemeral_secret(&self, key_id: KeyId) -> Result { - self.vault.delete_ephemeral_secret(key_id).await - } - - async fn list_ephemeral_secrets(&self) -> Result> { - self.vault.list_ephemeral_secrets().await - } -} - -#[async_trait] -impl PersistentSecretsStore for CoercedIdentitiesVault { - async fn create_persistent_secret(&self, attributes: SecretAttributes) -> Result { - self.vault.create_persistent_secret(attributes).await - } - - async fn delete_persistent_secret(&self, key_id: KeyId) -> Result { - self.vault.delete_persistent_secret(key_id).await - } -} - -#[async_trait] -impl SecretsStoreReader for CoercedIdentitiesVault { - async fn get_secret_attributes(&self, key_id: &KeyId) -> Result { - self.vault.get_secret_attributes(key_id).await - } - - async fn get_public_key(&self, key_id: &KeyId) -> Result { - self.vault.get_public_key(key_id).await - } - - async fn get_key_id(&self, public_key: &PublicKey) -> Result { - self.vault.get_key_id(public_key).await - } -} - -#[async_trait] -impl Signer for CoercedIdentitiesVault { - async fn sign(&self, key_id: &KeyId, data: &[u8]) -> Result { - self.vault.sign(key_id, data).await - } - async fn verify( - &self, - public_key: &PublicKey, - data: &[u8], - signature: &Signature, - ) -> Result { - self.vault.verify(public_key, data, signature).await - } -} - -/// Return this vault as a symmetric vault -pub fn to_symmetric_vault(vault: Arc) -> Arc { - Arc::new(CoercedIdentitiesVault { - vault: vault.clone(), - }) -} - -/// Return this vault as a XX vault -pub fn to_xx_vault(vault: Arc) -> Arc { - Arc::new(CoercedIdentitiesVault { - vault: vault.clone(), - }) -} - -/// Returns this vault as a XX initialized vault -pub fn to_xx_initialized(vault: Arc) -> Arc { - Arc::new(CoercedIdentitiesVault { - vault: vault.clone(), - }) -} diff --git a/implementations/rust/ockam/ockam_identity/src/identities/identity_builder.rs b/implementations/rust/ockam/ockam_identity/src/identities/identity_builder.rs new file mode 100644 index 00000000000..25259fd5829 --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/src/identities/identity_builder.rs @@ -0,0 +1,133 @@ +use ockam_core::compat::sync::Arc; +use ockam_core::Result; +use ockam_vault::{KeyId, SecretType}; + +use crate::models::TimestampInSeconds; +use crate::utils::now; +use crate::IdentitiesCreation; +use crate::{Identity, IdentityOptions}; + +/// Default TTL for an Identity key +pub const DEFAULT_IDENTITY_TTL: TimestampInSeconds = TimestampInSeconds(10 * 365 * 24 * 60 * 60); // Ten years + +enum Key { + Generate(SecretType), + Existing { key_id: KeyId, stype: SecretType }, +} + +enum Ttl { + CreatedNowWithTtl(TimestampInSeconds), + FullTimestamps { + created_at: TimestampInSeconds, + expires_at: TimestampInSeconds, + }, +} + +/// Builder for [`Identity`] +pub struct IdentityBuilder { + identities_creation: Arc, + + revoke_all_purpose_keys: bool, + key: Key, + ttl: Ttl, +} + +impl IdentityBuilder { + /// Constructor + pub fn new(identities_creation: Arc) -> Self { + Self { + identities_creation, + revoke_all_purpose_keys: false, + key: Key::Generate(SecretType::Ed25519), + ttl: Ttl::CreatedNowWithTtl(DEFAULT_IDENTITY_TTL), + } + } + + /// Use an existing key for the Identity (should be present in the corresponding [`SigningVault`]) + pub fn with_existing_key(mut self, key_id: KeyId, stype: SecretType) -> Self { + self.key = Key::Existing { key_id, stype }; + self + } + + /// Will generate a fresh key with the given type + pub fn with_random_key(mut self, key_type: SecretType) -> Self { + self.key = Key::Generate(key_type); + self + } + + /// Set created_at and expires_at timestamps + pub fn with_timestamps( + mut self, + created_at: TimestampInSeconds, + expires_at: TimestampInSeconds, + ) -> Self { + self.ttl = Ttl::FullTimestamps { + created_at, + expires_at, + }; + self + } + + /// Will set created_at to now and compute expires_at given the TTL + pub fn with_ttl(mut self, ttl_seconds: impl Into) -> Self { + self.ttl = Ttl::CreatedNowWithTtl(ttl_seconds.into()); + self + } + + /// Revoke all previously issued [`PurposeKey`]s + pub fn with_purpose_keys_revocation(mut self) -> Self { + self.revoke_all_purpose_keys = true; + self + } + + /// Create the corresponding [`IdentityOptions`] object + pub async fn build_options(self) -> Result { + let (key, stype) = match self.key { + Key::Generate(stype) => { + let attributes = stype.try_into()?; + let key = self + .identities_creation + .identity_vault + .generate_key(attributes) + .await?; + + (key, stype) + } + Key::Existing { key_id, stype } => (key_id, stype), + }; + + let (created_at, expires_at) = match self.ttl { + Ttl::CreatedNowWithTtl(ttl) => { + let created_at = now()?; + let expires_at = created_at + ttl; + + (created_at, expires_at) + } + Ttl::FullTimestamps { + created_at, + expires_at, + } => (created_at, expires_at), + }; + + let options = IdentityOptions::new( + key, + stype, + self.revoke_all_purpose_keys, + created_at, + expires_at, + ); + + Ok(options) + } + + /// Create the corresponding [`Identity`] + pub async fn build(self) -> Result { + let identities_creation = self.identities_creation.clone(); + + let options = self.build_options().await?; + + identities_creation + .create_identity_with_options(options) + .await + } +} diff --git a/implementations/rust/ockam/ockam_identity/src/identities/identity_keys.rs b/implementations/rust/ockam/ockam_identity/src/identities/identity_keys.rs index 6b42fcb2471..bf5bca29c90 100644 --- a/implementations/rust/ockam/ockam_identity/src/identities/identity_keys.rs +++ b/implementations/rust/ockam/ockam_identity/src/identities/identity_keys.rs @@ -1,439 +1,172 @@ -use crate::alloc::string::ToString; -use crate::identities::IdentitiesVault; -use crate::identity::IdentityChange::{CreateKey, RotateKey}; -use crate::identity::{ - ChangeIdentifier, CreateKeyChangeData, Identity, IdentityChangeConstants, - IdentityChangeHistory, IdentitySignedChange, KeyAttributes, RotateKeyChangeData, Signature, - SignatureType, +use crate::identity::Identity; +use crate::models::{ + Change, ChangeData, ChangeHash, ChangeHistory, ChangeSignature, VersionedData, }; -use crate::IdentityError; -use crate::IdentityError::InvalidInternalState; -use ockam_core::compat::string::String; +use crate::{IdentityError, IdentityOptions}; + use ockam_core::compat::sync::Arc; -use ockam_core::{Encodable, Result}; -use ockam_vault::{KeyId, SecretAttributes, Vault}; +use ockam_core::Result; +use ockam_vault::{KeyId, SecretType, SigningVault, VerifyingVault}; + +use tracing::error; /// This module supports the key operations related to identities pub struct IdentitiesKeys { - vault: Arc, + identity_vault: Arc, + verifying_vault: Arc, } impl IdentitiesKeys { - pub(crate) async fn create_initial_key( - &self, - key_id: Option<&KeyId>, - key_attribs: KeyAttributes, - ) -> Result { - let initial_change_id = self.make_change_identifier().await?; - let create_key_change = self - .make_create_key_change_static(key_id, initial_change_id, key_attribs.clone(), None) - .await?; - let change_history = IdentityChangeHistory::new(create_key_change); - - // Sanity checks - change_history.check_entire_consistency()?; - self.verify_all_existing_changes(&change_history).await?; - - Ok(change_history) - } + pub(crate) async fn create_initial_key(&self, options: IdentityOptions) -> Result { + let change = self.make_change(options, None).await?; + let change_history = ChangeHistory(vec![change]); + + let identity = Identity::import_from_change_history( + None, + change_history, + self.verifying_vault.clone(), + ) + .await?; - /// Initial `ChangeIdentifier` that is used as a previous_identifier of the first change - async fn make_change_identifier(&self) -> Result { - let hash = Vault::sha256(IdentityChangeConstants::INITIAL_CHANGE); - Ok(ChangeIdentifier::from_hash(hash)) + Ok(identity) } } -/// Public functions +/// Public functions impl IdentitiesKeys { /// Create a new identities keys module - pub fn new(vault: Arc) -> Self { - Self { vault } - } - - /// Create a new identities keys module with an in-memory vault - /// Sign some binary data with the signing key of an identity - pub async fn create_signature( - &self, - identity: &Identity, - data: &[u8], - key_label: Option<&str>, - ) -> Result { - let secret = self.get_secret_key(identity, key_label).await?; - self.vault.sign(&secret, data).await + pub fn new( + identity_vault: Arc, + verifying_vault: Arc, + ) -> Self { + Self { + identity_vault, + verifying_vault, + } } - /// Verify the signature of a piece of data - pub async fn verify_signature( + /// Rotate the Identity Key + pub async fn rotate_key_with_options( &self, - identity: &Identity, - signature: &ockam_vault::Signature, - data: &[u8], - key_label: Option<&str>, - ) -> Result { - let public_key = identity.get_public_key(key_label)?; - self.vault.verify(&public_key, data, signature).await - } + identity: Identity, + options: IdentityOptions, + ) -> Result { + let last_change = match identity.changes().last() { + Some(last_change) => last_change, + None => return Err(IdentityError::EmptyIdentity.into()), + }; - /// Generate and add a new key to this `Identity` with a given `label` - pub async fn create_key(&self, identity: &mut Identity, label: String) -> Result<()> { - let key_attribs = KeyAttributes::default_with_label(label); - let change = self - .make_create_key_change(identity, None, key_attribs) - .await?; - identity.add_change(change) - } + let last_secret_key = self.get_secret_key(&identity).await?; - /// Rotate an existing key with a given label - pub async fn rotate_key(&self, identity: &mut Identity, label: &str) -> Result<()> { let change = self - .make_rotate_key_change( - identity, - KeyAttributes::default_with_label(label.to_string()), + .make_change( + options, + Some((last_change.change_hash().clone(), last_secret_key.clone())), ) .await?; - identity.add_change(change) - } - - /// Add a new key to this `Identity` with a given `label` - pub async fn add_key( - &self, - identity: &mut Identity, - label: String, - secret: &KeyId, - ) -> Result<()> { - let secret_attributes = self.vault.get_secret_attributes(secret).await?; - let key_attribs = KeyAttributes::new(label, secret_attributes); - - let change = self - .make_create_key_change(identity, Some(secret), key_attribs) + let identity = identity + .add_change(change, self.verifying_vault.clone()) .await?; - identity.add_change(change) - } - - /// Verify all changes present in current `IdentityChangeHistory` - pub(crate) async fn verify_all_existing_changes( - &self, - identity_changes: &IdentityChangeHistory, - ) -> Result<()> { - for i in 0..identity_changes.as_ref().len() { - let existing_changes = &identity_changes.as_ref()[..i]; - let new_change = &identity_changes.as_ref()[i]; - self.verify_change(existing_changes, new_change).await? + if self + .identity_vault + .delete_key(last_secret_key) + .await + .is_err() + { + error!( + "Error deleting old Identity Key for {}", + identity.identifier() + ); } - Ok(()) - } - /// Return the secret key of an identity - pub async fn get_secret_key( - &self, - identity: &Identity, - key_label: Option<&str>, - ) -> Result { - let key = match key_label { - Some(label) => self.get_labelled_key(identity, label).await?, - None => self.get_root_secret_key(identity).await?, - }; - Ok(key) + Ok(identity) } - /// Rotate this `Identity` root key - pub async fn rotate_root_key(&self, identity: &mut Identity) -> Result<()> { - let change = self - .make_rotate_key_change( - identity, - KeyAttributes::default_with_label(IdentityChangeConstants::ROOT_LABEL.to_string()), - ) - .await?; - - identity.add_change(change) - } - - /// Creates a signed static key to use for 'xx' key exchange - pub async fn create_signed_static_key( - &self, - identity: &Identity, - ) -> Result<(KeyId, ockam_vault::Signature)> { - let static_key_id = self - .vault - .create_ephemeral_secret(SecretAttributes::X25519) - .await?; - - let public_static_key = self.vault.get_public_key(&static_key_id).await?; - - let signature = self - .create_signature(identity, public_static_key.data(), None) - .await?; - - Ok((static_key_id, signature)) + /// Return the secret key of an identity + pub async fn get_secret_key(&self, identity: &Identity) -> Result { + if let Some(last_change) = identity.changes().last() { + self.identity_vault + .get_key_id(last_change.primary_public_key()) + .await + } else { + Err(IdentityError::EmptyIdentity.into()) + } } } /// Private functions impl IdentitiesKeys { /// Create a new key - async fn make_create_key_change( - &self, - identity: &Identity, - secret: Option<&KeyId>, - key_attributes: KeyAttributes, - ) -> Result { - let change_history = identity.change_history(); - // Creating key after it was revoked is forbidden - if IdentityChangeHistory::find_last_key_change( - change_history.as_ref(), - key_attributes.label(), - ) - .is_ok() - { - return Err(InvalidInternalState.into()); - } - - let prev_id = match change_history.get_last_change_id() { - Ok(prev_id) => prev_id, - Err(_) => self.make_change_identifier().await?, - }; - - let root_secret = self.get_root_secret_key(identity).await?; - let root_key = Some(&root_secret); - - self.make_create_key_change_static(secret, prev_id, key_attributes, root_key) - .await - } - - /// Create a new key - async fn make_create_key_change_static( + async fn make_change( &self, - secret: Option<&KeyId>, - prev_id: ChangeIdentifier, - key_attributes: KeyAttributes, - root_key: Option<&KeyId>, - ) -> Result { - let secret_key = self.generate_key_if_needed(secret, &key_attributes).await?; - let public_key = self.vault.get_public_key(&secret_key).await?; - - let data = CreateKeyChangeData::new(prev_id, key_attributes, public_key); - - let change_block = CreateKey(data); - let change_block_binary = change_block - .encode() - .map_err(|_| IdentityError::BareError)?; - - let change_id = Vault::sha256(&change_block_binary); - let change_id = ChangeIdentifier::from_hash(change_id); - - let self_signature = self.vault.sign(&secret_key, change_id.as_ref()).await?; - let self_signature = Signature::new(SignatureType::SelfSign, self_signature); - - let mut signatures = vec![self_signature]; - - // If we have root_key passed we should sign using it - // If there is no root_key - we're creating new identity, so we just generated root_key - if let Some(root_key) = root_key { - let root_signature = self.vault.sign(root_key, change_id.as_ref()).await?; - let root_signature = Signature::new(SignatureType::RootSign, root_signature); - - signatures.push(root_signature); - } - - let signed_change = IdentitySignedChange::new(change_id, change_block, signatures); - - Ok(signed_change) - } - - async fn generate_key_if_needed( - &self, - secret: Option<&KeyId>, - key_attributes: &KeyAttributes, - ) -> Result { - if let Some(s) = secret { - Ok(s.clone()) - } else { - self.vault - .create_persistent_secret(key_attributes.secret_attributes()) - .await + identity_options: IdentityOptions, + previous: Option<(ChangeHash, KeyId)>, + ) -> Result { + match identity_options.stype { + SecretType::Ed25519 | SecretType::NistP256 => {} + + SecretType::Buffer | SecretType::Aes | SecretType::X25519 => { + return Err(IdentityError::InvalidKeyType.into()); + } } - } - - async fn get_labelled_key(&self, identity: &Identity, label: &str) -> Result { - let change = - IdentityChangeHistory::find_last_key_change(identity.change_history().as_ref(), label)? - .clone(); - self.get_secret_key_from_change(&change).await - } - - /// Rotate key change - async fn make_rotate_key_change( - &self, - identity: &mut Identity, - key_attributes: KeyAttributes, - ) -> Result { - let prev_change_id = identity.change_history.get_last_change_id()?; - - let last_change_in_chain = IdentityChangeHistory::find_last_key_change( - identity.change_history.as_ref(), - key_attributes.label(), - )? - .clone(); - - let last_key_in_chain = self - .get_secret_key_from_change(&last_change_in_chain) - .await?; - - let secret_attributes = key_attributes.secret_attributes(); - - let secret_key = self - .vault - .create_persistent_secret(secret_attributes) - .await?; - let public_key = self.vault.get_public_key(&secret_key).await?; - - let data = RotateKeyChangeData::new(prev_change_id, key_attributes, public_key); - - let change_block = RotateKey(data); - let change_block_binary = change_block - .encode() - .map_err(|_| IdentityError::BareError)?; - let change_id = Vault::sha256(&change_block_binary); - let change_id = ChangeIdentifier::from_hash(change_id); + let secret_key = identity_options.key; + let public_key = self.identity_vault.get_public_key(&secret_key).await?; + let stype = public_key.stype(); - let self_signature = self.vault.sign(&secret_key, change_id.as_ref()).await?; - let self_signature = Signature::new(SignatureType::SelfSign, self_signature); + let primary_public_key = public_key.try_into()?; - let root_key = self.get_root_secret_key(identity).await?; - - let root_signature = self.vault.sign(&root_key, change_id.as_ref()).await?; - let root_signature = Signature::new(SignatureType::RootSign, root_signature); - - let prev_signature = self - .vault - .sign(&last_key_in_chain, change_id.as_ref()) - .await?; - let prev_signature = Signature::new(SignatureType::PrevSign, prev_signature); - - let signed_change = IdentitySignedChange::new( - change_id, - change_block, - vec![self_signature, root_signature, prev_signature], - ); - - Ok(signed_change) - } - - /// Get [`Secret`] key. Key is uniquely identified by label in [`KeyAttributes`] - async fn get_root_secret_key(&self, identity: &Identity) -> Result { - self.get_labelled_key(identity, IdentityChangeConstants::ROOT_LABEL) - .await - } - - async fn get_secret_key_from_change(&self, change: &IdentitySignedChange) -> Result { - let public_key = change.change().public_key()?; - self.vault.get_key_id(&public_key).await - } - - /// Verify all changes present in current `IdentityChangeHistory` - pub async fn verify_changes(&self, identity: &Identity) -> Result<()> { - self.verify_all_existing_changes(&identity.change_history()) - .await - } - - /// WARNING: This function assumes all existing changes in chain are verified. - /// WARNING: Correctness of changes sequence is not verified here. - async fn verify_change( - &self, - existing_changes: &[IdentitySignedChange], - new_change: &IdentitySignedChange, - ) -> Result<()> { - let change_binary = new_change - .change() - .encode() - .map_err(|_| IdentityError::BareError)?; - - let change_id = Vault::sha256(&change_binary); - let change_id = ChangeIdentifier::from_hash(change_id); - - if &change_id != new_change.identifier() { - return Err(IdentityError::IdentityVerificationFailed.into()); // ChangeIdDoesNotMatch - } + let change_data = ChangeData { + previous_change: previous.as_ref().map(|x| x.0.clone()), + primary_public_key, + revoke_all_purpose_keys: identity_options.revoke_all_purpose_keys, + created_at: identity_options.created_at, + expires_at: identity_options.expires_at, + }; - struct SignaturesCheck { - self_sign: u8, - prev_sign: u8, - root_sign: u8, - } + let change_data = minicbor::to_vec(&change_data)?; - let mut signatures_check = match new_change.change() { - CreateKey(_) => { - // Should have self signature and root signature - // There is no Root signature for the very first change - let root_sign = u8::from(!existing_changes.is_empty()); - - SignaturesCheck { - self_sign: 1, - prev_sign: 0, - root_sign, - } - } - RotateKey(_) => { - // Should have self signature, root signature, and previous key signature - SignaturesCheck { - self_sign: 1, - prev_sign: 1, - root_sign: 1, - } - } + let versioned_data = VersionedData { + version: 1, + data: change_data, }; - for signature in new_change.signatures() { - let counter; - let public_key = match signature.stype() { - SignatureType::RootSign => { - if existing_changes.is_empty() { - return Err(IdentityError::IdentityVerificationFailed.into()); - } - - counter = &mut signatures_check.root_sign; - IdentityChangeHistory::get_current_root_public_key(existing_changes)? - } - SignatureType::SelfSign => { - counter = &mut signatures_check.self_sign; - new_change.change().public_key()? - } - SignatureType::PrevSign => { - counter = &mut signatures_check.prev_sign; - IdentityChangeHistory::get_public_key_static( - existing_changes, - new_change.change().label(), - )? - } - }; - - if *counter == 0 { - return Err(IdentityError::IdentityVerificationFailed.into()); - } - - if !self - .vault - .verify(&public_key, change_id.as_ref(), signature.data()) - .await? - { - return Err(IdentityError::IdentityVerificationFailed.into()); + let versioned_data = minicbor::to_vec(&versioned_data)?; + + let hash = self.verifying_vault.sha256(&versioned_data).await?; + + let self_signature = self.identity_vault.sign(&secret_key, hash.as_ref()).await?; + let self_signature = ChangeSignature::try_from_signature(self_signature, stype)?; + + // If we have previous_key passed we should sign using it + // If there is no previous_key - we're creating new identity, so we just generated the key + let previous_signature = match previous.map(|x| x.1) { + Some(previous_key) => { + let previous_signature = self + .identity_vault + .sign(&previous_key, hash.as_ref()) + .await?; + // TODO: Optimize + let previous_public_key = self.identity_vault.get_public_key(&previous_key).await?; + let previous_signature = ChangeSignature::try_from_signature( + previous_signature, + previous_public_key.stype(), + )?; + + Some(previous_signature) } + None => None, + }; - *counter -= 1; - } + let change = Change { + data: versioned_data, + signature: self_signature, + previous_signature, + }; - if signatures_check.prev_sign == 0 - && signatures_check.root_sign == 0 - && signatures_check.self_sign == 0 - { - Ok(()) - } else { - Err(IdentityError::IdentityVerificationFailed.into()) - } + Ok(change) } } @@ -441,9 +174,13 @@ impl IdentitiesKeys { mod test { use super::*; use crate::identities; + use crate::models::Identifier; + use crate::utils::now; + use core::str::FromStr; use ockam_core::errcode::{Kind, Origin}; use ockam_core::Error; use ockam_node::Context; + use ockam_vault::{SecretAttributes, SecretType}; fn test_error>(error: S) -> Result<()> { Err(Error::new_without_cause(Origin::Identity, Kind::Unknown).context("msg", error.into())) @@ -452,48 +189,109 @@ mod test { #[ockam_macros::test] async fn test_basic_identity_key_ops(ctx: &mut Context) -> Result<()> { let identities = identities(); - let identity_keys = identities.identities_keys(); - let mut identity = identities.identities_creation().create_identity().await?; + let identities_keys = identities.identities_keys(); - identity_keys.verify_changes(&identity).await?; - let secret1 = identity_keys.get_root_secret_key(&identity).await?; - let public1 = identity.get_root_public_key()?; - - identity_keys - .create_key(&mut identity, "Truck management".to_string()) + let key1 = identities_keys + .identity_vault + .generate_key(SecretAttributes::Ed25519) .await?; - identity_keys.verify_changes(&identity).await?; - let secret2 = identity_keys - .get_labelled_key(&identity, "Truck management") - .await?; - let public2 = identity.get_public_key(Some("Truck management"))?; + let now = now()?; + let created_at1 = now; + let expires_at1 = created_at1 + 120.into(); - if secret1 == secret2 { - return test_error("secret did not change after create_key"); - } + let options1 = IdentityOptions::new( + key1.clone(), + SecretType::Ed25519, + false, + created_at1, + expires_at1, + ); + let identity1 = identities_keys.create_initial_key(options1).await?; - if public1 == public2 { - return test_error("public did not change after create_key"); - } + // Identifier should not match + let res = Identity::import_from_change_history( + Some(&Identifier::from_str("Iabababababababababababababababababababab").unwrap()), + identity1.change_history().clone(), + identities.vault().verifying_vault, + ) + .await; + assert!(res.is_err()); + + // Check if verification succeeds + let _ = Identity::import_from_change_history( + Some(identity1.identifier()), + identity1.change_history().clone(), + identities.vault().verifying_vault, + ) + .await?; - identity_keys.rotate_root_key(&mut identity).await?; - identity_keys.verify_changes(&identity).await?; + let secret1 = identities_keys.get_secret_key(&identity1).await?; + let public1 = identity1.get_latest_public_key()?; + assert_eq!(secret1, key1); - let secret3 = identity_keys.get_root_secret_key(&identity).await?; - let public3 = identity.get_root_public_key()?; + let key2 = identities_keys + .identity_vault + .generate_key(SecretAttributes::Ed25519) + .await?; - identity_keys.rotate_root_key(&mut identity).await?; - identity_keys.verify_changes(&identity).await?; + let created_at2 = now + 10.into(); + let expires_at2 = created_at2 + 120.into(); + let options2 = IdentityOptions::new( + key2.clone(), + SecretType::Ed25519, + false, + created_at2, + expires_at2, + ); + let identity2 = identities_keys + .rotate_key_with_options(identity1, options2) + .await?; - if secret1 == secret3 { + // Identifier should not match + let res = Identity::import_from_change_history( + Some(&Identifier::from_str("Iabababababababababababababababababababab").unwrap()), + identity2.change_history().clone(), + identities.vault().verifying_vault, + ) + .await; + assert!(res.is_err()); + + // Check if verification succeeds + let _ = Identity::import_from_change_history( + Some(identity2.identifier()), + identity2.change_history().clone(), + identities.vault().verifying_vault, + ) + .await?; + + let secret2 = identities_keys.get_secret_key(&identity2).await?; + let public2 = identity2.get_latest_public_key()?; + assert_eq!(secret2, key2); + + if secret1 == secret2 { return test_error("secret did not change after rotate_key"); } - if public1 == public3 { + if public1 == public2 { return test_error("public did not change after rotate_key"); } + // Old key should be deleted + assert!(identities + .vault() + .identity_vault + .get_public_key(&secret1) + .await + .is_err()); + // New key should exist + assert!(identities + .vault() + .identity_vault + .get_public_key(&secret2) + .await + .is_ok()); + ctx.stop().await } } diff --git a/implementations/rust/ockam/ockam_identity/src/identities/identity_options.rs b/implementations/rust/ockam/ockam_identity/src/identities/identity_options.rs new file mode 100644 index 00000000000..292a70fcbd2 --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/src/identities/identity_options.rs @@ -0,0 +1,56 @@ +use ockam_vault::{KeyId, SecretType}; + +use crate::TimestampInSeconds; + +/// Options to create an Identity key +pub struct IdentityOptions { + pub(super) key: KeyId, + pub(super) stype: SecretType, + pub(super) revoke_all_purpose_keys: bool, + pub(super) created_at: TimestampInSeconds, + pub(super) expires_at: TimestampInSeconds, +} + +impl IdentityOptions { + /// Constructor + pub fn new( + key: KeyId, + stype: SecretType, + revoke_all_purpose_keys: bool, + created_at: TimestampInSeconds, + expires_at: TimestampInSeconds, + ) -> Self { + Self { + key, + stype, + revoke_all_purpose_keys, + created_at, + expires_at, + } + } + + /// New key + pub fn key(&self) -> &KeyId { + &self.key + } + + /// Secret key type + pub fn stype(&self) -> SecretType { + self.stype + } + + /// Revoke all PurposeKeys issued by previous Identity keys + pub fn revoke_all_purpose_keys(&self) -> bool { + self.revoke_all_purpose_keys + } + + /// Creation timestamp + pub fn created_at(&self) -> TimestampInSeconds { + self.created_at + } + + /// Expiration timestamp + pub fn expires_at(&self) -> TimestampInSeconds { + self.expires_at + } +} diff --git a/implementations/rust/ockam/ockam_identity/src/identities/mod.rs b/implementations/rust/ockam/ockam_identity/src/identities/mod.rs index 557f38ccf8e..469b141a565 100644 --- a/implementations/rust/ockam/ockam_identity/src/identities/mod.rs +++ b/implementations/rust/ockam/ockam_identity/src/identities/mod.rs @@ -2,8 +2,9 @@ mod identities; mod identities_builder; mod identities_creation; -mod identities_vault; +mod identity_builder; mod identity_keys; +mod identity_options; /// Identities storage functions pub mod storage; @@ -11,9 +12,7 @@ pub mod storage; pub use identities::*; pub use identities_builder::*; pub use identities_creation::*; -pub use identities_vault::*; +pub use identity_builder::*; pub use identity_keys::*; +pub use identity_options::*; pub use storage::*; - -#[cfg(test)] -mod tests; diff --git a/implementations/rust/ockam/ockam_identity/src/identities/storage/attributes_entry.rs b/implementations/rust/ockam/ockam_identity/src/identities/storage/attributes_entry.rs index 4a174402878..225aa8e1eee 100644 --- a/implementations/rust/ockam/ockam_identity/src/identities/storage/attributes_entry.rs +++ b/implementations/rust/ockam/ockam_identity/src/identities/storage/attributes_entry.rs @@ -1,8 +1,7 @@ -use crate::alloc::borrow::ToOwned; -use crate::credential::Timestamp; -use crate::identity::IdentityIdentifier; +use crate::models::{Identifier, TimestampInSeconds}; use minicbor::{Decode, Encode}; -use ockam_core::compat::{collections::BTreeMap, string::String, vec::Vec}; +use ockam_core::compat::borrow::ToOwned; +use ockam_core::compat::{collections::BTreeMap, vec::Vec}; use serde::{Deserialize, Serialize}; /// An entry on the AuthenticatedIdentities table. @@ -10,10 +9,11 @@ use serde::{Deserialize, Serialize}; #[rustfmt::skip] #[cbor(map)] pub struct AttributesEntry { - #[b(1)] attrs: BTreeMap>, - #[n(2)] added: Timestamp, - #[n(3)] expires: Option, - #[n(4)] attested_by: Option, + // TODO: Check how it looks serialized with both serde and minicbor + #[b(1)] attrs: BTreeMap, Vec>, + #[n(2)] added: TimestampInSeconds, + #[n(3)] expires: Option, + #[n(4)] attested_by: Option, } impl AttributesEntry { @@ -23,10 +23,10 @@ impl AttributesEntry { /// Constructor pub fn new( - attrs: BTreeMap>, - added: Timestamp, - expires: Option, - attested_by: Option, + attrs: BTreeMap, Vec>, + added: TimestampInSeconds, + expires: Option, + attested_by: Option, ) -> Self { Self { attrs, @@ -37,22 +37,22 @@ impl AttributesEntry { } /// The entry attributes - pub fn attrs(&self) -> &BTreeMap> { + pub fn attrs(&self) -> &BTreeMap, Vec> { &self.attrs } /// Expiration time for this entry - pub fn expires(&self) -> Option { + pub fn expires(&self) -> Option { self.expires } /// Date that the entry was added - pub fn added(&self) -> Timestamp { + pub fn added(&self) -> TimestampInSeconds { self.added } /// Who attested this attributes for this identity identifier - pub fn attested_by(&self) -> Option { + pub fn attested_by(&self) -> Option { self.attested_by.to_owned() } } diff --git a/implementations/rust/ockam/ockam_identity/src/identities/storage/identities_repository.rs b/implementations/rust/ockam/ockam_identity/src/identities/storage/identities_repository.rs deleted file mode 100644 index cfaf5e41236..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/identities/storage/identities_repository.rs +++ /dev/null @@ -1,298 +0,0 @@ -use ockam_core::compat::boxed::Box; -use ockam_core::compat::collections::BTreeMap; -use ockam_core::compat::sync::Arc; -use ockam_core::compat::vec::Vec; -use ockam_core::errcode::{Kind, Origin}; -use ockam_core::Result; -use ockam_core::{async_trait, Error}; - -use crate::alloc::string::ToString; -use crate::credential::Timestamp; -use crate::identities::storage::storage::{InMemoryStorage, Storage}; -use crate::identity::IdentityHistoryComparison; -use crate::identity::{Identity, IdentityChangeConstants, IdentityIdentifier}; -use crate::{AttributesEntry, IdentityError}; - -/// Repository for data related to identities: key changes and attributes -#[async_trait] -pub trait IdentitiesRepository: - IdentityAttributesReader + IdentityAttributesWriter + IdentitiesReader + IdentitiesWriter -{ - /// Restrict this repository as a reader for attributes - fn as_attributes_reader(&self) -> Arc; - - /// Restrict this repository as a writer for attributes - fn as_attributes_writer(&self) -> Arc; - - /// Restrict this repository as a reader for identities - fn as_identities_reader(&self) -> Arc; - - /// Restrict this repository as a writer for identities - fn as_identities_writer(&self) -> Arc; -} - -#[async_trait] -impl IdentitiesRepository for IdentitiesStorage { - fn as_attributes_reader(&self) -> Arc { - Arc::new(self.clone()) - } - - fn as_attributes_writer(&self) -> Arc { - Arc::new(self.clone()) - } - - fn as_identities_reader(&self) -> Arc { - Arc::new(self.clone()) - } - - fn as_identities_writer(&self) -> Arc { - Arc::new(self.clone()) - } -} - -/// Trait implementing read access to attributes -#[async_trait] -pub trait IdentityAttributesReader: Send + Sync + 'static { - /// Get the attributes associated with the given identity identifier - async fn get_attributes( - &self, - identity: &IdentityIdentifier, - ) -> Result>; - - /// List all identities with their attributes - async fn list(&self) -> Result>; -} - -/// Trait implementing write access to attributes -#[async_trait] -pub trait IdentityAttributesWriter: Send + Sync + 'static { - /// Set the attributes associated with the given identity identifier. - /// Previous values gets overridden. - async fn put_attributes( - &self, - identity: &IdentityIdentifier, - entry: AttributesEntry, - ) -> Result<()>; - - /// Store an attribute name/value pair for a given identity - async fn put_attribute_value( - &self, - subject: &IdentityIdentifier, - attribute_name: &str, - attribute_value: &str, - ) -> Result<()>; - - /// Remove all attributes for a given identity identifier - async fn delete(&self, identity: &IdentityIdentifier) -> Result<()>; -} - -/// Trait implementing write access to identities -#[async_trait] -pub trait IdentitiesWriter: Send + Sync + 'static { - /// Store changes if there are new key changes associated to that identity - /// Return an error if the current change history conflicts with the persisted one - async fn update_identity(&self, identity: &Identity) -> Result<()>; -} - -/// Trait implementing read access to identiets -#[async_trait] -pub trait IdentitiesReader: Send + Sync + 'static { - /// Return a persisted identity - async fn retrieve_identity(&self, identifier: &IdentityIdentifier) -> Result>; - - /// Return a persisted identity that is expected to be present and return and Error if this is not the case - async fn get_identity(&self, identifier: &IdentityIdentifier) -> Result { - match self.retrieve_identity(identifier).await? { - Some(identity) => Ok(identity), - None => Err(Error::new( - Origin::Core, - Kind::NotFound, - format!("identity not found for identifier {}", identifier), - )), - } - } -} - -/// Implementation of `IdentityAttributes` trait based on an underlying `Storage` -#[derive(Clone)] -pub struct IdentitiesStorage { - storage: Arc, -} - -impl Default for IdentitiesStorage { - fn default() -> IdentitiesStorage { - IdentitiesStorage { - storage: Arc::new(InMemoryStorage::new()), - } - } -} - -impl IdentitiesStorage { - /// Create a new storage for attributes - pub fn new(storage: Arc) -> Self { - Self { storage } - } - - /// Create a new storage for attributes - pub fn create() -> Arc { - Arc::new(Self::default()) - } - - /// Persist an Identity (overrides it) - async fn put_identity(&self, identity: &Identity) -> Result<()> { - self.storage - .set( - &identity.identifier().to_string(), - IdentityChangeConstants::CHANGE_HISTORY_KEY.to_string(), - identity.export()?, - ) - .await - } -} - -#[async_trait] -impl IdentityAttributesReader for IdentitiesStorage { - async fn get_attributes( - &self, - identity_id: &IdentityIdentifier, - ) -> Result> { - let id = identity_id.to_string(); - let entry = match self - .storage - .get(&id, IdentityChangeConstants::ATTRIBUTES_KEY) - .await? - { - Some(e) => e, - None => return Ok(None), - }; - - let entry: AttributesEntry = minicbor::decode(&entry)?; - - let now = Timestamp::now() - .ok_or_else(|| Error::new(Origin::Core, Kind::Internal, "invalid system time"))?; - match entry.expires() { - Some(exp) if exp <= now => { - self.storage - .del(&id, IdentityChangeConstants::ATTRIBUTES_KEY) - .await?; - Ok(None) - } - _ => Ok(Some(entry)), - } - } - - async fn list(&self) -> Result> { - let mut l = Vec::new(); - for id in self - .storage - .keys(IdentityChangeConstants::ATTRIBUTES_KEY) - .await? - { - let identity_identifier = IdentityIdentifier::try_from(id)?; - if let Some(attrs) = self.get_attributes(&identity_identifier).await? { - l.push((identity_identifier, attrs)) - } - } - Ok(l) - } -} - -#[async_trait] -impl IdentityAttributesWriter for IdentitiesStorage { - async fn put_attributes( - &self, - sender: &IdentityIdentifier, - entry: AttributesEntry, - ) -> Result<()> { - // TODO: Implement expiration mechanism in Storage - let entry = minicbor::to_vec(&entry)?; - - self.storage - .set( - &sender.to_string(), - IdentityChangeConstants::ATTRIBUTES_KEY.to_string(), - entry, - ) - .await?; - - Ok(()) - } - - /// Store an attribute name/value pair for a given identity - async fn put_attribute_value( - &self, - subject: &IdentityIdentifier, - attribute_name: &str, - attribute_value: &str, - ) -> Result<()> { - let mut attributes = match self.get_attributes(subject).await? { - Some(entry) => (*entry.attrs()).clone(), - None => BTreeMap::new(), - }; - attributes.insert( - attribute_name.to_string(), - attribute_value.as_bytes().to_vec(), - ); - let entry = AttributesEntry::new( - attributes, - Timestamp::now().unwrap(), - None, - Some(subject.clone()), - ); - self.put_attributes(subject, entry).await - } - - async fn delete(&self, identity: &IdentityIdentifier) -> Result<()> { - self.storage - .del( - identity.to_string().as_str(), - IdentityChangeConstants::ATTRIBUTES_KEY, - ) - .await - } -} - -#[async_trait] -impl IdentitiesWriter for IdentitiesStorage { - async fn update_identity(&self, identity: &Identity) -> Result<()> { - let should_set = if let Some(known) = self.retrieve_identity(&identity.identifier()).await? - { - match identity.changes().compare(known.changes()) { - IdentityHistoryComparison::Equal => false, /* Do nothing */ - IdentityHistoryComparison::Conflict => { - return Err(IdentityError::ConsistencyError.into()); - } - IdentityHistoryComparison::Newer => true, /* Update */ - IdentityHistoryComparison::Older => { - return Err(IdentityError::ConsistencyError.into()); - } - } - } else { - true - }; - - if should_set { - self.put_identity(identity).await?; - } - - Ok(()) - } -} - -#[async_trait] -impl IdentitiesReader for IdentitiesStorage { - async fn retrieve_identity(&self, identifier: &IdentityIdentifier) -> Result> { - if let Some(data) = self - .storage - .get( - &identifier.to_string(), - IdentityChangeConstants::CHANGE_HISTORY_KEY, - ) - .await? - { - Ok(Some(Identity::import(identifier, &data)?)) - } else { - Ok(None) - } - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/identities/storage/identities_repository_impl.rs b/implementations/rust/ockam/ockam_identity/src/identities/storage/identities_repository_impl.rs similarity index 95% rename from implementations/rust/ockam/ockam_identity/src/v2/identities/storage/identities_repository_impl.rs rename to implementations/rust/ockam/ockam_identity/src/identities/storage/identities_repository_impl.rs index 047094ffe86..f2c84584181 100644 --- a/implementations/rust/ockam/ockam_identity/src/v2/identities/storage/identities_repository_impl.rs +++ b/implementations/rust/ockam/ockam_identity/src/identities/storage/identities_repository_impl.rs @@ -6,11 +6,11 @@ use ockam_core::compat::sync::Arc; use ockam_core::compat::vec::Vec; use ockam_core::Result; -use super::super::super::identity::IdentityConstants; -use super::super::super::models::{ChangeHistory, Identifier}; -use super::super::super::storage::{InMemoryStorage, Storage}; -use super::super::super::utils::now; -use super::{ +use crate::identity::IdentityConstants; +use crate::models::{ChangeHistory, Identifier}; +use crate::storage::{InMemoryStorage, Storage}; +use crate::utils::now; +use crate::{ AttributesEntry, IdentitiesReader, IdentitiesRepository, IdentitiesWriter, IdentityAttributesReader, IdentityAttributesWriter, }; diff --git a/implementations/rust/ockam/ockam_identity/src/v2/identities/storage/identities_repository_trait.rs b/implementations/rust/ockam/ockam_identity/src/identities/storage/identities_repository_trait.rs similarity index 97% rename from implementations/rust/ockam/ockam_identity/src/v2/identities/storage/identities_repository_trait.rs rename to implementations/rust/ockam/ockam_identity/src/identities/storage/identities_repository_trait.rs index 0b715f6ecdf..0d305d25859 100644 --- a/implementations/rust/ockam/ockam_identity/src/v2/identities/storage/identities_repository_trait.rs +++ b/implementations/rust/ockam/ockam_identity/src/identities/storage/identities_repository_trait.rs @@ -5,8 +5,8 @@ use ockam_core::errcode::{Kind, Origin}; use ockam_core::Result; use ockam_core::{async_trait, Error}; -use super::super::super::models::{ChangeHistory, Identifier}; -use super::AttributesEntry; +use crate::models::{ChangeHistory, Identifier}; +use crate::AttributesEntry; /// Repository for data related to identities: key changes and attributes #[async_trait] diff --git a/implementations/rust/ockam/ockam_identity/src/identities/storage/lmdb_storage.rs b/implementations/rust/ockam/ockam_identity/src/identities/storage/lmdb_storage.rs deleted file mode 100644 index 87e7d1d4121..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/identities/storage/lmdb_storage.rs +++ /dev/null @@ -1,147 +0,0 @@ -use core::str; -use std::fmt; -use std::path::Path; - -use lmdb::{Cursor, Database, Environment, Transaction}; -use tokio_retry::strategy::{jitter, FixedInterval}; -use tokio_retry::Retry; -use tracing::debug; - -use ockam_core::async_trait; -use ockam_core::compat::boxed::Box; -use ockam_core::compat::string::String; -use ockam_core::compat::sync::Arc; -use ockam_core::compat::vec::Vec; -use ockam_core::errcode::{Kind, Origin}; -use ockam_core::{Error, Result}; -use ockam_node::tokio::task::{self, JoinError}; - -use crate::Storage; - -/// Storage using the LMDB database -#[derive(Clone)] -pub struct LmdbStorage { - /// lmdb da - pub env: Arc, - /// lmdb database file - pub map: Database, -} - -impl fmt::Debug for LmdbStorage { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Store") - } -} - -impl LmdbStorage { - /// Constructor - pub async fn new>(p: P) -> Result { - // creating a new database might be failing a few times - // if the files are currently being held by another pod which is shutting down. - // In that case we retry a few times, between 1 and 10 seconds. - let retry_strategy = FixedInterval::from_millis(1000) - .map(jitter) // add jitter to delays - .take(10); // limit to 10 retries - - let path: &Path = p.as_ref(); - Retry::spawn(retry_strategy, || async { Self::make(path).await }).await - } - - async fn make(p: &Path) -> Result { - debug!("create the LMDB database"); - std::fs::create_dir_all(p.parent().unwrap()) - .map_err(|e| Error::new(Origin::Node, Kind::Io, e))?; - let p = p.to_path_buf(); - let env = Environment::new() - .set_flags(lmdb::EnvironmentFlags::NO_SUB_DIR | lmdb::EnvironmentFlags::NO_TLS) - .set_max_dbs(1) - .open(p.as_ref()) - .map_err(map_lmdb_err)?; - let map = env - .create_db(Some("map"), lmdb::DatabaseFlags::empty()) - .map_err(map_lmdb_err)?; - Ok(LmdbStorage { - env: Arc::new(env), - map, - }) - } - - /// Write a new binary value for a given key in the database - pub async fn write(&self, k: String, v: Vec) -> Result<()> { - let d = self.clone(); - let t = move || { - let mut w = d.env.begin_rw_txn().map_err(map_lmdb_err)?; - w.put(d.map, &k, &v, lmdb::WriteFlags::empty()) - .map_err(map_lmdb_err)?; - w.commit().map_err(map_lmdb_err)?; - Ok(()) - }; - task::spawn_blocking(t).await.map_err(map_join_err)? - } - - /// Delete a database entry - pub async fn delete(&self, k: String) -> Result<()> { - let d = self.clone(); - let t = move || { - let mut w = d.env.begin_rw_txn().map_err(map_lmdb_err)?; - match w.del(d.map, &k, None) { - Ok(()) | Err(lmdb::Error::NotFound) => {} - Err(e) => return Err(map_lmdb_err(e)), - } - w.commit().map_err(map_lmdb_err)?; - Ok(()) - }; - task::spawn_blocking(t).await.map_err(map_join_err)? - } -} - -#[async_trait] -impl Storage for LmdbStorage { - async fn get(&self, id: &str, key: &str) -> Result>> { - let d = self.clone(); - let k = format!("{id}:{key}"); - let t = move || { - let r = d.env.begin_ro_txn().map_err(map_lmdb_err)?; - match r.get(d.map, &k) { - Ok(value) => Ok(Some(Vec::from(value))), - Err(lmdb::Error::NotFound) => Ok(None), - Err(e) => Err(map_lmdb_err(e)), - } - }; - task::spawn_blocking(t).await.map_err(map_join_err)? - } - - async fn set(&self, id: &str, key: String, val: Vec) -> Result<()> { - self.write(format!("{id}:{key}"), val).await - } - - async fn del(&self, id: &str, key: &str) -> Result<()> { - self.delete(format!("{id}:{key}")).await - } - - async fn keys(&self, namespace: &str) -> Result> { - let d = self.clone(); - let suffix = format!(":{}", namespace); - let t = move || { - let r = d.env.begin_ro_txn().map_err(map_lmdb_err)?; - let mut cursor = r.open_ro_cursor(d.map).map_err(map_lmdb_err)?; - Ok(cursor - .iter() - .filter_map(|r| { - let (k, _) = r.unwrap(); - let key = str::from_utf8(k).unwrap(); - key.rsplit_once(&suffix).map(|(k, _)| k.to_string()) - }) - .collect()) - }; - task::spawn_blocking(t).await.map_err(map_join_err)? - } -} - -fn map_join_err(err: JoinError) -> Error { - Error::new(Origin::Application, Kind::Io, err) -} - -fn map_lmdb_err(err: lmdb::Error) -> Error { - Error::new(Origin::Application, Kind::Io, err) -} diff --git a/implementations/rust/ockam/ockam_identity/src/identities/storage/mod.rs b/implementations/rust/ockam/ockam_identity/src/identities/storage/mod.rs index f0022438e31..5ac04e522a0 100644 --- a/implementations/rust/ockam/ockam_identity/src/identities/storage/mod.rs +++ b/implementations/rust/ockam/ockam_identity/src/identities/storage/mod.rs @@ -1,21 +1,7 @@ mod attributes_entry; -mod identities_repository; -/// LMDB implementation of the Storage trait -#[cfg(feature = "std")] -pub mod lmdb_storage; -/// Sqlite implementation of the Storage trait -#[cfg(feature = "sqlite")] -pub mod sqlite_storage; - -#[allow(clippy::module_inception)] -mod storage; +mod identities_repository_impl; +mod identities_repository_trait; pub use attributes_entry::*; -pub use identities_repository::*; - -#[cfg(feature = "std")] -pub use lmdb_storage::*; -pub use storage::*; - -#[cfg(feature = "sqlite")] -pub use sqlite_storage::*; +pub use identities_repository_impl::*; +pub use identities_repository_trait::*; diff --git a/implementations/rust/ockam/ockam_identity/src/identities/storage/storage.rs b/implementations/rust/ockam/ockam_identity/src/identities/storage/storage.rs deleted file mode 100644 index 62568c2d6d3..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/identities/storage/storage.rs +++ /dev/null @@ -1,91 +0,0 @@ -use ockam_core::async_trait; -use ockam_core::compat::{ - boxed::Box, - collections::BTreeMap, - string::{String, ToString}, - sync::{Arc, RwLock}, - vec::Vec, -}; -use ockam_core::Result; - -/// Storage for Authenticated data -#[async_trait] -pub trait Storage: Send + Sync + 'static { - /// Get entry - async fn get(&self, id: &str, key: &str) -> Result>>; - - /// Set entry - async fn set(&self, id: &str, key: String, val: Vec) -> Result<()>; - - /// Delete entry - async fn del(&self, id: &str, key: &str) -> Result<()>; - - /// List all keys of a given "type". TODO: we shouldn't store different things on a single - /// store. - async fn keys(&self, namespace: &str) -> Result>; -} - -/// Non-persistent table stored in RAM -#[derive(Clone, Default)] -pub struct InMemoryStorage { - map: Arc>>, -} - -type Attributes = BTreeMap>; - -impl InMemoryStorage { - /// Constructor - pub fn new() -> Self { - Default::default() - } - - /// Constructor - pub fn create() -> Arc { - Arc::new(Self::new()) - } -} - -#[async_trait] -impl Storage for InMemoryStorage { - async fn get(&self, id: &str, namespace: &str) -> Result>> { - let m = self.map.read().unwrap(); - if let Some(a) = m.get(namespace) { - return Ok(a.get(id).cloned()); - } - Ok(None) - } - - async fn set(&self, id: &str, namespace: String, val: Vec) -> Result<()> { - let mut m = self.map.write().unwrap(); - match m.get_mut(&namespace) { - Some(a) => { - a.insert(id.to_string(), val); - } - None => { - m.insert(namespace, BTreeMap::from([(id.to_string(), val)])); - } - } - Ok(()) - } - - async fn del(&self, id: &str, namespace: &str) -> Result<()> { - let mut m = self.map.write().unwrap(); - if let Some(a) = m.get_mut(namespace) { - a.remove(id); - if a.is_empty() { - m.remove(namespace); - } - } - Ok(()) - } - - async fn keys(&self, namespace: &str) -> Result> { - Ok(self - .map - .read() - .unwrap() - .get(namespace) - .map(|m| m.keys().cloned().collect()) - .unwrap_or_default()) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/identities/tests.rs b/implementations/rust/ockam/ockam_identity/src/identities/tests.rs deleted file mode 100644 index 06c56124b25..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/identities/tests.rs +++ /dev/null @@ -1,276 +0,0 @@ -use crate::identities::{self, IdentitiesKeys}; -use crate::identity::identity_change::IdentitySignedChange; -use crate::identity::{Identity, IdentityChangeHistory}; -use crate::Identities; -use ockam_core::async_trait; -use ockam_core::compat::sync::Arc; -use ockam_core::Result; -use ockam_node::Context; -use ockam_vault::{ - EphemeralSecretsStore, Implementation, KeyId, PersistentSecretsStore, PublicKey, Secret, - SecretAttributes, SecretsStoreReader, Signature, Signer, -}; -use ockam_vault::{StoredSecret, Vault}; -use rand::distributions::Standard; -use rand::prelude::Distribution; -use rand::{thread_rng, Rng}; -use std::collections::HashSet; -use std::sync::atomic::{AtomicBool, Ordering}; - -#[ockam_macros::test] -async fn test_invalid_signature(ctx: &mut Context) -> Result<()> { - for _ in 0..100 { - let crazy_vault = Arc::new(CrazyVault::new(0.1, Vault::new())); - let identities = Identities::builder() - .with_identities_vault(crazy_vault.clone()) - .build(); - let mut identity = identities.identities_creation().create_identity().await?; - let res = check_identity(&mut identity).await; - - if crazy_vault.forged_operation_occurred() { - assert!(res.is_err()); - break; - } else { - assert!(res.is_ok()) - } - - loop { - identities - .identities_keys() - .random_change(&mut identity) - .await?; - - let res = identities::identities() - .identities_creation() - .decode_identity(&identity.export()?) - .await; - if crazy_vault.forged_operation_occurred() { - assert!(res.is_err()); - break; - } else { - assert!(res.is_ok()) - } - } - } - - ctx.stop().await?; - - Ok(()) -} - -/// This function simulates an identity import to check its history -async fn check_identity(identity: &mut Identity) -> Result { - identities::identities() - .identities_creation() - .decode_identity(&identity.export()?) - .await -} - -#[ockam_macros::test] -async fn test_eject_signatures(ctx: &mut Context) -> Result<()> { - let crazy_vault = CrazyVault::new(0.1, Vault::new()); - - for _ in 0..100 { - let identities = crate::Identities::builder() - .with_identities_vault(Arc::new(crazy_vault.clone())) - .build(); - let mut identity = identities.identities_creation().create_identity().await?; - - let j: i32 = thread_rng().gen_range(0..10); - for _ in 0..j { - identities - .identities_keys() - .random_change(&mut identity) - .await?; - } - - let res = identities - .identities_creation() - .decode_identity(&identity.export()?) - .await; - assert!(res.is_ok()); - - let identity = eject_random_signature(&identity)?; - let res = identities - .identities_creation() - .decode_identity(&identity.export()?) - .await; - assert!(res.is_err()); - } - - ctx.stop().await?; - - Ok(()) -} - -pub fn eject_random_signature(identity: &Identity) -> Result { - let mut history = identity.change_history().as_ref().to_vec(); - - let i = thread_rng().gen_range(0..history.len()); - let change = &mut history[i]; - let mut signatures = change.signatures().to_vec(); - - signatures.remove(thread_rng().gen_range(0..signatures.len())); - - history[i] = IdentitySignedChange::new( - change.identifier().clone(), - change.change().clone(), - signatures, - ); - - let mut new_history = IdentityChangeHistory::new(history[0].clone()); - - for change in history.into_iter().skip(1) { - new_history.check_consistency_and_add_change(change)? - } - - Ok(new_history) -} - -impl IdentitiesKeys { - async fn random_change(&self, identity: &mut Identity) -> Result<()> { - enum Action { - CreateKey, - RotateKey, - } - - impl Distribution for Standard { - fn sample(&self, rng: &mut R) -> Action { - match rng.gen_range(0..2) { - 0 => Action::CreateKey, - 1 => Action::RotateKey, - _ => unimplemented!(), - } - } - } - - let action: Action = thread_rng().gen(); - - match action { - Action::CreateKey => { - let label: [u8; 16] = thread_rng().gen(); - let label = hex::encode(label); - self.create_key(identity, label).await?; - } - Action::RotateKey => { - let mut present_keys = HashSet::::new(); - for change in identity.change_history().as_ref() { - present_keys.insert(change.change().label().to_string()); - } - let present_keys: Vec = present_keys.into_iter().collect(); - let index = thread_rng().gen_range(0..present_keys.len()); - self.rotate_key(identity, &present_keys[index]).await?; - } - } - - Ok(()) - } -} - -#[derive(Clone)] -struct CrazyVault { - prob_to_produce_invalid_signature: f32, - forged_operation_occurred: Arc, - vault: Vault, -} - -impl Implementation for CrazyVault {} - -impl CrazyVault { - pub fn forged_operation_occurred(&self) -> bool { - self.forged_operation_occurred.load(Ordering::Relaxed) - } -} - -impl CrazyVault { - pub fn new(prob_to_produce_invalid_signature: f32, vault: Vault) -> Self { - Self { - prob_to_produce_invalid_signature, - forged_operation_occurred: Arc::new(false.into()), - vault, - } - } -} - -#[async_trait] -impl EphemeralSecretsStore for CrazyVault { - async fn create_ephemeral_secret(&self, attributes: SecretAttributes) -> Result { - self.vault.create_ephemeral_secret(attributes).await - } - - async fn import_ephemeral_secret( - &self, - secret: Secret, - attributes: SecretAttributes, - ) -> Result { - self.vault.import_ephemeral_secret(secret, attributes).await - } - - async fn get_ephemeral_secret( - &self, - key_id: &KeyId, - description: &str, - ) -> Result { - self.vault.get_ephemeral_secret(key_id, description).await - } - - async fn delete_ephemeral_secret(&self, key_id: KeyId) -> Result { - self.vault.delete_ephemeral_secret(key_id).await - } - - async fn list_ephemeral_secrets(&self) -> Result> { - self.vault.list_ephemeral_secrets().await - } -} - -#[async_trait] -impl PersistentSecretsStore for CrazyVault { - async fn create_persistent_secret(&self, attributes: SecretAttributes) -> Result { - self.vault.create_persistent_secret(attributes).await - } - - async fn delete_persistent_secret(&self, key_id: KeyId) -> Result { - self.vault.delete_persistent_secret(key_id).await - } -} - -#[async_trait] -impl SecretsStoreReader for CrazyVault { - async fn get_secret_attributes(&self, key_id: &KeyId) -> Result { - self.vault.get_secret_attributes(key_id).await - } - - async fn get_public_key(&self, key_id: &KeyId) -> Result { - self.vault.get_public_key(key_id).await - } - async fn get_key_id(&self, public_key: &PublicKey) -> Result { - self.vault.get_key_id(public_key).await - } -} - -#[async_trait] -impl Signer for CrazyVault { - async fn sign(&self, key_id: &KeyId, data: &[u8]) -> Result { - let mut signature = self.vault.sign(key_id, data).await?; - if thread_rng().gen_range(0.0..1.0) <= self.prob_to_produce_invalid_signature { - self.forged_operation_occurred - .store(true, Ordering::Relaxed); - use zeroize::Zeroize; - signature.zeroize(); - } - - Ok(signature) - } - async fn verify( - &self, - public_key: &PublicKey, - data: &[u8], - signature: &Signature, - ) -> Result { - if signature.as_ref().iter().all(|&x| x == 0) { - return Ok(true); - } - - self.vault.verify(public_key, data, signature).await - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/identity/constants.rs b/implementations/rust/ockam/ockam_identity/src/identity/constants.rs similarity index 100% rename from implementations/rust/ockam/ockam_identity/src/v2/identity/constants.rs rename to implementations/rust/ockam/ockam_identity/src/identity/constants.rs diff --git a/implementations/rust/ockam/ockam_identity/src/v2/identity/history_comparison.rs b/implementations/rust/ockam/ockam_identity/src/identity/history_comparison.rs similarity index 100% rename from implementations/rust/ockam/ockam_identity/src/v2/identity/history_comparison.rs rename to implementations/rust/ockam/ockam_identity/src/identity/history_comparison.rs diff --git a/implementations/rust/ockam/ockam_identity/src/identity/identity.rs b/implementations/rust/ockam/ockam_identity/src/identity/identity.rs index bbe02bce00d..8fd514b653b 100644 --- a/implementations/rust/ockam/ockam_identity/src/identity/identity.rs +++ b/implementations/rust/ockam/ockam_identity/src/identity/identity.rs @@ -1,89 +1,165 @@ -//! Identity history -use crate::identity::identity_change::IdentitySignedChange; -use crate::identity::identity_change_history::IdentityChangeHistory; -use crate::identity::identity_identifier::IdentityIdentifier; +use crate::models::{Change, ChangeHash, ChangeHistory, Identifier}; +use crate::verified_change::VerifiedChange; +use crate::IdentityError; use crate::IdentityHistoryComparison; + +use core::cmp::Ordering; +use core::fmt; use core::fmt::{Display, Formatter}; -use ockam_core::compat::fmt; -use ockam_core::compat::string::String; +use ockam_core::compat::sync::Arc; use ockam_core::compat::vec::Vec; use ockam_core::Result; -use ockam_vault::PublicKey; -use serde::{Deserialize, Serialize}; +use ockam_vault::{PublicKey, VerifyingVault}; -/// Identity implementation -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +/// Verified Identity +#[derive(Clone, Debug)] pub struct Identity { - pub(crate) identifier: IdentityIdentifier, - pub(crate) change_history: IdentityChangeHistory, + identifier: Identifier, + changes: Vec, + // We preserve the original change_history binary + // as serialization is not guaranteed to be deterministic + change_history: ChangeHistory, +} + +impl Eq for Identity {} + +impl PartialEq for Identity { + fn eq(&self, other: &Self) -> bool { + self.change_history == other.change_history + } } impl Identity { /// Create a new identity - pub fn new(identifier: IdentityIdentifier, change_history: IdentityChangeHistory) -> Self { + /// NOTE: This is intentionally private, so that the only way to create such struct is by + /// going through the verification process + fn new( + identifier: Identifier, + changes: Vec, + change_history: ChangeHistory, + ) -> Self { Self { identifier, + changes, change_history, } } /// Return the identity identifier - pub fn identifier(&self) -> IdentityIdentifier { - self.identifier.clone() + pub fn identifier(&self) -> &Identifier { + &self.identifier } - /// Export an `Identity` to the binary format - /// TODO: return a newtype instead of a raw vector - pub fn export(&self) -> Result> { - self.change_history.export() + /// Collection of parsed changes + pub fn changes(&self) -> &[VerifiedChange] { + self.changes.as_slice() } - /// Export an `Identity` as a hex-formatted string - pub fn export_hex(&self) -> Result { - Ok(hex::encode(self.export()?)) + /// `Identity` change history + pub fn change_history(&self) -> &ChangeHistory { + &self.change_history } - /// Add a new key change to the change history - pub fn add_change(&mut self, change: IdentitySignedChange) -> Result<()> { - self.change_history.add_change(change) + /// `Identity`'s latest [`ChangeHash`] + pub fn latest_change_hash(&self) -> Result<&ChangeHash> { + if let Some(latest_change) = self.changes.last() { + Ok(latest_change.change_hash()) + } else { + Err(IdentityError::EmptyIdentity.into()) + } } +} - /// `Identity` change history - pub fn change_history(&self) -> IdentityChangeHistory { - self.change_history.clone() +impl Identity { + /// Export an `Identity` to the binary format + pub fn export(&self) -> Result> { + self.change_history.export() } - /// Return the root public key of an identity - pub fn get_root_public_key(&self) -> Result { - self.change_history.get_root_public_key() - } + /// Import and verify Identity from the ChangeHistory + pub async fn import_from_change_history( + expected_identifier: Option<&Identifier>, + change_history: ChangeHistory, + vault: Arc, + ) -> Result { + let verified_changes = + Self::check_entire_consistency(&change_history.0, vault.clone()).await?; + Self::verify_all_existing_changes(&verified_changes, &change_history.0, vault).await?; - pub(crate) fn get_public_key(&self, key_label: Option<&str>) -> Result { - let key = match key_label { - Some(label) => self.get_labelled_public_key(label)?, - None => self.get_root_public_key()?, + let identifier = if let Some(first_change) = verified_changes.first() { + first_change.change_hash().clone().into() + } else { + return Err(IdentityError::IdentityVerificationFailed.into()); }; - Ok(key) - } - pub(crate) fn get_labelled_public_key(&self, label: &str) -> Result { - self.change_history.get_public_key(label) + if let Some(expected_identifier) = expected_identifier { + if &identifier != expected_identifier { + return Err(IdentityError::IdentityVerificationFailed.into()); + } + } + + let identity = Self::new(identifier, verified_changes, change_history); + + Ok(identity) } /// Create an Identity from serialized data - pub fn import(identifier: &IdentityIdentifier, data: &[u8]) -> Result { - let change_history = IdentityChangeHistory::import(data)?; - Ok(Identity::new(identifier.clone(), change_history)) + pub async fn import( + expected_identifier: Option<&Identifier>, + data: &[u8], + vault: Arc, + ) -> Result { + let change_history = ChangeHistory::import(data)?; + + Self::import_from_change_history(expected_identifier, change_history, vault).await } +} - /// Return the list of key changes for this identity - pub(crate) fn changes(&self) -> &IdentityChangeHistory { - &self.change_history +impl Identity { + /// Get latest public key + pub fn get_latest_public_key(&self) -> Result { + if let Some(last_change) = self.changes().last() { + Ok(last_change.primary_public_key().clone()) + } else { + Err(IdentityError::EmptyIdentity.into()) + } + } + + /// Get latest [`VerifiedChange`] + pub fn get_latest_change(&self) -> Result { + if let Some(last_change) = self.changes().last() { + Ok(last_change.clone()) + } else { + Err(IdentityError::EmptyIdentity.into()) + } + } + + /// Add a new key change to the change history + pub async fn add_change( + self, + change: Change, + vault: Arc, + ) -> Result { + // TODO: Optimize + let mut change_history = self.change_history; + change_history.0.push(change); + + Self::import_from_change_history(None, change_history, vault).await } /// Compare to a previously known state of the same `Identity` pub fn compare(&self, known: &Self) -> IdentityHistoryComparison { - self.change_history.compare(&known.change_history) + for change_pair in self.changes.iter().zip(known.changes.iter()) { + if change_pair.0.change_hash() != change_pair.1.change_hash() { + return IdentityHistoryComparison::Conflict; + } + } + + match self.changes.len().cmp(&known.changes.len()) { + Ordering::Less => IdentityHistoryComparison::Older, + Ordering::Equal => IdentityHistoryComparison::Equal, + Ordering::Greater => IdentityHistoryComparison::Newer, + } } } @@ -92,7 +168,7 @@ impl Display for Identity { let identifier = self.identifier(); writeln!(f, "Identifier: {identifier}")?; - let history: String = self.export_hex().map_err(|_| fmt::Error)?; + let history = hex::encode(self.export().map_err(|_| fmt::Error)?); writeln!(f, "Change history: {history}") } } @@ -100,21 +176,141 @@ impl Display for Identity { #[cfg(test)] mod tests { use super::*; + use crate::{identities, Identities, Vault}; + use core::str::FromStr; + use ockam_core::compat::rand::RngCore; + use ockam_vault::{Secret, SecretAttributes, SecretType, SoftwareSigningVault}; + use rand::thread_rng; - #[test] - fn test_display() { - let data = hex::decode("0144c7eb72dd1e633f38e0d0521e9d5eb5072f6418176529eb1b00189e4d69ad2e000547c93239ba3d818ec26c9cdadd2a35cbdf1fa3b6d1a731e06164b1079fb7b8084f434b414d5f524b03012000000020c6c52380125d42b0b4da922b1cff8503a258c3497ec8ac0b4a3baa0d9ca7b3780301014075064b902bda9d16db81ab5f38fbcf226a0e904e517a8c087d379ea139df1f2d7fee484ac7e1c2b7ab2da75f85adef6af7ddb05e7fa8faf180820cb9e86def02").unwrap(); - let identity = Identity::new( - IdentityIdentifier::from_hex( - "fa804b7fca12a19eed206ae180b5b576860ae6512f196c189d90661bcc434b50", - ), - IdentityChangeHistory::import(data.to_vec().as_slice()).unwrap(), - ); + #[tokio::test] + async fn test_display() { + let data = hex::decode("81a201583ba20101025835a4028201815820bd144a3f6472ba2215b6b86b2820b23304f9473622847ca80dfda0d10f12eebc03f4041a64c956a9051a64c956a9028201815840c1598a6f85215c118a4744310bebfae71ec19353e1ede1582787592013d65a70c80aa4a4855d16d9b696a887be9bd97b2271245124857d67c07e0203564c3706").unwrap(); + let identity = identities() + .identities_creation() + .import( + Some(&Identifier::from_str("Ie2424922b4194cd4ab57f952ef04c44e5e70ab2f").unwrap()), + &data, + ) + .await + .unwrap(); let actual = format!("{identity}"); - let expected = r#"Identifier: Pfa804b7fca12a19eed206ae180b5b576860ae6512f196c189d90661bcc434b50 -Change history: 0144c7eb72dd1e633f38e0d0521e9d5eb5072f6418176529eb1b00189e4d69ad2e000547c93239ba3d818ec26c9cdadd2a35cbdf1fa3b6d1a731e06164b1079fb7b8084f434b414d5f524b03012000000020c6c52380125d42b0b4da922b1cff8503a258c3497ec8ac0b4a3baa0d9ca7b3780301014075064b902bda9d16db81ab5f38fbcf226a0e904e517a8c087d379ea139df1f2d7fee484ac7e1c2b7ab2da75f85adef6af7ddb05e7fa8faf180820cb9e86def02 + let expected = r#"Identifier: Ie2424922b4194cd4ab57f952ef04c44e5e70ab2f +Change history: 81a201583ba20101025835a4028201815820bd144a3f6472ba2215b6b86b2820b23304f9473622847ca80dfda0d10f12eebc03f4041a64c956a9051a64c956a9028201815840c1598a6f85215c118a4744310bebfae71ec19353e1ede1582787592013d65a70c80aa4a4855d16d9b696a887be9bd97b2271245124857d67c07e0203564c3706 "#; assert_eq!(actual, expected) } + + #[tokio::test] + async fn test_compare() -> Result<()> { + let signing_vault0 = SoftwareSigningVault::create(); + let signing_vault01 = SoftwareSigningVault::create(); + let signing_vault02 = SoftwareSigningVault::create(); + + let mut key0_bin = [0u8; 32]; + thread_rng().fill_bytes(&mut key0_bin); + + let key0 = signing_vault0 + .import_key(Secret::new(key0_bin.to_vec()), SecretAttributes::Ed25519) + .await?; + let key01 = signing_vault01 + .import_key(Secret::new(key0_bin.to_vec()), SecretAttributes::Ed25519) + .await?; + let key02 = signing_vault02 + .import_key(Secret::new(key0_bin.to_vec()), SecretAttributes::Ed25519) + .await?; + + let identities0 = Identities::builder() + .with_vault(Vault::new( + signing_vault0, + Vault::create_secure_channel_vault(), + Vault::create_credential_vault(), + Vault::create_verifying_vault(), + )) + .build(); + + let identity0 = identities0 + .identities_creation() + .identity_builder() + .with_existing_key(key0, SecretType::Ed25519) + .build() + .await?; + let identifier = identity0.identifier().clone(); + let identity0_bin = identity0.export()?; + + let identities01 = Identities::builder() + .with_vault(Vault::new( + signing_vault01, + Vault::create_secure_channel_vault(), + Vault::create_credential_vault(), + Vault::create_verifying_vault(), + )) + .build(); + let identities02 = Identities::builder() + .with_vault(Vault::new( + signing_vault02, + Vault::create_secure_channel_vault(), + Vault::create_credential_vault(), + Vault::create_verifying_vault(), + )) + .build(); + + let identity01 = identities01 + .identities_creation() + .import_private_identity(&identity0_bin, &key01) + .await?; + assert_eq!(identity01.identifier(), &identifier); + let identity02 = identities02 + .identities_creation() + .import_private_identity(&identity0_bin, &key02) + .await?; + assert_eq!(identity02.identifier(), &identifier); + + identities01 + .identities_creation() + .rotate_identity(&identifier) + .await?; + let identity01 = identities01.get_identity(&identifier).await?; + + identities02 + .identities_creation() + .rotate_identity(&identifier) + .await?; + let identity02 = identities02.get_identity(&identifier).await?; + + assert_eq!( + identity0.compare(&identity0), + IdentityHistoryComparison::Equal + ); + assert_eq!( + identity01.compare(&identity01), + IdentityHistoryComparison::Equal + ); + assert_eq!( + identity02.compare(&identity02), + IdentityHistoryComparison::Equal + ); + assert_eq!( + identity0.compare(&identity01), + IdentityHistoryComparison::Older + ); + assert_eq!( + identity0.compare(&identity02), + IdentityHistoryComparison::Older + ); + assert_eq!( + identity01.compare(&identity0), + IdentityHistoryComparison::Newer + ); + assert_eq!( + identity02.compare(&identity0), + IdentityHistoryComparison::Newer + ); + assert_eq!( + identity01.compare(&identity02), + IdentityHistoryComparison::Conflict + ); + + Ok(()) + } } diff --git a/implementations/rust/ockam/ockam_identity/src/identity/identity_change/change_identifier.rs b/implementations/rust/ockam/ockam_identity/src/identity/identity_change/change_identifier.rs deleted file mode 100644 index b4a4e46f6b9..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/identity/identity_change/change_identifier.rs +++ /dev/null @@ -1,30 +0,0 @@ -use core::fmt::{Display, Formatter}; -use ockam_core::compat::string::String; -use serde::{Deserialize, Serialize}; - -/// Unique [`IdentityChange`](crate::IdentityChange) identifier, computed as SHA256 of the change data -#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Hash)] -pub struct ChangeIdentifier([u8; 32]); - -impl Display for ChangeIdentifier { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - write!(f, "{}", hex::encode(self.0)) - } -} - -impl AsRef<[u8]> for ChangeIdentifier { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -impl ChangeIdentifier { - /// Create identifier from public key hash - pub fn from_hash(hash: [u8; 32]) -> Self { - Self(hash) - } - /// Human-readable form of the id - pub fn to_string_representation(&self) -> String { - format!("E_ID.{}", hex::encode(self.0)) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/identity/identity_change/create_key.rs b/implementations/rust/ockam/ockam_identity/src/identity/identity_change/create_key.rs deleted file mode 100644 index 95a2ede66f6..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/identity/identity_change/create_key.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::identity::identity_change::ChangeIdentifier; -use crate::identity::identity_change::KeyAttributes; -use core::fmt; -use ockam_vault::PublicKey; -use serde::{Deserialize, Serialize}; - -/// Key change data creation -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct CreateKeyChangeData { - prev_change_id: ChangeIdentifier, - key_attributes: KeyAttributes, - public_key: PublicKey, -} - -impl CreateKeyChangeData { - /// Return key attributes - pub fn key_attributes(&self) -> &KeyAttributes { - &self.key_attributes - } - /// Return public key - pub fn public_key(&self) -> &PublicKey { - &self.public_key - } - /// Previous change identifier, used to create a chain - pub fn prev_change_id(&self) -> &ChangeIdentifier { - &self.prev_change_id - } -} - -impl CreateKeyChangeData { - /// Create new CreateKeyChangeData - pub fn new( - prev_change_id: ChangeIdentifier, - key_attributes: KeyAttributes, - public_key: PublicKey, - ) -> Self { - Self { - prev_change_id, - key_attributes, - public_key, - } - } -} - -impl fmt::Display for CreateKeyChangeData { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "prev_change_id:{} key attributes:{} public key:{}", - self.prev_change_id(), - self.key_attributes(), - self.public_key() - ) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/identity/identity_change/identity_change.rs b/implementations/rust/ockam/ockam_identity/src/identity/identity_change/identity_change.rs deleted file mode 100644 index f548e4bc0c0..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/identity/identity_change/identity_change.rs +++ /dev/null @@ -1,104 +0,0 @@ -use crate::identity::identity_change::CreateKeyChangeData; -use crate::identity::identity_change::RotateKeyChangeData; -use crate::identity::identity_change::{ChangeIdentifier, Signature}; -use core::fmt; -use ockam_core::compat::vec::Vec; -use ockam_core::Result; -use ockam_vault::PublicKey; -use serde::{Deserialize, Serialize}; - -/// Possible types of [`crate::SecureChannels`] changes -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub enum IdentityChange { - /// Create key - CreateKey(CreateKeyChangeData), - /// Rotate key - RotateKey(RotateKeyChangeData), -} - -impl fmt::Display for IdentityChange { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - IdentityChange::CreateKey(data) => write!(f, " CreateKey:{}", data), - IdentityChange::RotateKey(data) => write!(f, " RotateKey:{}", data), - } - } -} - -impl IdentityChange { - pub(crate) fn has_label(&self, label: &str) -> bool { - self.label() == label - } - - pub(crate) fn label(&self) -> &str { - match self { - IdentityChange::CreateKey(data) => data.key_attributes().label(), - IdentityChange::RotateKey(data) => data.key_attributes().label(), - } - } - - pub(crate) fn public_key(&self) -> Result { - Ok(match self { - IdentityChange::CreateKey(data) => data.public_key(), - IdentityChange::RotateKey(data) => data.public_key(), - } - .clone()) - } - - pub(crate) fn previous_change_identifier(&self) -> &ChangeIdentifier { - match self { - IdentityChange::CreateKey(data) => data.prev_change_id(), - IdentityChange::RotateKey(data) => data.prev_change_id(), - } - } -} - -/// [`crate::SecureChannels`]s are modified using a chain of changes. -/// Signatures are used to check change validity. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct IdentitySignedChange { - identifier: ChangeIdentifier, - change: IdentityChange, - signatures: Vec, -} - -impl IdentitySignedChange { - /// Unique [`ChangeIdentifier`] - pub fn identifier(&self) -> &ChangeIdentifier { - &self.identifier - } - /// Change been applied - pub fn change(&self) -> &IdentityChange { - &self.change - } - /// Signatures are used to check change validity. - pub fn signatures(&self) -> &[Signature] { - &self.signatures - } -} - -impl IdentitySignedChange { - /// Create a new identity change - pub fn new( - identifier: ChangeIdentifier, - change: IdentityChange, - signatures: Vec, - ) -> Self { - Self { - identifier, - change, - signatures, - } - } -} - -impl fmt::Display for IdentitySignedChange { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, " identifier: {}", self.identifier())?; - writeln!(f, " identity change: {}", self.change())?; - for s in self.signatures() { - writeln!(f, "signatures: {}", s)?; - } - Ok(()) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/identity/identity_change/identity_change_constants.rs b/implementations/rust/ockam/ockam_identity/src/identity/identity_change/identity_change_constants.rs deleted file mode 100644 index 30ab2fe9961..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/identity/identity_change/identity_change_constants.rs +++ /dev/null @@ -1,14 +0,0 @@ -/// `Identity`-related constants -pub struct IdentityChangeConstants; - -impl IdentityChangeConstants { - /// Sha256 of that value is used as previous change id for first change in a - /// [`crate::SecureChannels`] - pub const INITIAL_CHANGE: &'static [u8] = "OCKAM_INITIAL_CHANGE".as_bytes(); - /// Label for [`crate::SecureChannels`] update key - pub const ROOT_LABEL: &'static str = "OCKAM_RK"; - /// Change history key for AttributesStorage - pub const CHANGE_HISTORY_KEY: &'static str = "CHANGE_HISTORY"; - /// Attributes key for AttributesStorage - pub const ATTRIBUTES_KEY: &'static str = "ATTRIBUTES"; -} diff --git a/implementations/rust/ockam/ockam_identity/src/identity/identity_change/key_attributes.rs b/implementations/rust/ockam/ockam_identity/src/identity/identity_change/key_attributes.rs deleted file mode 100644 index 92b3b2fd3b7..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/identity/identity_change/key_attributes.rs +++ /dev/null @@ -1,110 +0,0 @@ -use core::fmt; -use minicbor::{Decode, Encode}; -use ockam_core::compat::string::String; -use ockam_vault::constants::AES256_SECRET_LENGTH_U32; -use ockam_vault::SecretAttributes; -use ockam_vault::SecretType; -use serde::{Deserialize, Serialize}; - -/// Attributes that are used to identify a key -#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] -pub struct KeyAttributes { - label: String, - secret_attributes: SecretAttributesV1, -} - -impl KeyAttributes { - /// Human-readable key name - pub fn label(&self) -> &str { - &self.label - } - /// `SecretAttributes` of the key - pub fn secret_attributes(&self) -> SecretAttributes { - match self.secret_attributes.stype { - SecretType::Buffer => SecretAttributes::Buffer(self.secret_attributes.length), - SecretType::Aes => { - if self.secret_attributes.length == AES256_SECRET_LENGTH_U32 { - SecretAttributes::Aes256 - } else { - SecretAttributes::Aes128 - } - } - SecretType::X25519 => SecretAttributes::X25519, - SecretType::Ed25519 => SecretAttributes::Ed25519, - SecretType::NistP256 => SecretAttributes::NistP256, - } - } -} - -impl KeyAttributes { - /// Default key with given label (Ed25519) - pub fn default_with_label(label: impl Into) -> Self { - Self::new(label.into(), SecretAttributes::Ed25519) - } - - /// Constructor - pub fn new(label: String, attributes: SecretAttributes) -> Self { - Self { - label, - secret_attributes: SecretAttributesV1 { - stype: attributes.secret_type(), - persistence: SecretPersistence::Persistent, - length: attributes.length(), - }, - } - } -} -impl fmt::Display for KeyAttributes { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - " label:{}, secrets:{}", - self.label(), - self.secret_attributes - ) - } -} - -/// Attributes for secrets -/// - a type indicating how the secret is generated: Aes, Ed25519 -/// - an expected length corresponding to the type -/// - the persistence field is not used anymore but should always be set to Persistent -#[derive(Serialize, Deserialize, Copy, Encode, Decode, Clone, Debug, Eq, PartialEq)] -#[rustfmt::skip] -pub struct SecretAttributesV1 { - #[n(1)] stype: SecretType, - #[n(2)] persistence: SecretPersistence, - #[n(3)] length: u32, -} - -impl fmt::Display for SecretAttributesV1 { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{:?}({:?}) len:{}", - self.stype, self.persistence, self.length - ) - } -} - -/// This enum is kept for backward compatibility reasons: -/// - it would not possible to remove the persistence field in SecretAttributes because then we can -/// not read secret attributes serialized with serde_bare (since the format is not auto-descriptive) -/// - identities have a signature which is only valid if that field is present in the identity key data -/// if we removed that field, recreating the signature on the change history of an identity would fail -/// -#[derive(Serialize, Deserialize, Copy, Clone, Encode, Decode, Debug, Eq, PartialEq)] -#[rustfmt::skip] -#[cbor(index_only)] -pub enum SecretPersistence { - /// unused - #[n(1)] Ephemeral, - /// unused - #[n(2)] Persistent, -} - -impl Default for SecretPersistence { - fn default() -> Self { - SecretPersistence::Persistent - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/identity/identity_change/mod.rs b/implementations/rust/ockam/ockam_identity/src/identity/identity_change/mod.rs deleted file mode 100644 index 798891e1f87..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/identity/identity_change/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -mod change_identifier; -mod create_key; -#[allow(clippy::module_inception)] -mod identity_change; -pub(crate) mod identity_change_constants; -mod key_attributes; -mod rotate_key; -mod signature; - -pub use change_identifier::*; -pub use create_key::*; -pub use identity_change::*; -pub use identity_change_constants::*; -pub use key_attributes::*; -pub use rotate_key::*; -pub use signature::*; diff --git a/implementations/rust/ockam/ockam_identity/src/identity/identity_change/rotate_key.rs b/implementations/rust/ockam/ockam_identity/src/identity/identity_change/rotate_key.rs deleted file mode 100644 index a16c066f4ba..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/identity/identity_change/rotate_key.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::identity::identity_change::ChangeIdentifier; -use crate::identity::identity_change::KeyAttributes; -use core::fmt; -use ockam_vault::PublicKey; -use serde::{Deserialize, Serialize}; - -/// RotateKeyChangeData -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct RotateKeyChangeData { - prev_change_id: ChangeIdentifier, - key_attributes: KeyAttributes, - public_key: PublicKey, -} - -impl RotateKeyChangeData { - /// Return key attributes - pub fn key_attributes(&self) -> &KeyAttributes { - &self.key_attributes - } - /// Return public key - pub fn public_key(&self) -> &PublicKey { - &self.public_key - } - /// Previous change identifier, used to create a chain - pub fn prev_change_id(&self) -> &ChangeIdentifier { - &self.prev_change_id - } -} - -impl RotateKeyChangeData { - /// Create RotateKeyChangeData - pub fn new( - prev_change_id: ChangeIdentifier, - key_attributes: KeyAttributes, - public_key: PublicKey, - ) -> Self { - Self { - prev_change_id, - key_attributes, - public_key, - } - } -} - -impl fmt::Display for RotateKeyChangeData { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "prev_change_id:{} key attributes:{} public key:{}", - self.prev_change_id(), - self.key_attributes(), - self.public_key() - ) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/identity/identity_change/signature.rs b/implementations/rust/ockam/ockam_identity/src/identity/identity_change/signature.rs deleted file mode 100644 index 0b5a4ad032f..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/identity/identity_change/signature.rs +++ /dev/null @@ -1,45 +0,0 @@ -use core::fmt; -use ockam_vault::Signature as OckamVaultSignature; -use serde::{Deserialize, Serialize}; - -/// Types of proof signatures. -#[derive(Serialize, Deserialize, Debug, Clone, Copy, Eq, PartialEq)] -pub enum SignatureType { - /// Root signature - RootSign, - /// Self signature - SelfSign, - /// Signature using previous key - PrevSign, -} - -/// Signature, its type and data -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct Signature { - stype: SignatureType, - data: OckamVaultSignature, -} - -impl Signature { - /// Return the signature type - pub fn stype(&self) -> &SignatureType { - &self.stype - } - /// Return signature data - pub fn data(&self) -> &OckamVaultSignature { - &self.data - } -} - -impl Signature { - /// Create a new signature - pub fn new(stype: SignatureType, data: OckamVaultSignature) -> Self { - Signature { stype, data } - } -} - -impl fmt::Display for Signature { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{:?} {}", self.stype(), hex::encode(self.data())) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/identity/identity_change_history.rs b/implementations/rust/ockam/ockam_identity/src/identity/identity_change_history.rs deleted file mode 100644 index 3672e8a6046..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/identity/identity_change_history.rs +++ /dev/null @@ -1,238 +0,0 @@ -//! Identity history -use crate::identity::identity_change::IdentityChange::CreateKey; -use crate::identity::identity_change::{ - ChangeIdentifier, IdentityChangeConstants, IdentitySignedChange, -}; -use crate::IdentityError; -use core::cmp::Ordering; -use core::fmt; -use minicbor::{Decode, Encode}; -use ockam_core::compat::string::String; -use ockam_core::compat::vec::Vec; -use ockam_core::Result; -use ockam_vault::PublicKey; -use serde::{Deserialize, Serialize}; - -/// Result of comparison of current `IdentityChangeHistory` to the `IdentityChangeHistory` -/// of the same Identity, that was known to us earlier -#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq)] -#[cbor(index_only)] -pub enum IdentityHistoryComparison { - /// No difference - #[n(1)] - Equal, - /// Some changes don't match between current identity and known identity - #[n(2)] - Conflict, - /// Current identity is more recent than known identity - #[n(3)] - Newer, - /// Known identity is more recent - #[n(4)] - Older, -} - -/// Full history of [`crate::secure_channels::SecureChannels`] changes. History and corresponding secret keys are enough to recreate [`crate::secure_channels::SecureChannels`] -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct IdentityChangeHistory(Vec); - -impl fmt::Display for IdentityChangeHistory { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "Change History:")?; - for (i_num, ident) in self.0.iter().enumerate() { - let public_key = ident.change().public_key().unwrap(); - writeln!(f, " Change[{}]:", i_num)?; - writeln!(f, " identifier: {}", ident.identifier())?; - writeln!(f, " change:")?; - writeln!( - f, - " prev_change_identifier: {}", - ident.change().previous_change_identifier() - )?; - writeln!(f, " label: {}", ident.change().label())?; - writeln!(f, " public_key: {}", public_key)?; - writeln!(f, " signatures:")?; - for (sig_num, sig) in ident.signatures().iter().enumerate() { - writeln!(f, " [{}]: {}", sig_num, sig)?; - } - } - Ok(()) - } -} - -impl IdentityChangeHistory { - /// Export `IdentityChangeHistory` to the binary format - pub fn export(&self) -> Result> { - serde_bare::to_vec(self).map_err(|_| IdentityError::ConsistencyError.into()) - } - - /// Export `IdentityChangeHistory` to the hex format - pub fn export_hex(&self) -> Result { - Ok(hex::encode(self.export()?)) - } - - /// Import `IdentityChangeHistory` from the binary format - pub fn import(data: &[u8]) -> Result { - let s: Self = serde_bare::from_slice(data).map_err(|_| IdentityError::ConsistencyError)?; - s.check_entire_consistency()?; - Ok(s) - } - - /// Import `IdentityChangeHistory` from hex format - pub fn import_hex(data: &str) -> Result { - Self::import( - hex::decode(data) - .map_err(|_| IdentityError::ConsistencyError)? - .as_slice(), - ) - } -} - -impl IdentityChangeHistory { - pub(crate) fn new(first_signed_change: IdentitySignedChange) -> Self { - Self(vec![first_signed_change]) - } - - pub(crate) fn check_consistency_and_add_change( - &mut self, - change: IdentitySignedChange, - ) -> Result<()> { - let slice = core::slice::from_ref(&change); - if !Self::check_consistency(self.as_ref(), slice) { - return Err(IdentityError::IdentityVerificationFailed.into()); - } - - self.0.push(change); - - Ok(()) - } -} - -impl AsRef<[IdentitySignedChange]> for IdentityChangeHistory { - fn as_ref(&self) -> &[IdentitySignedChange] { - &self.0 - } -} - -impl IdentityChangeHistory { - /// Compare current `IdentityChangeHistory` to the `IdentityChangeHistory` of the same Identity, - /// that was known to us earlier - pub fn compare(&self, known: &Self) -> IdentityHistoryComparison { - for change_pair in self.0.iter().zip(known.0.iter()) { - if change_pair.0.identifier() != change_pair.1.identifier() { - return IdentityHistoryComparison::Conflict; - } - } - - match self.0.len().cmp(&known.0.len()) { - Ordering::Less => IdentityHistoryComparison::Older, - Ordering::Equal => IdentityHistoryComparison::Equal, - Ordering::Greater => IdentityHistoryComparison::Newer, - } - } - - /// Get public key with the given label (name) - pub fn get_public_key(&self, label: &str) -> Result { - Self::get_public_key_static(self.as_ref(), label) - } - - /// Get first root public key - pub fn get_first_root_public_key(&self) -> Result { - // TODO: Support root key rotation - let root_change = match self.as_ref().first() { - Some(change) => change, - None => return Err(IdentityError::InvalidInternalState.into()), - }; - - let root_change = root_change.change(); - - let root_create_key_change = match root_change { - CreateKey(c) => c, - _ => return Err(IdentityError::InvalidInternalState.into()), - }; - - Ok(root_create_key_change.public_key().clone()) - } - - /// Get latest root public key - pub fn get_root_public_key(&self) -> Result { - self.get_public_key(IdentityChangeConstants::ROOT_LABEL) - } - - /// Check consistency of changes that are being added - pub fn check_entire_consistency(&self) -> Result<()> { - if !Self::check_consistency(&[], &self.0) { - return Err(IdentityError::ConsistencyError.into()); - } - Ok(()) - } -} - -// Pub crate API -impl IdentityChangeHistory { - pub(crate) fn add_change(&mut self, change: IdentitySignedChange) -> Result<()> { - self.check_consistency_and_add_change(change) - } - - pub(crate) fn get_last_change_id(&self) -> Result { - if let Some(e) = self.0.last() { - Ok(e.identifier().clone()) - } else { - Err(IdentityError::InvalidInternalState.into()) - } - } - - pub(crate) fn find_last_key_change<'a>( - existing_changes: &'a [IdentitySignedChange], - label: &str, - ) -> Result<&'a IdentitySignedChange> { - existing_changes - .iter() - .rev() - .find(|&e| e.change().has_label(label)) - .ok_or_else(|| IdentityError::InvalidInternalState.into()) - } - - pub(crate) fn find_last_key_change_public_key( - existing_changes: &[IdentitySignedChange], - label: &str, - ) -> Result { - let last_key_change = Self::find_last_key_change(existing_changes, label)?; - - last_key_change.change().public_key() - } - - pub(crate) fn get_current_root_public_key( - existing_changes: &[IdentitySignedChange], - ) -> Result { - Self::find_last_key_change_public_key(existing_changes, IdentityChangeConstants::ROOT_LABEL) - } - - pub(crate) fn get_public_key_static( - changes: &[IdentitySignedChange], - label: &str, - ) -> Result { - let change = Self::find_last_key_change(changes, label)?; - change.change().public_key() - } - - /// Check consistency of changes that are been added - pub(crate) fn check_consistency( - existing_changes: &[IdentitySignedChange], - new_changes: &[IdentitySignedChange], - ) -> bool { - let mut prev_change = existing_changes.last(); - - for change in new_changes.iter() { - // Changes should go in correct order as stated in previous_change_identifier field - if let Some(prev) = prev_change { - if prev.identifier() != change.change().previous_change_identifier() { - return false; // InvalidChainSequence - } - } - - prev_change = Some(change); - } - true - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/identity/identity_identifier.rs b/implementations/rust/ockam/ockam_identity/src/identity/identity_identifier.rs deleted file mode 100644 index de3718fb31c..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/identity/identity_identifier.rs +++ /dev/null @@ -1,163 +0,0 @@ -use crate::IdentityError; -use core::fmt::{Display, Formatter}; -use core::str::FromStr; -use minicbor::decode::{self, Decoder}; -use minicbor::{Decode, Encode}; -use ockam_core::compat::borrow::Cow; -use ockam_core::compat::string::{String, ToString}; -use ockam_core::env::FromString; -use ockam_core::{Error, Result}; -use ockam_vault::{KeyId, PublicKey, VaultSecurityModule}; -use serde::{Deserialize, Deserializer, Serialize}; - -/// An identifier of an Identity. -#[allow(clippy::derived_hash_with_manual_eq)] // we manually implement a constant time Eq -#[derive(Clone, Debug, Hash, Encode, Serialize, Default, PartialOrd, Ord)] -#[cbor(transparent)] -pub struct IdentityIdentifier(#[n(0)] KeyId); - -/// Unique [`crate::SecureChannels`] identifier, computed as SHA256 of root public key -impl IdentityIdentifier { - const PREFIX: &'static str = "P"; - - /// Create an IdentityIdentifier from a public key - pub fn from_public_key(public_key: &PublicKey) -> Self { - let hashed = VaultSecurityModule::sha256(public_key.data()); - Self::from_hex(hex::encode(hashed).as_str()) - } - - /// Create an IdentityIdentifier from a hashed and hex-encoded public key - pub fn from_hex(string: &str) -> Self { - Self(format!("{}{}", Self::PREFIX, string.trim())) - } - - pub(crate) fn ct_eq(&self, o: &Self) -> subtle::Choice { - use subtle::ConstantTimeEq; - self.0.as_bytes().ct_eq(o.0.as_bytes()) - } -} - -impl<'b, C> Decode<'b, C> for IdentityIdentifier { - fn decode(d: &mut Decoder<'b>, _: &mut C) -> Result { - d.str()?.try_into().map_err(decode::Error::message) - } -} - -impl<'de> Deserialize<'de> for IdentityIdentifier { - fn deserialize(d: D) -> Result - where - D: Deserializer<'de>, - { - >::deserialize(d)? - .as_ref() - .try_into() - .map_err(serde::de::Error::custom) - } -} - -impl Eq for IdentityIdentifier {} - -impl PartialEq for IdentityIdentifier { - fn eq(&self, o: &Self) -> bool { - self.ct_eq(o).into() - } -} - -impl Display for IdentityIdentifier { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - self.serialize(f) - } -} - -impl From for String { - fn from(id: IdentityIdentifier) -> Self { - id.0 - } -} - -impl From<&IdentityIdentifier> for String { - fn from(id: &IdentityIdentifier) -> Self { - id.0.to_string() - } -} - -impl TryFrom<&str> for IdentityIdentifier { - type Error = Error; - - fn try_from(value: &str) -> Result { - let value = value.trim(); - if value.starts_with(Self::PREFIX) { - Ok(Self(value.to_string())) - } else { - Err(IdentityError::InvalidIdentityId.into()) - } - } -} - -impl TryFrom for IdentityIdentifier { - type Error = Error; - - fn try_from(value: String) -> Result { - Self::try_from(value.as_str()) - } -} - -impl FromStr for IdentityIdentifier { - type Err = Error; - - fn from_str(s: &str) -> Result { - s.try_into() - } -} - -impl FromString for IdentityIdentifier { - fn from_string(s: &str) -> Result { - s.try_into() - } -} - -#[cfg(test)] -mod test { - use super::IdentityIdentifier; - use quickcheck::{quickcheck, Arbitrary, Gen}; - use serde::de::{value, Deserialize, IntoDeserializer}; - - #[derive(Debug, Clone)] - struct Id(IdentityIdentifier); - - impl Arbitrary for Id { - fn arbitrary(g: &mut Gen) -> Self { - Self(IdentityIdentifier::from_hex(&String::arbitrary(g))) - } - } - - impl IdentityIdentifier { - pub fn random() -> IdentityIdentifier { - Id::arbitrary(&mut Gen::new(32)).0 - } - } - - quickcheck! { - fn prop_to_str_from_str(val: Id) -> bool { - let s = val.0.to_string(); - val.0 == IdentityIdentifier::try_from(s).unwrap() - } - - fn prop_encode_decode(val: Id) -> bool { - let b = minicbor::to_vec(&val.0).unwrap(); - let i = minicbor::decode(&b).unwrap(); - val.0 == i - } - - fn prop_serialize_deserialize(val: Id) -> bool { - let s = val.0.to_string(); - let d = IntoDeserializer::::into_deserializer(s); - let i = IdentityIdentifier::deserialize(d).unwrap(); - val.0 == i - } - - fn prop_prefix(val: Id) -> bool { - val.0.0.starts_with(IdentityIdentifier::PREFIX) - } - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/identity/identity_verification.rs b/implementations/rust/ockam/ockam_identity/src/identity/identity_verification.rs similarity index 53% rename from implementations/rust/ockam/ockam_identity/src/v2/identity/identity_verification.rs rename to implementations/rust/ockam/ockam_identity/src/identity/identity_verification.rs index 628f9f6cd78..9b32b97bc2c 100644 --- a/implementations/rust/ockam/ockam_identity/src/v2/identity/identity_verification.rs +++ b/implementations/rust/ockam/ockam_identity/src/identity/identity_verification.rs @@ -1,69 +1,76 @@ -use super::super::models::{ - Change, ChangeData, ChangeHash, ChangeSignature, VersionedData, CHANGE_HASH_LEN, -}; -use super::super::verified_change::VerifiedChange; -use super::super::{IdentitiesVault, Identity, IdentityError}; +use crate::models::{Change, ChangeData, ChangeHash, ChangeSignature, CHANGE_HASH_LEN}; +use crate::verified_change::VerifiedChange; +use crate::{Identity, IdentityError}; +use arrayref::array_ref; use ockam_core::compat::sync::Arc; use ockam_core::compat::vec::Vec; use ockam_core::Result; -use ockam_vault::{PublicKey, SecretType, Signature, Vault}; +use ockam_vault::constants::SHA256_LENGTH; +use ockam_vault::{PublicKey, SecretType, Signature, VerifyingVault}; struct ChangeDetails { version: u8, change_hash: ChangeHash, + change_full_hash: [u8; SHA256_LENGTH], change_data: ChangeData, } impl Identity { - pub(crate) fn compute_change_hash_from_data(data: &[u8]) -> ChangeHash { - let hash = Vault::sha256(data); - Self::compute_change_hash_from_hash(hash) - } - - pub(crate) fn compute_change_hash_from_hash(hash: [u8; 32]) -> ChangeHash { - let change_hash = hash[0..CHANGE_HASH_LEN].try_into().unwrap(); - ChangeHash::new(change_hash) + pub(crate) fn compute_change_hash_from_hash(hash: [u8; 32]) -> Result { + Ok(ChangeHash(*array_ref!(hash, 0, CHANGE_HASH_LEN))) } /// Check consistency of changes that are being added - pub async fn check_entire_consistency(changes: &[Change]) -> Result> { - let to_be_verified_changes = Self::check_consistency(&[], changes).await?; + pub async fn check_entire_consistency( + changes: &[Change], + verifying_vault: Arc, + ) -> Result> { + let to_be_verified_changes = + Self::check_consistency(None, changes, verifying_vault).await?; Ok(to_be_verified_changes) } - fn get_change_details(change: &Change) -> Result { - let change_hash = Self::compute_change_hash_from_data(&change.data); - let versioned_data: VersionedData = minicbor::decode(&change.data)?; + async fn get_change_details( + change: &Change, + vault: Arc, + ) -> Result { + let change_full_hash = vault.sha256(&change.data).await?; + let change_hash = Self::compute_change_hash_from_hash(change_full_hash)?; + let versioned_data = change.get_versioned_data()?; if versioned_data.version != 1 { return Err(IdentityError::UnknownIdentityVersion.into()); } - let change_data: ChangeData = minicbor::decode(&versioned_data.data)?; + let change_data = ChangeData::get_data(&versioned_data)?; Ok(ChangeDetails { version: versioned_data.version, change_hash, + change_full_hash, change_data, }) } - /// Check consistency of changes that are been added + /// Check consistency of changes that are being added async fn check_consistency( - existing_changes: &[Change], + last_known_change: Option<&Change>, new_changes: &[Change], + vault: Arc, ) -> Result> { let mut to_be_verified_changes = Vec::with_capacity(new_changes.len()); - let mut previous_change_details = match existing_changes.last() { - Some(previous_change) => Some(Self::get_change_details(previous_change)?), + let mut previous_change_details = match last_known_change { + Some(previous_change) => { + Some(Self::get_change_details(previous_change, vault.clone()).await?) + } None => None, }; for change in new_changes.iter() { - let change_details = Self::get_change_details(change)?; + let change_details = Self::get_change_details(change, vault.clone()).await?; if let Some(previous_change_details) = previous_change_details { if previous_change_details.version > change_details.version { @@ -71,21 +78,31 @@ impl Identity { return Err(IdentityError::IdentityVerificationFailed.into()); } - if let Some(previous_change_hash) = &change_details.change_data.previous_change { - if &previous_change_details.change_hash != previous_change_hash { - // Corrupted changes sequence - return Err(IdentityError::IdentityVerificationFailed.into()); - } + if previous_change_details.change_data.created_at + > change_details.change_data.created_at + { + // The older key can't be created after the newer + return Err(IdentityError::IdentityVerificationFailed.into()); + } + + // This is intentionally allowed: + // change_details.change_data.created_at > previous_change_details.change_data.expires_at + + if Some(&previous_change_details.change_hash) + != change_details.change_data.previous_change.as_ref() + { + // Corrupted changes sequence + return Err(IdentityError::IdentityVerificationFailed.into()); } } else if change_details.change_data.previous_change.is_some() { - // Should empty + // Should be empty return Err(IdentityError::IdentityVerificationFailed.into()); } to_be_verified_changes.push(VerifiedChange::new( + change_details.change_data.clone(), change_details.change_hash.clone(), change_details.change_data.primary_public_key.clone().into(), - change_details.change_data.revoke_all_purpose_keys, )); previous_change_details = Some(change_details); @@ -98,17 +115,21 @@ impl Identity { pub(crate) async fn verify_all_existing_changes( to_be_verified_changes: &[VerifiedChange], changes: &[Change], - vault: Arc, + vault: Arc, ) -> Result<()> { - let len = to_be_verified_changes.len(); - if len != changes.len() { + if to_be_verified_changes.len() != changes.len() { return Err(IdentityError::IdentityVerificationFailed.into()); } - for i in 0..len { - let existing_changes = &to_be_verified_changes[..i]; + for i in 0..to_be_verified_changes.len() { + let last_verified_change = if i == 0 { + None + } else { + Some(&to_be_verified_changes[i - 1]) + }; + let new_change = &changes[i]; - Self::verify_change(existing_changes, new_change, vault.clone()).await? + Self::verify_change_signatures(last_verified_change, new_change, vault.clone()).await? } Ok(()) } @@ -117,7 +138,7 @@ impl Identity { public_key: &PublicKey, hash: [u8; 32], signature: &ChangeSignature, - vault: Arc, + vault: Arc, ) -> Result { let signature = match signature { ChangeSignature::Ed25519Signature(signature) => { @@ -139,26 +160,18 @@ impl Identity { /// WARNING: This function assumes all existing changes in chain are verified. /// WARNING: Correctness of changes sequence is not verified here. - async fn verify_change( - existing_changes: &[VerifiedChange], + async fn verify_change_signatures( + last_verified_change: Option<&VerifiedChange>, new_change: &Change, - vault: Arc, + vault: Arc, ) -> Result<()> { - let hash = Vault::sha256(&new_change.data); - - let versioned_data: VersionedData = minicbor::decode(&new_change.data)?; - - if versioned_data.version != 1 { - return Err(IdentityError::UnknownIdentityVersion.into()); - } - - let change_data: ChangeData = minicbor::decode(&versioned_data.data)?; + let new_change_details = Self::get_change_details(new_change, vault.clone()).await?; - if let Some(last_verified_change) = existing_changes.last() { + if let Some(last_verified_change) = last_verified_change { if let Some(previous_signature) = &new_change.previous_signature { if !Self::verify_change_signature( last_verified_change.primary_public_key(), - hash, + new_change_details.change_full_hash, previous_signature, vault.clone(), ) @@ -167,13 +180,18 @@ impl Identity { return Err(IdentityError::IdentityVerificationFailed.into()); } } else { + // Previous signature should be present if it's not the first change return Err(IdentityError::IdentityVerificationFailed.into()); } } if !Self::verify_change_signature( - &change_data.primary_public_key.clone().into(), - hash, + &new_change_details + .change_data + .primary_public_key + .clone() + .into(), + new_change_details.change_full_hash, &new_change.signature, vault, ) diff --git a/implementations/rust/ockam/ockam_identity/src/identity/mod.rs b/implementations/rust/ockam/ockam_identity/src/identity/mod.rs index b8999dfc56b..674b50311a2 100644 --- a/implementations/rust/ockam/ockam_identity/src/identity/mod.rs +++ b/implementations/rust/ockam/ockam_identity/src/identity/mod.rs @@ -1,11 +1,12 @@ +mod constants; +mod history_comparison; #[allow(clippy::module_inception)] mod identity; -/// List of key changes associated to an identity -pub mod identity_change; -mod identity_change_history; -mod identity_identifier; +mod identity_verification; +pub use constants::*; +pub use history_comparison::*; pub use identity::*; -pub use identity_change::*; -pub use identity_change_history::*; -pub use identity_identifier::*; + +/// Verified Changes of an [`Identity`] +pub mod verified_change; diff --git a/implementations/rust/ockam/ockam_identity/src/v2/identity/verified_change.rs b/implementations/rust/ockam/ockam_identity/src/identity/verified_change.rs similarity index 68% rename from implementations/rust/ockam/ockam_identity/src/v2/identity/verified_change.rs rename to implementations/rust/ockam/ockam_identity/src/identity/verified_change.rs index 5c7c86202e9..482f8dc5522 100644 --- a/implementations/rust/ockam/ockam_identity/src/v2/identity/verified_change.rs +++ b/implementations/rust/ockam/ockam_identity/src/identity/verified_change.rs @@ -1,39 +1,39 @@ -use super::super::models::ChangeHash; +use crate::models::{ChangeData, ChangeHash}; use ockam_vault::PublicKey; /// Verified Changes of an [`Identity`] #[derive(Clone, Debug)] pub struct VerifiedChange { + data: ChangeData, change_hash: ChangeHash, primary_public_key: PublicKey, - revoke_all_purpose_keys: bool, } impl VerifiedChange { - /// [`ChangeHash`] - pub fn change_hash(&self) -> &ChangeHash { - &self.change_hash - } - - /// [`PrimaryPublicKey`] - pub fn primary_public_key(&self) -> &PublicKey { - &self.primary_public_key - } - - /// Are purpose keys revoked with this rotation - pub fn revoke_all_purpose_keys(&self) -> bool { - self.revoke_all_purpose_keys - } - pub(crate) fn new( + data: ChangeData, change_hash: ChangeHash, primary_public_key: PublicKey, - revoke_all_purpose_keys: bool, ) -> Self { Self { + data, change_hash, primary_public_key, - revoke_all_purpose_keys, } } + + /// [`ChangeData`] + pub fn data(&self) -> &ChangeData { + &self.data + } + + /// [`ChangeHash`] + pub fn change_hash(&self) -> &ChangeHash { + &self.change_hash + } + + /// [`PrimaryPublicKey`] + pub fn primary_public_key(&self) -> &PublicKey { + &self.primary_public_key + } } diff --git a/implementations/rust/ockam/ockam_identity/src/lib.rs b/implementations/rust/ockam/ockam_identity/src/lib.rs index c8ee8c29e06..49151948155 100644 --- a/implementations/rust/ockam/ockam_identity/src/lib.rs +++ b/implementations/rust/ockam/ockam_identity/src/lib.rs @@ -32,37 +32,53 @@ extern crate core; #[macro_use] extern crate alloc; -/// Data types supporting the creation of a credential -pub mod credential; +/// Utilities +pub mod utils; -/// Services for creating and validating credentials -pub mod credentials; +/// Errors +mod error; -/// Service for the management of identities +/// On-the-wire data types +pub mod models; + +/// Service for the management of Identities pub mod identities; -/// Data types representing an identity +/// Data types representing a verified Identity pub mod identity; +/// Service for the management of Purpose keys +pub mod purpose_keys; + +/// Data types representing a verified Purpose Key +pub mod purpose_key; + +/// Services for creating and validating credentials +pub mod credentials; + /// Data types supporting the creation of a secure channels pub mod secure_channel; /// Service supporting the creation of secure channel listener and connection to a listener pub mod secure_channels; -/// Errors -mod error; +/// Storage functions +pub mod storage; + +/// Vault +pub mod vault; /// /// Exports /// -pub use credential::*; pub use credentials::*; pub use error::*; pub use identities::*; pub use identity::*; +pub use purpose_key::*; +pub use purpose_keys::*; pub use secure_channel::*; pub use secure_channels::*; +pub use vault::*; -/// New version of Identity implementation meant to replace the current one when ready -pub mod v2; +pub use models::{Attributes, Credential, Identifier, TimestampInSeconds}; diff --git a/implementations/rust/ockam/ockam_identity/src/v2/models/change_history.rs b/implementations/rust/ockam/ockam_identity/src/models/change_history.rs similarity index 75% rename from implementations/rust/ockam/ockam_identity/src/v2/models/change_history.rs rename to implementations/rust/ockam/ockam_identity/src/models/change_history.rs index 7d047a4d89a..e2853ae1325 100644 --- a/implementations/rust/ockam/ockam_identity/src/v2/models/change_history.rs +++ b/implementations/rust/ockam/ockam_identity/src/models/change_history.rs @@ -1,11 +1,9 @@ -use super::super::models::{ +use crate::models::{ ChangeHash, Ed25519PublicKey, Ed25519Signature, P256ECDSAPublicKey, P256ECDSASignature, TimestampInSeconds, }; use minicbor::{Decode, Encode}; use ockam_core::compat::vec::Vec; -use ockam_core::Result; -use ockam_vault::PublicKey; /// Identity Change History #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] @@ -13,24 +11,6 @@ use ockam_vault::PublicKey; #[cbor(transparent)] pub struct ChangeHistory(#[n(0)] pub Vec); -impl AsRef<[Change]> for ChangeHistory { - fn as_ref(&self) -> &[Change] { - self.0.as_ref() - } -} - -impl ChangeHistory { - /// Export [`ChangeHistory`] to a binary format using CBOR - pub fn export(&self) -> Result> { - Ok(minicbor::to_vec(self)?) - } - - /// Import [`ChangeHistory`] from a binary format using CBOR - pub fn import(data: &[u8]) -> Result { - Ok(minicbor::decode(data)?) - } -} - /// Individual Identity change which implies replacing the old key #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)] #[rustfmt::skip] @@ -86,12 +66,3 @@ pub enum PrimaryPublicKey { /// ECDSA P256 Public Key #[n(2)] P256ECDSAPublicKey(#[n(0)] P256ECDSAPublicKey), } - -impl From for PublicKey { - fn from(value: PrimaryPublicKey) -> Self { - match value { - PrimaryPublicKey::Ed25519PublicKey(value) => Self::from(value), - PrimaryPublicKey::P256ECDSAPublicKey(value) => Self::from(value), - } - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/models/credential.rs b/implementations/rust/ockam/ockam_identity/src/models/credential.rs similarity index 96% rename from implementations/rust/ockam/ockam_identity/src/v2/models/credential.rs rename to implementations/rust/ockam/ockam_identity/src/models/credential.rs index 489bb98f053..1b3defe5ba9 100644 --- a/implementations/rust/ockam/ockam_identity/src/v2/models/credential.rs +++ b/implementations/rust/ockam/ockam_identity/src/models/credential.rs @@ -1,6 +1,7 @@ -use super::super::models::{ +use crate::models::{ ChangeHash, Ed25519Signature, Identifier, P256ECDSASignature, TimestampInSeconds, }; +use minicbor::bytes::ByteVec; use minicbor::{Decode, Encode}; use ockam_core::compat::{collections::BTreeMap, vec::Vec}; @@ -59,5 +60,5 @@ pub struct Attributes { /// [`SchemaId`] that determines which keys&values to expect in the [`Attributes`] #[n(1)] pub schema: SchemaId, /// Set of keys&values - #[n(2)] pub map: BTreeMap, Vec>, + #[n(2)] pub map: BTreeMap, } diff --git a/implementations/rust/ockam/ockam_identity/src/v2/models/credential_and_purpose_key.rs b/implementations/rust/ockam/ockam_identity/src/models/credential_and_purpose_key.rs similarity index 89% rename from implementations/rust/ockam/ockam_identity/src/v2/models/credential_and_purpose_key.rs rename to implementations/rust/ockam/ockam_identity/src/models/credential_and_purpose_key.rs index 7efb46f1029..c2af6b9cc82 100644 --- a/implementations/rust/ockam/ockam_identity/src/v2/models/credential_and_purpose_key.rs +++ b/implementations/rust/ockam/ockam_identity/src/models/credential_and_purpose_key.rs @@ -1,4 +1,4 @@ -use super::super::models::{Credential, PurposeKeyAttestation}; +use crate::models::{Credential, PurposeKeyAttestation}; use minicbor::{Decode, Encode}; /// [`Credential`] and the corresponding [`PurposeKeyAttestation`] that was used to issue that diff --git a/implementations/rust/ockam/ockam_identity/src/models/identifiers.rs b/implementations/rust/ockam/ockam_identity/src/models/identifiers.rs new file mode 100644 index 00000000000..c4affe91fee --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/src/models/identifiers.rs @@ -0,0 +1,16 @@ +/// Identifier length +pub const IDENTIFIER_LEN: usize = 20; + +/// ChangeHash length +pub const CHANGE_HASH_LEN: usize = 20; + +/// Unique identifier for an [`super::super::identity::Identity`] +/// Equals to the [`ChangeHash`] of the first [`super::Change`] in the [`super::ChangeHistory`] +/// Computed as truncated SHA256 of the first [`super::ChangeData`] CBOR binary +#[derive(Clone, Debug, Hash, Ord, PartialOrd, Eq, PartialEq)] +pub struct Identifier(pub [u8; IDENTIFIER_LEN]); + +/// Unique identifier for a [`super::Change`] +/// Computed as truncated SHA256 of the corresponding [`super::ChangeData`] CBOR binary +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ChangeHash(pub [u8; CHANGE_HASH_LEN]); diff --git a/implementations/rust/ockam/ockam_identity/src/v2/models/mod.rs b/implementations/rust/ockam/ockam_identity/src/models/mod.rs similarity index 97% rename from implementations/rust/ockam/ockam_identity/src/v2/models/mod.rs rename to implementations/rust/ockam/ockam_identity/src/models/mod.rs index 6f20dfaf272..0b87630d229 100644 --- a/implementations/rust/ockam/ockam_identity/src/v2/models/mod.rs +++ b/implementations/rust/ockam/ockam_identity/src/models/mod.rs @@ -6,6 +6,7 @@ mod public_keys; mod purpose_key_attestation; mod signatures; mod timestamp; +mod utils; mod versioned_data; pub use change_history::*; diff --git a/implementations/rust/ockam/ockam_identity/src/models/public_keys.rs b/implementations/rust/ockam/ockam_identity/src/models/public_keys.rs new file mode 100644 index 00000000000..f8337c0a437 --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/src/models/public_keys.rs @@ -0,0 +1,11 @@ +/// Ed25519 Public Key +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Ed25519PublicKey(pub [u8; 32]); + +/// X25519 Public Key +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct X25519PublicKey(pub [u8; 32]); + +/// P256 Public Key +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct P256ECDSAPublicKey(pub [u8; 65]); diff --git a/implementations/rust/ockam/ockam_identity/src/v2/models/purpose_key_attestation.rs b/implementations/rust/ockam/ockam_identity/src/models/purpose_key_attestation.rs similarity index 77% rename from implementations/rust/ockam/ockam_identity/src/v2/models/purpose_key_attestation.rs rename to implementations/rust/ockam/ockam_identity/src/models/purpose_key_attestation.rs index c5f35d37cfc..173b944126f 100644 --- a/implementations/rust/ockam/ockam_identity/src/v2/models/purpose_key_attestation.rs +++ b/implementations/rust/ockam/ockam_identity/src/models/purpose_key_attestation.rs @@ -1,12 +1,11 @@ -use super::super::models::{ +use ockam_core::compat::vec::Vec; + +use crate::models::{ ChangeHash, Ed25519PublicKey, Ed25519Signature, Identifier, P256ECDSAPublicKey, P256ECDSASignature, TimestampInSeconds, X25519PublicKey, }; -use super::super::IdentityError; + use minicbor::{Decode, Encode}; -use ockam_core::compat::vec::Vec; -use ockam_core::{Error, Result}; -use ockam_vault::{PublicKey, SecretType}; /// Self-signed Attestation of an [`super::super::identity::Identity`] associating /// a [`super::super::purpose_key::PurposeKey`] with itself @@ -70,24 +69,3 @@ pub enum CredentialSigningKey { /// ECDSA P256 Public Key #[n(2)] P256ECDSAPublicKey(#[n(0)] P256ECDSAPublicKey), } - -impl From for PublicKey { - fn from(value: CredentialSigningKey) -> Self { - match value { - CredentialSigningKey::Ed25519PublicKey(key) => key.into(), - CredentialSigningKey::P256ECDSAPublicKey(key) => key.into(), - } - } -} - -impl TryFrom for CredentialSigningKey { - type Error = Error; - - fn try_from(value: PublicKey) -> Result { - match value.stype() { - SecretType::Ed25519 => Ok(Self::Ed25519PublicKey(value.try_into()?)), - SecretType::NistP256 => Ok(Self::P256ECDSAPublicKey(value.try_into()?)), - _ => Err(IdentityError::InvalidKeyType.into()), - } - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/models/signatures.rs b/implementations/rust/ockam/ockam_identity/src/models/signatures.rs new file mode 100644 index 00000000000..3694306bf32 --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/src/models/signatures.rs @@ -0,0 +1,7 @@ +/// EdDSA Ed25519 Signature +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct Ed25519Signature(pub [u8; 64]); + +/// ECDSA P256 Signature +#[derive(PartialEq, Eq, Clone, Debug)] +pub struct P256ECDSASignature(pub [u8; 64]); diff --git a/implementations/rust/ockam/ockam_identity/src/models/timestamp.rs b/implementations/rust/ockam/ockam_identity/src/models/timestamp.rs new file mode 100644 index 00000000000..041f0d03c31 --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/src/models/timestamp.rs @@ -0,0 +1,9 @@ +use minicbor::{Decode, Encode}; +use serde::{Deserialize, Serialize}; + +/// Timestamp in seconds (UTC) +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Decode, Serialize, Deserialize)] +#[rustfmt::skip] +#[cbor(transparent)] +#[serde(transparent)] +pub struct TimestampInSeconds(#[n(0)] pub u64); diff --git a/implementations/rust/ockam/ockam_identity/src/models/utils/change_history.rs b/implementations/rust/ockam/ockam_identity/src/models/utils/change_history.rs new file mode 100644 index 00000000000..be9f3bd2962 --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/src/models/utils/change_history.rs @@ -0,0 +1,103 @@ +use crate::models::utils::get_versioned_data; +use crate::models::{ + Change, ChangeData, ChangeHistory, ChangeSignature, Ed25519PublicKey, Ed25519Signature, + P256ECDSAPublicKey, P256ECDSASignature, PrimaryPublicKey, VersionedData, +}; +use crate::IdentityError; + +use ockam_core::compat::vec::Vec; +use ockam_core::{Error, Result}; +use ockam_vault::{PublicKey, SecretType, Signature}; + +impl Change { + /// Extract [`VersionedData`] + pub fn get_versioned_data(&self) -> Result { + get_versioned_data(&self.data) + } +} + +impl ChangeData { + /// Extract [`ChangeData`] from [`VersionedData`] + pub fn get_data(versioned_data: &VersionedData) -> Result { + Ok(minicbor::decode(&versioned_data.data)?) + } +} + +impl ChangeHistory { + /// Export [`ChangeHistory`] to a binary format using CBOR + pub fn export(&self) -> Result> { + Ok(minicbor::to_vec(self)?) + } + + /// Import [`ChangeHistory`] from a binary format using CBOR + pub fn import(data: &[u8]) -> Result { + Ok(minicbor::decode(data)?) + } +} + +impl From for PublicKey { + fn from(value: PrimaryPublicKey) -> Self { + match value { + PrimaryPublicKey::Ed25519PublicKey(value) => Self::from(value), + PrimaryPublicKey::P256ECDSAPublicKey(value) => Self::from(value), + } + } +} + +impl TryFrom for PrimaryPublicKey { + type Error = Error; + + fn try_from(value: PublicKey) -> Result { + match value.stype() { + SecretType::Ed25519 => Ok(Self::Ed25519PublicKey(Ed25519PublicKey( + value + .data() + .try_into() + .map_err(|_| IdentityError::InvalidKeyData)?, + ))), + SecretType::NistP256 => Ok(Self::P256ECDSAPublicKey(P256ECDSAPublicKey( + value + .data() + .try_into() + .map_err(|_| IdentityError::InvalidKeyData)?, + ))), + + SecretType::X25519 | SecretType::Buffer | SecretType::Aes => { + Err(IdentityError::InvalidKeyType.into()) + } + } + } +} + +impl From for Signature { + fn from(value: ChangeSignature) -> Self { + match value { + ChangeSignature::Ed25519Signature(value) => Self::new(value.0.to_vec()), + ChangeSignature::P256ECDSASignature(value) => Self::new(value.0.to_vec()), + } + } +} + +impl ChangeSignature { + /// Try to create a [`ChangeSignature`] using a binary [`Signature`] and its type + pub fn try_from_signature(signature: Signature, stype: SecretType) -> Result { + match stype { + SecretType::Ed25519 => Ok(Self::Ed25519Signature(Ed25519Signature( + signature + .as_ref() + .try_into() + .map_err(|_| IdentityError::InvalidSignatureData)?, + ))), + SecretType::NistP256 => Ok(Self::P256ECDSASignature(P256ECDSASignature( + signature + .as_ref() + .try_into() + .map_err(|_| IdentityError::InvalidSignatureData)?, + ))), + + SecretType::Buffer | SecretType::Aes | SecretType::X25519 => { + Err(IdentityError::InvalidKeyType.into()) + } + } + } +} diff --git a/implementations/rust/ockam/ockam_identity/src/models/utils/credentials.rs b/implementations/rust/ockam/ockam_identity/src/models/utils/credentials.rs new file mode 100644 index 00000000000..b97cdd214a7 --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/src/models/utils/credentials.rs @@ -0,0 +1,55 @@ +use crate::models::utils::get_versioned_data; +use crate::models::{ + CredentialData, CredentialSignature, Ed25519Signature, P256ECDSASignature, VersionedData, +}; +use crate::{Credential, IdentityError}; + +use ockam_core::Result; +use ockam_vault::{SecretType, Signature}; + +impl Credential { + /// Extract [`VersionedData`] + pub fn get_versioned_data(&self) -> Result { + get_versioned_data(&self.data) + } +} + +impl CredentialData { + /// Extract [`CredentialData`] from [`VersionedData`] + pub fn get_data(versioned_data: &VersionedData) -> Result { + Ok(minicbor::decode(&versioned_data.data)?) + } +} + +impl From for Signature { + fn from(value: CredentialSignature) -> Self { + match value { + CredentialSignature::Ed25519Signature(value) => Self::new(value.0.to_vec()), + CredentialSignature::P256ECDSASignature(value) => Self::new(value.0.to_vec()), + } + } +} + +impl CredentialSignature { + /// Try to create a [`CredentialSignature`] using a binary [`Signature`] and its type + pub fn try_from_signature(signature: Signature, stype: SecretType) -> Result { + match stype { + SecretType::Ed25519 => Ok(Self::Ed25519Signature(Ed25519Signature( + signature + .as_ref() + .try_into() + .map_err(|_| IdentityError::InvalidSignatureData)?, + ))), + SecretType::NistP256 => Ok(Self::P256ECDSASignature(P256ECDSASignature( + signature + .as_ref() + .try_into() + .map_err(|_| IdentityError::InvalidSignatureData)?, + ))), + + SecretType::Buffer | SecretType::Aes | SecretType::X25519 => { + Err(IdentityError::InvalidKeyType.into()) + } + } + } +} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/models/identifiers.rs b/implementations/rust/ockam/ockam_identity/src/models/utils/identifiers.rs similarity index 81% rename from implementations/rust/ockam/ockam_identity/src/v2/models/identifiers.rs rename to implementations/rust/ockam/ockam_identity/src/models/utils/identifiers.rs index 6f8d667fa1e..c3b7508409e 100644 --- a/implementations/rust/ockam/ockam_identity/src/v2/models/identifiers.rs +++ b/implementations/rust/ockam/ockam_identity/src/models/utils/identifiers.rs @@ -1,28 +1,37 @@ +use ockam_core::compat::{string::String, vec::Vec}; +use ockam_core::env::FromString; +use ockam_core::{Error, Result}; + +use crate::{Identifier, IdentityError}; + +use crate::models::{ChangeHash, CHANGE_HASH_LEN, IDENTIFIER_LEN}; use core::fmt::{Display, Formatter}; use core::ops::Deref; use core::str::FromStr; use minicbor::bytes::ByteArray; use minicbor::encode::Write; use minicbor::{Decode, Decoder, Encode, Encoder}; -use ockam_core::compat::{string::String, vec::Vec}; -use ockam_core::env::FromString; -use ockam_core::{Error, Result}; -use serde::{Deserialize, Serialize}; - -use super::super::IdentityError; - -/// Identifier length -pub const IDENTIFIER_LEN: usize = 20; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + +impl Serialize for Identifier { + fn serialize(&self, serializer: S) -> core::result::Result + where + S: Serializer, + { + serializer.serialize_str(&String::from(self)) + } +} -/// ChangeHash length -pub const CHANGE_HASH_LEN: usize = 20; +impl<'de> Deserialize<'de> for Identifier { + fn deserialize(deserializer: D) -> core::result::Result + where + D: Deserializer<'de>, + { + let str: String = Deserialize::deserialize(deserializer)?; -/// Unique identifier for an [`super::super::identity::Identity`] -/// Equals to the [`ChangeHash`] of the first [`super::Change`] in the [`super::ChangeHistory`] -/// Computed as truncated SHA256 of the first [`super::ChangeData`] CBOR binary -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Identifier([u8; IDENTIFIER_LEN]); + Self::try_from(str).map_err(de::Error::custom) + } +} impl Encode for Identifier { fn encode( @@ -44,19 +53,6 @@ impl<'b, C> Decode<'b, C> for Identifier { impl Identifier { const PREFIX: &'static str = "I"; - - pub(crate) fn ct_eq(&self, o: &Self) -> subtle::Choice { - use subtle::ConstantTimeEq; - self.0.as_ref().ct_eq(o.0.as_ref()) - } -} - -impl Eq for Identifier {} - -impl PartialEq for Identifier { - fn eq(&self, o: &Self) -> bool { - self.ct_eq(o).into() - } } impl Display for Identifier { @@ -142,12 +138,6 @@ impl From for Identifier { } } -/// Unique identifier for a [`super::Change`] -/// Computed as truncated SHA256 of the corresponding [`super::ChangeData`] CBOR binary -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(transparent)] -pub struct ChangeHash([u8; CHANGE_HASH_LEN]); - impl Encode for ChangeHash { fn encode( &self, @@ -166,25 +156,6 @@ impl<'b, C> Decode<'b, C> for ChangeHash { } } -impl ChangeHash { - pub(crate) fn new(hash: [u8; CHANGE_HASH_LEN]) -> Self { - Self(hash) - } - - fn ct_eq(&self, o: &Self) -> subtle::Choice { - use subtle::ConstantTimeEq; - self.0.as_ref().ct_eq(o.0.as_ref()) - } -} - -impl Eq for ChangeHash {} - -impl PartialEq for ChangeHash { - fn eq(&self, o: &Self) -> bool { - self.ct_eq(o).into() - } -} - impl Display for ChangeHash { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { f.write_str(&String::from(self)) diff --git a/implementations/rust/ockam/ockam_identity/src/models/utils/mod.rs b/implementations/rust/ockam/ockam_identity/src/models/utils/mod.rs new file mode 100644 index 00000000000..06642d4f44c --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/src/models/utils/mod.rs @@ -0,0 +1,14 @@ +use minicbor::Decode; +use ockam_core::Result; + +fn get_versioned_data<'a, T: Decode<'a, ()>>(data: &'a [u8]) -> Result { + Ok(minicbor::decode(data)?) +} + +mod change_history; +mod credentials; +mod identifiers; +mod public_keys; +mod purpose_key_attestation; +mod signatures; +mod timestamp; diff --git a/implementations/rust/ockam/ockam_identity/src/v2/models/public_keys.rs b/implementations/rust/ockam/ockam_identity/src/models/utils/public_keys.rs similarity index 80% rename from implementations/rust/ockam/ockam_identity/src/v2/models/public_keys.rs rename to implementations/rust/ockam/ockam_identity/src/models/utils/public_keys.rs index 9a72f0cb3e7..31f7ed91295 100644 --- a/implementations/rust/ockam/ockam_identity/src/v2/models/public_keys.rs +++ b/implementations/rust/ockam/ockam_identity/src/models/utils/public_keys.rs @@ -1,22 +1,13 @@ -use super::super::IdentityError; +use crate::models::{Ed25519PublicKey, P256ECDSAPublicKey, X25519PublicKey}; +use crate::IdentityError; + +use ockam_core::{Error, Result}; +use ockam_vault::{PublicKey, SecretType}; + use core::ops::Deref; use minicbor::bytes::ByteArray; use minicbor::encode::Write; use minicbor::{Decode, Decoder, Encode, Encoder}; -use ockam_core::{Error, Result}; -use ockam_vault::{PublicKey, SecretType}; - -/// Ed25519 Public Key -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Ed25519PublicKey(pub [u8; 32]); - -/// X25519 Public Key -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct X25519PublicKey(pub [u8; 32]); - -/// P256 Public Key -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct P256ECDSAPublicKey(pub [u8; 64]); impl Encode for Ed25519PublicKey { fn encode( @@ -66,7 +57,7 @@ impl Encode for P256ECDSAPublicKey { impl<'b, C> Decode<'b, C> for P256ECDSAPublicKey { fn decode(d: &mut Decoder<'b>, ctx: &mut C) -> Result { - let data = ByteArray::<64>::decode(d, ctx)?; + let data = ByteArray::<65>::decode(d, ctx)?; Ok(Self(*data.deref())) } @@ -96,9 +87,10 @@ impl TryFrom for Ed25519PublicKey { fn try_from(value: PublicKey) -> Result { match value.stype() { SecretType::Ed25519 => { - let data = value.data()[0..32] + let data = value + .data() .try_into() - .map_err(|_| IdentityError::InvalidKeyType)?; + .map_err(|_| IdentityError::InvalidKeyData)?; Ok(Self(data)) } _ => Err(IdentityError::InvalidKeyType.into()), @@ -112,9 +104,10 @@ impl TryFrom for X25519PublicKey { fn try_from(value: PublicKey) -> Result { match value.stype() { SecretType::X25519 => { - let data = value.data()[0..32] + let data = value + .data() .try_into() - .map_err(|_| IdentityError::InvalidKeyType)?; + .map_err(|_| IdentityError::InvalidKeyData)?; Ok(Self(data)) } _ => Err(IdentityError::InvalidKeyType.into()), @@ -128,9 +121,10 @@ impl TryFrom for P256ECDSAPublicKey { fn try_from(value: PublicKey) -> Result { match value.stype() { SecretType::NistP256 => { - let data = value.data()[0..64] + let data = value + .data() .try_into() - .map_err(|_| IdentityError::InvalidKeyType)?; + .map_err(|_| IdentityError::InvalidKeyData)?; Ok(Self(data)) } _ => Err(IdentityError::InvalidKeyType.into()), diff --git a/implementations/rust/ockam/ockam_identity/src/models/utils/purpose_key_attestation.rs b/implementations/rust/ockam/ockam_identity/src/models/utils/purpose_key_attestation.rs new file mode 100644 index 00000000000..2d8f69c4dbd --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/src/models/utils/purpose_key_attestation.rs @@ -0,0 +1,80 @@ +use crate::models::utils::get_versioned_data; +use crate::models::{ + CredentialSigningKey, Ed25519Signature, P256ECDSASignature, PurposeKeyAttestation, + PurposeKeyAttestationData, PurposeKeyAttestationSignature, VersionedData, +}; +use crate::IdentityError; + +use ockam_core::{Error, Result}; +use ockam_vault::{PublicKey, SecretType, Signature}; + +impl PurposeKeyAttestation { + /// Extract [`VersionedData`] + pub fn get_versioned_data(&self) -> Result { + get_versioned_data(&self.data) + } +} + +impl PurposeKeyAttestationData { + /// Extract [`PurposeKeyAttestationData`] from [`VersionedData`] + pub fn get_data(versioned_data: &VersionedData) -> Result { + Ok(minicbor::decode(&versioned_data.data)?) + } +} + +impl From for Signature { + fn from(value: PurposeKeyAttestationSignature) -> Self { + match value { + PurposeKeyAttestationSignature::Ed25519Signature(value) => Self::new(value.0.to_vec()), + PurposeKeyAttestationSignature::P256ECDSASignature(value) => { + Self::new(value.0.to_vec()) + } + } + } +} + +impl PurposeKeyAttestationSignature { + /// Try to create a [`PurposeKeyAttestationSignature`] using a binary [`Signature`] and its type + pub fn try_from_signature(signature: Signature, stype: SecretType) -> Result { + match stype { + SecretType::Ed25519 => Ok(Self::Ed25519Signature(Ed25519Signature( + signature + .as_ref() + .try_into() + .map_err(|_| IdentityError::InvalidSignatureData)?, + ))), + SecretType::NistP256 => Ok(Self::P256ECDSASignature(P256ECDSASignature( + signature + .as_ref() + .try_into() + .map_err(|_| IdentityError::InvalidSignatureData)?, + ))), + + SecretType::Buffer | SecretType::Aes | SecretType::X25519 => { + Err(IdentityError::InvalidKeyType.into()) + } + } + } +} + +impl From for PublicKey { + fn from(value: CredentialSigningKey) -> Self { + match value { + CredentialSigningKey::Ed25519PublicKey(key) => key.into(), + CredentialSigningKey::P256ECDSAPublicKey(key) => key.into(), + } + } +} + +impl TryFrom for CredentialSigningKey { + type Error = Error; + + fn try_from(value: PublicKey) -> Result { + match value.stype() { + SecretType::Ed25519 => Ok(Self::Ed25519PublicKey(value.try_into()?)), + SecretType::NistP256 => Ok(Self::P256ECDSAPublicKey(value.try_into()?)), + + _ => Err(IdentityError::InvalidKeyType.into()), + } + } +} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/models/signatures.rs b/implementations/rust/ockam/ockam_identity/src/models/utils/signatures.rs similarity index 75% rename from implementations/rust/ockam/ockam_identity/src/v2/models/signatures.rs rename to implementations/rust/ockam/ockam_identity/src/models/utils/signatures.rs index c59464c01da..7c7d1f8f5b5 100644 --- a/implementations/rust/ockam/ockam_identity/src/v2/models/signatures.rs +++ b/implementations/rust/ockam/ockam_identity/src/models/utils/signatures.rs @@ -1,15 +1,9 @@ +use crate::models::{Ed25519Signature, P256ECDSASignature}; use core::ops::Deref; use minicbor::bytes::ByteArray; use minicbor::encode::Write; use minicbor::{Decode, Decoder, Encode, Encoder}; - -/// EdDSA Ed25519 Signature -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct Ed25519Signature(pub [u8; 64]); - -/// ECDSA P256 Signature -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct P256ECDSASignature(pub [u8; 64]); +use ockam_vault::Signature; impl Encode for Ed25519Signature { fn encode( @@ -46,3 +40,15 @@ impl<'b, C> Decode<'b, C> for P256ECDSASignature { Ok(Self(*data.deref())) } } + +impl From for Signature { + fn from(value: Ed25519Signature) -> Self { + Self::new(value.0.to_vec()) + } +} + +impl From for Signature { + fn from(value: P256ECDSASignature) -> Self { + Self::new(value.0.to_vec()) + } +} diff --git a/implementations/rust/ockam/ockam_identity/src/models/utils/timestamp.rs b/implementations/rust/ockam/ockam_identity/src/models/utils/timestamp.rs new file mode 100644 index 00000000000..f3dd1ebc36a --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/src/models/utils/timestamp.rs @@ -0,0 +1,31 @@ +use crate::TimestampInSeconds; + +impl core::ops::Deref for TimestampInSeconds { + type Target = u64; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From for TimestampInSeconds { + fn from(value: u64) -> Self { + Self(value) + } +} + +impl core::ops::Add for TimestampInSeconds { + type Output = TimestampInSeconds; + + fn add(self, rhs: TimestampInSeconds) -> Self::Output { + TimestampInSeconds(self.0 + rhs.0) + } +} + +impl core::ops::Sub for TimestampInSeconds { + type Output = TimestampInSeconds; + + fn sub(self, rhs: TimestampInSeconds) -> Self::Output { + TimestampInSeconds(self.0 - rhs.0) + } +} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/models/versioned_data.rs b/implementations/rust/ockam/ockam_identity/src/models/versioned_data.rs similarity index 100% rename from implementations/rust/ockam/ockam_identity/src/v2/models/versioned_data.rs rename to implementations/rust/ockam/ockam_identity/src/models/versioned_data.rs diff --git a/implementations/rust/ockam/ockam_identity/src/v2/purpose_key/mod.rs b/implementations/rust/ockam/ockam_identity/src/purpose_key/mod.rs similarity index 100% rename from implementations/rust/ockam/ockam_identity/src/v2/purpose_key/mod.rs rename to implementations/rust/ockam/ockam_identity/src/purpose_key/mod.rs diff --git a/implementations/rust/ockam/ockam_identity/src/v2/purpose_key/purpose.rs b/implementations/rust/ockam/ockam_identity/src/purpose_key/purpose.rs similarity index 100% rename from implementations/rust/ockam/ockam_identity/src/v2/purpose_key/purpose.rs rename to implementations/rust/ockam/ockam_identity/src/purpose_key/purpose.rs diff --git a/implementations/rust/ockam/ockam_identity/src/v2/purpose_key/purpose_key.rs b/implementations/rust/ockam/ockam_identity/src/purpose_key/purpose_key.rs similarity index 77% rename from implementations/rust/ockam/ockam_identity/src/v2/purpose_key/purpose_key.rs rename to implementations/rust/ockam/ockam_identity/src/purpose_key/purpose_key.rs index 42344bc06e9..0912462a3e4 100644 --- a/implementations/rust/ockam/ockam_identity/src/v2/purpose_key/purpose_key.rs +++ b/implementations/rust/ockam/ockam_identity/src/purpose_key/purpose_key.rs @@ -1,13 +1,14 @@ -use super::super::models::{Identifier, PurposeKeyAttestation, PurposeKeyAttestationData}; -use super::super::Purpose; -use ockam_vault::{KeyId, SecretType}; +use ockam_vault::{KeyId, PublicKey, SecretType}; + +use crate::models::{Identifier, PurposeKeyAttestation, PurposeKeyAttestationData}; +use crate::Purpose; /// Own PurposeKey #[derive(Clone, Debug)] pub struct PurposeKey { subject: Identifier, key_id: KeyId, - stype: SecretType, + public_key: PublicKey, purpose: Purpose, data: PurposeKeyAttestationData, attestation: PurposeKeyAttestation, @@ -18,7 +19,7 @@ impl PurposeKey { pub fn new( subject: Identifier, key_id: KeyId, - stype: SecretType, + public_key: PublicKey, purpose: Purpose, data: PurposeKeyAttestationData, attestation: PurposeKeyAttestation, @@ -26,7 +27,7 @@ impl PurposeKey { Self { subject, key_id, - stype, + public_key, purpose, data, attestation, @@ -40,6 +41,14 @@ impl PurposeKey { pub fn key_id(&self) -> &KeyId { &self.key_id } + /// Public Key + pub fn public_key(&self) -> &PublicKey { + &self.public_key + } + /// Secret Type + pub fn stype(&self) -> SecretType { + self.public_key.stype() + } /// Purpose of the Purpose Key pub fn purpose(&self) -> Purpose { self.purpose @@ -48,10 +57,6 @@ impl PurposeKey { pub fn attestation(&self) -> &PurposeKeyAttestation { &self.attestation } - /// Secret Type - pub fn stype(&self) -> SecretType { - self.stype - } /// Data inside [`PurposeKeyAttestation`] pub fn data(&self) -> &PurposeKeyAttestationData { &self.data diff --git a/implementations/rust/ockam/ockam_identity/src/purpose_keys/mod.rs b/implementations/rust/ockam/ockam_identity/src/purpose_keys/mod.rs new file mode 100644 index 00000000000..6908928d1a9 --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/src/purpose_keys/mod.rs @@ -0,0 +1,15 @@ +mod purpose_key_builder; +mod purpose_key_options; +#[allow(clippy::module_inception)] +mod purpose_keys; +mod purpose_keys_creation; +mod purpose_keys_verification; + +pub use purpose_key_builder::*; +pub use purpose_key_options::*; +pub use purpose_keys::*; +pub use purpose_keys_creation::*; +pub use purpose_keys_verification::*; + +/// Purpose Keys storage functions +pub mod storage; diff --git a/implementations/rust/ockam/ockam_identity/src/purpose_keys/purpose_key_builder.rs b/implementations/rust/ockam/ockam_identity/src/purpose_keys/purpose_key_builder.rs new file mode 100644 index 00000000000..f3480f82ccf --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/src/purpose_keys/purpose_key_builder.rs @@ -0,0 +1,168 @@ +use ockam_core::compat::sync::Arc; +use ockam_core::Result; +use ockam_vault::{KeyId, PublicKey, SecretType}; + +use crate::models::TimestampInSeconds; +use crate::utils::now; +use crate::{ + Identifier, Purpose, PurposeKey, PurposeKeyKey, PurposeKeyOptions, PurposeKeysCreation, +}; + +/// Default TTL for an Identity key +pub const DEFAULT_PURPOSE_KEY_TTL: TimestampInSeconds = TimestampInSeconds(5 * 365 * 24 * 60 * 60); // Five years + +enum Key { + Generate(SecretType), + Existing { key_id: KeyId, stype: SecretType }, + OnlyPublic(PublicKey), +} + +enum Ttl { + CreatedNowWithTtl(TimestampInSeconds), + FullTimestamps { + created_at: TimestampInSeconds, + expires_at: TimestampInSeconds, + }, +} + +/// Builder for [`PurposeKey`] +pub struct PurposeKeyBuilder { + purpose_keys_creation: Arc, + + identifier: Identifier, + purpose: Purpose, + key: Key, + ttl: Ttl, +} + +impl PurposeKeyBuilder { + /// Constructor + pub fn new( + purpose_keys_creation: Arc, + identifier: Identifier, + purpose: Purpose, + ) -> Self { + let key = match purpose { + Purpose::SecureChannel => Key::Generate(SecretType::X25519), + Purpose::Credentials => Key::Generate(SecretType::Ed25519), + }; + + Self { + purpose_keys_creation, + identifier, + purpose, + key, + ttl: Ttl::CreatedNowWithTtl(DEFAULT_PURPOSE_KEY_TTL), + } + } + + /// Use an existing key for the Identity (should be present in the corresponding Vault) + pub fn with_existing_key(mut self, key_id: KeyId, stype: SecretType) -> Self { + self.key = Key::Existing { key_id, stype }; + self + } + + /// Will generate a fresh key with the given type + pub fn with_random_key(mut self, key_type: SecretType) -> Self { + self.key = Key::Generate(key_type); + self + } + + /// Only public key is available, which is enough to attest it + /// However, the calling side is then responsible for possession and proper use of the + /// corresponding secret key + pub fn with_public_key(mut self, public_key: PublicKey) -> Self { + self.key = Key::OnlyPublic(public_key); + self + } + + /// Set created_at and expires_at timestamps + pub fn with_timestamps( + mut self, + created_at: TimestampInSeconds, + expires_at: TimestampInSeconds, + ) -> Self { + self.ttl = Ttl::FullTimestamps { + created_at, + expires_at, + }; + self + } + + /// Will set created_at to now and compute expires_at given the TTL + pub fn with_ttl(mut self, ttl_seconds: impl Into) -> Self { + self.ttl = Ttl::CreatedNowWithTtl(ttl_seconds.into()); + self + } + + /// Create the corresponding [`PurposeKeyOptions`] object + pub async fn build_options(self) -> Result { + let (key, stype) = match self.key { + Key::Generate(stype) => { + let attributes = stype.try_into()?; + let key = match &self.purpose { + Purpose::SecureChannel => { + let key_id = self + .purpose_keys_creation + .vault() + .secure_channel_vault + .generate_static_secret(attributes) + .await?; + PurposeKeyKey::Secret(key_id) + } + Purpose::Credentials => { + let key_id = self + .purpose_keys_creation + .vault() + .credential_vault + .generate_key(attributes) + .await?; + PurposeKeyKey::Secret(key_id) + } + }; + + (key, stype) + } + Key::Existing { key_id, stype } => (PurposeKeyKey::Secret(key_id), stype), + Key::OnlyPublic(public_key) => { + let stype = public_key.stype(); + (PurposeKeyKey::Public(public_key), stype) + } + }; + + let (created_at, expires_at) = match self.ttl { + Ttl::CreatedNowWithTtl(ttl) => { + let created_at = now()?; + let expires_at = created_at + ttl; + + (created_at, expires_at) + } + Ttl::FullTimestamps { + created_at, + expires_at, + } => (created_at, expires_at), + }; + + let options = PurposeKeyOptions::new( + self.identifier, + self.purpose, + key, + stype, + created_at, + expires_at, + ); + + Ok(options) + } + + /// Create the corresponding [`PurposeKey`] + pub async fn build(self) -> Result { + let purpose_keys_creation = self.purpose_keys_creation.clone(); + + let options = self.build_options().await?; + + purpose_keys_creation + .create_purpose_key_with_options(options) + .await + } +} diff --git a/implementations/rust/ockam/ockam_identity/src/purpose_keys/purpose_key_options.rs b/implementations/rust/ockam/ockam_identity/src/purpose_keys/purpose_key_options.rs new file mode 100644 index 00000000000..2cd211c0acd --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/src/purpose_keys/purpose_key_options.rs @@ -0,0 +1,75 @@ +use ockam_vault::{KeyId, PublicKey, SecretType}; + +use crate::{Identifier, Purpose, TimestampInSeconds}; + +#[derive(Clone)] +/// PurposeKey key. +pub enum PurposeKeyKey { + /// We have access to the PurposeKey secret to key to then use it + Secret(KeyId), + /// Only Public Key accessible, we can still attest such PurposeKey, but won't be able to use it. + /// The calling side may use corresponding secret key though. + Public(PublicKey), +} + +/// Options to create a Purpose Key +#[derive(Clone)] +pub struct PurposeKeyOptions { + pub(super) identifier: Identifier, + pub(super) purpose: Purpose, + pub(super) key: PurposeKeyKey, + pub(super) stype: SecretType, + pub(super) created_at: TimestampInSeconds, + pub(super) expires_at: TimestampInSeconds, +} + +impl PurposeKeyOptions { + /// Constructor + pub fn new( + identifier: Identifier, + purpose: Purpose, + key: PurposeKeyKey, + stype: SecretType, + created_at: TimestampInSeconds, + expires_at: TimestampInSeconds, + ) -> Self { + Self { + identifier, + purpose, + key, + stype, + created_at, + expires_at, + } + } + + /// [`Identifier`] of the issuer + pub fn identifier(&self) -> &Identifier { + &self.identifier + } + + /// [`Purpose`] + pub fn purpose(&self) -> Purpose { + self.purpose + } + + /// Key + pub fn key(&self) -> &PurposeKeyKey { + &self.key + } + + /// Secret key type + pub fn stype(&self) -> SecretType { + self.stype + } + + /// Creation timestamp + pub fn created_at(&self) -> TimestampInSeconds { + self.created_at + } + + /// Expiration timestamp + pub fn expires_at(&self) -> TimestampInSeconds { + self.expires_at + } +} diff --git a/implementations/rust/ockam/ockam_identity/src/purpose_keys/purpose_keys.rs b/implementations/rust/ockam/ockam_identity/src/purpose_keys/purpose_keys.rs new file mode 100644 index 00000000000..d1803f697e8 --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/src/purpose_keys/purpose_keys.rs @@ -0,0 +1,171 @@ +use ockam_core::compat::sync::Arc; + +use crate::purpose_keys::storage::PurposeKeysRepository; +use crate::{ + IdentitiesKeys, IdentitiesReader, PurposeKeysCreation, PurposeKeysVerification, Vault, +}; + +/// This struct supports all the services related to identities +#[derive(Clone)] +pub struct PurposeKeys { + vault: Vault, + identities_reader: Arc, + identity_keys: Arc, + repository: Arc, +} + +impl PurposeKeys { + /// Create a new identities module + pub fn new( + vault: Vault, + identities_reader: Arc, + identity_keys: Arc, + repository: Arc, + ) -> Self { + Self { + vault, + identities_reader, + identity_keys, + repository, + } + } + + /// Return [`PurposeKeysRepository`] instance + pub fn repository(&self) -> Arc { + self.repository.clone() + } + + /// Create [`PurposeKeysCreation`] + pub fn purpose_keys_creation(&self) -> Arc { + Arc::new(PurposeKeysCreation::new( + self.vault.clone(), + self.identities_reader.clone(), + self.identity_keys.clone(), + self.repository.clone(), + )) + } + + /// Create [`PurposeKeysVerification`] + pub fn purpose_keys_verification(&self) -> Arc { + Arc::new(PurposeKeysVerification::new( + self.vault.verifying_vault.clone(), + self.identities_reader.clone(), + )) + } +} + +#[cfg(test)] +mod tests { + use crate::{identities, Purpose}; + use ockam_core::Result; + + #[tokio::test] + async fn create_purpose_keys() -> Result<()> { + let identities = identities(); + let identities_creation = identities.identities_creation(); + let purpose_keys = identities.purpose_keys(); + + let identity = identities_creation.create_identity().await?; + let credentials_key = purpose_keys + .purpose_keys_creation() + .create_purpose_key(identity.identifier(), Purpose::Credentials) + .await?; + let secure_channel_key = purpose_keys + .purpose_keys_creation() + .create_purpose_key(identity.identifier(), Purpose::SecureChannel) + .await?; + + let credentials_key = purpose_keys + .purpose_keys_verification() + .verify_purpose_key_attestation( + Some(identity.identifier()), + credentials_key.attestation(), + ) + .await?; + let secure_channel_key = purpose_keys + .purpose_keys_verification() + .verify_purpose_key_attestation( + Some(identity.identifier()), + secure_channel_key.attestation(), + ) + .await?; + + assert_eq!(identity.identifier(), &credentials_key.subject); + assert_eq!(identity.identifier(), &secure_channel_key.subject); + + Ok(()) + } + + #[tokio::test] + async fn test_purpose_keys_are_persisted() -> Result<()> { + let identities = identities(); + let identities_creation = identities.identities_creation(); + let purpose_keys = identities.purpose_keys(); + + let identity = identities_creation.create_identity().await?; + + let credentials_key = purpose_keys + .purpose_keys_creation() + .create_purpose_key(identity.identifier(), Purpose::Credentials) + .await?; + + assert!(purpose_keys + .repository() + .retrieve_purpose_key(identity.identifier(), Purpose::Credentials) + .await? + .is_some()); + assert!(purpose_keys + .repository() + .retrieve_purpose_key(identity.identifier(), Purpose::SecureChannel) + .await? + .is_none()); + + let secure_channel_key = purpose_keys + .purpose_keys_creation() + .create_purpose_key(identity.identifier(), Purpose::SecureChannel) + .await?; + + let key = purpose_keys + .repository() + .retrieve_purpose_key(identity.identifier(), Purpose::Credentials) + .await? + .unwrap(); + purpose_keys + .purpose_keys_verification() + .verify_purpose_key_attestation(Some(identity.identifier()), &key) + .await + .unwrap(); + assert_eq!(&key, credentials_key.attestation()); + + let key = purpose_keys + .repository() + .retrieve_purpose_key(identity.identifier(), Purpose::SecureChannel) + .await? + .unwrap(); + purpose_keys + .purpose_keys_verification() + .verify_purpose_key_attestation(Some(identity.identifier()), &key) + .await + .unwrap(); + assert_eq!(&key, secure_channel_key.attestation()); + + let credentials_key2 = purpose_keys + .purpose_keys_creation() + .create_purpose_key(identity.identifier(), Purpose::Credentials) + .await?; + + let key = purpose_keys + .repository() + .retrieve_purpose_key(identity.identifier(), Purpose::Credentials) + .await? + .unwrap(); + purpose_keys + .purpose_keys_verification() + .verify_purpose_key_attestation(Some(identity.identifier()), &key) + .await + .unwrap(); + assert_eq!(&key, credentials_key2.attestation()); + + Ok(()) + } +} diff --git a/implementations/rust/ockam/ockam_identity/src/purpose_keys/purpose_keys_creation.rs b/implementations/rust/ockam/ockam_identity/src/purpose_keys/purpose_keys_creation.rs new file mode 100644 index 00000000000..20761c52aaa --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/src/purpose_keys/purpose_keys_creation.rs @@ -0,0 +1,311 @@ +use ockam_core::compat::sync::Arc; +use ockam_core::{Error, Result}; +use ockam_vault::SecretType; + +use crate::models::{ + Identifier, PurposeKeyAttestation, PurposeKeyAttestationData, PurposeKeyAttestationSignature, + PurposePublicKey, VersionedData, +}; +use crate::purpose_keys::storage::PurposeKeysRepository; +use crate::{ + IdentitiesKeys, IdentitiesReader, Identity, IdentityError, Purpose, PurposeKey, + PurposeKeyBuilder, PurposeKeyKey, PurposeKeyOptions, PurposeKeysVerification, Vault, +}; + +/// This struct supports all the services related to identities +#[derive(Clone)] +pub struct PurposeKeysCreation { + vault: Vault, + identities_reader: Arc, + identity_keys: Arc, + repository: Arc, +} + +impl PurposeKeysCreation { + /// Create a new identities module + pub(crate) fn new( + vault: Vault, + identities_reader: Arc, + identity_keys: Arc, + repository: Arc, + ) -> Self { + Self { + vault, + identities_reader, + identity_keys, + repository, + } + } + + /// Return [`PurposeKeysRepository`] instance + pub fn repository(&self) -> Arc { + self.repository.clone() + } + + /// Create [`PurposeKeysVerification`] + pub fn purpose_keys_verification(&self) -> Arc { + Arc::new(PurposeKeysVerification::new( + self.vault.verifying_vault.clone(), + self.identities_reader.clone(), + )) + } + + /// Get an instance of [`PurposeKeyBuilder`] + pub fn purpose_key_builder( + &self, + identifier: &Identifier, + purpose: Purpose, + ) -> PurposeKeyBuilder { + PurposeKeyBuilder::new( + Arc::new(Self::new( + self.vault.clone(), + self.identities_reader.clone(), + self.identity_keys.clone(), + self.repository.clone(), + )), + identifier.clone(), + purpose, + ) + } + + /// Return the [`Vault`] + pub fn vault(&self) -> &Vault { + &self.vault + } +} + +impl PurposeKeysCreation { + /// Create a [`PurposeKey`] + pub async fn create_purpose_key( + &self, + identifier: &Identifier, + purpose: Purpose, + ) -> Result { + let builder = self.purpose_key_builder(identifier, purpose); + builder.build().await + } + + /// Create a [`PurposeKey`] + pub async fn create_purpose_key_with_options( + &self, + options: PurposeKeyOptions, + ) -> Result { + // TODO: Check if such key already exists and rewrite it correctly (also delete from the Vault) + + let mut attestation_options = options.clone(); + + let (secret_key, public_key) = match options.key { + PurposeKeyKey::Secret(key_id) => match options.purpose { + Purpose::SecureChannel => { + let public_key = self + .vault + .secure_channel_vault + .get_public_key(&key_id) + .await?; + (key_id, public_key) + } + Purpose::Credentials => { + let public_key = self.vault.credential_vault.get_public_key(&key_id).await?; + (key_id, public_key) + } + }, + PurposeKeyKey::Public(_) => { + return Err(IdentityError::ExpectedSecretKeyInsteadOfPublic.into()); + } + }; + + attestation_options.key = PurposeKeyKey::Public(public_key.clone()); + + let (attestation, attestation_data) = self.attest_purpose_key(attestation_options).await?; + + let identifier = options.identifier.clone(); + + self.repository + .set_purpose_key(&identifier, options.purpose, &attestation) + .await?; + + let purpose_key = PurposeKey::new( + identifier, + secret_key, + public_key, + options.purpose, + attestation_data, + attestation, + ); + + Ok(purpose_key) + } + + /// Attest a PurposeKey given its public key + pub async fn attest_purpose_key( + &self, + options: PurposeKeyOptions, + ) -> Result<(PurposeKeyAttestation, PurposeKeyAttestationData)> { + let public_key = match options.key { + PurposeKeyKey::Secret { .. } => { + return Err(IdentityError::ExpectedPublicKeyInsteadOfSecret.into()) + } + PurposeKeyKey::Public(public_key) => public_key, + }; + + let public_key = match options.purpose { + Purpose::SecureChannel => { + match options.stype { + SecretType::X25519 => {} + + SecretType::Buffer + | SecretType::Aes + | SecretType::Ed25519 + | SecretType::NistP256 => { + return Err(IdentityError::InvalidKeyType.into()); + } + } + + PurposePublicKey::SecureChannelStaticKey( + public_key + .try_into() + .map_err(|_| IdentityError::InvalidKeyType)?, + ) + } + Purpose::Credentials => { + match options.stype { + SecretType::Ed25519 | SecretType::NistP256 => {} + + SecretType::Buffer | SecretType::Aes | SecretType::X25519 => { + return Err(IdentityError::InvalidKeyType.into()); + } + } + + PurposePublicKey::CredentialSigningKey( + public_key + .try_into() + .map_err(|_| IdentityError::InvalidKeyType)?, + ) + } + }; + + let identifier = options.identifier.clone(); + let identity_change_history = self.identities_reader.get_identity(&identifier).await?; + let identity = Identity::import_from_change_history( + Some(&identifier), + identity_change_history, + self.vault.verifying_vault.clone(), + ) + .await?; + + let purpose_key_attestation_data = PurposeKeyAttestationData { + subject: identifier, + subject_latest_change_hash: identity.latest_change_hash()?.clone(), + public_key, + created_at: options.created_at, + expires_at: options.expires_at, + }; + + let purpose_key_attestation_data_binary = minicbor::to_vec(&purpose_key_attestation_data)?; + + let versioned_data = VersionedData { + version: 1, + data: purpose_key_attestation_data_binary, + }; + let versioned_data = minicbor::to_vec(&versioned_data)?; + + let versioned_data_hash = self.vault.verifying_vault.sha256(&versioned_data).await?; + + let signing_key = self.identity_keys.get_secret_key(&identity).await?; + // TODO: Optimize + let public_key = self + .vault + .identity_vault + .get_public_key(&signing_key) + .await?; + let signature = self + .vault + .identity_vault + .sign(&signing_key, &versioned_data_hash) + .await?; + let signature = + PurposeKeyAttestationSignature::try_from_signature(signature, public_key.stype())?; + + let attestation = PurposeKeyAttestation { + data: versioned_data, + signature, + }; + + Ok((attestation, purpose_key_attestation_data)) + } + + /// Will try to get own Purpose Key from the repository, if that doesn't succeed - new one + /// will be generated + pub async fn get_or_create_purpose_key( + &self, + identifier: &Identifier, + purpose: Purpose, + ) -> Result { + let existent_key = async { + let purpose_key_attestation = + self.repository.get_purpose_key(identifier, purpose).await?; + + let purpose_key = self.import_purpose_key(&purpose_key_attestation).await?; + + Ok::(purpose_key) + } + .await; + + match existent_key { + Ok(purpose_key) => Ok(purpose_key), + // TODO: Should it be customizable? + Err(_) => self.create_purpose_key(identifier, purpose).await, + } + } + + /// Get own Purpose Key from the repository + pub async fn get_purpose_key( + &self, + identifier: &Identifier, + purpose: Purpose, + ) -> Result { + let purpose_key_attestation = self.repository.get_purpose_key(identifier, purpose).await?; + + self.import_purpose_key(&purpose_key_attestation).await + } + + /// Import own [`PurposeKey`] from its [`PurposeKeyAttestation`] + /// It's assumed that the corresponding secret exists in the Vault + pub async fn import_purpose_key( + &self, + attestation: &PurposeKeyAttestation, + ) -> Result { + let purpose_key_data = self + .purpose_keys_verification() + .verify_purpose_key_attestation(None, attestation) + .await?; + + let (purpose, key_id, public_key) = match purpose_key_data.public_key.clone() { + PurposePublicKey::SecureChannelStaticKey(public_key) => { + let public_key = public_key.into(); + let key_id = self + .vault + .secure_channel_vault + .get_key_id(&public_key) + .await?; + (Purpose::SecureChannel, key_id, public_key) + } + PurposePublicKey::CredentialSigningKey(public_key) => { + let public_key = public_key.into(); + let key_id = self.vault.credential_vault.get_key_id(&public_key).await?; + (Purpose::Credentials, key_id, public_key) + } + }; + + let purpose_key = PurposeKey::new( + purpose_key_data.subject.clone(), + key_id, + public_key, + purpose, + purpose_key_data, + attestation.clone(), + ); + + Ok(purpose_key) + } +} diff --git a/implementations/rust/ockam/ockam_identity/src/purpose_keys/purpose_keys_verification.rs b/implementations/rust/ockam/ockam_identity/src/purpose_keys/purpose_keys_verification.rs new file mode 100644 index 00000000000..855d1374b0c --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/src/purpose_keys/purpose_keys_verification.rs @@ -0,0 +1,123 @@ +use ockam_core::compat::sync::Arc; +use ockam_core::Result; +use ockam_vault::VerifyingVault; + +use crate::models::{Identifier, PurposeKeyAttestation, PurposeKeyAttestationData}; +use crate::utils::now; +use crate::{IdentitiesReader, Identity, IdentityError, TimestampInSeconds}; + +/// We allow purpose keys to be created in the future related to this machine's time due to +/// possible time dyssynchronization +const MAX_ALLOWED_TIME_DRIFT: TimestampInSeconds = TimestampInSeconds(5); + +/// This struct supports all the services related to identities +#[derive(Clone)] +pub struct PurposeKeysVerification { + verifying_vault: Arc, + identities_reader: Arc, +} + +impl PurposeKeysVerification { + /// Create a new identities module + pub(crate) fn new( + verifying_vault: Arc, + identities_reader: Arc, + ) -> Self { + Self { + verifying_vault, + identities_reader, + } + } +} + +impl PurposeKeysVerification { + /// Verify a [`PurposeKeyAttestation`] + pub async fn verify_purpose_key_attestation( + &self, + expected_subject: Option<&Identifier>, + attestation: &PurposeKeyAttestation, + ) -> Result { + let versioned_data_hash = self.verifying_vault.sha256(&attestation.data).await?; + + let versioned_data = attestation.get_versioned_data()?; + + if versioned_data.version != 1 { + return Err(IdentityError::PurposeKeyAttestationVerificationFailed.into()); + } + + let purpose_key_data = PurposeKeyAttestationData::get_data(&versioned_data)?; + + if let Some(expected_subject) = expected_subject { + if expected_subject != &purpose_key_data.subject { + // We expected purpose key that belongs to someone else + return Err(IdentityError::PurposeKeyAttestationVerificationFailed.into()); + } + } + + let change_history = self + .identities_reader + .get_identity(&purpose_key_data.subject) + .await?; + let identity = Identity::import_from_change_history( + Some(&purpose_key_data.subject), + change_history, + self.verifying_vault.clone(), + ) + .await?; + + let latest_change = identity.get_latest_change()?; + + // TODO: We should inspect purpose_key_data.subject_latest_change_hash, the possibilities are: + // 1) It's equal to the latest Change we know about, this is the default case and + // this is the only case that the code below handles currently + // 2) We haven't yet discovered that new Change, therefore we can't verify such PurposeKey + // 3) It references previous Change from the known to us history, we might accept such + // PurposeKey, but not if the next Change has revoke_all_purpose_keys == true + // 4) It references Change even older. IMO we shouldn't accept such PurposeKeys + + if &purpose_key_data.subject_latest_change_hash != latest_change.change_hash() { + // Only verifying with the latest key is currently implemented, see the `TODO` above + return Err(IdentityError::PurposeKeyAttestationVerificationFailed.into()); + } + + if purpose_key_data.expires_at > latest_change.data().expires_at { + // PurposeKey validity time range should be inside the identity key validity time range + return Err(IdentityError::PurposeKeyAttestationVerificationFailed.into()); + } + + if purpose_key_data.created_at < latest_change.data().created_at { + // PurposeKey validity time range should be inside the identity key validity time range + return Err(IdentityError::PurposeKeyAttestationVerificationFailed.into()); + } + + let now = now()?; + + if purpose_key_data.created_at > now + && purpose_key_data.created_at - now > MAX_ALLOWED_TIME_DRIFT + { + // PurposeKey can't be created in the future + return Err(IdentityError::PurposeKeyAttestationVerificationFailed.into()); + } + + if purpose_key_data.expires_at < now { + // PurposeKey expired + return Err(IdentityError::PurposeKeyAttestationVerificationFailed.into()); + } + + let identity_public_key = latest_change.primary_public_key(); + + if !self + .verifying_vault + .verify( + identity_public_key, + &versioned_data_hash, + &attestation.signature.clone().into(), + ) + .await? + { + return Err(IdentityError::PurposeKeyAttestationVerificationFailed.into()); + } + + Ok(purpose_key_data) + } +} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/purpose_keys/storage/mod.rs b/implementations/rust/ockam/ockam_identity/src/purpose_keys/storage/mod.rs similarity index 100% rename from implementations/rust/ockam/ockam_identity/src/v2/purpose_keys/storage/mod.rs rename to implementations/rust/ockam/ockam_identity/src/purpose_keys/storage/mod.rs diff --git a/implementations/rust/ockam/ockam_identity/src/v2/purpose_keys/storage/purpose_keys_repository_impl.rs b/implementations/rust/ockam/ockam_identity/src/purpose_keys/storage/purpose_keys_repository_impl.rs similarity index 88% rename from implementations/rust/ockam/ockam_identity/src/v2/purpose_keys/storage/purpose_keys_repository_impl.rs rename to implementations/rust/ockam/ockam_identity/src/purpose_keys/storage/purpose_keys_repository_impl.rs index cc335b74895..3cbd77f20bf 100644 --- a/implementations/rust/ockam/ockam_identity/src/v2/purpose_keys/storage/purpose_keys_repository_impl.rs +++ b/implementations/rust/ockam/ockam_identity/src/purpose_keys/storage/purpose_keys_repository_impl.rs @@ -4,11 +4,11 @@ use ockam_core::compat::string::{String, ToString}; use ockam_core::compat::sync::Arc; use ockam_core::Result; -use super::super::super::identity::IdentityConstants; -use super::super::super::models::{Identifier, PurposeKeyAttestation}; -use super::super::super::storage::{InMemoryStorage, Storage}; -use super::super::super::Purpose; -use super::{PurposeKeysReader, PurposeKeysRepository, PurposeKeysWriter}; +use crate::identity::IdentityConstants; +use crate::models::{Identifier, PurposeKeyAttestation}; +use crate::purpose_keys::storage::{PurposeKeysReader, PurposeKeysRepository, PurposeKeysWriter}; +use crate::storage::{InMemoryStorage, Storage}; +use crate::Purpose; /// Storage for own [`super::super::super::purpose_key::PurposeKey`]s #[derive(Clone)] diff --git a/implementations/rust/ockam/ockam_identity/src/v2/purpose_keys/storage/purpose_keys_repository_trait.rs b/implementations/rust/ockam/ockam_identity/src/purpose_keys/storage/purpose_keys_repository_trait.rs similarity index 95% rename from implementations/rust/ockam/ockam_identity/src/v2/purpose_keys/storage/purpose_keys_repository_trait.rs rename to implementations/rust/ockam/ockam_identity/src/purpose_keys/storage/purpose_keys_repository_trait.rs index 3ac7cf1abf3..3ecbae40295 100644 --- a/implementations/rust/ockam/ockam_identity/src/v2/purpose_keys/storage/purpose_keys_repository_trait.rs +++ b/implementations/rust/ockam/ockam_identity/src/purpose_keys/storage/purpose_keys_repository_trait.rs @@ -4,8 +4,8 @@ use ockam_core::errcode::{Kind, Origin}; use ockam_core::Result; use ockam_core::{async_trait, Error}; -use super::super::super::models::{Identifier, PurposeKeyAttestation}; -use super::super::super::Purpose; +use crate::models::{Identifier, PurposeKeyAttestation}; +use crate::Purpose; // TODO: Only one PurposeKey per Purpose per Identity is supported for now diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/access_control/credential_access_control.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/access_control/credential_access_control.rs index 4c128cd2542..8b532ba83fa 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/access_control/credential_access_control.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/access_control/credential_access_control.rs @@ -1,22 +1,23 @@ -use crate::identities::IdentitiesRepository; -use crate::secure_channel::local_info::IdentitySecureChannelLocalInfo; use core::fmt::{Debug, Formatter}; use ockam_core::access_control::IncomingAccessControl; -use ockam_core::compat::{boxed::Box, string::String, sync::Arc, vec::Vec}; +use ockam_core::compat::{boxed::Box, sync::Arc, vec::Vec}; use ockam_core::Result; use ockam_core::{async_trait, RelayMessage}; +use crate::identities::IdentitiesRepository; +use crate::secure_channel::local_info::IdentitySecureChannelLocalInfo; + /// Access control checking that message senders have a specific set of attributes #[derive(Clone)] pub struct CredentialAccessControl { - required_attributes: Vec<(String, Vec)>, + required_attributes: Vec<(Vec, Vec)>, storage: Arc, } impl CredentialAccessControl { /// Create a new credential access control pub fn new( - required_attributes: &[(String, Vec)], + required_attributes: &[(Vec, Vec)], storage: Arc, ) -> Self { Self { diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/access_control/identity_access_control.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/access_control/identity_access_control.rs index a88a5889313..a5919548d57 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/access_control/identity_access_control.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/access_control/identity_access_control.rs @@ -1,26 +1,25 @@ -use crate::identity::IdentityIdentifier; -use crate::secure_channel::local_info::IdentitySecureChannelLocalInfo; use ockam_core::access_control::IncomingAccessControl; use ockam_core::async_trait; use ockam_core::compat::boxed::Box; use ockam_core::compat::vec::Vec; use ockam_core::{RelayMessage, Result}; +use crate::models::Identifier; +use crate::secure_channel::local_info::IdentitySecureChannelLocalInfo; + /// Builder for `Identity`-related AccessControls pub struct IdentityAccessControlBuilder; impl IdentityAccessControlBuilder { /// `IncomingAccessControl` that checks if the author of the message possesses /// given `IdentityIdentifier` - pub fn new_with_id(their_identity_id: IdentityIdentifier) -> IdentityIdAccessControl { + pub fn new_with_id(their_identity_id: Identifier) -> IdentityIdAccessControl { IdentityIdAccessControl::new(vec![their_identity_id]) } /// `IncomingAccessControl` that checks if the author of the message possesses /// an `IdentityIdentifier` from the pre-known list - pub fn new_with_ids( - identity_ids: impl Into>, - ) -> IdentityIdAccessControl { + pub fn new_with_ids(identity_ids: impl Into>) -> IdentityIdAccessControl { IdentityIdAccessControl::new(identity_ids.into()) } @@ -46,22 +45,14 @@ impl IncomingAccessControl for IdentityAnyIdAccessControl { /// from a pre-known list #[derive(Clone, Debug)] pub struct IdentityIdAccessControl { - identity_ids: Vec, + identity_ids: Vec, } impl IdentityIdAccessControl { /// Constructor - pub fn new(identity_ids: Vec) -> Self { + pub fn new(identity_ids: Vec) -> Self { Self { identity_ids } } - - fn contains(&self, their_id: &IdentityIdentifier) -> bool { - let mut found = subtle::Choice::from(0); - for trusted_id in &self.identity_ids { - found |= trusted_id.ct_eq(their_id); - } - found.into() - } } #[async_trait] @@ -70,7 +61,9 @@ impl IncomingAccessControl for IdentityIdAccessControl { if let Ok(msg_identity_id) = IdentitySecureChannelLocalInfo::find_info(relay_msg.local_message()) { - Ok(self.contains(&msg_identity_id.their_identity_id())) + Ok(self + .identity_ids + .contains(&msg_identity_id.their_identity_id())) } else { Ok(false) } diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/addresses.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/addresses.rs index 9614a2ade68..7de61dc8ebd 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/addresses.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/addresses.rs @@ -1,6 +1,7 @@ -use crate::secure_channel::role::Role; use ockam_core::Address; +use crate::secure_channel::role::Role; + // Previously there were regular ephemeral secure channel encryptor&decryptor // and identity secure channel encryptor&decryptor. // Now this logic is merged into one encryptor&decryptor pair, but for backwards diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/decryptor.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/decryptor.rs index e1853efd1c2..d2647553d0d 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/decryptor.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/decryptor.rs @@ -1,26 +1,24 @@ -use crate::secure_channel::encryptor::{Encryptor, KEY_RENEWAL_INTERVAL}; -use crate::secure_channel::key_tracker::KeyTracker; -use crate::secure_channel::nonce_tracker::NonceTracker; -use crate::secure_channel::Addresses; -use crate::XXInitializedVault; -use crate::{ - DecryptionRequest, DecryptionResponse, IdentityError, IdentityIdentifier, - IdentitySecureChannelLocalInfo, -}; use ockam_core::compat::sync::Arc; use ockam_core::compat::vec::Vec; use ockam_core::{Any, Result, Routed, TransportMessage}; use ockam_core::{Decodable, LocalMessage}; use ockam_node::Context; -use ockam_vault::KeyId; -use tracing::debug; -use tracing::warn; +use ockam_vault::{KeyId, SecureChannelVault}; + +use crate::models::Identifier; +use crate::secure_channel::encryptor::{Encryptor, KEY_RENEWAL_INTERVAL}; +use crate::secure_channel::key_tracker::KeyTracker; +use crate::secure_channel::nonce_tracker::NonceTracker; +use crate::secure_channel::Addresses; +use crate::{DecryptionRequest, DecryptionResponse, IdentityError, IdentitySecureChannelLocalInfo}; + +use tracing::{debug, warn}; pub(crate) struct DecryptorHandler { //for debug purposes only pub(crate) role: &'static str, pub(crate) addresses: Addresses, - pub(crate) their_identity_id: IdentityIdentifier, + pub(crate) their_identity_id: Identifier, pub(crate) decryptor: Decryptor, } @@ -29,8 +27,8 @@ impl DecryptorHandler { role: &'static str, addresses: Addresses, key: KeyId, - vault: Arc, - their_identity_id: IdentityIdentifier, + vault: Arc, + their_identity_id: Identifier, ) -> Self { Self { role, @@ -124,13 +122,13 @@ impl DecryptorHandler { } pub(crate) struct Decryptor { - vault: Arc, + vault: Arc, key_tracker: KeyTracker, nonce_tracker: NonceTracker, } impl Decryptor { - pub fn new(key_id: KeyId, vault: Arc) -> Self { + pub fn new(key_id: KeyId, vault: Arc) -> Self { Self { vault, key_tracker: KeyTracker::new(key_id, KEY_RENEWAL_INTERVAL), @@ -173,7 +171,7 @@ impl Decryptor { if result.is_ok() { self.nonce_tracker = nonce_tracker; if let Some(key_to_delete) = self.key_tracker.update_key(key)? { - self.vault.delete_ephemeral_secret(key_to_delete).await?; + self.vault.delete_secret(key_to_delete).await?; } } result @@ -182,10 +180,10 @@ impl Decryptor { /// Remove the channel keys on shutdown pub(crate) async fn shutdown(&self) -> Result<()> { self.vault - .delete_ephemeral_secret(self.key_tracker.current_key.clone()) + .delete_secret(self.key_tracker.current_key.clone()) .await?; if let Some(previous_key) = self.key_tracker.previous_key.clone() { - self.vault.delete_ephemeral_secret(previous_key).await?; + self.vault.delete_secret(previous_key).await?; }; Ok(()) } diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor.rs index 57744444736..94c9892999d 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor.rs @@ -1,15 +1,15 @@ -use crate::IdentityError; -use crate::XXInitializedVault; use ockam_core::compat::sync::Arc; use ockam_core::compat::vec::Vec; use ockam_core::errcode::{Kind, Origin}; use ockam_core::{Error, Result}; -use ockam_vault::{KeyId, Secret}; +use ockam_vault::{KeyId, Secret, SecureChannelVault}; + +use crate::IdentityError; pub(crate) struct Encryptor { key: KeyId, nonce: u64, - vault: Arc, + vault: Arc, } // To simplify the implementation we use the same constant for the size of the message @@ -30,7 +30,7 @@ impl Encryptor { (b, n) } - pub async fn rekey(vault: &Arc, key: &KeyId) -> Result { + pub async fn rekey(vault: &Arc, key: &KeyId) -> Result { let nonce_buffer = Self::convert_nonce_from_u64(u64::MAX).1; let zeroes = [0u8; 32]; @@ -56,7 +56,7 @@ impl Encryptor { if current_nonce > 0 && current_nonce % KEY_RENEWAL_INTERVAL == 0 { let new_key = Self::rekey(&self.vault, &self.key).await?; let old_key = core::mem::replace(&mut self.key, new_key); - self.vault.delete_ephemeral_secret(old_key).await?; + self.vault.delete_secret(old_key).await?; } let (small_nonce, nonce) = Self::convert_nonce_from_u64(current_nonce); @@ -73,12 +73,12 @@ impl Encryptor { Ok(res) } - pub fn new(key: KeyId, nonce: u64, vault: Arc) -> Self { + pub fn new(key: KeyId, nonce: u64, vault: Arc) -> Self { Self { key, nonce, vault } } pub(crate) async fn shutdown(&self) -> Result<()> { - if !self.vault.delete_ephemeral_secret(self.key.clone()).await? { + if !self.vault.delete_secret(self.key.clone()).await? { Err(Error::new( Origin::Ockam, Kind::Internal, diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor_worker.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor_worker.rs index c917fa8bca8..a40445c2ff8 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor_worker.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/encryptor_worker.rs @@ -1,13 +1,14 @@ -use crate::secure_channel::addresses::Addresses; -use crate::secure_channel::api::{EncryptionRequest, EncryptionResponse}; -use crate::secure_channel::encryptor::Encryptor; -use crate::IdentityError; use ockam_core::compat::boxed::Box; use ockam_core::{async_trait, Decodable, Encodable, Route}; use ockam_core::{Any, Result, Routed, TransportMessage, Worker}; use ockam_node::Context; use tracing::debug; +use crate::secure_channel::addresses::Addresses; +use crate::secure_channel::api::{EncryptionRequest, EncryptionResponse}; +use crate::secure_channel::encryptor::Encryptor; +use crate::IdentityError; + pub(crate) struct EncryptorWorker { //for debug purposes only role: &'static str, diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/handshake.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/handshake.rs index 8bc6c1acea6..1041e0de6ae 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/handshake.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/handshake.rs @@ -1,18 +1,18 @@ -use crate::secure_channel::handshake::error::XXError; -use crate::secure_channel::handshake::handshake_state_machine::{HandshakeKeys, Status}; -use crate::secure_channel::Role; -use crate::XXVault; use arrayref::array_ref; use ockam_core::compat::sync::Arc; use ockam_core::compat::vec::Vec; use ockam_core::errcode::{Kind, Origin}; use ockam_core::{Error, Result}; -use ockam_vault::constants::{AES256_SECRET_LENGTH_USIZE, CURVE25519_PUBLIC_LENGTH_USIZE}; +use ockam_vault::constants::{AES256_SECRET_LENGTH_USIZE, X25519_PUBLIC_LENGTH_USIZE}; use ockam_vault::SecretType::X25519; -use ockam_vault::{KeyId, PublicKey, Secret, SecretAttributes}; +use ockam_vault::{KeyId, PublicKey, Secret, SecretAttributes, SecureChannelVault}; use sha2::{Digest, Sha256}; use Status::*; +use crate::secure_channel::handshake::error::XXError; +use crate::secure_channel::handshake::handshake_state_machine::{HandshakeKeys, Status}; +use crate::secure_channel::Role; + /// The number of bytes in a SHA256 digest pub const SHA256_SIZE_U32: u32 = 32; /// The number of bytes in a SHA256 digest @@ -25,7 +25,7 @@ pub const AES_GCM_TAGSIZE_USIZE: usize = 16; /// encrypt messages /// The variables used in the protocol itself: s, e, rs, re,... are handled in `HandshakeState` pub(super) struct Handshake { - vault: Arc, + vault: Arc, pub(super) state: HandshakeState, } @@ -205,7 +205,7 @@ impl Handshake { }); // now remove the ephemeral keys which are not useful anymore self.state = state; - self.delete_handshake_keys().await?; + self.delete_ephemeral_keys().await?; Ok(()) } @@ -220,7 +220,10 @@ impl Handshake { impl Handshake { /// Create a new handshake - pub(super) async fn new(vault: Arc, static_key: KeyId) -> Result { + pub(super) async fn new( + vault: Arc, + static_key: KeyId, + ) -> Result { // 1. generate an ephemeral key pair for this handshake and set it to e let ephemeral_key = Self::generate_ephemeral_key(vault.clone()).await?; @@ -270,7 +273,7 @@ impl Handshake { // The Diffie-Hellman secret is not useful anymore // we can delete it from memory - self.vault.delete_ephemeral_secret(dh).await?; + self.vault.delete_secret(dh).await?; let [new_ck, new_k]: [KeyId; 2] = hkdf_output .try_into() @@ -278,11 +281,11 @@ impl Handshake { let old_ck = state.take_ck()?; state.ck = Some(new_ck); - self.vault.delete_ephemeral_secret(old_ck).await?; + self.vault.delete_secret(old_ck).await?; let old_k = state.take_k()?; state.k = Some(new_k); - self.vault.delete_ephemeral_secret(old_k).await?; + self.vault.delete_secret(old_k).await?; state.n = 0; Ok(()) @@ -306,8 +309,8 @@ impl Handshake { .try_into() .map_err(|_| XXError::InternalVaultError)?; - self.vault.delete_ephemeral_secret(state.take_ck()?).await?; - self.vault.delete_ephemeral_secret(state.take_k()?).await?; + self.vault.delete_secret(state.take_ck()?).await?; + self.vault.delete_secret(state.take_k()?).await?; Ok((k1, k2)) } @@ -341,10 +344,9 @@ impl Handshake { Ok(result) } - async fn delete_handshake_keys(&mut self) -> Result<()> { - for key_id in vec![self.state.take_s()?, self.state.take_e()?] { - self.vault.delete_ephemeral_secret(key_id).await?; - } + async fn delete_ephemeral_keys(&mut self) -> Result<()> { + _ = self.vault.delete_secret(self.state.take_e()?).await?; + Ok(()) } } @@ -357,9 +359,9 @@ impl Handshake { } /// Generate an ephemeral key for the key exchange - async fn generate_ephemeral_key(vault: Arc) -> Result { + async fn generate_ephemeral_key(vault: Arc) -> Result { vault - .create_ephemeral_secret(SecretAttributes::X25519) + .generate_ephemeral_secret(SecretAttributes::X25519) .await } @@ -429,7 +431,7 @@ impl Handshake { /// Size of a public key fn key_size() -> usize { - CURVE25519_PUBLIC_LENGTH_USIZE + X25519_PUBLIC_LENGTH_USIZE } /// Size of an encrypted key @@ -484,16 +486,6 @@ impl HandshakeState { *array_ref![digest, 0, 32] } - pub(super) fn take_s(&mut self) -> Result { - self.s.take().ok_or_else(|| { - Error::new( - Origin::KeyExchange, - Kind::Invalid, - "key id s should have been set", - ) - }) - } - pub(super) fn take_e(&mut self) -> Result { self.e.take().ok_or_else(|| { Error::new( @@ -588,17 +580,20 @@ impl HandshakeState { #[cfg(test)] mod tests { use super::*; - use crate::{identities, to_xx_vault}; + use crate::identities; use hex::decode; use ockam_core::Result; + use ockam_node::InMemoryKeyValueStorage; + use ockam_vault::SoftwareSecureChannelVault; #[tokio::test] async fn test_initialization() -> Result<()> { - let identities = identities(); - let vault = to_xx_vault(identities.vault()); + let vault = Arc::new(SoftwareSecureChannelVault::new( + InMemoryKeyValueStorage::create(), + )); let static_key = vault - .create_ephemeral_secret(SecretAttributes::X25519) + .generate_static_secret(SecretAttributes::X25519) .await?; let mut handshake = Handshake::new(vault.clone(), static_key).await?; handshake.initialize().await?; @@ -610,9 +605,7 @@ mod tests { assert_eq!(handshake.state.h, exp_h); - let ck = vault - .get_ephemeral_secret(handshake.state.ck()?, "ck") - .await?; + let ck = vault.get_ephemeral_secret(handshake.state.ck()?)?; assert_eq!( ck.secret().as_ref(), @@ -678,41 +671,45 @@ mod tests { } async fn check_handshake(messages: HandshakeMessages) -> Result<()> { - let vault = to_xx_vault(identities().vault()); + let vault = identities().vault(); let initiator_static_key_id = vault - .import_ephemeral_secret( + .secure_channel_vault + .import_static_secret( Secret::new(messages.initiator_static_key), SecretAttributes::X25519, ) .await?; let initiator_ephemeral_key_id = vault + .secure_channel_vault .import_ephemeral_secret( Secret::new(messages.initiator_ephemeral_key), SecretAttributes::X25519, ) .await?; let mut initiator = Handshake::new_with_keys( - vault.clone(), + vault.secure_channel_vault.clone(), initiator_static_key_id, initiator_ephemeral_key_id, ) .await?; let responder_static_key_id = vault - .import_ephemeral_secret( + .secure_channel_vault + .import_static_secret( Secret::new(messages.responder_static_key), SecretAttributes::X25519, ) .await?; let responder_ephemeral_key_id = vault + .secure_channel_vault .import_ephemeral_secret( Secret::new(messages.responder_ephemeral_key), SecretAttributes::X25519, ) .await?; let mut responder = Handshake::new_with_keys( - vault.clone(), + vault.secure_channel_vault.clone(), responder_static_key_id, responder_ephemeral_key_id, ) @@ -756,7 +753,7 @@ mod tests { impl Handshake { /// Initialize the handshake async fn new_with_keys( - vault: Arc, + vault: Arc, static_key: KeyId, ephemeral_key: KeyId, ) -> Result { diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/handshake_state_machine.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/handshake_state_machine.rs index b62f7d43a26..c4005ea24b5 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/handshake_state_machine.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/handshake_state_machine.rs @@ -1,14 +1,16 @@ -use crate::{ - Credential, Credentials, Identities, Identity, IdentityError, IdentityIdentifier, - SecureChannelTrustInfo, TrustContext, TrustPolicy, XXVault, -}; +use minicbor::{Decode, Encode}; use ockam_core::compat::sync::Arc; use ockam_core::compat::{boxed::Box, vec::Vec}; -use ockam_core::errcode::{Kind, Origin}; -use ockam_core::{async_trait, Error, Message, Result}; -use ockam_vault::{KeyId, PublicKey, SecretAttributes, Signature}; -use serde::{Deserialize, Serialize}; -use tracing::info; +use ockam_core::{async_trait, Result}; +use ockam_vault::{KeyId, PublicKey, SecretType}; +use tracing::debug; + +use crate::models::{ + ChangeHistory, CredentialAndPurposeKey, Identifier, PurposeKeyAttestation, PurposePublicKey, +}; +use crate::{ + Identities, Identity, IdentityError, SecureChannelTrustInfo, TrustContext, TrustPolicy, +}; /// Interface for a state machine in a key exchange protocol #[async_trait] @@ -54,33 +56,33 @@ pub(super) struct HandshakeKeys { #[derive(Debug, Clone)] pub(super) struct HandshakeResults { pub(super) handshake_keys: HandshakeKeys, - pub(super) their_identifier: IdentityIdentifier, + pub(super) their_identifier: Identifier, } /// This struct implements functions common to both initiator and the responder state machines pub(super) struct CommonStateMachine { - pub(super) vault: Arc, pub(super) identities: Arc, - pub(super) identifier: IdentityIdentifier, - pub(super) credentials: Vec, + pub(super) identifier: Identifier, + pub(super) purpose_key_attestation: PurposeKeyAttestation, + pub(super) credentials: Vec, pub(super) trust_policy: Arc, pub(super) trust_context: Option, - their_identifier: Option, + their_identifier: Option, } impl CommonStateMachine { pub(super) fn new( - vault: Arc, identities: Arc, - identifier: IdentityIdentifier, - credentials: Vec, + identifier: Identifier, + purpose_key_attestation: PurposeKeyAttestation, + credentials: Vec, trust_policy: Arc, trust_context: Option, ) -> Self { Self { - vault, identities, identifier, + purpose_key_attestation, credentials, trust_policy, trust_context, @@ -88,38 +90,29 @@ impl CommonStateMachine { } } - /// Generate a handshake static key - /// for now this is an ephemeral key but it the future we can use a more permanent key - /// for the current identity - pub(super) async fn get_static_key(&self) -> Result { - self.vault - .create_ephemeral_secret(SecretAttributes::X25519) - .await - } - /// Prepare a payload containing the identity of the current party and serialize it. /// That payload contains: /// - /// - the current identity - /// - a signature of the static key used during the handshake - /// - the identity credentials + /// - the current Identity Change History + /// - the current Secure Channel Purpose Key Attestation + /// - the Identity Credentials and corresponding Credentials Purpose Key Attestations /// - pub(super) async fn make_identity_payload(&self, static_key: &KeyId) -> Result> { + pub(super) async fn make_identity_payload(&self) -> Result> { // prepare the payload that will be sent either in message 2 or message 3 - let identity = self + let change_history = self .identities .repository() .get_identity(&self.identifier) .await?; let payload = IdentityAndCredentials { - identity: identity.export()?, - signature: self.sign_static_key(identity, static_key).await?, + change_history, + purpose_key_attestation: self.purpose_key_attestation.clone(), credentials: self.credentials.clone(), }; - Ok(serde_bare::to_vec(&payload)?) + Ok(minicbor::to_vec(payload)?) } - /// Verify the identity sent by the other party: the signature and the credentials must be valid + /// Verify the identity sent by the other party: the Purpose Key and the credentials must be valid /// If everything is valid, store the identity identifier which will used to make the /// final state machine result pub(super) async fn verify_identity( @@ -127,91 +120,78 @@ impl CommonStateMachine { peer: IdentityAndCredentials, peer_public_key: &PublicKey, ) -> Result<()> { - let identity = self.decode_identity(peer.identity).await?; - self.verify_signature(&identity, &peer.signature, peer_public_key) - .await?; - self.verify_credentials(&identity, peer.credentials).await?; - self.their_identifier = Some(identity.identifier()); - Ok(()) - } - - /// Deserialize a payload as D from a bare encoding - pub(super) fn deserialize_payload Deserialize<'a>>(payload: Vec) -> Result { - serde_bare::from_slice(payload.as_slice()) - .map_err(|error| Error::new(Origin::Channel, Kind::Invalid, error)) - } - - /// Sign the static key used in the key exchange with the identity private key - async fn sign_static_key(&self, identity: Identity, key_id: &KeyId) -> Result { - let public_static_key = self.vault.get_public_key(key_id).await?; - self.identities - .identities_keys() - .create_signature(&identity, public_static_key.data(), None) - .await - } + let identity = Identity::import_from_change_history( + None, + peer.change_history.clone(), + self.identities.vault().verifying_vault, + ) + .await?; - /// Decode an Identity that was encoded with a BARE encoding - async fn decode_identity(&self, encoded: Vec) -> Result { self.identities .identities_creation() - .decode_identity(encoded.as_slice()) - .await - } + .update_identity(&identity) + .await?; - /// Verify that the signature was signed with the public key associated to the other party identity - async fn verify_signature( - &self, - their_identity: &Identity, - their_signature: &Signature, - their_public_key: &PublicKey, - ) -> Result<()> { - // verify the signature of the static key used during noise exchanges - // actually matches the signature of the identity - let signature_verified = self + let purpose_key = self .identities - .identities_keys() - .verify_signature( - their_identity, - their_signature, - their_public_key.data(), - None, + .purpose_keys() + .purpose_keys_verification() + .verify_purpose_key_attestation( + Some(identity.identifier()), + &peer.purpose_key_attestation, ) .await?; - if !signature_verified { - Err(IdentityError::SecureChannelVerificationFailed.into()) - } else { - Ok(()) + if peer_public_key.stype() != SecretType::X25519 { + return Err(IdentityError::InvalidKeyType.into()); + } + + match &purpose_key.public_key { + PurposePublicKey::SecureChannelStaticKey(public_key) => { + if public_key.0 != peer_public_key.data() { + return Err(IdentityError::InvalidKeyData.into()); + } + } + PurposePublicKey::CredentialSigningKey(_) => { + return Err(IdentityError::InvalidKeyType.into()) + } } + + self.verify_credentials(identity.identifier(), peer.credentials) + .await?; + self.their_identifier = Some(identity.identifier().clone()); + Ok(()) } /// Verify that the credentials sent by the other party are valid using a trust context /// and store them async fn verify_credentials( &self, - their_identity: &Identity, - credentials: Vec, + their_identifier: &Identifier, + credentials: Vec, ) -> Result<()> { // check our TrustPolicy - let trust_info = SecureChannelTrustInfo::new(their_identity.identifier.clone()); + let trust_info = SecureChannelTrustInfo::new(their_identifier.clone()); let trusted = self.trust_policy.check(&trust_info).await?; if !trusted { // TODO: Shutdown? Communicate error? return Err(IdentityError::SecureChannelTrustCheckFailed.into()); } - info!( + debug!( "Initiator checked trust policy for SecureChannel from: {}", - their_identity.identifier + their_identifier ); if let Some(trust_context) = &self.trust_context { for credential in credentials { let result = self .identities + .credentials() + .credentials_verification() .receive_presented_credential( - &their_identity.identifier, - &[trust_context.authority()?.identity().await?], - credential, + their_identifier, + &[trust_context.authority()?.identifier().clone()], + &credential, ) .await; @@ -225,12 +205,6 @@ impl CommonStateMachine { return Err(IdentityError::SecureChannelVerificationFailed.into()); }; - // store identity for future validation - self.identities - .repository() - .update_identity(their_identity) - .await?; - Ok(()) } @@ -252,14 +226,17 @@ impl CommonStateMachine { } /// This internal structure is used as a payload in the XX protocol -#[derive(Debug, Clone, Serialize, Deserialize, Message)] +#[derive(Debug, Clone, Encode, Decode)] +#[rustfmt::skip] +#[cbor(map)] pub(super) struct IdentityAndCredentials { /// Exported identity - pub(super) identity: Vec, - /// The signature guarantees that the other end has access to the private key of the identity - /// The signature refers to the static key of the noise ('x') and is made with the static + #[n(1)] pub(super) change_history: ChangeHistory, + /// The Purpose Key guarantees that the other end has access to the private key of the identity + /// The Purpose Key here is also the static key of the noise ('x') and is issued with the static /// key of the identity - pub(super) signature: Signature, - /// Credentials associated to the identity - pub(super) credentials: Vec, + #[n(2)] pub(super) purpose_key_attestation: PurposeKeyAttestation, + /// Credentials associated to the identity along with corresponding Credentials Purpose Keys + /// to verify those Credentials + #[n(3)] pub(super) credentials: Vec, } diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/handshake_worker.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/handshake_worker.rs index ee1c61ee9b8..c0ef006d1e2 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/handshake_worker.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/handshake_worker.rs @@ -1,4 +1,17 @@ -use crate::credential::Credential; +use alloc::sync::Arc; +use core::time::Duration; +use ockam_core::compat::{boxed::Box, vec::Vec}; +use ockam_core::errcode::{Kind, Origin}; +use ockam_core::{ + AllowAll, Any, Decodable, DenyAll, Error, Mailbox, Mailboxes, OutgoingAccessControl, Route, + Routed, +}; +use ockam_core::{AllowOnwardAddress, Result, Worker}; +use ockam_node::callback::CallbackSender; +use ockam_node::{Context, WorkerBuilder}; +use tracing::{debug, info}; + +use crate::models::{CredentialAndPurposeKey, Identifier}; use crate::secure_channel::decryptor::DecryptorHandler; use crate::secure_channel::encryptor::Encryptor; use crate::secure_channel::encryptor_worker::EncryptorWorker; @@ -13,21 +26,9 @@ use crate::secure_channel::handshake::initiator_state_machine::InitiatorStateMac use crate::secure_channel::handshake::responder_state_machine::ResponderStateMachine; use crate::secure_channel::{Addresses, Role}; use crate::{ - to_xx_initialized, to_xx_vault, IdentityError, IdentityIdentifier, SecureChannelRegistryEntry, - SecureChannels, TrustContext, TrustPolicy, + IdentityError, PurposeKey, SecureChannelRegistryEntry, SecureChannels, TrustContext, + TrustPolicy, }; -use alloc::sync::Arc; -use core::time::Duration; -use ockam_core::compat::{boxed::Box, vec::Vec}; -use ockam_core::errcode::{Kind, Origin}; -use ockam_core::{ - AllowAll, Any, Decodable, DenyAll, Error, Mailbox, Mailboxes, OutgoingAccessControl, Route, - Routed, -}; -use ockam_core::{AllowOnwardAddress, Result, Worker}; -use ockam_node::callback::CallbackSender; -use ockam_node::{Context, WorkerBuilder}; -use tracing::{debug, info}; /// This struct implements a Worker receiving and sending messages /// on one side of the secure channel creation as specified with its role: INITIATOR or REPSONDER @@ -35,7 +36,7 @@ pub(crate) struct HandshakeWorker { secure_channels: Arc, callback_sender: Option>, state_machine: Box, - identifier: IdentityIdentifier, + identifier: Identifier, addresses: Addresses, role: Role, remote_route: Option, @@ -52,7 +53,7 @@ impl Worker for HandshakeWorker { async fn initialize(&mut self, context: &mut Self::Context) -> Result<()> { match self.state_machine.on_event(Initialize).await? { SendMessage(message) => { - info!( + debug!( "remote route {:?}, decryptor remote {:?}", self.remote_route.clone(), self.addresses.decryptor_remote.clone() @@ -152,16 +153,17 @@ impl HandshakeWorker { context: &Context, secure_channels: Arc, addresses: Addresses, - identifier: IdentityIdentifier, + identifier: Identifier, + purpose_key: PurposeKey, trust_policy: Arc, decryptor_outgoing_access_control: Arc, - credentials: Vec, + credentials: Vec, trust_context: Option, remote_route: Option, timeout: Option, role: Role, ) -> Result<()> { - let vault = to_xx_vault(secure_channels.vault()); + let vault = secure_channels.identities.vault().secure_channel_vault; let identities = secure_channels.identities(); let state_machine: Box = if role.is_initiator() { Box::new( @@ -169,6 +171,7 @@ impl HandshakeWorker { vault, identities, identifier.clone(), + purpose_key, credentials, trust_policy, trust_context, @@ -181,6 +184,7 @@ impl HandshakeWorker { vault, identities, identifier.clone(), + purpose_key, credentials, trust_policy, trust_context, @@ -289,7 +293,7 @@ impl HandshakeWorker { self.role.str(), self.addresses.clone(), handshake_results.handshake_keys.decryption_key, - to_xx_initialized(self.secure_channels.identities.vault()), + self.secure_channels.identities.vault().secure_channel_vault, handshake_results.their_identifier.clone(), ); @@ -302,7 +306,7 @@ impl HandshakeWorker { Encryptor::new( handshake_results.handshake_keys.encryption_key, 0, - to_xx_initialized(self.secure_channels.identities.vault()), + self.secure_channels.identities.vault().secure_channel_vault, ), ); diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/initiator_state_machine.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/initiator_state_machine.rs index b2756eb43a8..acb19478713 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/initiator_state_machine.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/initiator_state_machine.rs @@ -1,22 +1,24 @@ -use crate::secure_channel::handshake::error::XXError; -use crate::secure_channel::handshake::handshake::Handshake; -use crate::secure_channel::handshake::handshake_state_machine::{ - Action, CommonStateMachine, Event, HandshakeKeys, HandshakeResults, IdentityAndCredentials, - StateMachine, Status, -}; -use crate::{Credential, Identities, IdentityIdentifier, Role, TrustContext, TrustPolicy, XXVault}; use delegate::delegate; use ockam_core::async_trait; use ockam_core::compat::sync::Arc; use ockam_core::compat::{boxed::Box, vec::Vec}; use ockam_core::errcode::{Kind, Origin}; use ockam_core::{Error, Result}; -use ockam_vault::PublicKey; +use ockam_vault::{PublicKey, SecureChannelVault}; use Action::*; use Event::*; use Role::*; use Status::*; +use crate::models::{CredentialAndPurposeKey, Identifier}; +use crate::secure_channel::handshake::error::XXError; +use crate::secure_channel::handshake::handshake::Handshake; +use crate::secure_channel::handshake::handshake_state_machine::{ + Action, CommonStateMachine, Event, HandshakeKeys, HandshakeResults, IdentityAndCredentials, + StateMachine, Status, +}; +use crate::{Identities, PurposeKey, Role, TrustContext, TrustPolicy}; + /// Implementation of a state machine for the key exchange on the initiator side #[async_trait] impl StateMachine for InitiatorStateMachine { @@ -35,8 +37,8 @@ impl StateMachine for InitiatorStateMachine { // Process message 2 and send message 3 (WaitingForMessage2, ReceivedMessage(message)) => { let message2_payload = self.decode_message2(&message).await?; - let their_identity_payload = - CommonStateMachine::deserialize_payload(message2_payload)?; + let their_identity_payload: IdentityAndCredentials = + minicbor::decode(&message2_payload)?; self.verify_identity(their_identity_payload, &self.handshake.state.rs()?.clone()) .await?; let identity_payload = self @@ -94,27 +96,27 @@ impl InitiatorStateMachine { impl InitiatorStateMachine { pub async fn new( - vault: Arc, + vault: Arc, identities: Arc, - identifier: IdentityIdentifier, - credentials: Vec, + identifier: Identifier, + purpose_key: PurposeKey, + credentials: Vec, trust_policy: Arc, trust_context: Option, ) -> Result { let common = CommonStateMachine::new( - vault.clone(), identities, identifier, + purpose_key.attestation().clone(), credentials, trust_policy, trust_context, ); - let static_key = common.get_static_key().await?; - let identity_payload = common.make_identity_payload(&static_key).await?; + let identity_payload = common.make_identity_payload().await?; Ok(InitiatorStateMachine { common, - handshake: Handshake::new(vault.clone(), static_key).await?, + handshake: Handshake::new(vault, purpose_key.key_id().clone()).await?, identity_payload: Some(identity_payload), }) } diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/responder_state_machine.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/responder_state_machine.rs index 1c652fe85ef..d8555ce3312 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/responder_state_machine.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/handshake/responder_state_machine.rs @@ -1,22 +1,24 @@ -use crate::secure_channel::handshake::error::XXError; -use crate::secure_channel::handshake::handshake::Handshake; -use crate::secure_channel::handshake::handshake_state_machine::{ - Action, CommonStateMachine, Event, HandshakeKeys, HandshakeResults, IdentityAndCredentials, - StateMachine, Status, -}; -use crate::{Credential, Identities, IdentityIdentifier, Role, TrustContext, TrustPolicy, XXVault}; use async_trait::async_trait; use delegate::delegate; use ockam_core::compat::sync::Arc; use ockam_core::compat::{boxed::Box, vec::Vec}; use ockam_core::errcode::{Kind, Origin}; use ockam_core::{Error, Result}; -use ockam_vault::PublicKey; +use ockam_vault::{PublicKey, SecureChannelVault}; use Action::*; use Event::*; use Role::*; use Status::*; +use crate::models::{CredentialAndPurposeKey, Identifier}; +use crate::secure_channel::handshake::error::XXError; +use crate::secure_channel::handshake::handshake::Handshake; +use crate::secure_channel::handshake::handshake_state_machine::{ + Action, CommonStateMachine, Event, HandshakeKeys, HandshakeResults, IdentityAndCredentials, + StateMachine, Status, +}; +use crate::{Identities, PurposeKey, Role, TrustContext, TrustPolicy}; + /// Implementation of a state machine for the key exchange on the responder side #[async_trait] impl StateMachine for ResponderStateMachine { @@ -44,8 +46,8 @@ impl StateMachine for ResponderStateMachine { // Process message 3 (WaitingForMessage3, ReceivedMessage(message)) => { let message3_payload = self.decode_message3(&message).await?; - let their_identity_payload = - CommonStateMachine::deserialize_payload(message3_payload)?; + let their_identity_payload: IdentityAndCredentials = + minicbor::decode(&message3_payload)?; self.verify_identity(their_identity_payload, &self.handshake.state.rs()?.clone()) .await?; self.set_final_state(Responder).await?; @@ -97,27 +99,27 @@ impl ResponderStateMachine { impl ResponderStateMachine { pub async fn new( - vault: Arc, + vault: Arc, identities: Arc, - identifier: IdentityIdentifier, - credentials: Vec, + identifier: Identifier, + purpose_key: PurposeKey, + credentials: Vec, trust_policy: Arc, trust_context: Option, ) -> Result { let common = CommonStateMachine::new( - vault.clone(), identities, identifier, + purpose_key.attestation().clone(), credentials, trust_policy, trust_context, ); - let static_key = common.get_static_key().await?; - let identity_payload = common.make_identity_payload(&static_key).await?; + let identity_payload = common.make_identity_payload().await?; Ok(ResponderStateMachine { common, - handshake: Handshake::new(vault.clone(), static_key).await?, + handshake: Handshake::new(vault, purpose_key.key_id().clone()).await?, identity_payload: Some(identity_payload), }) } diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/key_tracker.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/key_tracker.rs index 4640c6563f4..643b7f53620 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/key_tracker.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/key_tracker.rs @@ -1,9 +1,10 @@ -use crate::IdentityError; use ockam_core::Result; use ockam_vault::KeyId; use tracing::debug; use tracing::warn; +use crate::IdentityError; + pub(crate) struct KeyTracker { pub(crate) current_key: KeyId, pub(crate) previous_key: Option, diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/listener.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/listener.rs index becb79e8c7e..511622a55bf 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/listener.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/listener.rs @@ -1,25 +1,27 @@ -use crate::secure_channel::addresses::Addresses; -use crate::secure_channel::handshake_worker::HandshakeWorker; -use crate::secure_channel::options::SecureChannelListenerOptions; -use crate::secure_channel::role::Role; -use crate::secure_channels::secure_channels::SecureChannels; -use crate::{Credential, IdentityIdentifier}; use ockam_core::compat::boxed::Box; use ockam_core::compat::sync::Arc; use ockam_core::compat::vec::Vec; use ockam_core::{Address, Any, Result, Routed, Worker}; use ockam_node::Context; +use crate::models::{CredentialAndPurposeKey, Identifier}; +use crate::secure_channel::addresses::Addresses; +use crate::secure_channel::handshake_worker::HandshakeWorker; +use crate::secure_channel::options::SecureChannelListenerOptions; +use crate::secure_channel::role::Role; +use crate::secure_channels::secure_channels::SecureChannels; +use crate::Purpose; + pub(crate) struct IdentityChannelListener { secure_channels: Arc, - identifier: IdentityIdentifier, + identifier: Identifier, options: SecureChannelListenerOptions, } impl IdentityChannelListener { fn new( secure_channels: Arc, - identifier: IdentityIdentifier, + identifier: Identifier, options: SecureChannelListenerOptions, ) -> Self { Self { @@ -32,7 +34,7 @@ impl IdentityChannelListener { pub async fn create( ctx: &Context, secure_channels: Arc, - identifier: &IdentityIdentifier, + identifier: &Identifier, address: Address, options: SecureChannelListenerOptions, ) -> Result<()> { @@ -47,7 +49,7 @@ impl IdentityChannelListener { /// If credentials are not provided via list in options /// get them from the trust context - async fn get_credentials(&self, ctx: &mut Context) -> Result> { + async fn get_credentials(&self, ctx: &mut Context) -> Result> { let credentials = if self.options.credentials.is_empty() { if let Some(trust_context) = &self.options.trust_context { vec![ @@ -88,11 +90,21 @@ impl Worker for IdentityChannelListener { let credentials = self.get_credentials(ctx).await?; + // TODO: Allow manual PurposeKey management + let purpose_key = self + .secure_channels + .identities + .purpose_keys() + .purpose_keys_creation() + .get_or_create_purpose_key(&self.identifier, Purpose::SecureChannel) + .await?; + HandshakeWorker::create( ctx, self.secure_channels.clone(), addresses.clone(), self.identifier.clone(), + purpose_key, self.options.trust_policy.clone(), access_control.decryptor_outgoing_access_control, credentials, diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/local_info.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/local_info.rs index 8afd8734569..6969f29c847 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/local_info.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/local_info.rs @@ -1,16 +1,17 @@ -use crate::identity::IdentityIdentifier; -use crate::IdentityError; use ockam_core::compat::vec::Vec; use ockam_core::{Decodable, Encodable, LocalInfo, LocalMessage, Result}; use serde::{Deserialize, Serialize}; +use crate::models::Identifier; +use crate::IdentityError; + /// Identity SecureChannel LocalInfo unique Identifier pub const IDENTITY_SECURE_CHANNEL_IDENTIFIER: &str = "IDENTITY_SECURE_CHANNEL_IDENTIFIER"; /// Identity SecureChannel LocalInfo used for LocalMessage #[derive(Serialize, Deserialize)] pub struct IdentitySecureChannelLocalInfo { - their_identity_id: IdentityIdentifier, + their_identity_id: Identifier, } impl IdentitySecureChannelLocalInfo { @@ -55,7 +56,7 @@ impl IdentitySecureChannelLocalInfo { impl IdentitySecureChannelLocalInfo { /// Key exchange name - pub fn their_identity_id(&self) -> IdentityIdentifier { + pub fn their_identity_id(&self) -> Identifier { self.their_identity_id.clone() } } @@ -65,7 +66,7 @@ impl IdentitySecureChannelLocalInfo { /// replacing any pre-existing entries pub fn mark( mut local_info: Vec, - their_identity_id: IdentityIdentifier, + their_identity_id: Identifier, ) -> Result> { // strip out any pre-existing IdentitySecureChannelLocalInfo local_info.retain(|x| x.type_identifier() != IDENTITY_SECURE_CHANNEL_IDENTIFIER); diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/mod.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/mod.rs index f4e2002d3b1..fb5ea4577f7 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/mod.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/mod.rs @@ -30,8 +30,10 @@ pub use trust_policy::*; #[cfg(test)] mod tests { use crate::secure_channel::{decryptor::Decryptor, encryptor::Encryptor}; + use crate::Vault; + use ockam_core::compat::rand::RngCore; use ockam_core::Result; - use ockam_vault::{EphemeralSecretsStore, SecretAttributes, Vault}; + use ockam_vault::{Secret, SecretAttributes}; use rand::seq::SliceRandom; use rand::thread_rng; @@ -131,18 +133,21 @@ mod tests { } async fn create_encryptor_decryptor() -> Result<(Encryptor, Decryptor)> { - let vault1 = Vault::create(); - let vault2 = Vault::create(); + let vault1 = Vault::create_secure_channel_vault(); + let vault2 = Vault::create_secure_channel_vault(); + + let mut rng = thread_rng(); + let mut key = [0u8; 32]; + rng.fill_bytes(&mut key); let secret_attrs = SecretAttributes::Aes256; - let key_on_v1 = vault1.create_ephemeral_secret(secret_attrs).await.unwrap(); - let secret = vault1 - .get_ephemeral_secret(&key_on_v1, "secret") + let key_on_v1 = vault1 + .import_ephemeral_secret(Secret::new(key.to_vec()), secret_attrs) .await .unwrap(); let key_on_v2 = vault2 - .import_ephemeral_secret(secret.secret().clone(), secret_attrs) + .import_ephemeral_secret(Secret::new(key.to_vec()), secret_attrs) .await .unwrap(); diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/options.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/options.rs index fae0adb4186..02b72a6d811 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/options.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/options.rs @@ -1,13 +1,16 @@ -use crate::secure_channel::Addresses; -use crate::{Credential, TrustContext, TrustEveryonePolicy, TrustPolicy}; -use core::fmt; -use core::fmt::Formatter; -use core::time::Duration; use ockam_core::compat::sync::Arc; use ockam_core::compat::vec::Vec; use ockam_core::flow_control::{FlowControlId, FlowControlOutgoingAccessControl, FlowControls}; use ockam_core::{Address, OutgoingAccessControl, Result}; +use crate::models::CredentialAndPurposeKey; +use crate::secure_channel::Addresses; +use crate::{TrustContext, TrustEveryonePolicy, TrustPolicy}; + +use core::fmt; +use core::fmt::Formatter; +use core::time::Duration; + const DEFAULT_TIMEOUT: Duration = Duration::from_secs(120); /// Trust options for a Secure Channel @@ -15,7 +18,7 @@ pub struct SecureChannelOptions { pub(crate) flow_control_id: FlowControlId, pub(crate) trust_policy: Arc, pub(crate) trust_context: Option, - pub(crate) credentials: Vec, + pub(crate) credentials: Vec, pub(crate) timeout: Duration, } @@ -49,13 +52,13 @@ impl SecureChannelOptions { } /// Adds provided credentials - pub fn with_credentials(mut self, credentials: Vec) -> Self { + pub fn with_credentials(mut self, credentials: Vec) -> Self { self.credentials.extend(credentials); self } /// Adds a single credential - pub fn with_credential(mut self, credential: Credential) -> Self { + pub fn with_credential(mut self, credential: CredentialAndPurposeKey) -> Self { self.credentials.push(credential); self } @@ -125,7 +128,7 @@ pub struct SecureChannelListenerOptions { pub(crate) flow_control_id: FlowControlId, pub(crate) trust_policy: Arc, pub(crate) trust_context: Option, - pub(crate) credentials: Vec, + pub(crate) credentials: Vec, } impl fmt::Debug for SecureChannelListenerOptions { @@ -159,13 +162,13 @@ impl SecureChannelListenerOptions { } /// Adds provided credentials - pub fn with_credentials(mut self, credentials: Vec) -> Self { + pub fn with_credentials(mut self, credentials: Vec) -> Self { self.credentials.extend(credentials); self } /// Adds a single credential - pub fn with_credential(mut self, credential: Credential) -> Self { + pub fn with_credential(mut self, credential: CredentialAndPurposeKey) -> Self { self.credentials.push(credential); self } diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/registry.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/registry.rs index 439255c67f0..b07f353251f 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/registry.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/registry.rs @@ -1,10 +1,11 @@ -use crate::identity::IdentityIdentifier; -use crate::IdentityError; use ockam_core::compat::collections::BTreeMap; use ockam_core::compat::sync::{Arc, RwLock}; use ockam_core::compat::vec::Vec; use ockam_core::{Address, Result}; +use crate::models::Identifier; +use crate::IdentityError; + /// Known information about particular SecureChannel #[derive(Clone, Debug)] pub struct SecureChannelRegistryEntry { @@ -13,8 +14,8 @@ pub struct SecureChannelRegistryEntry { decryptor_messaging_address: Address, decryptor_api_address: Address, is_initiator: bool, - my_id: IdentityIdentifier, - their_id: IdentityIdentifier, + my_id: Identifier, + their_id: Identifier, their_decryptor_address: Address, } @@ -27,8 +28,8 @@ impl SecureChannelRegistryEntry { decryptor_messaging_address: Address, decryptor_api_address: Address, is_initiator: bool, - my_id: IdentityIdentifier, - their_id: IdentityIdentifier, + my_id: Identifier, + their_id: Identifier, their_decryptor_address: Address, ) -> Self { Self { @@ -69,13 +70,13 @@ impl SecureChannelRegistryEntry { } /// Our `IdentityIdentifier` - pub fn my_id(&self) -> IdentityIdentifier { - self.my_id.clone() + pub fn my_id(&self) -> &Identifier { + &self.my_id } /// Their `IdentityIdentifier` - pub fn their_id(&self) -> IdentityIdentifier { - self.their_id.clone() + pub fn their_id(&self) -> &Identifier { + &self.their_id } /// Their `Decryptor` address diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/all_trust_policy.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/all_trust_policy.rs index 7e2f1a974c4..666ef3b2791 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/all_trust_policy.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/all_trust_policy.rs @@ -1,8 +1,9 @@ -use crate::secure_channel::trust_policy::{SecureChannelTrustInfo, TrustPolicy}; use ockam_core::async_trait; use ockam_core::compat::boxed::Box; use ockam_core::{AsyncTryClone, Result}; +use crate::secure_channel::trust_policy::{SecureChannelTrustInfo, TrustPolicy}; + /// Succeeds only if both `TrustPolicy` checks succeeded #[derive(AsyncTryClone)] #[async_try_clone(crate = "ockam_core")] @@ -28,7 +29,7 @@ impl TrustPolicy for AllTrustPolicy { #[cfg(test)] mod test { - use crate::identity::IdentityIdentifier; + use crate::models::Identifier; use crate::secure_channel::trust_policy::{SecureChannelTrustInfo, TrustPolicy}; use ockam_core::async_trait; use ockam_core::Result; @@ -45,7 +46,7 @@ mod test { } } - let id = IdentityIdentifier::random(); + let id = Identifier::try_from("Iabababababababababababababababababababab").unwrap(); let trust_info = SecureChannelTrustInfo::new(id); assert!(TrustPolicyStub(true) diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/any_trust_policy.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/any_trust_policy.rs index 9739dd7d79b..5091de579d9 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/any_trust_policy.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/any_trust_policy.rs @@ -1,8 +1,9 @@ -use crate::secure_channel::trust_policy::{SecureChannelTrustInfo, TrustPolicy}; use ockam_core::async_trait; use ockam_core::compat::boxed::Box; use ockam_core::{AsyncTryClone, Result}; +use crate::secure_channel::trust_policy::{SecureChannelTrustInfo, TrustPolicy}; + /// Succeeds if any or both `TrustPolicy` checks succeeded #[derive(AsyncTryClone)] #[async_try_clone(crate = "ockam_core")] @@ -29,7 +30,7 @@ impl TrustPolicy for AnyTrustPolicy { #[cfg(test)] mod test { - use crate::identity::IdentityIdentifier; + use crate::models::Identifier; use crate::secure_channel::{SecureChannelTrustInfo, TrustPolicy}; use ockam_core::async_trait; use ockam_core::Result; @@ -46,7 +47,7 @@ mod test { } } - let id = IdentityIdentifier::random(); + let id = Identifier::try_from("Iabababababababababababababababababababab").unwrap(); let trust_info = SecureChannelTrustInfo::new(id); assert!(TrustPolicyStub(true) diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/mod.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/mod.rs index 43b1ed9a1ac..86e7f3f67a4 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/mod.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/mod.rs @@ -4,7 +4,6 @@ mod trust_everyone_policy; mod trust_identifier_policy; mod trust_multi_identifier_policy; mod trust_policy_type; -mod trust_public_key_policy; pub use all_trust_policy::*; pub use any_trust_policy::*; @@ -12,4 +11,3 @@ pub use trust_everyone_policy::*; pub use trust_identifier_policy::*; pub use trust_multi_identifier_policy::*; pub use trust_policy_type::*; -pub use trust_public_key_policy::*; diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/trust_everyone_policy.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/trust_everyone_policy.rs index 62f24d0d150..d8d20ab6fd6 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/trust_everyone_policy.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/trust_everyone_policy.rs @@ -1,8 +1,9 @@ -use crate::secure_channel::trust_policy::{SecureChannelTrustInfo, TrustPolicy}; use ockam_core::async_trait; use ockam_core::compat::boxed::Box; use ockam_core::{allow, Result}; +use crate::secure_channel::trust_policy::{SecureChannelTrustInfo, TrustPolicy}; + /// Trust any participant #[derive(Clone)] pub struct TrustEveryonePolicy; diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/trust_identifier_policy.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/trust_identifier_policy.rs index 4c41060ab29..ed06278cf96 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/trust_identifier_policy.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/trust_identifier_policy.rs @@ -1,18 +1,19 @@ -use crate::identity::IdentityIdentifier; -use crate::secure_channel::trust_policy::{SecureChannelTrustInfo, TrustPolicy}; use ockam_core::async_trait; use ockam_core::compat::boxed::Box; use ockam_core::Result; +use crate::models::Identifier; +use crate::secure_channel::trust_policy::{SecureChannelTrustInfo, TrustPolicy}; + /// `TrustPolicy` based on pre-known `IdentityIdentifier` of the other participant #[derive(Clone)] pub struct TrustIdentifierPolicy { - their_identity_id: IdentityIdentifier, + their_identity_id: Identifier, } impl TrustIdentifierPolicy { /// Constructor - pub fn new(their_identity_id: IdentityIdentifier) -> Self { + pub fn new(their_identity_id: Identifier) -> Self { Self { their_identity_id } } } diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/trust_multi_identifier_policy.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/trust_multi_identifier_policy.rs index 517ed1a88ac..51a935165de 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/trust_multi_identifier_policy.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/trust_multi_identifier_policy.rs @@ -1,36 +1,29 @@ -use crate::identity::IdentityIdentifier; -use crate::secure_channel::trust_policy::{SecureChannelTrustInfo, TrustPolicy}; use ockam_core::compat::boxed::Box; use ockam_core::compat::string::String; use ockam_core::compat::string::ToString; use ockam_core::{async_trait, compat::vec::Vec, Result}; use tracing::info; +use crate::models::Identifier; +use crate::trust_policy::{SecureChannelTrustInfo, TrustPolicy}; + /// `TrustPolicy` based on list of pre-known `IdentityIdentifier`s of the possible participants #[derive(Clone)] pub struct TrustMultiIdentifiersPolicy { - identity_ids: Vec, + identity_ids: Vec, } impl TrustMultiIdentifiersPolicy { /// Constructor - pub fn new(identity_ids: Vec) -> Self { + pub fn new(identity_ids: Vec) -> Self { Self { identity_ids } } - - fn contains(&self, their_id: &IdentityIdentifier) -> bool { - let mut found = subtle::Choice::from(0); - for trusted_id in &*self.identity_ids { - found |= trusted_id.ct_eq(their_id); - } - found.into() - } } #[async_trait] impl TrustPolicy for TrustMultiIdentifiersPolicy { async fn check(&self, trust_info: &SecureChannelTrustInfo) -> Result { - if !self.contains(trust_info.their_identity_id()) { + if !self.identity_ids.contains(trust_info.their_identity_id()) { info!( "{} is not one of the trusted identifiers {}", trust_info.their_identity_id(), diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/trust_policy_type.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/trust_policy_type.rs index b9b363aef76..4bc0c8503d4 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/trust_policy_type.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/trust_policy_type.rs @@ -5,26 +5,26 @@ use ockam_core::{ }; use serde::{Deserialize, Serialize}; -use crate::identity::IdentityIdentifier; +use crate::models::Identifier; use crate::secure_channel::trust_policy::{AllTrustPolicy, AnyTrustPolicy}; /// Authenticated data of the newly created SecureChannel to perform `TrustPolicy` check #[derive(Clone, Serialize, Deserialize)] pub struct SecureChannelTrustInfo { /// identity of the other end of the secure channel - pub their_identity_id: IdentityIdentifier, + pub their_identity_id: Identifier, } impl SecureChannelTrustInfo { /// `IdentityIdentifier` of the other participant - pub fn their_identity_id(&self) -> &IdentityIdentifier { + pub fn their_identity_id(&self) -> &Identifier { &self.their_identity_id } } impl SecureChannelTrustInfo { /// Constructor - pub fn new(their_identity_id: IdentityIdentifier) -> Self { + pub fn new(their_identity_id: Identifier) -> Self { Self { their_identity_id } } } diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/trust_public_key_policy.rs b/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/trust_public_key_policy.rs deleted file mode 100644 index ddbea1aff9a..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/secure_channel/trust_policy/trust_public_key_policy.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::identities::Identities; -use crate::secure_channel::trust_policy::{SecureChannelTrustInfo, TrustPolicy}; -use ockam_core::async_trait; -use ockam_core::compat::boxed::Box; -use ockam_core::compat::string::String; -use ockam_core::compat::sync::Arc; -use ockam_core::Result; -use ockam_vault::PublicKey; - -/// `TrustPolicy` based on pre-known `PublicKey` of the other participant -pub struct TrustPublicKeyPolicy { - public_key: PublicKey, - public_key_label: String, - identities: Arc, -} - -impl TrustPublicKeyPolicy { - /// Constructor - pub fn new( - public_key: PublicKey, - public_key_label: impl Into, - identities: Arc, - ) -> Self { - Self { - public_key, - public_key_label: public_key_label.into(), - identities, - } - } -} - -#[async_trait] -impl TrustPolicy for TrustPublicKeyPolicy { - async fn check(&self, trust_info: &SecureChannelTrustInfo) -> Result { - let their_identity = match self - .identities - .identities_repository - .retrieve_identity(trust_info.their_identity_id()) - .await? - { - Some(their_identity) => their_identity, - None => return Ok(false), - }; - - match their_identity.get_labelled_public_key(&self.public_key_label) { - Ok(pub_key) => Ok(pub_key == self.public_key), - _ => Ok(false), - } - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channels/secure_channels.rs b/implementations/rust/ockam/ockam_identity/src/secure_channels/secure_channels.rs index ad3b6ec6691..b9a73532be9 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channels/secure_channels.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channels/secure_channels.rs @@ -1,16 +1,16 @@ +use ockam_core::compat::sync::Arc; +use ockam_core::Result; +use ockam_core::{Address, Route}; +use ockam_node::Context; + use crate::identities::Identities; -use crate::identities::IdentitiesVault; -use crate::identity::IdentityIdentifier; +use crate::models::Identifier; use crate::secure_channel::handshake_worker::HandshakeWorker; use crate::secure_channel::{ Addresses, IdentityChannelListener, Role, SecureChannelListenerOptions, SecureChannelOptions, SecureChannelRegistry, }; -use crate::{SecureChannel, SecureChannelListener, SecureChannelsBuilder}; -use ockam_core::compat::sync::Arc; -use ockam_core::Result; -use ockam_core::{Address, Route}; -use ockam_node::Context; +use crate::{Purpose, SecureChannel, SecureChannelListener, SecureChannelsBuilder, Vault}; /// Identity implementation #[derive(Clone)] @@ -36,9 +36,9 @@ impl SecureChannels { self.identities.clone() } - /// Return the vault associated to this service - pub fn vault(&self) -> Arc { - self.identities.vault.clone() + /// Vault + pub fn vault(&self) -> Vault { + self.identities.vault() } /// Return the secure channel registry @@ -60,7 +60,7 @@ impl SecureChannels { pub async fn create_secure_channel_listener( &self, ctx: &Context, - identifier: &IdentityIdentifier, + identifier: &Identifier, address: impl Into
, options: impl Into, ) -> Result { @@ -84,7 +84,7 @@ impl SecureChannels { pub async fn create_secure_channel( &self, ctx: &Context, - identifier: &IdentityIdentifier, + identifier: &Identifier, route: impl Into, options: impl Into, ) -> Result { @@ -97,11 +97,20 @@ impl SecureChannels { options.setup_flow_control(ctx.flow_controls(), &addresses, next)?; let access_control = options.create_access_control(ctx.flow_controls()); + // TODO: Allow manual PurposeKey management + let purpose_key = self + .identities + .purpose_keys() + .purpose_keys_creation() + .get_or_create_purpose_key(identifier, Purpose::SecureChannel) + .await?; + HandshakeWorker::create( ctx, Arc::new(self.clone()), addresses.clone(), identifier.clone(), + purpose_key, options.trust_policy, access_control.decryptor_outgoing_access_control, options.credentials, diff --git a/implementations/rust/ockam/ockam_identity/src/secure_channels/secure_channels_builder.rs b/implementations/rust/ockam/ockam_identity/src/secure_channels/secure_channels_builder.rs index 74317c79c2d..b5e1e1727c2 100644 --- a/implementations/rust/ockam/ockam_identity/src/secure_channels/secure_channels_builder.rs +++ b/implementations/rust/ockam/ockam_identity/src/secure_channels/secure_channels_builder.rs @@ -1,13 +1,15 @@ -use crate::identities::{Identities, IdentitiesRepository, Storage}; +use ockam_core::compat::sync::Arc; + +use crate::identities::{Identities, IdentitiesRepository}; use crate::secure_channel::SecureChannelRegistry; use crate::secure_channels::SecureChannels; -use crate::{IdentitiesBuilder, IdentitiesVault}; -use ockam_core::compat::sync::Arc; -use ockam_vault::VaultStorage; +use crate::storage::Storage; +use crate::{IdentitiesBuilder, Vault, VaultStorage}; /// This struct supports all the services related to secure channels #[derive(Clone)] pub struct SecureChannelsBuilder { + // FIXME: This is very strange dependency pub(crate) identities_builder: IdentitiesBuilder, pub(crate) registry: SecureChannelRegistry, } @@ -18,45 +20,40 @@ pub fn secure_channels() -> Arc { } impl SecureChannelsBuilder { - /// Set a specific storage for the secure channels vault - pub fn with_vault_storage(&mut self, storage: VaultStorage) -> SecureChannelsBuilder { + /// With Software Vault with given Storage + pub fn with_vault_storage(mut self, storage: VaultStorage) -> Self { self.identities_builder = self.identities_builder.with_vault_storage(storage); - self.clone() + self } - /// Set a specific vault for secure channels - pub fn with_identities_vault( - &mut self, - vault: Arc, - ) -> SecureChannelsBuilder { - self.identities_builder = self.identities_builder.with_identities_vault(vault); - self.clone() + /// Set [`Vault`] + pub fn with_vault(mut self, vault: Vault) -> Self { + self.identities_builder = self.identities_builder.with_vault(vault); + self } /// Set a specific storage for the identities repository - pub fn with_identities_storage(&mut self, storage: Arc) -> SecureChannelsBuilder { + pub fn with_identities_storage(mut self, storage: Arc) -> Self { self.identities_builder = self.identities_builder.with_identities_storage(storage); - self.clone() + self } /// Set a specific identities repository - pub fn with_identities_repository( - &mut self, - repository: Arc, - ) -> SecureChannelsBuilder { + pub fn with_identities_repository(mut self, repository: Arc) -> Self { self.identities_builder = self .identities_builder .with_identities_repository(repository); - self.clone() + self } /// Set a specific identities - pub fn with_identities(&mut self, identities: Arc) -> SecureChannelsBuilder { + pub fn with_identities(mut self, identities: Arc) -> Self { self.identities_builder = self .identities_builder .with_identities_repository(identities.repository()) - .with_identities_vault(identities.vault()); - self.clone() + .with_vault(identities.vault()) + .with_purpose_keys_repository(identities.purpose_keys_repository()); + self } /// Set a specific channel registry @@ -70,10 +67,8 @@ impl SecureChannelsBuilder { /// Return the vault used by this builder /// Build secure channels - pub fn build(&self) -> Arc { - Arc::new(SecureChannels::new( - self.identities_builder.build(), - self.registry.clone(), - )) + pub fn build(self) -> Arc { + let identities = self.identities_builder.build(); + Arc::new(SecureChannels::new(identities, self.registry.clone())) } } diff --git a/implementations/rust/ockam/ockam_identity/src/v2/storage/lmdb_storage.rs b/implementations/rust/ockam/ockam_identity/src/storage/lmdb_storage.rs similarity index 99% rename from implementations/rust/ockam/ockam_identity/src/v2/storage/lmdb_storage.rs rename to implementations/rust/ockam/ockam_identity/src/storage/lmdb_storage.rs index f0651e0d704..c6fb0507589 100644 --- a/implementations/rust/ockam/ockam_identity/src/v2/storage/lmdb_storage.rs +++ b/implementations/rust/ockam/ockam_identity/src/storage/lmdb_storage.rs @@ -1,12 +1,3 @@ -use core::str; -use std::fmt; -use std::path::Path; - -use lmdb::{Cursor, Database, Environment, Transaction}; -use tokio_retry::strategy::{jitter, FixedInterval}; -use tokio_retry::Retry; -use tracing::debug; - use ockam_core::async_trait; use ockam_core::compat::boxed::Box; use ockam_core::compat::string::String; @@ -16,7 +7,15 @@ use ockam_core::errcode::{Kind, Origin}; use ockam_core::{Error, Result}; use ockam_node::tokio::task::{self, JoinError}; -use super::Storage; +use crate::storage::Storage; + +use core::str; +use lmdb::{Cursor, Database, Environment, Transaction}; +use std::fmt; +use std::path::Path; +use tokio_retry::strategy::{jitter, FixedInterval}; +use tokio_retry::Retry; +use tracing::debug; /// Storage using the LMDB database #[derive(Clone)] diff --git a/implementations/rust/ockam/ockam_identity/src/v2/storage/memory.rs b/implementations/rust/ockam/ockam_identity/src/storage/memory.rs similarity index 98% rename from implementations/rust/ockam/ockam_identity/src/v2/storage/memory.rs rename to implementations/rust/ockam/ockam_identity/src/storage/memory.rs index b44b0a33345..fb0822837b8 100644 --- a/implementations/rust/ockam/ockam_identity/src/v2/storage/memory.rs +++ b/implementations/rust/ockam/ockam_identity/src/storage/memory.rs @@ -1,4 +1,3 @@ -use super::Storage; use ockam_core::async_trait; use ockam_core::compat::{ boxed::Box, @@ -9,6 +8,8 @@ use ockam_core::compat::{ }; use ockam_core::Result; +use crate::storage::Storage; + /// Non-persistent table stored in RAM #[derive(Clone, Default)] pub struct InMemoryStorage { diff --git a/implementations/rust/ockam/ockam_identity/src/v2/storage/mod.rs b/implementations/rust/ockam/ockam_identity/src/storage/mod.rs similarity index 100% rename from implementations/rust/ockam/ockam_identity/src/v2/storage/mod.rs rename to implementations/rust/ockam/ockam_identity/src/storage/mod.rs diff --git a/implementations/rust/ockam/ockam_identity/src/identities/storage/sqlite_storage.rs b/implementations/rust/ockam/ockam_identity/src/storage/sqlite_storage.rs similarity index 99% rename from implementations/rust/ockam/ockam_identity/src/identities/storage/sqlite_storage.rs rename to implementations/rust/ockam/ockam_identity/src/storage/sqlite_storage.rs index 38c501f15db..5da866126d8 100644 --- a/implementations/rust/ockam/ockam_identity/src/identities/storage/sqlite_storage.rs +++ b/implementations/rust/ockam/ockam_identity/src/storage/sqlite_storage.rs @@ -1,4 +1,3 @@ -use crate::Storage; use core::str; use ockam_core::async_trait; use ockam_core::compat::sync::{Arc, Mutex}; @@ -13,6 +12,8 @@ use tokio_retry::strategy::{jitter, FixedInterval}; use tokio_retry::Retry; use tracing::debug; +use Storage; + /// Storage using the Sqlite database #[derive(Clone)] pub struct SqliteStorage { diff --git a/implementations/rust/ockam/ockam_identity/src/v2/storage/storage.rs b/implementations/rust/ockam/ockam_identity/src/storage/storage.rs similarity index 100% rename from implementations/rust/ockam/ockam_identity/src/v2/storage/storage.rs rename to implementations/rust/ockam/ockam_identity/src/storage/storage.rs diff --git a/implementations/rust/ockam/ockam_identity/src/v2/utils.rs b/implementations/rust/ockam/ockam_identity/src/utils.rs similarity index 75% rename from implementations/rust/ockam/ockam_identity/src/v2/utils.rs rename to implementations/rust/ockam/ockam_identity/src/utils.rs index 6705a00b4d9..75ce52a035e 100644 --- a/implementations/rust/ockam/ockam_identity/src/v2/utils.rs +++ b/implementations/rust/ockam/ockam_identity/src/utils.rs @@ -1,34 +1,36 @@ +use minicbor::bytes::ByteVec; use ockam_core::compat::collections::BTreeMap; use ockam_core::compat::vec::Vec; use ockam_core::Result; -use super::models::{Attributes, SchemaId, TimestampInSeconds}; +use crate::models::{Attributes, SchemaId, TimestampInSeconds}; +use crate::IdentityError; /// Create a new timestamp using the system time #[cfg(feature = "std")] pub fn now() -> Result { if let Ok(now) = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) { - Ok(TimestampInSeconds::new(now.as_secs())) + Ok(TimestampInSeconds(now.as_secs())) } else { - Err(super::IdentityError::UnknownTimestamp.into()) + Err(IdentityError::UnknownTimestamp.into()) } } /// Create a new timestamp using the system time #[cfg(not(feature = "std"))] pub fn now() -> Result { - Err(super::IdentityError::UnknownTimestamp.into()) + Err(IdentityError::UnknownTimestamp.into()) } /// Add a number of seconds to the [`TimestampInSeconds`] pub fn add_seconds(timestamp: &TimestampInSeconds, seconds: u64) -> TimestampInSeconds { - TimestampInSeconds::new(timestamp.saturating_add(seconds)) + TimestampInSeconds(timestamp.saturating_add(seconds)) } /// Convenient builder for the [`Attributes`] struct pub struct AttributesBuilder { schema_id: SchemaId, - map: BTreeMap, Vec>, + map: BTreeMap, } impl AttributesBuilder { @@ -42,7 +44,7 @@ impl AttributesBuilder { /// Add an attributes to the [`Attributes`] pub fn with_attribute(mut self, key: impl Into>, value: impl Into>) -> Self { - self.map.insert(key.into(), value.into()); + self.map.insert(key.into().into(), value.into().into()); self } diff --git a/implementations/rust/ockam/ockam_identity/src/v2/credentials/authority_service.rs b/implementations/rust/ockam/ockam_identity/src/v2/credentials/authority_service.rs deleted file mode 100644 index 12f7a387583..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/credentials/authority_service.rs +++ /dev/null @@ -1,84 +0,0 @@ -use super::super::credentials::credentials_retriever::CredentialsRetriever; -use super::super::models::{CredentialAndPurposeKey, Identifier, TimestampInSeconds}; -use super::super::utils::{add_seconds, now}; -use super::super::{Credentials, IdentityError}; - -use ockam_core::compat::sync::Arc; -use ockam_core::compat::sync::RwLock; -use ockam_core::Result; -use ockam_node::Context; - -/// An AuthorityService represents an authority which issued credentials -#[derive(Clone)] -pub struct AuthorityService { - credentials: Arc, - identifier: Identifier, - own_credential: Option>, - inner_cache: Arc>>, -} - -#[derive(Clone)] -struct CachedCredential { - credential: CredentialAndPurposeKey, - valid_until: TimestampInSeconds, -} - -impl AuthorityService { - /// Create a new authority service - pub fn new( - credentials: Arc, - identifier: Identifier, - own_credential: Option>, - ) -> Self { - Self { - credentials, - identifier, - own_credential, - inner_cache: Arc::new(RwLock::new(None)), - } - } - - /// Retrieve the credential for an identity within this authority - pub async fn credential( - &self, - ctx: &Context, - for_identity: &Identifier, - ) -> Result { - { - // check if we have a valid cached credential - let guard = self.inner_cache.read().unwrap(); - let now = now()?; - if let Some(cache) = guard.as_ref() { - // add an extra minute to have a bit of leeway for clock skew - if cache.valid_until > add_seconds(&now, 60) { - return Ok(cache.credential.clone()); - } - } - } - - // in order to keep the locking schema simple, we allow multiple concurrent retrievals - let retriever = self - .own_credential - .clone() - .ok_or(IdentityError::UnknownAuthority)?; - let credential = retriever.retrieve(ctx, for_identity).await?; - - let credential_data = self - .credentials - .verify_credential(for_identity, &[self.identifier.clone()], &credential) - .await?; - - let mut guard = self.inner_cache.write().unwrap(); - *guard = Some(CachedCredential { - credential: credential.clone(), - valid_until: credential_data.credential_data.expires_at, - }); - - Ok(credential) - } - - /// Issuer [`Identifier`] - pub fn identifier(&self) -> &Identifier { - &self.identifier - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/credentials/credentials.rs b/implementations/rust/ockam/ockam_identity/src/v2/credentials/credentials.rs deleted file mode 100644 index 2d9fa118aa2..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/credentials/credentials.rs +++ /dev/null @@ -1,301 +0,0 @@ -use super::super::identities::AttributesEntry; -use super::super::models::{ - Attributes, Credential, CredentialAndPurposeKey, CredentialData, CredentialSignature, - Ed25519Signature, Identifier, PurposeKeyAttestationData, PurposePublicKey, VersionedData, -}; -use super::super::utils::{add_seconds, now}; -use super::super::{ - IdentitiesRepository, IdentitiesVault, Identity, IdentityError, Purpose, PurposeKeys, -}; - -use core::time::Duration; -use ockam_core::compat::sync::Arc; -use ockam_core::compat::vec::Vec; -use ockam_core::Result; -use ockam_vault::{SecretType, Signature, Vault}; - -/// Structure with both [`CredentialData`] and [`PurposeKeyAttestationData`] that we get -/// after parsing and verifying corresponding [`Credential`] and [`super::super::models::PurposeKeyAttestation`] -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct CredentialAndPurposeKeyData { - /// [`CredentialData`] - pub credential_data: CredentialData, - /// [`PurposeKeyAttestationData`] - pub purpose_key_data: PurposeKeyAttestationData, -} - -/// Service for managing [`Credential`]s -pub struct Credentials { - vault: Arc, - purpose_keys: Arc, - identities_repository: Arc, -} - -impl Credentials { - ///Constructor - pub fn new( - vault: Arc, - purpose_keys: Arc, - identities_repository: Arc, - ) -> Self { - Self { - vault, - purpose_keys, - identities_repository, - } - } - - /// Vault - pub fn vault(&self) -> Arc { - self.vault.clone() - } - - /// [`PurposeKeys`] - pub fn purpose_keys(&self) -> Arc { - self.purpose_keys.clone() - } - - /// [`IdentitiesRepository`] - pub fn identities_repository(&self) -> Arc { - self.identities_repository.clone() - } -} - -impl Credentials { - /// Verify a [`Credential`] - pub async fn verify_credential( - &self, - subject: &Identifier, - authorities: &[Identifier], - credential_and_purpose_key: &CredentialAndPurposeKey, - ) -> Result { - let purpose_key_data = self - .purpose_keys - .verify_purpose_key_attestation(&credential_and_purpose_key.purpose_key_attestation) - .await?; - - if !authorities.contains(&purpose_key_data.subject) { - return Err(IdentityError::UnknownAuthority.into()); - } - - let public_key = match purpose_key_data.public_key.clone() { - PurposePublicKey::SecureChannelStaticKey(_) => { - return Err(IdentityError::InvalidKeyType.into()) - } - - PurposePublicKey::CredentialSigningKey(public_key) => public_key, - }; - - let public_key = public_key.into(); - - let versioned_data_hash = Vault::sha256(&credential_and_purpose_key.credential.data); - - let signature = match &credential_and_purpose_key.credential.signature { - CredentialSignature::Ed25519Signature(signature) => { - Signature::new(signature.0.to_vec()) - } - CredentialSignature::P256ECDSASignature(_) => { - return Err(IdentityError::InvalidKeyType.into()) - } - }; - - if !self - .vault - .verify(&public_key, &versioned_data_hash, &signature) - .await? - { - return Err(IdentityError::CredentialVerificationFailed.into()); - } - - let versioned_data: VersionedData = - minicbor::decode(&credential_and_purpose_key.credential.data)?; - if versioned_data.version != 1 { - return Err(IdentityError::UnknownCredentialVersion.into()); - } - - let credential_data: CredentialData = minicbor::decode(&versioned_data.data)?; - - if credential_data.subject.as_ref() != Some(subject) { - return Err(IdentityError::CredentialVerificationFailed.into()); - } - - if credential_data.created_at < purpose_key_data.created_at { - return Err(IdentityError::CredentialVerificationFailed.into()); - } - - if credential_data.expires_at > purpose_key_data.expires_at { - return Err(IdentityError::CredentialVerificationFailed.into()); - } - - let now = now()?; - - if credential_data.created_at > now { - return Err(IdentityError::CredentialVerificationFailed.into()); - } - - if credential_data.expires_at < now { - return Err(IdentityError::CredentialVerificationFailed.into()); - } - - // FIXME: credential_data.subject_latest_change_hash - // FIXME: Verify if given authority is allowed to issue credentials with given Schema - // FIXME: Verify if Schema aligns with Attributes - - Ok(CredentialAndPurposeKeyData { - credential_data, - purpose_key_data, - }) - } - - /// Issue a [`Credential`] - pub async fn issue_credential( - &self, - issuer: &Identifier, - subject: &Identifier, - subject_attributes: Attributes, - ttl: Duration, - ) -> Result { - let issuer_purpose_key = self - .purpose_keys - .repository() - .get_purpose_key(issuer, Purpose::Credentials) - .await?; - - let issuer_purpose_key = self - .purpose_keys - .import_purpose_key(&issuer_purpose_key) - .await?; - - let subject_change_history = self.identities_repository.get_identity(subject).await?; - let subject_identity = Identity::import_from_change_history( - Some(subject), - subject_change_history, - self.vault.clone(), - ) - .await?; - - let created_at = now()?; - let expires_at = add_seconds(&created_at, ttl.as_secs()); - - let credential_data = CredentialData { - subject: Some(subject.clone()), - subject_latest_change_hash: Some(subject_identity.latest_change_hash()?.clone()), - subject_attributes, - created_at, - expires_at, - }; - let credential_data = minicbor::to_vec(credential_data)?; - - let versioned_data = VersionedData { - version: 1, - data: credential_data, - }; - let versioned_data = minicbor::to_vec(&versioned_data)?; - - let versioned_data_hash = Vault::sha256(&versioned_data); - - if issuer_purpose_key.purpose() != Purpose::Credentials { - return Err(IdentityError::InvalidKeyType.into()); - } - - if issuer_purpose_key.stype() != SecretType::Ed25519 { - return Err(IdentityError::InvalidKeyType.into()); - } - - let signature = self - .vault - .sign(issuer_purpose_key.key_id(), &versioned_data_hash) - .await?; - let signature: Vec = signature.into(); - let signature = Ed25519Signature(signature.try_into().unwrap()); - let signature = CredentialSignature::Ed25519Signature(signature); - - let credential = Credential { - data: versioned_data, - signature, - }; - - let res = CredentialAndPurposeKey { - credential, - purpose_key_attestation: issuer_purpose_key.attestation().clone(), - }; - - Ok(res) - } - - /// Receive someone's [`Credential`]: verify and put attributes from it to the storage - pub async fn receive_presented_credential( - &self, - subject: &Identifier, - authorities: &[Identifier], - credential_and_purpose_key_attestation: &CredentialAndPurposeKey, - ) -> Result<()> { - let credential_data = self - .verify_credential(subject, authorities, credential_and_purpose_key_attestation) - .await?; - - self.identities_repository - .put_attributes( - subject, - AttributesEntry::new( - credential_data.credential_data.subject_attributes.map, - now()?, - Some(credential_data.credential_data.expires_at), - Some(credential_data.purpose_key_data.subject), - ), - ) - .await?; - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::super::super::identities::identities; - use super::super::super::models::SchemaId; - use super::*; - use ockam_core::compat::collections::BTreeMap; - - #[tokio::test] - async fn test_issue_credential() -> Result<()> { - let identities = identities(); - let creation = identities.identities_creation(); - - let issuer = creation.create_identity().await?; - let subject = creation.create_identity().await?; - - let _credentials_key = identities - .purpose_keys() - .create_purpose_key(issuer.identifier(), Purpose::Credentials) - .await?; - - let credentials = identities.credentials(); - - let mut map: BTreeMap, Vec> = Default::default(); - map.insert(b"key".to_vec(), b"value".to_vec()); - let subject_attributes = Attributes { - schema: SchemaId(1), - map, - }; - - let credential = credentials - .issue_credential( - issuer.identifier(), - subject.identifier(), - subject_attributes, - Duration::from_secs(60), - ) - .await?; - - let _res = credentials - .verify_credential( - subject.identifier(), - &[issuer.identifier().clone()], - &credential, - ) - .await?; - - Ok(()) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/credentials/credentials_issuer.rs b/implementations/rust/ockam/ockam_identity/src/v2/credentials/credentials_issuer.rs deleted file mode 100644 index 2ca196d96cb..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/credentials/credentials_issuer.rs +++ /dev/null @@ -1,159 +0,0 @@ -use core::time::Duration; -use minicbor::Decoder; -use tracing::trace; - -use ockam_core::api::{Method, Request, Response}; -use ockam_core::compat::boxed::Box; -use ockam_core::compat::string::String; -use ockam_core::compat::string::ToString; -use ockam_core::compat::sync::Arc; -use ockam_core::compat::vec::Vec; -use ockam_core::{api, Result, Route, Routed, Worker}; -use ockam_node::{Context, RpcClient}; - -use super::super::models::{Attributes, CredentialAndPurposeKey, Identifier, SchemaId}; -use super::super::utils::AttributesBuilder; -use super::super::{Credentials, IdentitiesRepository, IdentitySecureChannelLocalInfo}; - -/// Name of the attribute identifying the trust context for that attribute, meaning -/// from which set of trusted authorities the attribute comes from -pub const TRUST_CONTEXT_ID: &[u8] = b"trust_context_id"; - -/// Identifier for the schema of a project credential -pub const PROJECT_MEMBER_SCHEMA: SchemaId = SchemaId(1); - -/// Maximum duration for a valid credential in seconds (30 days) -pub const MAX_CREDENTIAL_VALIDITY: Duration = Duration::from_secs(30 * 24 * 3600); - -/// This struct runs as a Worker to issue credentials based on a request/response protocol -pub struct CredentialsIssuer { - identities_repository: Arc, - credentials: Arc, - issuer: Identifier, - subject_attributes: Attributes, -} - -impl CredentialsIssuer { - /// Create a new credentials issuer - pub async fn new( - identities_repository: Arc, - credentials: Arc, - issuer: &Identifier, - trust_context: String, - ) -> Result { - let subject_attributes = AttributesBuilder::with_schema(PROJECT_MEMBER_SCHEMA) - .with_attribute(TRUST_CONTEXT_ID.to_vec(), trust_context.as_bytes().to_vec()) - .build(); - - Ok(Self { - identities_repository, - credentials, - issuer: issuer.clone(), - subject_attributes, - }) - } - - async fn issue_credential( - &self, - subject: &Identifier, - ) -> Result> { - let entry = match self - .identities_repository - .as_attributes_reader() - .get_attributes(subject) - .await? - { - Some(entry) => entry, - None => return Ok(None), - }; - - let mut subject_attributes = self.subject_attributes.clone(); - for (key, value) in entry.attrs().iter() { - subject_attributes.map.insert(key.clone(), value.clone()); - } - - let credential = self - .credentials - .issue_credential( - subject, - &self.issuer, - subject_attributes, - MAX_CREDENTIAL_VALIDITY, - ) - .await?; - - Ok(Some(credential)) - } -} - -#[ockam_core::worker] -impl Worker for CredentialsIssuer { - type Context = Context; - type Message = Vec; - - async fn handle_message(&mut self, c: &mut Context, m: Routed) -> Result<()> { - if let Ok(i) = IdentitySecureChannelLocalInfo::find_info(m.local_message()) { - let from = i.their_identity_id(); - let mut dec = Decoder::new(m.as_body()); - let req: Request = dec.decode()?; - trace! { - target: "ockam_identity::credentials::credential_issuer", - from = %from, - id = %req.id(), - method = ?req.method(), - path = %req.path(), - body = %req.has_body(), - "request" - } - let res = match (req.method(), req.path()) { - (Some(Method::Post), "/") | (Some(Method::Post), "/credential") => { - match self.issue_credential(&from).await { - Ok(Some(crd)) => Response::ok(req.id()).body(crd).to_vec()?, - Ok(None) => { - // Again, this has already been checked by the access control, so if we - // reach this point there is an error actually. - api::forbidden(&req, "unauthorized member").to_vec()? - } - Err(error) => api::internal_error(&req, &error.to_string()).to_vec()?, - } - } - _ => api::unknown_path(&req).to_vec()?, - }; - c.send(m.return_route(), res).await - } else { - secure_channel_required(c, m).await - } - } -} - -/// Return a response on the return route stating that a secure channel is needed to access -/// the service -pub async fn secure_channel_required(c: &mut Context, m: Routed>) -> Result<()> { - // This was, actually, already checked by the access control. So if we reach this point - // it means there is a bug. Also, if it' already checked, we should receive the Peer' - // identity, not an Option to the peer' identity. - let mut dec = Decoder::new(m.as_body()); - let req: Request = dec.decode()?; - let res = api::forbidden(&req, "secure channel required").to_vec()?; - c.send(m.return_route(), res).await -} - -/// Client for a credentials issuer -pub struct CredentialsIssuerClient { - client: RpcClient, -} - -impl CredentialsIssuerClient { - /// Create a new credentials issuer client - /// The route needs to be a secure channel - pub async fn new(route: Route, ctx: &Context) -> Result { - Ok(CredentialsIssuerClient { - client: RpcClient::new(route, ctx).await?, - }) - } - - /// Return a credential for the identity which initiated the secure channel - pub async fn credential(&self) -> Result { - self.client.request(&Request::post("/")).await - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/credentials/credentials_retriever.rs b/implementations/rust/ockam/ockam_identity/src/v2/credentials/credentials_retriever.rs deleted file mode 100644 index 4433bd2fede..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/credentials/credentials_retriever.rs +++ /dev/null @@ -1,127 +0,0 @@ -use serde::{Deserialize, Serialize}; -use tracing::{debug, trace}; - -use ockam_core::compat::boxed::Box; -use ockam_core::compat::sync::Arc; -use ockam_core::{async_trait, route, Address, Result, Route}; -use ockam_node::Context; - -use super::super::models::{CredentialAndPurposeKey, Identifier}; -use super::super::CredentialsIssuerClient; -use super::super::{SecureChannelOptions, SecureChannels, TrustMultiIdentifiersPolicy}; - -/// Trait for retrieving a credential for a given identity -#[async_trait] -pub trait CredentialsRetriever: Send + Sync + 'static { - /// Retrieve a credential for an identity - async fn retrieve( - &self, - ctx: &Context, - for_identity: &Identifier, - ) -> Result; -} - -/// Credentials retriever that retrieves a credential from memory -pub struct CredentialsMemoryRetriever { - credential_and_purpose_key: CredentialAndPurposeKey, -} - -impl CredentialsMemoryRetriever { - /// Create a new CredentialsMemoryRetriever - pub fn new(credential_and_purpose_key: CredentialAndPurposeKey) -> Self { - Self { - credential_and_purpose_key, - } - } -} - -#[async_trait] -impl CredentialsRetriever for CredentialsMemoryRetriever { - /// Retrieve a credential stored in memory - async fn retrieve( - &self, - _ctx: &Context, - _for_identity: &Identifier, - ) -> Result { - Ok(self.credential_and_purpose_key.clone()) - } -} - -/// Credentials retriever for credentials located on a different node -pub struct RemoteCredentialsRetriever { - secure_channels: Arc, - issuer: RemoteCredentialsRetrieverInfo, -} - -impl RemoteCredentialsRetriever { - /// Create a new remote credential retriever - pub fn new( - secure_channels: Arc, - issuer: RemoteCredentialsRetrieverInfo, - ) -> Self { - Self { - secure_channels, - issuer, - } - } -} - -#[async_trait] -impl CredentialsRetriever for RemoteCredentialsRetriever { - async fn retrieve( - &self, - ctx: &Context, - for_identity: &Identifier, - ) -> Result { - debug!("Getting credential from : {}", &self.issuer.route); - let resolved_route = ctx - .resolve_transport_route(self.issuer.route.clone()) - .await?; - trace!( - "Getting credential from resolved route: {}", - resolved_route.clone() - ); - - let allowed = vec![self.issuer.identifier.clone()]; - debug!("Create secure channel to authority"); - - let options = SecureChannelOptions::new() - .with_trust_policy(TrustMultiIdentifiersPolicy::new(allowed)); - - let sc = self - .secure_channels - .create_secure_channel(ctx, for_identity, resolved_route.clone(), options) - .await?; - - debug!("Created secure channel to project authority"); - - let client = - CredentialsIssuerClient::new(route![sc, self.issuer.service_address.clone()], ctx) - .await?; - - let credential = client.credential().await?; - Ok(credential) - } -} - -/// Information necessary to connect to a remote credential retriever -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct RemoteCredentialsRetrieverInfo { - /// Issuer identity, used to validate retrieved credentials - pub identifier: Identifier, - /// Route used to establish a secure channel to the remote node - pub route: Route, - /// Address of the credentials service on the remote node - pub service_address: Address, -} - -impl RemoteCredentialsRetrieverInfo { - /// Create new information for a credential retriever - pub fn new(identifier: Identifier, route: Route, service_address: Address) -> Self { - Self { - identifier, - route, - service_address, - } - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/credentials/credentials_server.rs b/implementations/rust/ockam/ockam_identity/src/v2/credentials/credentials_server.rs deleted file mode 100644 index 4a4ae8de681..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/credentials/credentials_server.rs +++ /dev/null @@ -1,165 +0,0 @@ -use async_trait::async_trait; -use minicbor::Decoder; -use ockam_core::api::{Request, Response, Status}; -use ockam_core::compat::boxed::Box; -use ockam_core::compat::sync::Arc; -use ockam_core::errcode::{Kind, Origin}; -use ockam_core::{Address, Error, Result, Route}; -use ockam_node::api::{request, request_with_local_info}; -use ockam_node::{Context, WorkerBuilder}; - -use super::super::credentials::credentials_server_worker::CredentialsServerWorker; -use super::super::credentials::Credentials; -use super::super::models::{CredentialAndPurposeKey, Identifier}; -use super::super::{IdentitySecureChannelLocalInfo, TrustContext}; - -/// This trait allows an identity to send its credential to another identity -/// located at the end of a secure channel route -#[async_trait] -pub trait CredentialsServer: Send + Sync { - /// Present credential to other party, route shall use secure channel. Other party is expected - /// to present its credential in response, otherwise this call errors. - /// - async fn present_credential_mutual( - &self, - ctx: &Context, - route: Route, - authorities: &[Identifier], - credential: CredentialAndPurposeKey, - ) -> Result<()>; - - /// Present credential to other party, route shall use secure channel - async fn present_credential( - &self, - ctx: &Context, - route: Route, - credential: CredentialAndPurposeKey, - ) -> Result<()>; - - /// Start this service as a worker - async fn start( - &self, - ctx: &Context, - trust_context: TrustContext, - identifier: Identifier, - address: Address, - present_back: bool, - ) -> Result<()>; -} - -/// Implementation of the CredentialsService -pub struct CredentialsServerModule { - credentials: Arc, -} - -#[async_trait] -impl CredentialsServer for CredentialsServerModule { - /// Present credential to other party, route shall use secure channel. Other party is expected - /// to present its credential in response, otherwise this call errors. - async fn present_credential_mutual( - &self, - ctx: &Context, - route: Route, - authorities: &[Identifier], - credential: CredentialAndPurposeKey, - ) -> Result<()> { - let path = "actions/present_mutual"; - let (buf, local_info) = request_with_local_info( - ctx, - "credential", - None, - route, - Request::post(path).body(credential), - ) - .await?; - - let their_id = - IdentitySecureChannelLocalInfo::find_info_from_list(&local_info)?.their_identity_id(); - - let mut dec = Decoder::new(&buf); - let res: Response = dec.decode()?; - match res.status() { - Some(Status::Ok) => {} - Some(s) => { - return Err(Error::new( - Origin::Application, - Kind::Invalid, - format!("credential presentation failed: {}", s), - )); - } - _ => { - return Err(Error::new( - Origin::Application, - Kind::Invalid, - "credential presentation failed", - )); - } - } - - let credential_and_purpose_key: CredentialAndPurposeKey = dec.decode()?; - self.credentials - .receive_presented_credential(&their_id, authorities, &credential_and_purpose_key) - .await?; - - Ok(()) - } - - /// Present credential to other party, route shall use secure channel - async fn present_credential( - &self, - ctx: &Context, - route: Route, - credential: CredentialAndPurposeKey, - ) -> Result<()> { - let buf = request( - ctx, - "credential", - None, - route, - Request::post("actions/present").body(credential), - ) - .await?; - - let res: Response = minicbor::decode(&buf)?; - match res.status() { - Some(Status::Ok) => Ok(()), - _ => Err(Error::new( - Origin::Application, - Kind::Invalid, - "credential presentation failed", - )), - } - } - - /// Start worker that will be available to receive others attributes and put them into storage, - /// after successful verification - async fn start( - &self, - ctx: &Context, - trust_context: TrustContext, - identifier: Identifier, - address: Address, - present_back: bool, - ) -> Result<()> { - let worker = CredentialsServerWorker::new( - self.credentials.clone(), - trust_context, - identifier, - present_back, - ); - - WorkerBuilder::new(worker) - .with_address(address) - .start(ctx) - .await?; - - Ok(()) - } -} - -impl CredentialsServerModule { - /// Create a CredentialsService. It is simply backed by the Credentials interface - pub fn new(credentials: Arc) -> Self { - Self { credentials } - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/credentials/credentials_server_worker.rs b/implementations/rust/ockam/ockam_identity/src/v2/credentials/credentials_server_worker.rs deleted file mode 100644 index 7deeee29a30..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/credentials/credentials_server_worker.rs +++ /dev/null @@ -1,200 +0,0 @@ -use minicbor::Decoder; -use tracing::{debug, error, info, trace, warn}; - -use ockam_core::api::{Error, Id, Request, Response, ResponseBuilder, Status}; -use ockam_core::async_trait; -use ockam_core::compat::boxed::Box; -use ockam_core::compat::{string::ToString, sync::Arc, vec::Vec}; -use ockam_core::{Result, Routed, Worker}; -use ockam_node::Context; - -use super::super::credentials::Credentials; -use super::super::models::{CredentialAndPurposeKey, Identifier}; -use super::super::{IdentitySecureChannelLocalInfo, TrustContext}; - -const TARGET: &str = "ockam::credential_exchange_worker::service"; - -/// Worker responsible for receiving and verifying other party's credential -pub struct CredentialsServerWorker { - credentials: Arc, - trust_context: TrustContext, - identifier: Identifier, - present_back: bool, -} - -impl CredentialsServerWorker { - pub fn new( - credentials: Arc, - trust_context: TrustContext, - identifier: Identifier, - present_back: bool, - ) -> Self { - Self { - credentials, - trust_context, - identifier, - present_back, - } - } -} - -impl CredentialsServerWorker { - async fn handle_request( - &mut self, - ctx: &mut Context, - req: &Request, - sender: Identifier, - dec: &mut Decoder<'_>, - ) -> Result> { - trace! { - target: TARGET, - id = %req.id(), - method = ?req.method(), - path = %req.path(), - body = %req.has_body(), - "request" - } - - use ockam_core::api::Method::*; - let path = req.path(); - let path_segments = req.path_segments::<5>(); - let method = match req.method() { - Some(m) => m, - None => { - return Ok(Response::bad_request(req.id()) - .body("Invalid method") - .to_vec()?); - } - }; - - let r = match (method, path_segments.as_slice()) { - (Post, ["actions", "present"]) => { - debug!( - "Received one-way credential presentation request from {}", - sender - ); - let credential_and_purpose_key: CredentialAndPurposeKey = dec.decode()?; - - let res = self - .credentials - .receive_presented_credential( - &sender, - self.trust_context.authorities().await?.as_slice(), - &credential_and_purpose_key, - ) - .await; - - match res { - Ok(()) => { - debug!("One-way credential presentation request processed successfully with {}", sender); - Response::ok(req.id()).to_vec()? - } - Err(err) => { - debug!( - "One-way credential presentation request processing error: {} for {}", - err, sender - ); - Self::bad_request(req.id(), req.path(), &err.to_string()).to_vec()? - } - } - } - (Post, ["actions", "present_mutual"]) => { - debug!( - "Received mutual credential presentation request from {}", - sender - ); - let credential_and_purpose_key: CredentialAndPurposeKey = dec.decode()?; - - // FIXME info!("presented credential {}", credential); - let res = self - .credentials - .receive_presented_credential( - &sender, - self.trust_context.authorities().await?.as_slice(), - &credential_and_purpose_key, - ) - .await; - - if let Err(err) = res { - debug!( - "Mutual credential presentation request processing error: {} from {}", - err, sender - ); - Self::bad_request(req.id(), req.path(), &err.to_string()).to_vec()? - } else { - debug!( - "Mutual credential presentation request processed successfully with {}", - sender - ); - let credential = self - .trust_context - .authority()? - .credential(ctx, &self.identifier) - .await; - match credential.as_ref() { - Ok(p) if self.present_back => { - info!("Mutual credential presentation request processed successfully with {}. Responding with own credential...", sender); - Response::ok(req.id()).body(p).to_vec()? - } - _ => { - info!("Mutual credential presentation request processed successfully with {}. No credential to respond!", sender); - Response::ok(req.id()).to_vec()? - } - } - } - } - - // ==*== Catch-all for Unimplemented APIs ==*== - _ => { - warn!(%method, %path, "Called invalid endpoint"); - Response::bad_request(req.id()) - .body(format!("Invalid endpoint: {}", path)) - .to_vec()? - } - }; - Ok(r) - } - - /// Create a generic bad request response. - pub fn bad_request<'a>(id: Id, path: &'a str, msg: &'a str) -> ResponseBuilder { - let e = Error::new(path).with_message(msg); - Response::bad_request(id).body(e) - } -} - -#[async_trait] -impl Worker for CredentialsServerWorker { - type Message = Vec; - type Context = Context; - - async fn handle_message( - &mut self, - ctx: &mut Self::Context, - msg: Routed, - ) -> Result<()> { - let mut dec = Decoder::new(msg.as_body()); - let req: Request = match dec.decode() { - Ok(r) => r, - Err(e) => { - error!("failed to decode request: {:?}", e); - return Ok(()); - } - }; - - let sender = - IdentitySecureChannelLocalInfo::find_info(msg.local_message())?.their_identity_id(); - - let r = match self.handle_request(ctx, &req, sender, &mut dec).await { - Ok(r) => r, - // If an error occurs, send a response with the error code so the listener can - // fail fast instead of failing silently here and force the listener to timeout. - Err(err) => { - error!(?err, "Failed to handle message"); - Response::builder(req.id(), Status::InternalServerError) - .body(err.to_string()) - .to_vec()? - } - }; - ctx.send(msg.return_route(), r).await - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/credentials/mod.rs b/implementations/rust/ockam/ockam_identity/src/v2/credentials/mod.rs deleted file mode 100644 index dae3626cdd7..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/credentials/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -mod authority_service; -#[allow(clippy::module_inception)] -mod credentials; -mod credentials_issuer; -mod credentials_retriever; -mod credentials_server; -mod credentials_server_worker; -mod one_time_code; -mod trust_context; - -pub use authority_service::*; -pub use credentials::*; -pub use credentials_issuer::*; -pub use credentials_retriever::*; -pub use credentials_server::*; -pub use one_time_code::*; -pub use trust_context::*; diff --git a/implementations/rust/ockam/ockam_identity/src/v2/credentials/one_time_code.rs b/implementations/rust/ockam/ockam_identity/src/v2/credentials/one_time_code.rs deleted file mode 100644 index a374c3a9cf1..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/credentials/one_time_code.rs +++ /dev/null @@ -1,134 +0,0 @@ -use core::str::FromStr; -use minicbor::bytes::ByteArray; -use minicbor::{Decode, Encode}; -use ockam_core::compat::rand; -use ockam_core::compat::rand::RngCore; -use ockam_core::compat::string::{String, ToString}; -use ockam_core::errcode::{Kind, Origin}; -use ockam_core::Error; -use ockam_core::Result; -use serde::{Deserialize, Serialize}; - -/// A one-time code can be used to enroll -/// a node with some authenticated attributes -/// It can be retrieve with a command like `ockam project ticket --attribute component=control` -#[derive(Debug, Clone, Decode, Encode, PartialEq, Eq)] -#[rustfmt::skip] -#[cbor(map)] -pub struct OneTimeCode { - #[cfg(feature = "tag")] - #[n(0)] tag: TypeTag<5112299>, - #[n(1)] code: ByteArray<32>, -} - -impl OneTimeCode { - /// Create a random token - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - let mut code = [0; 32]; - rand::thread_rng().fill_bytes(&mut code); - OneTimeCode::from(code) - } - - /// Return the code as a byte slice - pub fn code(&self) -> &[u8; 32] { - &self.code - } -} - -impl From<[u8; 32]> for OneTimeCode { - /// Create a OneTimeCode from a byte slice - fn from(code: [u8; 32]) -> Self { - OneTimeCode { - #[cfg(feature = "tag")] - tag: TypeTag, - code: code.into(), - } - } -} - -impl FromStr for OneTimeCode { - type Err = Error; - - /// Create a OneTimeCode from a string slice - /// The code is expected to be encoded as hexadecimal - fn from_str(s: &str) -> Result { - let bytes = hex::decode(s).map_err(|e| error(format!("{e}")))?; - let code: OneTimeCode = OneTimeCode::from( - <[u8; 32]>::try_from(bytes.as_slice()).map_err(|e| error(format!("{e}")))?, - ); - Ok(code) - } -} - -impl ToString for OneTimeCode { - /// Return the OneTimeCode as a String - /// It is encoded as hexadecimal - fn to_string(&self) -> String { - hex::encode(self.code()) - } -} - -impl Serialize for OneTimeCode { - fn serialize(&self, serializer: S) -> core::result::Result - where - S: serde::Serializer, - { - serializer.serialize_str(self.to_string().as_str()) - } -} - -impl<'de> Deserialize<'de> for OneTimeCode { - fn deserialize(deserializer: D) -> core::result::Result - where - D: serde::Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - OneTimeCode::from_str(s.as_str()).map_err(serde::de::Error::custom) - } -} - -/// Create an Identity Error -fn error(message: String) -> Error { - Error::new(Origin::Identity, Kind::Invalid, message.as_str()) -} - -#[cfg(test)] -mod test { - use super::*; - use quickcheck::{Arbitrary, Gen}; - use quickcheck_macros::quickcheck; - - #[quickcheck] - fn test_from_to_string(one_time_code: OneTimeCode) -> bool { - OneTimeCode::from_str(one_time_code.to_string().as_str()).ok() == Some(one_time_code) - } - - impl Arbitrary for OneTimeCode { - fn arbitrary(g: &mut Gen) -> Self { - OneTimeCode::from(Bytes32::arbitrary(g).bytes) - } - } - - /// Newtype to generate an arbitrary array of 32 bytes - /// This can be refactored into a ockam_quickcheck crate if we accumulate - /// more useful arbitraries which can be shared by several crates - #[derive(Clone)] - struct Bytes32 { - bytes: [u8; 32], - } - - impl Arbitrary for Bytes32 { - fn arbitrary(g: &mut Gen) -> Bytes32 { - let init: [u8; 32] = <[u8; 32]>::default(); - Bytes32 { - bytes: init.map(|_| ::arbitrary(g)), - } - } - - /// there is no meaningful shrinking in general for a random array of bytes - fn shrink(&self) -> Box> { - Box::new(std::iter::empty()) - } - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/credentials/trust_context.rs b/implementations/rust/ockam/ockam_identity/src/v2/credentials/trust_context.rs deleted file mode 100644 index 808701dffa7..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/credentials/trust_context.rs +++ /dev/null @@ -1,40 +0,0 @@ -use ockam_core::compat::string::String; -use ockam_core::compat::vec::Vec; -use ockam_core::Result; - -use super::super::models::Identifier; -use super::super::{AuthorityService, IdentityError}; - -/// A trust context defines which authorities are trusted to attest to which attributes, within a context. -/// Our first implementation assumes that there is only one authority and it is trusted to attest to all attributes within this context. -#[derive(Clone)] -pub struct TrustContext { - /// This is the ID of the trust context; which is primarily used for ABAC policies - id: String, - /// Authority capable of retrieving credentials - authority: Option, -} - -impl TrustContext { - /// Create a new Trust Context - pub fn new(id: String, authority: Option) -> Self { - Self { id, authority } - } - - /// Return the ID of the Trust Context - pub fn id(&self) -> &str { - &self.id - } - - /// Return the Authority of the Trust Context - pub fn authority(&self) -> Result<&AuthorityService> { - self.authority - .as_ref() - .ok_or_else(|| IdentityError::UnknownAuthority.into()) - } - - /// Return the authority identities attached to this trust context - pub async fn authorities(&self) -> Result> { - Ok(vec![self.authority()?.identifier().clone()]) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/error.rs b/implementations/rust/ockam/ockam_identity/src/v2/error.rs deleted file mode 100644 index d48c9afb934..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/error.rs +++ /dev/null @@ -1,61 +0,0 @@ -use ockam_core::{ - errcode::{Kind, Origin}, - Error, -}; - -/// Identity crate error -#[derive(Clone, Copy, Debug)] -pub enum IdentityError { - /// Invalid key type - InvalidKeyType = 1, - /// Invalid Identifier format - InvalidIdentifier, - /// Identity Change History is empty - EmptyIdentity, - /// Identity Verification Failed - IdentityVerificationFailed, - /// PurposeKeyAttestation Verification Failed - PurposeKeyAttestationVerificationFailed, - /// Credential Verification Failed - CredentialVerificationFailed, - /// Error occurred while getting current UTC Timestamp - UnknownTimestamp, - /// Unknown Authority - UnknownAuthority, - /// Unknown version of the Credential - UnknownCredentialVersion, - /// Unknown version of the Identity - UnknownIdentityVersion, - /// SecureChannelVerificationFailed - SecureChannelVerificationFailed, - /// SecureChannelTrustCheckFailed - SecureChannelTrustCheckFailed, - /// Invalid Nonce value - InvalidNonce, - /// Nonce overflow - NonceOverflow, - /// Unknown message destination - UnknownChannelMsgDestination, - /// Invalid LocalInfo type - InvalidLocalInfoType, - /// Duplicate Secure Channel - DuplicateSecureChannel, - /// Consistency Error - ConsistencyError, -} - -impl ockam_core::compat::error::Error for IdentityError {} -impl core::fmt::Display for IdentityError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - core::fmt::Debug::fmt(self, f) - } -} - -impl From for Error { - #[track_caller] - fn from(err: IdentityError) -> Self { - let kind = Kind::Unknown; // FIXME: fill these in with more - // meaningful error kinds - Error::new(Origin::Identity, kind, err) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/identities/identities.rs b/implementations/rust/ockam/ockam_identity/src/v2/identities/identities.rs deleted file mode 100644 index 60c6af9d7de..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/identities/identities.rs +++ /dev/null @@ -1,95 +0,0 @@ -use super::super::identities::{IdentitiesKeys, IdentitiesRepository, IdentitiesVault}; -use super::super::purpose_keys::storage::{PurposeKeysRepository, PurposeKeysStorage}; -use super::super::{ - Credentials, CredentialsServer, CredentialsServerModule, IdentitiesBuilder, IdentitiesCreation, - IdentitiesReader, IdentitiesStorage, PurposeKeys, -}; - -use ockam_core::compat::sync::Arc; -use ockam_vault::Vault; - -/// This struct supports all the services related to identities -#[derive(Clone)] -pub struct Identities { - vault: Arc, - identities_repository: Arc, - purpose_keys_repository: Arc, -} - -impl Identities { - /// Return the identities vault - pub fn vault(&self) -> Arc { - self.vault.clone() - } - - /// Return the identities repository - pub fn repository(&self) -> Arc { - self.identities_repository.clone() - } - - /// Return the [`PurposeKeys`] instance - pub fn purpose_keys(&self) -> Arc { - Arc::new(PurposeKeys::new( - self.vault.clone(), - self.identities_repository.as_identities_reader(), - self.identities_keys(), - self.purpose_keys_repository.clone(), - )) - } - - /// Return the identities keys management service - pub fn identities_keys(&self) -> Arc { - Arc::new(IdentitiesKeys::new(self.vault.clone())) - } - - /// Return the identities creation service - pub fn identities_creation(&self) -> Arc { - Arc::new(IdentitiesCreation::new( - self.repository(), - self.vault.clone(), - )) - } - - /// Return the identities reader - pub fn identities_reader(&self) -> Arc { - self.repository().as_identities_reader() - } - - /// Return the identities credentials service - pub fn credentials(&self) -> Arc { - Arc::new(Credentials::new( - self.vault(), - self.purpose_keys(), - self.identities_repository.clone(), - )) - } - - /// Return the identities credentials server - pub fn credentials_server(&self) -> Arc { - Arc::new(CredentialsServerModule::new(self.credentials())) - } -} - -impl Identities { - /// Create a new identities module - pub(crate) fn new( - vault: Arc, - identities_repository: Arc, - purpose_keys_repository: Arc, - ) -> Identities { - Identities { - vault, - identities_repository, - purpose_keys_repository, - } - } - - /// Return a default builder for identities - pub fn builder() -> IdentitiesBuilder { - IdentitiesBuilder { - vault: Vault::create(), - repository: IdentitiesStorage::create(), - purpose_keys_repository: PurposeKeysStorage::create(), - } - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/identities/identities_builder.rs b/implementations/rust/ockam/ockam_identity/src/v2/identities/identities_builder.rs deleted file mode 100644 index c35c018f822..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/identities/identities_builder.rs +++ /dev/null @@ -1,70 +0,0 @@ -use super::super::identities::{ - Identities, IdentitiesRepository, IdentitiesStorage, IdentitiesVault, -}; -use super::super::purpose_keys::storage::PurposeKeysRepository; -use super::super::storage::Storage; - -use ockam_core::compat::sync::Arc; -use ockam_vault::{Vault, VaultStorage}; - -/// Builder for Identities services -#[derive(Clone)] -pub struct IdentitiesBuilder { - pub(crate) vault: Arc, - pub(crate) repository: Arc, - pub(crate) purpose_keys_repository: Arc, -} - -/// Return a default identities -pub fn identities() -> Arc { - Identities::builder().build() -} - -impl IdentitiesBuilder { - /// Set a specific storage for the identities vault - pub fn with_vault_storage(&mut self, storage: VaultStorage) -> IdentitiesBuilder { - self.with_identities_vault(Vault::create_with_persistent_storage(storage)) - } - - /// Set a specific identities vault - pub fn with_identities_vault(&mut self, vault: Arc) -> IdentitiesBuilder { - self.vault = vault; - self.clone() - } - - /// Set a specific storage for identities - pub fn with_identities_storage(&mut self, storage: Arc) -> IdentitiesBuilder { - self.with_identities_repository(Arc::new(IdentitiesStorage::new(storage))) - } - - /// Set a specific repository - pub fn with_identities_repository( - &mut self, - repository: Arc, - ) -> IdentitiesBuilder { - self.repository = repository; - self.clone() - } - - fn vault(&self) -> Arc { - self.vault.clone() - } - - fn repository(&self) -> Arc { - self.repository.clone() - } - - // TODO: Extend possibilities to create that repository - fn purpose_keys_repository(&self) -> Arc { - self.purpose_keys_repository.clone() - } - - /// Build identities - pub fn build(&self) -> Arc { - Arc::new(Identities::new( - self.vault(), - self.repository(), - self.purpose_keys_repository(), - )) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/identities/identities_creation.rs b/implementations/rust/ockam/ockam_identity/src/v2/identities/identities_creation.rs deleted file mode 100644 index d03108ea555..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/identities/identities_creation.rs +++ /dev/null @@ -1,102 +0,0 @@ -use ockam_core::compat::sync::Arc; -use ockam_core::Result; -use ockam_vault::KeyId; - -use super::super::models::Identifier; -use super::super::{IdentitiesKeys, IdentitiesRepository, IdentitiesVault, Identity}; - -/// This struct supports functions for the creation and import of identities using an IdentityVault -pub struct IdentitiesCreation { - repository: Arc, - vault: Arc, -} - -impl IdentitiesCreation { - /// Create a new identities import module - pub fn new( - repository: Arc, - vault: Arc, - ) -> IdentitiesCreation { - IdentitiesCreation { repository, vault } - } - - /// Import and verify identity from its binary format - pub async fn import( - &self, - expected_identifier: Option<&Identifier>, - data: &[u8], - ) -> Result { - Identity::import(expected_identifier, data, self.vault.clone()).await - } - - /// Create an Identity - pub async fn create_identity(&self) -> Result { - // TODO: Consider creating PurposeKeys by default - self.make_and_persist_identity(None).await - } -} - -impl IdentitiesCreation { - /// Make a new identity with its key and attributes - /// and persist it - async fn make_and_persist_identity(&self, key_id: Option<&KeyId>) -> Result { - let identity_keys = IdentitiesKeys::new(self.vault.clone()); - let identity = identity_keys.create_initial_key(key_id).await?; - self.repository - .update_identity(identity.identifier(), identity.change_history()) - .await?; - Ok(identity) - } -} - -#[cfg(test)] -mod tests { - use super::super::super::models::Identifier; - use super::super::identities; - use super::*; - use core::str::FromStr; - - #[tokio::test] - async fn test_identity_creation() -> Result<()> { - let identities = identities(); - let creation = identities.identities_creation(); - let repository = identities.repository(); - let keys = identities.identities_keys(); - - let identity = creation.create_identity().await?; - let actual = repository.get_identity(identity.identifier()).await?; - - let actual = Identity::import_from_change_history( - Some(identity.identifier()), - actual, - identities.vault(), - ) - .await?; - assert_eq!( - actual, identity, - "the identity can be retrieved from the repository" - ); - - let actual = repository.retrieve_identity(identity.identifier()).await?; - assert!(actual.is_some()); - let actual = Identity::import_from_change_history( - Some(identity.identifier()), - actual.unwrap(), - identities.vault(), - ) - .await?; - assert_eq!( - actual, identity, - "the identity can be retrieved from the repository as an Option" - ); - - let another_identifier = Identifier::from_str("Ie92f183eb4c324804ef4d62962dea94cf095a265")?; - let missing = repository.retrieve_identity(&another_identifier).await?; - assert_eq!(missing, None, "a missing identity returns None"); - - let root_key = keys.get_secret_key(&identity).await; - assert!(root_key.is_ok(), "there is a key for the created identity"); - - Ok(()) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/identities/identities_vault.rs b/implementations/rust/ockam/ockam_identity/src/v2/identities/identities_vault.rs deleted file mode 100644 index 7b2aebc0c3d..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/identities/identities_vault.rs +++ /dev/null @@ -1,131 +0,0 @@ -use ockam_core::compat::boxed::Box; -use ockam_core::compat::sync::Arc; -use ockam_core::compat::vec::Vec; -use ockam_core::{async_trait, Result}; -use ockam_vault::{ - AsymmetricVault, EphemeralSecretsStore, Implementation, KeyId, PersistentSecretsStore, - SecretsStore, SecretsStoreReader, SymmetricVault, -}; -use ockam_vault::{PublicKey, Secret, SecretAttributes}; -use ockam_vault::{Signature, Signer, StoredSecret}; - -/// Traits required for a Vault implementation suitable for use in an Identity -/// Vault with XX required functionality -pub trait IdentitiesVault: XXVault + PersistentSecretsStore + Signer {} - -impl IdentitiesVault for D where D: XXVault + PersistentSecretsStore + Signer {} - -/// Vault with XX required functionality -pub trait XXVault: SecretsStore + AsymmetricVault + SymmetricVault + Send + Sync + 'static {} - -impl XXVault for D where - D: SecretsStore + AsymmetricVault + SymmetricVault + Send + Sync + 'static -{ -} - -/// Vault with required functionalities after XX key exchange -pub trait XXInitializedVault: SecretsStore + SymmetricVault + Send + Sync + 'static {} - -impl XXInitializedVault for D where D: SecretsStore + SymmetricVault + Send + Sync + 'static {} - -/// This struct is used to compensate for the lack of non-experimental trait upcasting in Rust -/// We encapsulate an IdentitiesVault and delegate the implementation of all the functions of -/// the various traits inherited by IdentitiesVault: SymmetricVault, SecretVault, etc... -struct CoercedIdentitiesVault { - vault: Arc, -} - -impl Implementation for CoercedIdentitiesVault {} - -#[async_trait] -impl EphemeralSecretsStore for CoercedIdentitiesVault { - async fn create_ephemeral_secret(&self, attributes: SecretAttributes) -> Result { - self.vault.create_ephemeral_secret(attributes).await - } - - async fn import_ephemeral_secret( - &self, - secret: Secret, - attributes: SecretAttributes, - ) -> Result { - self.vault.import_ephemeral_secret(secret, attributes).await - } - - async fn get_ephemeral_secret( - &self, - key_id: &KeyId, - description: &str, - ) -> Result { - self.vault.get_ephemeral_secret(key_id, description).await - } - - async fn delete_ephemeral_secret(&self, key_id: KeyId) -> Result { - self.vault.delete_ephemeral_secret(key_id).await - } - - async fn list_ephemeral_secrets(&self) -> Result> { - self.vault.list_ephemeral_secrets().await - } -} - -#[async_trait] -impl PersistentSecretsStore for CoercedIdentitiesVault { - async fn create_persistent_secret(&self, attributes: SecretAttributes) -> Result { - self.vault.create_persistent_secret(attributes).await - } - - async fn delete_persistent_secret(&self, key_id: KeyId) -> Result { - self.vault.delete_persistent_secret(key_id).await - } -} - -#[async_trait] -impl SecretsStoreReader for CoercedIdentitiesVault { - async fn get_secret_attributes(&self, key_id: &KeyId) -> Result { - self.vault.get_secret_attributes(key_id).await - } - - async fn get_public_key(&self, key_id: &KeyId) -> Result { - self.vault.get_public_key(key_id).await - } - - async fn get_key_id(&self, public_key: &PublicKey) -> Result { - self.vault.get_key_id(public_key).await - } -} - -#[async_trait] -impl Signer for CoercedIdentitiesVault { - async fn sign(&self, key_id: &KeyId, data: &[u8]) -> Result { - self.vault.sign(key_id, data).await - } - async fn verify( - &self, - public_key: &PublicKey, - data: &[u8], - signature: &Signature, - ) -> Result { - self.vault.verify(public_key, data, signature).await - } -} - -/// Return this vault as a symmetric vault -pub fn to_symmetric_vault(vault: Arc) -> Arc { - Arc::new(CoercedIdentitiesVault { - vault: vault.clone(), - }) -} - -/// Return this vault as a XX vault -pub fn to_xx_vault(vault: Arc) -> Arc { - Arc::new(CoercedIdentitiesVault { - vault: vault.clone(), - }) -} - -/// Returns this vault as a XX initialized vault -pub fn to_xx_initialized(vault: Arc) -> Arc { - Arc::new(CoercedIdentitiesVault { - vault: vault.clone(), - }) -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/identities/identity_keys.rs b/implementations/rust/ockam/ockam_identity/src/v2/identities/identity_keys.rs deleted file mode 100644 index d4ec224e153..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/identities/identity_keys.rs +++ /dev/null @@ -1,207 +0,0 @@ -use super::super::identities::IdentitiesVault; -use super::super::identity::Identity; -use super::super::models::{ - Change, ChangeData, ChangeHash, ChangeHistory, ChangeSignature, Ed25519PublicKey, - Ed25519Signature, PrimaryPublicKey, VersionedData, -}; -use super::super::utils::{add_seconds, now}; -use super::super::IdentityError; - -use ockam_core::compat::sync::Arc; -use ockam_core::Result; -use ockam_vault::{KeyId, SecretAttributes, Vault}; - -/// This module supports the key operations related to identities -pub struct IdentitiesKeys { - vault: Arc, -} - -impl IdentitiesKeys { - pub(crate) async fn create_initial_key(&self, key_id: Option<&KeyId>) -> Result { - let change = self.make_change(key_id, None).await?; - let change_history = ChangeHistory(vec![change]); - - let identity = - Identity::import_from_change_history(None, change_history, self.vault.clone()).await?; - - Ok(identity) - } -} - -/// Public functions -impl IdentitiesKeys { - /// Create a new identities keys module - pub fn new(vault: Arc) -> Self { - Self { vault } - } - - /// Rotate an existing key with a given label - pub async fn rotate_key(&self, identity: Identity) -> Result { - let last_change = match identity.changes().last() { - Some(last_change) => last_change, - None => return Err(IdentityError::EmptyIdentity.into()), - }; - - // TODO: Delete the previous key from the Vault - let last_secret_key = self.get_secret_key(&identity).await?; - - let change = self - .make_change( - None, - Some((last_change.change_hash().clone(), last_secret_key)), - ) - .await?; - - identity.add_change(change, self.vault.clone()).await - } - - /// Return the secret key of an identity - pub async fn get_secret_key(&self, identity: &Identity) -> Result { - if let Some(last_change) = identity.changes().last() { - self.vault - .get_key_id(last_change.primary_public_key()) - .await - } else { - Err(IdentityError::EmptyIdentity.into()) - } - } -} - -/// Private functions -impl IdentitiesKeys { - /// Create a new key - async fn make_change( - &self, - secret: Option<&KeyId>, - previous: Option<(ChangeHash, KeyId)>, - ) -> Result { - let secret_key = self.generate_key_if_needed(secret).await?; - let public_key = self.vault.get_public_key(&secret_key).await?; - - let public_key = Ed25519PublicKey(public_key.data().try_into().unwrap()); // FIXME - - let created_at = now()?; - let ten_years = 10 * 365 * 24 * 60 * 60; // TODO: Allow to customize - let expires_at = add_seconds(&created_at, ten_years); - - let change_data = ChangeData { - previous_change: previous.as_ref().map(|x| x.0.clone()), - primary_public_key: PrimaryPublicKey::Ed25519PublicKey(public_key), - revoke_all_purpose_keys: false, // TODO: Allow to choose - created_at, - expires_at, - }; - - let change_data = minicbor::to_vec(&change_data)?; - - let versioned_data = VersionedData { - version: 1, - data: change_data, - }; - - let versioned_data = minicbor::to_vec(&versioned_data)?; - - let hash = Vault::sha256(&versioned_data); - - let self_signature = self.vault.sign(&secret_key, hash.as_ref()).await?; - let self_signature = Ed25519Signature(self_signature.as_ref().try_into().unwrap()); // FIXME - let self_signature = ChangeSignature::Ed25519Signature(self_signature); - - // If we have previous_key passed we should sign using it - // If there is no previous_key - we're creating new identity, so we just generated the key - let previous_signature = match previous.map(|x| x.1) { - Some(previous_key) => { - let previous_signature = self.vault.sign(&previous_key, hash.as_ref()).await?; - let previous_signature = - Ed25519Signature(previous_signature.as_ref().try_into().unwrap()); // FIXME - let previous_signature = ChangeSignature::Ed25519Signature(previous_signature); - - Some(previous_signature) - } - None => None, - }; - - let change = Change { - data: versioned_data, - signature: self_signature, - previous_signature, - }; - - Ok(change) - } - - async fn generate_key_if_needed(&self, secret: Option<&KeyId>) -> Result { - if let Some(s) = secret { - Ok(s.clone()) - } else { - self.vault - .create_persistent_secret(SecretAttributes::Ed25519 /* FIXME */) - .await - } - } -} - -#[cfg(test)] -mod test { - use super::super::super::models::Identifier; - use super::super::identities; - use super::*; - use core::str::FromStr; - use ockam_core::errcode::{Kind, Origin}; - use ockam_core::Error; - use ockam_node::Context; - - fn test_error>(error: S) -> Result<()> { - Err(Error::new_without_cause(Origin::Identity, Kind::Unknown).context("msg", error.into())) - } - - #[ockam_macros::test] - async fn test_basic_identity_key_ops(ctx: &mut Context) -> Result<()> { - let identities = identities(); - let identity_keys = identities.identities_keys(); - let identity = identities.identities_creation().create_identity().await?; - - // Identifier should not match - let res = Identity::import_from_change_history( - Some(&Identifier::from_str("Iabababababababababababababababababababab").unwrap()), - identity.change_history().clone(), - identities.vault(), - ) - .await; - assert!(res.is_err()); - - // Check if verification succeeds - let _ = Identity::import_from_change_history( - Some(identity.identifier()), - identity.change_history().clone(), - identities.vault(), - ) - .await?; - - let secret1 = identity_keys.get_secret_key(&identity).await?; - let public1 = identity.get_public_key()?; - - let identity = identity_keys.rotate_key(identity).await?; - - // Check if verification succeeds - let _ = Identity::import_from_change_history( - Some(identity.identifier()), - identity.change_history().clone(), - identities.vault(), - ) - .await?; - - let secret2 = identity_keys.get_secret_key(&identity).await?; - let public2 = identity.get_public_key()?; - - if secret1 == secret2 { - return test_error("secret did not change after rotate_key"); - } - - if public1 == public2 { - return test_error("public did not change after rotate_key"); - } - - ctx.stop().await - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/identities/mod.rs b/implementations/rust/ockam/ockam_identity/src/v2/identities/mod.rs deleted file mode 100644 index 557f38ccf8e..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/identities/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -#[allow(clippy::module_inception)] -mod identities; -mod identities_builder; -mod identities_creation; -mod identities_vault; -mod identity_keys; - -/// Identities storage functions -pub mod storage; - -pub use identities::*; -pub use identities_builder::*; -pub use identities_creation::*; -pub use identities_vault::*; -pub use identity_keys::*; -pub use storage::*; - -#[cfg(test)] -mod tests; diff --git a/implementations/rust/ockam/ockam_identity/src/v2/identities/storage/attributes_entry.rs b/implementations/rust/ockam/ockam_identity/src/v2/identities/storage/attributes_entry.rs deleted file mode 100644 index 7e08437abc6..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/identities/storage/attributes_entry.rs +++ /dev/null @@ -1,57 +0,0 @@ -use super::super::super::models::{Identifier, TimestampInSeconds}; -use minicbor::{Decode, Encode}; -use ockam_core::compat::borrow::ToOwned; -use ockam_core::compat::{collections::BTreeMap, vec::Vec}; -use serde::{Deserialize, Serialize}; - -/// An entry on the AuthenticatedIdentities table. -#[derive(Debug, Clone, Encode, Decode, PartialEq, Eq, Serialize, Deserialize)] -#[rustfmt::skip] -#[cbor(map)] -pub struct AttributesEntry { - #[b(1)] attrs: BTreeMap, Vec>, - #[n(2)] added: TimestampInSeconds, - #[n(3)] expires: Option, - #[n(4)] attested_by: Option, -} - -impl AttributesEntry { - //TODO: since we are converting from HashMap to BTreeMap in different parts, - // it will make sense to have a constructor here taking a HashMap and doing - // the conversion here. Better: standardize on either of the above for attributes. - - /// Constructor - pub fn new( - attrs: BTreeMap, Vec>, - added: TimestampInSeconds, - expires: Option, - attested_by: Option, - ) -> Self { - Self { - attrs, - added, - expires, - attested_by, - } - } - - /// The entry attributes - pub fn attrs(&self) -> &BTreeMap, Vec> { - &self.attrs - } - - /// Expiration time for this entry - pub fn expires(&self) -> Option { - self.expires - } - - /// Date that the entry was added - pub fn added(&self) -> TimestampInSeconds { - self.added - } - - /// Who attested this attributes for this identity identifier - pub fn attested_by(&self) -> Option { - self.attested_by.to_owned() - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/identities/storage/mod.rs b/implementations/rust/ockam/ockam_identity/src/v2/identities/storage/mod.rs deleted file mode 100644 index 5ac04e522a0..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/identities/storage/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod attributes_entry; -mod identities_repository_impl; -mod identities_repository_trait; - -pub use attributes_entry::*; -pub use identities_repository_impl::*; -pub use identities_repository_trait::*; diff --git a/implementations/rust/ockam/ockam_identity/src/v2/identities/tests.rs b/implementations/rust/ockam/ockam_identity/src/v2/identities/tests.rs deleted file mode 100644 index 828112c0dba..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/identities/tests.rs +++ /dev/null @@ -1,201 +0,0 @@ -use super::super::identity::Identity; -use super::super::models::ChangeHistory; -use super::super::Identities; -use ockam_core::async_trait; -use ockam_core::compat::sync::Arc; -use ockam_core::Result; -use ockam_node::Context; -use ockam_vault::{ - EphemeralSecretsStore, Implementation, KeyId, PersistentSecretsStore, PublicKey, Secret, - SecretAttributes, SecretsStoreReader, Signature, Signer, -}; -use ockam_vault::{StoredSecret, Vault}; -use rand::{thread_rng, Rng}; -use std::sync::atomic::{AtomicBool, Ordering}; - -#[ockam_macros::test] -async fn test_invalid_signature(ctx: &mut Context) -> Result<()> { - for _ in 0..100 { - let crazy_vault = Arc::new(CrazyVault::new(0.1, Vault::new())); - let identities = Identities::builder() - .with_identities_vault(crazy_vault.clone()) - .build(); - let mut identity = identities.identities_creation().create_identity().await?; - let res = check_identity(&identity).await; - - if crazy_vault.forged_operation_occurred() { - assert!(res.is_err()); - break; - } else { - assert!(res.is_ok()) - } - - loop { - identity = identities.identities_keys().rotate_key(identity).await?; - - let res = check_identity(&identity).await; - if crazy_vault.forged_operation_occurred() { - assert!(res.is_err()); - break; - } else { - assert!(res.is_ok()) - } - } - } - - ctx.stop().await?; - - Ok(()) -} - -/// This function simulates an identity import to check its history -async fn check_identity(identity: &Identity) -> Result { - Identity::import( - Some(identity.identifier()), - &identity.export()?, - Vault::create(), - ) - .await -} - -#[ockam_macros::test] -async fn test_eject_signatures(ctx: &mut Context) -> Result<()> { - for _ in 0..10 { - let identities = Identities::builder().build(); - let mut identity = identities.identities_creation().create_identity().await?; - - let j: i32 = thread_rng().gen_range(1..10); - for _ in 0..j { - identity = identities.identities_keys().rotate_key(identity).await?; - } - - let res = check_identity(&identity).await; - assert!(res.is_ok()); - - let change_history = eject_random_signature(&identity)?; - let res = - Identity::import_from_change_history(None, change_history, identities.vault()).await; - assert!(res.is_err()); - } - - ctx.stop().await?; - - Ok(()) -} - -pub fn eject_random_signature(identity: &Identity) -> Result { - let mut history = identity.change_history().clone(); - - let i = thread_rng().gen_range(1..history.0.len()); - let change = &mut history.0[i]; - change.previous_signature = None; - - Ok(history) -} - -#[derive(Clone)] -struct CrazyVault { - prob_to_produce_invalid_signature: f32, - forged_operation_occurred: Arc, - vault: Vault, -} - -impl Implementation for CrazyVault {} - -impl CrazyVault { - pub fn forged_operation_occurred(&self) -> bool { - self.forged_operation_occurred.load(Ordering::Relaxed) - } -} - -impl CrazyVault { - pub fn new(prob_to_produce_invalid_signature: f32, vault: Vault) -> Self { - Self { - prob_to_produce_invalid_signature, - forged_operation_occurred: Arc::new(false.into()), - vault, - } - } -} - -#[async_trait] -impl EphemeralSecretsStore for CrazyVault { - async fn create_ephemeral_secret(&self, attributes: SecretAttributes) -> Result { - self.vault.create_ephemeral_secret(attributes).await - } - - async fn import_ephemeral_secret( - &self, - secret: Secret, - attributes: SecretAttributes, - ) -> Result { - self.vault.import_ephemeral_secret(secret, attributes).await - } - - async fn get_ephemeral_secret( - &self, - key_id: &KeyId, - description: &str, - ) -> Result { - self.vault.get_ephemeral_secret(key_id, description).await - } - - async fn delete_ephemeral_secret(&self, key_id: KeyId) -> Result { - self.vault.delete_ephemeral_secret(key_id).await - } - - async fn list_ephemeral_secrets(&self) -> Result> { - self.vault.list_ephemeral_secrets().await - } -} - -#[async_trait] -impl PersistentSecretsStore for CrazyVault { - async fn create_persistent_secret(&self, attributes: SecretAttributes) -> Result { - self.vault.create_persistent_secret(attributes).await - } - - async fn delete_persistent_secret(&self, key_id: KeyId) -> Result { - self.vault.delete_persistent_secret(key_id).await - } -} - -#[async_trait] -impl SecretsStoreReader for CrazyVault { - async fn get_secret_attributes(&self, key_id: &KeyId) -> Result { - self.vault.get_secret_attributes(key_id).await - } - - async fn get_public_key(&self, key_id: &KeyId) -> Result { - self.vault.get_public_key(key_id).await - } - async fn get_key_id(&self, public_key: &PublicKey) -> Result { - self.vault.get_key_id(public_key).await - } -} - -#[async_trait] -impl Signer for CrazyVault { - async fn sign(&self, key_id: &KeyId, data: &[u8]) -> Result { - let mut signature = self.vault.sign(key_id, data).await?; - if thread_rng().gen_range(0.0..1.0) <= self.prob_to_produce_invalid_signature { - self.forged_operation_occurred - .store(true, Ordering::Relaxed); - signature = Signature::new(vec![0; signature.as_ref().len()]); - } - - Ok(signature) - } - async fn verify( - &self, - public_key: &PublicKey, - data: &[u8], - signature: &Signature, - ) -> Result { - if signature.as_ref().iter().all(|&x| x == 0) { - return Ok(true); - } - - self.vault.verify(public_key, data, signature).await - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/identity/identity.rs b/implementations/rust/ockam/ockam_identity/src/v2/identity/identity.rs deleted file mode 100644 index 1f0a09397e5..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/identity/identity.rs +++ /dev/null @@ -1,235 +0,0 @@ -use super::super::models::{Change, ChangeHash, ChangeHistory, Identifier}; -use super::super::IdentitiesVault; -use super::super::IdentityError; -use super::verified_change::VerifiedChange; -use super::IdentityHistoryComparison; - -use core::cmp::Ordering; -use core::fmt; -use core::fmt::{Display, Formatter}; -use ockam_core::compat::sync::Arc; -use ockam_core::compat::vec::Vec; -use ockam_core::Result; -use ockam_vault::PublicKey; - -/// Identity implementation -#[derive(Clone, Debug)] -pub struct Identity { - identifier: Identifier, - changes: Vec, - // We preserve the original change_history binary - // as serialization is not guaranteed to be deterministic - change_history: ChangeHistory, -} - -impl Eq for Identity {} - -impl PartialEq for Identity { - fn eq(&self, other: &Self) -> bool { - self.change_history == other.change_history - } -} - -impl Identity { - /// Create a new identity - fn new( - identifier: Identifier, - changes: Vec, - change_history: ChangeHistory, - ) -> Self { - Self { - identifier, - changes, - change_history, - } - } - - /// Return the identity identifier - pub fn identifier(&self) -> &Identifier { - &self.identifier - } - - /// Collection of parsed changes - pub fn changes(&self) -> &[VerifiedChange] { - self.changes.as_slice() - } - - /// `Identity` change history - pub fn change_history(&self) -> &ChangeHistory { - &self.change_history - } - - /// `Identity`'s latest [`ChangeHash`] - pub fn latest_change_hash(&self) -> Result<&ChangeHash> { - if let Some(latest_change) = self.changes.last() { - Ok(latest_change.change_hash()) - } else { - Err(IdentityError::EmptyIdentity.into()) - } - } -} - -impl Identity { - /// Export an `Identity` to the binary format - pub fn export(&self) -> Result> { - self.change_history.export() - } - - /// Import and verify Identity from the ChangeHistory - pub async fn import_from_change_history( - expected_identifier: Option<&Identifier>, - change_history: ChangeHistory, - vault: Arc, - ) -> Result { - let verified_changes = Self::check_entire_consistency(&change_history.0).await?; - Self::verify_all_existing_changes(&verified_changes, &change_history.0, vault).await?; - - let identifier = if let Some(first_change) = verified_changes.first() { - first_change.change_hash().clone().into() - } else { - return Err(IdentityError::IdentityVerificationFailed.into()); - }; - - if let Some(expected_identifier) = expected_identifier { - if &identifier != expected_identifier { - return Err(IdentityError::IdentityVerificationFailed.into()); - } - } - - // TODO: Should we check any timestamps here? - // e.g., if the creation of the new key is before the expiration of the previous? - // or if the latest key is not expired? - - let identity = Self::new(identifier, verified_changes, change_history); - - Ok(identity) - } - - /// Create an Identity from serialized data - pub async fn import( - expected_identifier: Option<&Identifier>, - data: &[u8], - vault: Arc, - ) -> Result { - let change_history = ChangeHistory::import(data)?; - - Self::import_from_change_history(expected_identifier, change_history, vault).await - } -} - -impl Identity { - /// Get latest public key - pub fn get_public_key(&self) -> Result { - if let Some(last_change) = self.changes().last() { - Ok(last_change.primary_public_key().clone()) - } else { - Err(IdentityError::EmptyIdentity.into()) - } - } - /// Add a new key change to the change history - pub async fn add_change( - self, - change: Change, - vault: Arc, - ) -> Result { - // TODO: Optimize - let mut change_history = self.change_history; - change_history.0.push(change); - - Self::import_from_change_history(None, change_history, vault).await - } - - /// Compare to a previously known state of the same `Identity` - pub fn compare(&self, known: &Self) -> IdentityHistoryComparison { - for change_pair in self.changes.iter().zip(known.changes.iter()) { - if change_pair.0.change_hash() != change_pair.1.change_hash() { - return IdentityHistoryComparison::Conflict; - } - } - - match self.changes.len().cmp(&known.changes.len()) { - Ordering::Less => IdentityHistoryComparison::Older, - Ordering::Equal => IdentityHistoryComparison::Equal, - Ordering::Greater => IdentityHistoryComparison::Newer, - } - } -} - -impl Display for Identity { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let identifier = self.identifier(); - writeln!(f, "Identifier: {identifier}")?; - - let history = hex::encode(self.export().map_err(|_| fmt::Error)?); - writeln!(f, "Change history: {history}") - } -} - -#[cfg(test)] -mod tests { - use super::super::super::identities; - use super::*; - use core::str::FromStr; - - #[tokio::test] - async fn test_display() { - let data = hex::decode("81a201583ba20101025835a4028201815820bd144a3f6472ba2215b6b86b2820b23304f9473622847ca80dfda0d10f12eebc03f4041a64c956a9051a64c956a9028201815840c1598a6f85215c118a4744310bebfae71ec19353e1ede1582787592013d65a70c80aa4a4855d16d9b696a887be9bd97b2271245124857d67c07e0203564c3706").unwrap(); - let identity = identities() - .identities_creation() - .import( - Some(&Identifier::from_str("Ie2424922b4194cd4ab57f952ef04c44e5e70ab2f").unwrap()), - &data, - ) - .await - .unwrap(); - - let actual = format!("{identity}"); - let expected = r#"Identifier: Ie2424922b4194cd4ab57f952ef04c44e5e70ab2f -Change history: 81a201583ba20101025835a4028201815820bd144a3f6472ba2215b6b86b2820b23304f9473622847ca80dfda0d10f12eebc03f4041a64c956a9051a64c956a9028201815840c1598a6f85215c118a4744310bebfae71ec19353e1ede1582787592013d65a70c80aa4a4855d16d9b696a887be9bd97b2271245124857d67c07e0203564c3706 -"#; - assert_eq!(actual, expected) - } - - #[tokio::test] - async fn test_compare() -> Result<()> { - let identities = identities(); - let identity1 = identities.identities_creation().create_identity().await?; - - let identity2 = identities - .identities_keys() - .rotate_key(identity1.clone()) - .await?; - - let identity3 = identities - .identities_keys() - .rotate_key(identity1.clone()) - .await?; - - assert_eq!( - identity1.compare(&identity1), - IdentityHistoryComparison::Equal - ); - assert_eq!( - identity2.compare(&identity2), - IdentityHistoryComparison::Equal - ); - assert_eq!( - identity3.compare(&identity3), - IdentityHistoryComparison::Equal - ); - assert_eq!( - identity1.compare(&identity2), - IdentityHistoryComparison::Older - ); - assert_eq!( - identity2.compare(&identity1), - IdentityHistoryComparison::Newer - ); - assert_eq!( - identity2.compare(&identity3), - IdentityHistoryComparison::Conflict - ); - - Ok(()) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/identity/mod.rs b/implementations/rust/ockam/ockam_identity/src/v2/identity/mod.rs deleted file mode 100644 index 674b50311a2..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/identity/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -mod constants; -mod history_comparison; -#[allow(clippy::module_inception)] -mod identity; -mod identity_verification; - -pub use constants::*; -pub use history_comparison::*; -pub use identity::*; - -/// Verified Changes of an [`Identity`] -pub mod verified_change; diff --git a/implementations/rust/ockam/ockam_identity/src/v2/mod.rs b/implementations/rust/ockam/ockam_identity/src/v2/mod.rs deleted file mode 100644 index c05941f5a92..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/mod.rs +++ /dev/null @@ -1,44 +0,0 @@ -/// Utilities -pub mod utils; - -/// Errors -mod error; - -/// On-the-wire data types -pub mod models; - -/// Service for the management of Identities -pub mod identities; - -/// Data types representing a verified Identity -pub mod identity; - -/// Service for the management of Purpose keys -pub mod purpose_keys; - -/// Data types representing a verified Purpose Key -pub mod purpose_key; - -/// Services for creating and validating credentials -pub mod credentials; - -/// Data types supporting the creation of a secure channels -pub mod secure_channel; - -/// Service supporting the creation of secure channel listener and connection to a listener -pub mod secure_channels; - -/// Storage functions -pub mod storage; - -/// -/// Exports -/// -pub use credentials::*; -pub use error::*; -pub use identities::*; -pub use identity::*; -pub use purpose_key::*; -pub use purpose_keys::*; -pub use secure_channel::*; -pub use secure_channels::*; diff --git a/implementations/rust/ockam/ockam_identity/src/v2/models/timestamp.rs b/implementations/rust/ockam/ockam_identity/src/v2/models/timestamp.rs deleted file mode 100644 index cfe50d61609..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/models/timestamp.rs +++ /dev/null @@ -1,25 +0,0 @@ -use core::ops::Deref; -use minicbor::{Decode, Encode}; -use serde::{Deserialize, Serialize}; - -/// Timestamp in seconds (UTC) -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Decode, Serialize, Deserialize)] -#[rustfmt::skip] -#[cbor(transparent)] -#[serde(transparent)] -pub struct TimestampInSeconds(#[n(0)] u64); - -impl TimestampInSeconds { - /// Create a new [`TimestampInSeconds`] - pub fn new(timestamp: u64) -> Self { - Self(timestamp) - } -} - -impl Deref for TimestampInSeconds { - type Target = u64; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/purpose_keys/mod.rs b/implementations/rust/ockam/ockam_identity/src/v2/purpose_keys/mod.rs deleted file mode 100644 index d522ee21de5..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/purpose_keys/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -#[allow(clippy::module_inception)] -mod purpose_keys; - -pub use purpose_keys::*; - -/// Purpose Keys storage functions -pub mod storage; diff --git a/implementations/rust/ockam/ockam_identity/src/v2/purpose_keys/purpose_keys.rs b/implementations/rust/ockam/ockam_identity/src/v2/purpose_keys/purpose_keys.rs deleted file mode 100644 index 6f788fe7d4f..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/purpose_keys/purpose_keys.rs +++ /dev/null @@ -1,332 +0,0 @@ -use super::super::models::{ - Ed25519Signature, Identifier, PurposeKeyAttestation, PurposeKeyAttestationData, - PurposeKeyAttestationSignature, PurposePublicKey, VersionedData, -}; -use super::super::utils::{add_seconds, now}; -use super::super::{ - IdentitiesKeys, IdentitiesReader, IdentitiesVault, Identity, IdentityError, Purpose, PurposeKey, -}; -use super::storage::PurposeKeysRepository; - -use ockam_core::compat::sync::Arc; -use ockam_core::Result; -use ockam_vault::{SecretAttributes, SecretType, Signature, Vault}; - -/// This struct supports all the services related to identities -#[derive(Clone)] -pub struct PurposeKeys { - vault: Arc, - identities_reader: Arc, - identity_keys: Arc, - repository: Arc, -} - -impl PurposeKeys { - /// Return the identities vault - pub fn vault(&self) -> Arc { - self.vault.clone() - } -} - -impl PurposeKeys { - /// Create a new identities module - pub(crate) fn new( - vault: Arc, - identities_reader: Arc, - identity_keys: Arc, - repository: Arc, - ) -> Self { - Self { - vault, - identities_reader, - identity_keys, - repository, - } - } - - /// Return [`PurposeKeysRepository`] instance - pub fn repository(&self) -> Arc { - self.repository.clone() - } -} - -impl PurposeKeys { - /// Create a [`PurposeKey`] - pub async fn create_purpose_key( - &self, - identifier: &Identifier, - purpose: Purpose, - ) -> Result { - // TODO: Check if such key already exists and rewrite it correctly (also delete from the Vault) - - let identity_change_history = self.identities_reader.get_identity(identifier).await?; - let identity = Identity::import_from_change_history( - Some(identifier), - identity_change_history, - self.vault(), - ) - .await?; - - // FIXME - let secret_attributes = match &purpose { - Purpose::SecureChannel => SecretAttributes::X25519, - Purpose::Credentials => SecretAttributes::Ed25519, - }; - let secret_key = self - .vault - .create_ephemeral_secret(secret_attributes) - .await?; - - let public_key = self.vault.get_public_key(&secret_key).await?; - - let public_key = match &purpose { - Purpose::SecureChannel => { - PurposePublicKey::SecureChannelStaticKey(public_key.try_into().unwrap()) - } - Purpose::Credentials => { - PurposePublicKey::CredentialSigningKey(public_key.try_into().unwrap()) - } - }; - - let created_at = now()?; - // TODO: allow customizing ttl - // TODO: check if expiration is before the purpose key expiration - let five_years = 5 * 365 * 24 * 60 * 60; - let expires_at = add_seconds(&created_at, five_years); - - let purpose_key_attestation_data = PurposeKeyAttestationData { - subject: identity.identifier().clone(), - subject_latest_change_hash: identity.latest_change_hash()?.clone(), - public_key, - created_at, - expires_at, - }; - - let purpose_key_attestation_data_binary = minicbor::to_vec(&purpose_key_attestation_data)?; - - let versioned_data = VersionedData { - version: 1, - data: purpose_key_attestation_data_binary, - }; - let versioned_data = minicbor::to_vec(&versioned_data)?; - - let versioned_data_hash = Vault::sha256(&versioned_data); - - let signing_key = self.identity_keys.get_secret_key(&identity).await?; - let signature = self.vault.sign(&signing_key, &versioned_data_hash).await?; - let signature = Ed25519Signature(signature.as_ref().try_into().unwrap()); // FIXME - let signature = PurposeKeyAttestationSignature::Ed25519Signature(signature); - - let attestation = PurposeKeyAttestation { - data: versioned_data, - signature, - }; - - self.repository - .set_purpose_key(identifier, purpose, &attestation) - .await?; - - let purpose_key = PurposeKey::new( - identifier.clone(), - secret_key, - SecretType::Ed25519, - purpose, - purpose_key_attestation_data, - attestation, - ); - - Ok(purpose_key) - } - - /// Verify a [`PurposeKeyAttestation`] - pub async fn verify_purpose_key_attestation( - &self, - attestation: &PurposeKeyAttestation, - ) -> Result { - let versioned_data_hash = Vault::sha256(&attestation.data); - - let versioned_data: VersionedData = minicbor::decode(&attestation.data)?; - - if versioned_data.version != 1 { - return Err(IdentityError::PurposeKeyAttestationVerificationFailed.into()); - } - - let purpose_key_data: PurposeKeyAttestationData = minicbor::decode(&versioned_data.data)?; - - let change_history = self - .identities_reader - .get_identity(&purpose_key_data.subject) - .await?; - let identity = Identity::import_from_change_history( - Some(&purpose_key_data.subject), - change_history, - self.vault.clone(), - ) - .await?; - - // TODO: We might accept a signature from previous key - // TODO: Check if purpose key expiration is before the corresponding Identity public key expiration - let public_key = identity.get_public_key()?; - - let signature = if let PurposeKeyAttestationSignature::Ed25519Signature(signature) = - &attestation.signature - { - Signature::new(signature.0.to_vec()) - } else { - return Err(IdentityError::PurposeKeyAttestationVerificationFailed.into()); - }; - - if !self - .vault - .verify(&public_key, &versioned_data_hash, &signature) - .await? - { - return Err(IdentityError::PurposeKeyAttestationVerificationFailed.into()); - } - - let now = now()?; - - if purpose_key_data.created_at > now { - return Err(IdentityError::PurposeKeyAttestationVerificationFailed.into()); - } - - if purpose_key_data.expires_at < now { - return Err(IdentityError::PurposeKeyAttestationVerificationFailed.into()); - } - - // FIXME: purpose_key_data.subject_latest_change_hash; - - Ok(purpose_key_data) - } - - /// Import own [`PurposeKey`] from its [`PurposeKeyAttestation`] - /// It's assumed that the corresponding secret exists in the Vault - pub async fn import_purpose_key( - &self, - attestation: &PurposeKeyAttestation, - ) -> Result { - let purpose_key_data = self.verify_purpose_key_attestation(attestation).await?; - - let (purpose, public_key) = match purpose_key_data.public_key.clone() { - PurposePublicKey::SecureChannelStaticKey(public_key) => { - (Purpose::SecureChannel, public_key.into()) - } - PurposePublicKey::CredentialSigningKey(public_key) => { - (Purpose::Credentials, public_key.into()) - } - }; - - let key_id = self.vault.get_key_id(&public_key).await?; - - let purpose_key = PurposeKey::new( - purpose_key_data.subject.clone(), - key_id, - SecretType::Ed25519, - purpose, - purpose_key_data, - attestation.clone(), - ); - - Ok(purpose_key) - } -} - -#[cfg(test)] -mod tests { - use super::super::super::{identities, Purpose}; - use super::*; - - #[tokio::test] - async fn create_purpose_keys() -> Result<()> { - let identities = identities(); - let identities_creation = identities.identities_creation(); - let purpose_keys = identities.purpose_keys(); - - let identity = identities_creation.create_identity().await?; - let credentials_key = purpose_keys - .create_purpose_key(identity.identifier(), Purpose::Credentials) - .await?; - let secure_channel_key = purpose_keys - .create_purpose_key(identity.identifier(), Purpose::SecureChannel) - .await?; - - let credentials_key = purpose_keys - .verify_purpose_key_attestation(credentials_key.attestation()) - .await?; - let secure_channel_key = purpose_keys - .verify_purpose_key_attestation(secure_channel_key.attestation()) - .await?; - - assert_eq!(identity.identifier(), &credentials_key.subject); - assert_eq!(identity.identifier(), &secure_channel_key.subject); - - Ok(()) - } - - #[tokio::test] - async fn test_purpose_keys_are_persisted() -> Result<()> { - let identities = identities(); - let identities_creation = identities.identities_creation(); - let purpose_keys = identities.purpose_keys(); - - let identity = identities_creation.create_identity().await?; - - let credentials_key = purpose_keys - .create_purpose_key(identity.identifier(), Purpose::Credentials) - .await?; - - assert!(purpose_keys - .repository() - .retrieve_purpose_key(identity.identifier(), Purpose::Credentials) - .await? - .is_some()); - assert!(purpose_keys - .repository() - .retrieve_purpose_key(identity.identifier(), Purpose::SecureChannel) - .await? - .is_none()); - - let secure_channel_key = purpose_keys - .create_purpose_key(identity.identifier(), Purpose::SecureChannel) - .await?; - - let key = purpose_keys - .repository() - .retrieve_purpose_key(identity.identifier(), Purpose::Credentials) - .await? - .unwrap(); - purpose_keys - .verify_purpose_key_attestation(&key) - .await - .unwrap(); - assert_eq!(&key, credentials_key.attestation()); - - let key = purpose_keys - .repository() - .retrieve_purpose_key(identity.identifier(), Purpose::SecureChannel) - .await? - .unwrap(); - purpose_keys - .verify_purpose_key_attestation(&key) - .await - .unwrap(); - assert_eq!(&key, secure_channel_key.attestation()); - - let credentials_key2 = purpose_keys - .create_purpose_key(identity.identifier(), Purpose::Credentials) - .await?; - - let key = purpose_keys - .repository() - .retrieve_purpose_key(identity.identifier(), Purpose::Credentials) - .await? - .unwrap(); - purpose_keys - .verify_purpose_key_attestation(&key) - .await - .unwrap(); - assert_eq!(&key, credentials_key2.attestation()); - - Ok(()) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/access_control/credential_access_control.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/access_control/credential_access_control.rs deleted file mode 100644 index 48462c9d905..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/access_control/credential_access_control.rs +++ /dev/null @@ -1,71 +0,0 @@ -use core::fmt::{Debug, Formatter}; -use ockam_core::access_control::IncomingAccessControl; -use ockam_core::compat::{boxed::Box, sync::Arc, vec::Vec}; -use ockam_core::Result; -use ockam_core::{async_trait, RelayMessage}; - -use super::super::super::identities::IdentitiesRepository; -use super::super::super::secure_channel::local_info::IdentitySecureChannelLocalInfo; - -/// Access control checking that message senders have a specific set of attributes -#[derive(Clone)] -pub struct CredentialAccessControl { - required_attributes: Vec<(Vec, Vec)>, - storage: Arc, -} - -impl CredentialAccessControl { - /// Create a new credential access control - pub fn new( - required_attributes: &[(Vec, Vec)], - storage: Arc, - ) -> Self { - Self { - required_attributes: required_attributes.to_vec(), - storage, - } - } -} - -impl Debug for CredentialAccessControl { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - let attributes = format!("{:?}", self.required_attributes.iter().map(|x| &x.0)); - - f.debug_struct("Credential Access Control") - .field("Required attributes", &attributes) - .finish() - } -} - -#[async_trait] -impl IncomingAccessControl for CredentialAccessControl { - async fn is_authorized(&self, relay_message: &RelayMessage) -> Result { - if let Ok(msg_identity_id) = - IdentitySecureChannelLocalInfo::find_info(relay_message.local_message()) - { - let attributes = match self - .storage - .get_attributes(&msg_identity_id.their_identity_id()) - .await? - { - Some(a) => a, - None => return Ok(false), // No attributes for that Identity - }; - - for required_attribute in self.required_attributes.iter() { - let attr_val = match attributes.attrs().get(&required_attribute.0) { - Some(v) => v, - None => return Ok(false), // No required key - }; - - if &required_attribute.1 != attr_val { - return Ok(false); // Value doesn't match - } - } - - Ok(true) - } else { - Ok(false) - } - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/access_control/identity_access_control.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/access_control/identity_access_control.rs deleted file mode 100644 index d988cdc915a..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/access_control/identity_access_control.rs +++ /dev/null @@ -1,77 +0,0 @@ -use ockam_core::access_control::IncomingAccessControl; -use ockam_core::async_trait; -use ockam_core::compat::boxed::Box; -use ockam_core::compat::vec::Vec; -use ockam_core::{RelayMessage, Result}; - -use super::super::super::models::Identifier; -use super::super::super::secure_channel::local_info::IdentitySecureChannelLocalInfo; - -/// Builder for `Identity`-related AccessControls -pub struct IdentityAccessControlBuilder; - -impl IdentityAccessControlBuilder { - /// `IncomingAccessControl` that checks if the author of the message possesses - /// given `IdentityIdentifier` - pub fn new_with_id(their_identity_id: Identifier) -> IdentityIdAccessControl { - IdentityIdAccessControl::new(vec![their_identity_id]) - } - - /// `IncomingAccessControl` that checks if the author of the message possesses - /// an `IdentityIdentifier` from the pre-known list - pub fn new_with_ids(identity_ids: impl Into>) -> IdentityIdAccessControl { - IdentityIdAccessControl::new(identity_ids.into()) - } - - /// `IncomingAccessControl` that checks if message was sent through a SecureChannel - pub fn new_with_any_id() -> IdentityAnyIdAccessControl { - IdentityAnyIdAccessControl - } -} - -/// `IncomingAccessControl` check that succeeds if message came through a SecureChannel -/// with any `IdentityIdentifier` (i.e. any SecureChannel) -#[derive(Debug)] -pub struct IdentityAnyIdAccessControl; - -#[async_trait] -impl IncomingAccessControl for IdentityAnyIdAccessControl { - async fn is_authorized(&self, relay_msg: &RelayMessage) -> Result { - Ok(IdentitySecureChannelLocalInfo::find_info(relay_msg.local_message()).is_ok()) - } -} - -/// `IncomingAccessControl` check that succeeds if message came from some `IdentityIdentifier` -/// from a pre-known list -#[derive(Clone, Debug)] -pub struct IdentityIdAccessControl { - identity_ids: Vec, -} - -impl IdentityIdAccessControl { - /// Constructor - pub fn new(identity_ids: Vec) -> Self { - Self { identity_ids } - } - - fn contains(&self, their_id: &Identifier) -> bool { - let mut found = subtle::Choice::from(0); - for trusted_id in &self.identity_ids { - found |= trusted_id.ct_eq(their_id); - } - found.into() - } -} - -#[async_trait] -impl IncomingAccessControl for IdentityIdAccessControl { - async fn is_authorized(&self, relay_msg: &RelayMessage) -> Result { - if let Ok(msg_identity_id) = - IdentitySecureChannelLocalInfo::find_info(relay_msg.local_message()) - { - Ok(self.contains(&msg_identity_id.their_identity_id())) - } else { - Ok(false) - } - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/access_control/mod.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/access_control/mod.rs deleted file mode 100644 index 6a423b63442..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/access_control/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod credential_access_control; -mod identity_access_control; - -pub use credential_access_control::*; -pub use identity_access_control::*; diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/addresses.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/addresses.rs deleted file mode 100644 index 66a4e882de4..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/addresses.rs +++ /dev/null @@ -1,47 +0,0 @@ -use ockam_core::Address; - -use super::super::secure_channel::role::Role; - -// Previously there were regular ephemeral secure channel encryptor&decryptor -// and identity secure channel encryptor&decryptor. -// Now this logic is merged into one encryptor&decryptor pair, but for backwards -// compatibility each of them have more addresses to simulate old behaviour. -#[derive(Clone, Debug)] -pub(crate) struct Addresses { - // Used to send decrypted messages and secure channel creation completion notification - pub(crate) decryptor_internal: Address, - // Used for KeyExchange and receiving encrypted messages - pub(crate) decryptor_remote: Address, - // Used to encrypt messages without sending them with Ockam Routing to the other end of the channel - pub(crate) decryptor_api: Address, - - // Encryptor worker address used to receive plain messages that will be encrypted and forwarded - // to the other end of the channel - pub(crate) encryptor: Address, - // Used to decrypt messages that were received though some channel other than Ockam Routing from the other end of the channel - pub(crate) encryptor_api: Address, -} - -impl Addresses { - pub(crate) fn generate(role: Role) -> Self { - let role_str = role.str(); - let decryptor_internal = - Address::random_tagged(&format!("SecureChannel.{}.decryptor.internal", role_str)); - let decryptor_remote = - Address::random_tagged(&format!("SecureChannel.{}.decryptor.remote", role_str)); - let decryptor_api = - Address::random_tagged(&format!("SecureChannel.{}.decryptor.api", role_str)); - - let encryptor = Address::random_tagged(&format!("SecureChannel.{}.encryptor", role_str)); - let encryptor_api = - Address::random_tagged(&format!("SecureChannel.{}.encryptor.api", role_str)); - - Self { - decryptor_internal, - decryptor_remote, - decryptor_api, - encryptor, - encryptor_api, - } - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/api.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/api.rs deleted file mode 100644 index a0daab91b6f..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/api.rs +++ /dev/null @@ -1,30 +0,0 @@ -use ockam_core::compat::vec::Vec; -use ockam_core::Error; -use ockam_core::Message; -use serde::{Deserialize, Serialize}; - -/// Request type for `EncryptorWorker` API Address -#[derive(Serialize, Deserialize, Message)] -pub struct EncryptionRequest(pub Vec); - -/// Response type for `EncryptorWorker` API Address -#[derive(Serialize, Deserialize, Message)] -pub enum EncryptionResponse { - /// Success - Ok(Vec), - /// Error - Err(Error), -} - -/// Request type for `Decryptor` API Address (the `Decryptor` is accessible through the `HandshakeWorker`) -#[derive(Serialize, Deserialize, Message)] -pub struct DecryptionRequest(pub Vec); - -/// Response type for `Decryptor` API Address (the `Decryptor` is accessible through the `HandshakeWorker`) -#[derive(Serialize, Deserialize, Message)] -pub enum DecryptionResponse { - /// Success - Ok(Vec), - /// Error - Err(Error), -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/decryptor.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/decryptor.rs deleted file mode 100644 index f6626031f3d..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/decryptor.rs +++ /dev/null @@ -1,193 +0,0 @@ -use ockam_core::compat::sync::Arc; -use ockam_core::compat::vec::Vec; -use ockam_core::{Any, Result, Routed, TransportMessage}; -use ockam_core::{Decodable, LocalMessage}; -use ockam_node::Context; -use ockam_vault::KeyId; -use tracing::debug; -use tracing::warn; - -use super::super::models::Identifier; -use super::super::secure_channel::encryptor::{Encryptor, KEY_RENEWAL_INTERVAL}; -use super::super::secure_channel::key_tracker::KeyTracker; -use super::super::secure_channel::nonce_tracker::NonceTracker; -use super::super::secure_channel::Addresses; -use super::super::XXInitializedVault; -use super::super::{ - DecryptionRequest, DecryptionResponse, IdentityError, IdentitySecureChannelLocalInfo, -}; - -pub(crate) struct DecryptorHandler { - //for debug purposes only - pub(crate) role: &'static str, - pub(crate) addresses: Addresses, - pub(crate) their_identity_id: Identifier, - pub(crate) decryptor: Decryptor, -} - -impl DecryptorHandler { - pub fn new( - role: &'static str, - addresses: Addresses, - key: KeyId, - vault: Arc, - their_identity_id: Identifier, - ) -> Self { - Self { - role, - addresses, - their_identity_id, - decryptor: Decryptor::new(key, vault), - } - } - - pub(crate) async fn handle_decrypt_api( - &mut self, - ctx: &mut Context, - msg: Routed, - ) -> Result<()> { - debug!( - "SecureChannel {} received Decrypt API {}", - self.role, &self.addresses.decryptor_remote - ); - - let return_route = msg.return_route(); - - // Decode raw payload binary - let request = DecryptionRequest::decode(&msg.into_transport_message().payload)?; - - // Decrypt the binary - let decrypted_payload = self.decryptor.decrypt(&request.0).await; - - let response = match decrypted_payload { - Ok(payload) => DecryptionResponse::Ok(payload), - Err(err) => DecryptionResponse::Err(err), - }; - - // Send reply to the caller - ctx.send_from_address(return_route, response, self.addresses.decryptor_api.clone()) - .await?; - - Ok(()) - } - - pub(crate) async fn handle_decrypt( - &mut self, - ctx: &mut Context, - msg: Routed, - ) -> Result<()> { - debug!( - "SecureChannel {} received Decrypt {}", - self.role, &self.addresses.decryptor_remote - ); - - // Decode raw payload binary - let payload = Vec::::decode(&msg.into_transport_message().payload)?; - - // Decrypt the binary - let decrypted_payload = self.decryptor.decrypt(&payload).await?; - - // Encrypted data should be a TransportMessage - let mut transport_message = TransportMessage::decode(&decrypted_payload)?; - - // Add encryptor hop in the return_route (instead of our address) - transport_message - .return_route - .modify() - .prepend(self.addresses.encryptor.clone()); - - // Mark message LocalInfo with IdentitySecureChannelLocalInfo, - // replacing any pre-existing entries - let local_info = - IdentitySecureChannelLocalInfo::mark(vec![], self.their_identity_id.clone())?; - - let msg = LocalMessage::new(transport_message, local_info); - - match ctx - .forward_from_address(msg, self.addresses.decryptor_internal.clone()) - .await - { - Ok(_) => Ok(()), - Err(err) => { - warn!( - "{} forwarding decrypted message from {}", - err, &self.addresses.encryptor - ); - Ok(()) - } - } - } - - /// Remove the channel keys on shutdown - pub(crate) async fn shutdown(&self) -> Result<()> { - self.decryptor.shutdown().await - } -} - -pub(crate) struct Decryptor { - vault: Arc, - key_tracker: KeyTracker, - nonce_tracker: NonceTracker, -} - -impl Decryptor { - pub fn new(key_id: KeyId, vault: Arc) -> Self { - Self { - vault, - key_tracker: KeyTracker::new(key_id, KEY_RENEWAL_INTERVAL), - nonce_tracker: NonceTracker::new(), - } - } - - /// Restore 12-byte nonce needed for AES GCM from 8 byte that we use for noise - fn convert_nonce_from_small(b: &[u8]) -> Result<(u64, [u8; 12])> { - let bytes: [u8; 8] = b.try_into().map_err(|_| IdentityError::InvalidNonce)?; - - let nonce = u64::from_be_bytes(bytes); - - Ok((nonce, Encryptor::convert_nonce_from_u64(nonce).1)) - } - - pub async fn decrypt(&mut self, payload: &[u8]) -> Result> { - if payload.len() < 8 { - return Err(IdentityError::InvalidNonce.into()); - } - - let (nonce, nonce_buffer) = Self::convert_nonce_from_small(&payload[..8])?; - let nonce_tracker = self.nonce_tracker.mark(nonce)?; - - // get the key corresponding to the current nonce and - // rekey if necessary - let key = if let Some(key) = self.key_tracker.get_key(nonce)? { - key - } else { - Encryptor::rekey(&self.vault, &self.key_tracker.current_key).await? - }; - - // to improve protection against connection disruption attacks, we want to validate the - // message with a decryption _before_ committing to the new state - let result = self - .vault - .aead_aes_gcm_decrypt(&key, &payload[8..], &nonce_buffer, &[]) - .await; - - if result.is_ok() { - self.nonce_tracker = nonce_tracker; - if let Some(key_to_delete) = self.key_tracker.update_key(key)? { - self.vault.delete_ephemeral_secret(key_to_delete).await?; - } - } - result - } - - /// Remove the channel keys on shutdown - pub(crate) async fn shutdown(&self) -> Result<()> { - self.vault - .delete_ephemeral_secret(self.key_tracker.current_key.clone()) - .await?; - if let Some(previous_key) = self.key_tracker.previous_key.clone() { - self.vault.delete_ephemeral_secret(previous_key).await?; - }; - Ok(()) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/encryptor.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/encryptor.rs deleted file mode 100644 index 0dde30e048c..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/encryptor.rs +++ /dev/null @@ -1,95 +0,0 @@ -use ockam_core::compat::sync::Arc; -use ockam_core::compat::vec::Vec; -use ockam_core::errcode::{Kind, Origin}; -use ockam_core::{Error, Result}; -use ockam_vault::{KeyId, Secret}; - -use super::super::IdentityError; -use super::super::XXInitializedVault; - -pub(crate) struct Encryptor { - key: KeyId, - nonce: u64, - vault: Arc, -} - -// To simplify the implementation we use the same constant for the size of the message -// window we accept with the message period used to rekey. -// This means we only need to keep the current key and the previous one. -pub(crate) const KEY_RENEWAL_INTERVAL: u64 = 32; - -impl Encryptor { - /// We use u64 nonce since it's convenient to work with it (e.g. increment) - /// But we use 8-byte be format to send it over to the other side (according to noise spec) - /// And we use 12-byte be format for encryption, since AES-GCM wants 12 bytes - pub(crate) fn convert_nonce_from_u64(nonce: u64) -> ([u8; 8], [u8; 12]) { - let mut n: [u8; 12] = [0; 12]; - let b: [u8; 8] = nonce.to_be_bytes(); - - n[4..].copy_from_slice(&b); - - (b, n) - } - - pub async fn rekey(vault: &Arc, key: &KeyId) -> Result { - let nonce_buffer = Self::convert_nonce_from_u64(u64::MAX).1; - let zeroes = [0u8; 32]; - - let new_key_buffer = vault - .aead_aes_gcm_encrypt(key, &zeroes, &nonce_buffer, &[]) - .await?; - - let attributes = vault.get_secret_attributes(key).await?; - - vault - .import_ephemeral_secret(Secret::new(new_key_buffer[0..32].to_vec()), attributes) - .await - } - - pub async fn encrypt(&mut self, payload: &[u8]) -> Result> { - let current_nonce = self.nonce; - if current_nonce == u64::MAX { - return Err(IdentityError::NonceOverflow.into()); - } - - self.nonce += 1; - - if current_nonce > 0 && current_nonce % KEY_RENEWAL_INTERVAL == 0 { - let new_key = Self::rekey(&self.vault, &self.key).await?; - let old_key = core::mem::replace(&mut self.key, new_key); - self.vault.delete_ephemeral_secret(old_key).await?; - } - - let (small_nonce, nonce) = Self::convert_nonce_from_u64(current_nonce); - - let mut cipher_text = self - .vault - .aead_aes_gcm_encrypt(&self.key, payload, &nonce, &[]) - .await?; - - let mut res = Vec::new(); - res.extend_from_slice(&small_nonce); - res.append(&mut cipher_text); - - Ok(res) - } - - pub fn new(key: KeyId, nonce: u64, vault: Arc) -> Self { - Self { key, nonce, vault } - } - - pub(crate) async fn shutdown(&self) -> Result<()> { - if !self.vault.delete_ephemeral_secret(self.key.clone()).await? { - Err(Error::new( - Origin::Ockam, - Kind::Internal, - format!( - "the key id {} could not be deleted in the Encryptor shutdown", - self.key - ), - )) - } else { - Ok(()) - } - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/encryptor_worker.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/encryptor_worker.rs deleted file mode 100644 index ee27181ba73..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/encryptor_worker.rs +++ /dev/null @@ -1,131 +0,0 @@ -use ockam_core::compat::boxed::Box; -use ockam_core::{async_trait, Decodable, Encodable, Route}; -use ockam_core::{Any, Result, Routed, TransportMessage, Worker}; -use ockam_node::Context; -use tracing::debug; - -use super::super::secure_channel::addresses::Addresses; -use super::super::secure_channel::api::{EncryptionRequest, EncryptionResponse}; -use super::super::secure_channel::encryptor::Encryptor; -use super::super::IdentityError; - -pub(crate) struct EncryptorWorker { - //for debug purposes only - role: &'static str, - addresses: Addresses, - remote_route: Route, - encryptor: Encryptor, -} - -impl EncryptorWorker { - pub fn new( - role: &'static str, - addresses: Addresses, - remote_route: Route, - encryptor: Encryptor, - ) -> Self { - Self { - role, - addresses, - remote_route, - encryptor, - } - } - - async fn handle_encrypt_api( - &mut self, - ctx: &mut ::Context, - msg: Routed<::Message>, - ) -> Result<()> { - debug!( - "SecureChannel {} received Encrypt API {}", - self.role, &self.addresses.encryptor - ); - - let return_route = msg.return_route(); - - // Decode raw payload binary - let request = EncryptionRequest::decode(&msg.into_transport_message().payload)?; - - // Encrypt the message - let encrypted_payload = self.encryptor.encrypt(&request.0).await; - - let response = match encrypted_payload { - Ok(payload) => EncryptionResponse::Ok(payload), - Err(err) => EncryptionResponse::Err(err), - }; - - // Send the reply to the caller - ctx.send_from_address(return_route, response, self.addresses.encryptor_api.clone()) - .await?; - - Ok(()) - } - - async fn handle_encrypt( - &mut self, - ctx: &mut ::Context, - msg: Routed<::Message>, - ) -> Result<()> { - debug!( - "SecureChannel {} received Encrypt {}", - self.role, &self.addresses.encryptor - ); - - let mut onward_route = msg.onward_route(); - let return_route = msg.return_route(); - - // Remove our address - let _ = onward_route.step(); - - let msg = TransportMessage::v1( - onward_route, - return_route, - msg.into_transport_message().payload, - ); - - // Encrypt the message - let encrypted_payload = self.encryptor.encrypt(&msg.encode()?).await?; - - // Send the message to the decryptor on the other side - ctx.send_from_address( - self.remote_route.clone(), - encrypted_payload, - self.addresses.encryptor.clone(), - ) - .await?; - - Ok(()) - } -} - -#[async_trait] -impl Worker for EncryptorWorker { - type Message = Any; - type Context = Context; - - async fn handle_message( - &mut self, - ctx: &mut Self::Context, - msg: Routed, - ) -> Result<()> { - let msg_addr = msg.msg_addr(); - - if msg_addr == self.addresses.encryptor { - self.handle_encrypt(ctx, msg).await?; - } else if msg_addr == self.addresses.encryptor_api { - self.handle_encrypt_api(ctx, msg).await?; - } else { - return Err(IdentityError::UnknownChannelMsgDestination.into()); - } - - Ok(()) - } - - async fn shutdown(&mut self, context: &mut Self::Context) -> Result<()> { - let _ = context - .stop_worker(self.addresses.decryptor_internal.clone()) - .await; - self.encryptor.shutdown().await - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/error.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/error.rs deleted file mode 100644 index 0f5fb1a9b0f..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/error.rs +++ /dev/null @@ -1,42 +0,0 @@ -use ockam_core::compat::{error::Error as StdError, fmt}; -use ockam_core::{ - errcode::{Kind, Origin}, - Error, -}; - -/// Represents the failures that can occur in -/// an Ockam XX Key Agreement -#[derive(Clone, Copy, Debug)] -pub enum XXError { - /// An internal Vault error has occurred. - InternalVaultError, - /// A message had an unexpected length. - MessageLenMismatch, - /// Invalid internal state. - InvalidInternalState, -} - -impl StdError for XXError {} - -impl fmt::Display for XXError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::InternalVaultError => write!(f, "internal vault error"), - Self::MessageLenMismatch => write!(f, "message length mismatch"), - Self::InvalidInternalState => write!(f, "invalid internal state"), - } - } -} - -impl From for Error { - #[track_caller] - fn from(err: XXError) -> Self { - let kind = match err { - XXError::InternalVaultError => Kind::Internal, - XXError::MessageLenMismatch => Kind::Misuse, - XXError::InvalidInternalState => Kind::Internal, - }; - - Error::new(Origin::KeyExchange, kind, err) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/handshake.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/handshake.rs deleted file mode 100644 index 86ab01bda24..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/handshake.rs +++ /dev/null @@ -1,764 +0,0 @@ -use arrayref::array_ref; -use ockam_core::compat::sync::Arc; -use ockam_core::compat::vec::Vec; -use ockam_core::errcode::{Kind, Origin}; -use ockam_core::{Error, Result}; -use ockam_vault::constants::{AES256_SECRET_LENGTH_USIZE, CURVE25519_PUBLIC_LENGTH_USIZE}; -use ockam_vault::SecretType::X25519; -use ockam_vault::{KeyId, PublicKey, Secret, SecretAttributes}; -use sha2::{Digest, Sha256}; -use Status::*; - -use super::super::super::secure_channel::handshake::error::XXError; -use super::super::super::secure_channel::handshake::handshake_state_machine::{ - HandshakeKeys, Status, -}; -use super::super::super::secure_channel::Role; -use super::super::super::XXVault; - -/// The number of bytes in a SHA256 digest -pub const SHA256_SIZE_U32: u32 = 32; -/// The number of bytes in a SHA256 digest -pub const SHA256_SIZE_USIZE: usize = 32; -/// The number of bytes in AES-GCM tag -pub const AES_GCM_TAGSIZE_USIZE: usize = 16; - -/// Implementation of a Handshake for the noise protocol -/// The first members are used in the implementation of some of the protocol steps, for example to -/// encrypt messages -/// The variables used in the protocol itself: s, e, rs, re,... are handled in `HandshakeState` -pub(super) struct Handshake { - vault: Arc, - pub(super) state: HandshakeState, -} - -/// Top-level functions used in the initiator and responder state machines -/// Each function makes mutable copy of the state to modify it in order to make the code more compact -/// and avoid self.state.xxx = ... -impl Handshake { - /// Initialize the handshake variables - pub(super) async fn initialize(&mut self) -> Result<()> { - let mut state = self.state.clone(); - state.h = *Self::protocol_name(); - state.k = Some( - self.import_k_secret(vec![0u8; AES256_SECRET_LENGTH_USIZE]) - .await?, - ); - state.ck = Some( - self.import_ck_secret(Self::protocol_name().to_vec()) - .await?, - ); - - state.h = HandshakeState::sha256(&state.h); - self.state = state; - Ok(()) - } - - /// Encode the first message, sent from the initiator to the responder - pub(super) async fn encode_message1(&mut self, payload: &[u8]) -> Result> { - let mut state = self.state.clone(); - // output e.pubKey - let e_pub_key = self.get_public_key(state.e()?).await?; - state.mix_hash(e_pub_key.data()); - let mut message = e_pub_key.data().to_vec(); - - // output message 1 payload - message.extend_from_slice(payload); - state.mix_hash(payload); - - self.state = state; - Ok(message) - } - - /// Decode the first message to get the ephemeral public key sent by the initiator - pub(super) async fn decode_message1(&mut self, message: &[u8]) -> Result> { - let mut state = self.state.clone(); - // read e.pubKey - let key = Self::read_key(message)?; - state.mix_hash(key); - - state.re = Some(PublicKey::new(key.to_vec(), X25519)); - - // decode payload - let payload = Self::read_message1_payload(message)?; - state.mix_hash(payload); - - self.state = state; - Ok(payload.to_vec()) - } - - /// Encode the second message from the responder to the initiator - /// That message contains: the responder ephemeral public key + a Diffie-Hellman key + - /// an encrypted payload containing the responder identity / signature / credentials - pub(super) async fn encode_message2(&mut self, payload: &[u8]) -> Result> { - let mut state = self.state.clone(); - // output e.pubKey - let e_pub_key = self.get_public_key(state.e()?).await?; - state.mix_hash(e_pub_key.data()); - let mut message2 = e_pub_key.data().to_vec(); - - // ck, k = HKDF(ck, DH(e, re), 2) - let dh = self.dh(state.e()?, state.re()?).await?; - self.hkdf(&mut state, dh).await?; - - // encrypt and output s.pubKey - let s_pub_key = self.get_public_key(state.s()?).await?; - let c = self.encrypt_and_hash(&mut state, s_pub_key.data()).await?; - message2.extend_from_slice(c.as_slice()); - - // ck, k = HKDF(ck, DH(s, re), 2) - let dh = self.dh(state.s()?, state.re()?).await?; - self.hkdf(&mut state, dh).await?; - - // encrypt and output payload - let c = self.encrypt_and_hash(&mut state, payload).await?; - message2.extend(c); - self.state = state; - Ok(message2) - } - - /// Decode the second message sent by the responder - pub(super) async fn decode_message2(&mut self, message: &[u8]) -> Result> { - let mut state = self.state.clone(); - // decode re.pubKey - let re_pub_key = Self::read_key(message)?; - state.re = Some(PublicKey::new(re_pub_key.to_vec(), X25519)); - state.mix_hash(re_pub_key); - - // ck, k = HKDF(ck, DH(e, re), 2) - let dh = self.dh(state.e()?, state.re()?).await?; - self.hkdf(&mut state, dh).await?; - - // decrypt rs.pubKey - let rs_pub_key = Self::read_message2_encrypted_key(message)?; - state.rs = Some(PublicKey::new( - self.hash_and_decrypt(&mut state, rs_pub_key).await?, - X25519, - )); - - // ck, k = HKDF(ck, DH(e, rs), 2) - let dh = self.dh(state.e()?, state.rs()?).await?; - self.hkdf(&mut state, dh).await?; - - // decrypt payload - let c = Self::read_message2_payload(message)?; - let payload = self.hash_and_decrypt(&mut state, c).await?; - - self.state = state; - Ok(payload) - } - - /// Encode the third message from the initiator to the responder - /// That message contains: the initiator static public key (encrypted) + a Diffie-Hellman key + - /// an encrypted payload containing the initiator identity / signature / credentials - pub(super) async fn encode_message3(&mut self, payload: &[u8]) -> Result> { - let mut state = self.state.clone(); - // encrypt s.pubKey - let s_pub_key = self.get_public_key(state.s()?).await?; - let c = self.encrypt_and_hash(&mut state, s_pub_key.data()).await?; - let mut message3 = c.to_vec(); - - // ck, k = HKDF(ck, DH(s, re), 2) - let dh = self.dh(state.s()?, state.re()?).await?; - self.hkdf(&mut state, dh).await?; - - // encrypt payload - let c = self.encrypt_and_hash(&mut state, payload).await?; - message3.extend(c); - - self.state = state; - Ok(message3) - } - - /// Decode the third message sent by the initiator - pub(super) async fn decode_message3(&mut self, message: &[u8]) -> Result> { - let mut state = self.state.clone(); - // decrypt rs key - let rs_pub_key = Self::read_message3_encrypted_key(message)?; - state.rs = Some(PublicKey::new( - self.hash_and_decrypt(&mut state, rs_pub_key).await?, - X25519, - )); - - // ck, k = HKDF(ck, DH(e, rs), 2), n = 0 - let dh = self.dh(state.e()?, state.rs()?).await?; - self.hkdf(&mut state, dh).await?; - - // decrypt payload - let c = Self::read_message3_payload(message)?; - let payload = self.hash_and_decrypt(&mut state, c).await?; - self.state = state; - Ok(payload) - } - - /// Set the final state of the state machine by creating the encryption / decryption keys - /// and return the other party identity - pub(super) async fn set_final_state(&mut self, role: Role) -> Result<()> { - // k1, k2 = HKDF(ck, zerolen, 2) - let mut state = self.state.clone(); - let (k1, k2) = self.compute_final_keys(&mut state).await?; - let (encryption_key, decryption_key) = if role.is_initiator() { - (k2, k1) - } else { - (k1, k2) - }; - state.status = Ready(HandshakeKeys { - encryption_key, - decryption_key, - }); - // now remove the ephemeral keys which are not useful anymore - self.state = state; - self.delete_ephemeral_keys().await?; - Ok(()) - } - - /// Return the final results of the handshake if we reached the final state - pub(super) fn get_handshake_keys(&self) -> Option { - match &self.state.status { - Ready(keys) => Some(keys.clone()), - _ => None, - } - } -} - -impl Handshake { - /// Create a new handshake - pub(super) async fn new(vault: Arc, static_key: KeyId) -> Result { - // 1. generate an ephemeral key pair for this handshake and set it to e - let ephemeral_key = Self::generate_ephemeral_key(vault.clone()).await?; - - // 2. initialize the handshake - // We currently don't use any payload for message 1 - Ok(Handshake { - vault, - state: HandshakeState::new(static_key, ephemeral_key), - }) - } - - /// Import the k secret - async fn import_k_secret(&self, content: Vec) -> Result { - self.vault - .import_ephemeral_secret(Secret::new(content), Self::k_attributes()) - .await - } - - /// Import the ck secret - async fn import_ck_secret(&self, content: Vec) -> Result { - self.vault - .import_ephemeral_secret(Secret::new(content), Self::ck_attributes()) - .await - } - - /// Return the public key corresponding to a given key id - async fn get_public_key(&self, key_id: &KeyId) -> Result { - self.vault.get_public_key(key_id).await - } - - /// Compute a Diffie-Hellman key between a given key id and the other party public key - async fn dh(&self, key_id: &KeyId, public_key: &PublicKey) -> Result { - self.vault.ec_diffie_hellman(key_id, public_key).await - } - - /// Compute two derived ck, and k keys based on existing ck and k keys + a Diffie-Hellman key - async fn hkdf(&self, state: &mut HandshakeState, dh: KeyId) -> Result<()> { - let hkdf_output = self - .vault - .hkdf_sha256( - state.ck()?, - b"", - Some(&dh), - vec![Self::ck_attributes(), Self::k_attributes()], - ) - .await?; - - // The Diffie-Hellman secret is not useful anymore - // we can delete it from memory - self.vault.delete_ephemeral_secret(dh).await?; - - let [new_ck, new_k]: [KeyId; 2] = hkdf_output - .try_into() - .map_err(|_| XXError::InternalVaultError)?; - - let old_ck = state.take_ck()?; - state.ck = Some(new_ck); - self.vault.delete_ephemeral_secret(old_ck).await?; - - let old_k = state.take_k()?; - state.k = Some(new_k); - self.vault.delete_ephemeral_secret(old_k).await?; - - state.n = 0; - Ok(()) - - //_ => , - } - - /// Compute the final encryption and decryption keys - async fn compute_final_keys(&self, state: &mut HandshakeState) -> Result<(KeyId, KeyId)> { - let hkdf_output = self - .vault - .hkdf_sha256( - state.ck()?, - b"", - None, - vec![Self::k_attributes(), Self::k_attributes()], - ) - .await?; - - let [k1, k2]: [KeyId; 2] = hkdf_output - .try_into() - .map_err(|_| XXError::InternalVaultError)?; - - self.vault.delete_ephemeral_secret(state.take_ck()?).await?; - self.vault.delete_ephemeral_secret(state.take_k()?).await?; - - Ok((k1, k2)) - } - - /// Decrypt a ciphertext 'c' using the key 'k' and the additional data 'h' - async fn hash_and_decrypt(&self, state: &mut HandshakeState, c: &[u8]) -> Result> { - let mut nonce = [0u8; 12]; - nonce[4..].copy_from_slice(&state.n.to_be_bytes()); - let result = self - .vault - .aead_aes_gcm_decrypt(state.k()?, c, nonce.as_ref(), &state.h) - .await - .map(|b| b.to_vec())?; - state.mix_hash(c); - state.n += 1; - Ok(result) - } - - /// Encrypt a plaintext 'c' using the key 'k' and the additional data 'h' - async fn encrypt_and_hash(&self, state: &mut HandshakeState, p: &[u8]) -> Result> { - let mut nonce = [0u8; 12]; - nonce[4..].copy_from_slice(&state.n.to_be_bytes()); - - let result = self - .vault - .aead_aes_gcm_encrypt(state.k()?, p, nonce.as_ref(), &state.h) - .await? - .to_vec(); - state.mix_hash(result.as_slice()); - state.n += 1; - Ok(result) - } - - async fn delete_ephemeral_keys(&mut self) -> Result<()> { - _ = self - .vault - .delete_ephemeral_secret(self.state.take_e()?) - .await?; - - Ok(()) - } -} - -/// Static functions -impl Handshake { - /// Protocol name, used as a secret during the handshake initialization, padded to 32 bytes - fn protocol_name() -> &'static [u8; 32] { - b"Noise_XX_25519_AESGCM_SHA256\0\0\0\0" - } - - /// Generate an ephemeral key for the key exchange - async fn generate_ephemeral_key(vault: Arc) -> Result { - vault - .create_ephemeral_secret(SecretAttributes::X25519) - .await - } - - /// Secret attributes for the ck key - fn ck_attributes() -> SecretAttributes { - SecretAttributes::Buffer(SHA256_SIZE_U32) - } - - /// Secret attributes for the k key - fn k_attributes() -> SecretAttributes { - SecretAttributes::Aes256 - } - - /// Read the message 1 payload which is present after the public key - fn read_message1_payload(message: &[u8]) -> Result<&[u8]> { - Self::read_end(message, Self::key_size()) - } - - /// Read the message 2 encrypted key, which is present after the public key - fn read_message2_encrypted_key(message: &[u8]) -> Result<&[u8]> { - Self::read_middle(message, Self::key_size(), Self::encrypted_key_size()) - } - - /// Read the message 2 encrypted payload, which is present after the encrypted key - fn read_message2_payload(message: &[u8]) -> Result<&[u8]> { - Self::read_end(message, Self::key_size() + Self::encrypted_key_size()) - } - - /// Read the message 3 encrypted key at the beginning of the message - fn read_message3_encrypted_key(message: &[u8]) -> Result<&[u8]> { - Self::read_start(message, Self::encrypted_key_size()) - } - - /// Read the message 3 payload which is present after the encrypted key - fn read_message3_payload(message: &[u8]) -> Result<&[u8]> { - Self::read_end(message, Self::encrypted_key_size()) - } - - /// Read the first 'length' bytes of the message - fn read_start(message: &[u8], length: usize) -> Result<&[u8]> { - if message.len() < length { - return Err(XXError::MessageLenMismatch.into()); - } - Ok(&message[0..length]) - } - - /// Read the bytes of the message after the first 'drop_length' bytes - fn read_end(message: &[u8], drop_length: usize) -> Result<&[u8]> { - if message.len() < drop_length { - return Err(XXError::MessageLenMismatch.into()); - } - Ok(&message[drop_length..]) - } - - /// Read 'length' bytes of the message after the first 'drop_length' bytes - fn read_middle(message: &[u8], drop_length: usize, length: usize) -> Result<&[u8]> { - if message.len() < drop_length + length { - return Err(XXError::MessageLenMismatch.into()); - } - Ok(&message[drop_length..(drop_length + length)]) - } - - /// Read the bytes of a key at the beginning of a message - fn read_key(message: &[u8]) -> Result<&[u8]> { - Self::read_start(message, Self::key_size()) - } - - /// Size of a public key - fn key_size() -> usize { - CURVE25519_PUBLIC_LENGTH_USIZE - } - - /// Size of an encrypted key - fn encrypted_key_size() -> usize { - Self::key_size() + AES_GCM_TAGSIZE_USIZE - } -} - -/// The `HandshakeState` contains all the variables necessary to follow the Noise protocol -#[derive(Debug, Clone)] -pub(super) struct HandshakeState { - pub(super) s: Option, - e: Option, - k: Option, - re: Option, - pub(super) rs: Option, - n: u64, - h: [u8; SHA256_SIZE_USIZE], - ck: Option, - pub(super) status: Status, -} - -impl HandshakeState { - /// Create a new HandshakeState with: - /// - a static key - /// - an ephemeral key - /// - a payload - pub(super) fn new(s: KeyId, e: KeyId) -> HandshakeState { - HandshakeState { - s: Some(s), - e: Some(e), - k: None, - re: None, - rs: None, - n: 0, - h: [0u8; SHA256_SIZE_USIZE], - ck: None, - status: Initial, - } - } - - /// h = SHA256(h || data) - pub(super) fn mix_hash(&mut self, data: &[u8]) { - let mut input = Vec::with_capacity(SHA256_SIZE_USIZE + data.len()); - input.extend_from_slice(&self.h); - input.extend_from_slice(data); - self.h = Self::sha256(&input); - } - - pub(super) fn sha256(data: &[u8]) -> [u8; 32] { - let digest = Sha256::digest(data); - *array_ref![digest, 0, 32] - } - - pub(super) fn take_e(&mut self) -> Result { - self.e.take().ok_or_else(|| { - Error::new( - Origin::KeyExchange, - Kind::Invalid, - "key id e should have been set", - ) - }) - } - - pub(super) fn take_k(&mut self) -> Result { - self.k.take().ok_or_else(|| { - Error::new( - Origin::KeyExchange, - Kind::Invalid, - "key id k should have been set", - ) - }) - } - - pub(super) fn take_ck(&mut self) -> Result { - self.ck.take().ok_or_else(|| { - Error::new( - Origin::KeyExchange, - Kind::Invalid, - "key id ck should have been set", - ) - }) - } - - pub(super) fn s(&self) -> Result<&KeyId> { - self.s.as_ref().ok_or_else(|| { - Error::new( - Origin::KeyExchange, - Kind::Invalid, - "key id s should have been set", - ) - }) - } - - pub(super) fn e(&self) -> Result<&KeyId> { - self.e.as_ref().ok_or_else(|| { - Error::new( - Origin::KeyExchange, - Kind::Invalid, - "key id e should have been set", - ) - }) - } - - pub(super) fn k(&self) -> Result<&KeyId> { - self.k.as_ref().ok_or_else(|| { - Error::new( - Origin::KeyExchange, - Kind::Invalid, - "key id k should have been set", - ) - }) - } - - pub(super) fn ck(&self) -> Result<&KeyId> { - self.ck.as_ref().ok_or_else(|| { - Error::new( - Origin::KeyExchange, - Kind::Invalid, - "key id ck should have been set", - ) - }) - } - - pub(super) fn re(&self) -> Result<&PublicKey> { - self.re.as_ref().ok_or_else(|| { - Error::new( - Origin::KeyExchange, - Kind::Invalid, - "public key id re should have been set", - ) - }) - } - - pub(super) fn rs(&self) -> Result<&PublicKey> { - self.rs.as_ref().ok_or_else(|| { - Error::new( - Origin::KeyExchange, - Kind::Invalid, - "public key id rs should have been set", - ) - }) - } -} - -#[cfg(test)] -mod tests { - use super::super::super::super::{identities, to_xx_vault}; - use super::*; - use hex::decode; - use ockam_core::Result; - - #[tokio::test] - async fn test_initialization() -> Result<()> { - let identities = identities(); - let vault = to_xx_vault(identities.vault()); - - let static_key = vault - .create_ephemeral_secret(SecretAttributes::X25519) - .await?; - let mut handshake = Handshake::new(vault.clone(), static_key).await?; - handshake.initialize().await?; - - let exp_h = [ - 93, 247, 43, 103, 185, 101, 173, 209, 22, 143, 10, 108, 117, 109, 242, 28, 32, 79, 126, - 100, 252, 104, 43, 230, 163, 171, 75, 104, 44, 141, 182, 75, - ]; - - assert_eq!(handshake.state.h, exp_h); - - let ck = vault - .get_ephemeral_secret(handshake.state.ck()?, "ck") - .await?; - - assert_eq!( - ck.secret().as_ref(), - *b"Noise_XX_25519_AESGCM_SHA256\0\0\0\0" - ); - assert_eq!(handshake.state.n, 0); - Ok(()) - } - - #[tokio::test] - async fn test_full_handshake1() -> Result<()> { - let handshake_messages = HandshakeMessages { - initiator_static_key: decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), - initiator_ephemeral_key: decode("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f").unwrap(), - responder_static_key: decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20").unwrap(), - responder_ephemeral_key: decode("4142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60").unwrap(), - message1_payload: decode("").unwrap(), - message1_ciphertext: decode("358072d6365880d1aeea329adf9121383851ed21a28e3b75e965d0d2cd166254").unwrap(), - message2_payload: decode("").unwrap(), - message2_ciphertext: decode("64b101b1d0be5a8704bd078f9895001fc03e8e9f9522f188dd128d9846d484665393019dbd6f438795da206db0886610b26108e424142c2e9b5fd1f7ea70cde8767ce62d7e3c0e9bcefe4ab872c0505b9e824df091b74ffe10a2b32809cab21f").unwrap(), - message3_payload: decode("").unwrap(), - message3_ciphertext: decode("e610eadc4b00c17708bf223f29a66f02342fbedf6c0044736544b9271821ae40e70144cecd9d265dffdc5bb8e051c3f83db32a425e04d8f510c58a43325fbc56").unwrap(), - }; - - check_handshake(handshake_messages).await?; - Ok(()) - } - - #[tokio::test] - async fn test_full_handshake2() -> Result<()> { - let handshake_messages = HandshakeMessages { - initiator_static_key: decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap(), - initiator_ephemeral_key: decode("202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f").unwrap(), - responder_static_key: decode("0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20").unwrap(), - responder_ephemeral_key: decode("4142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60").unwrap(), - message1_payload: decode("746573745f6d73675f30").unwrap(), - message1_ciphertext: decode("358072d6365880d1aeea329adf9121383851ed21a28e3b75e965d0d2cd166254746573745f6d73675f30").unwrap(), - message2_payload: decode("746573745f6d73675f31").unwrap(), - message2_ciphertext: decode("64b101b1d0be5a8704bd078f9895001fc03e8e9f9522f188dd128d9846d484665393019dbd6f438795da206db0886610b26108e424142c2e9b5fd1f7ea70cde8c9f29dcec8d3ab554f4a5330657867fe4917917195c8cf360e08d6dc5f71baf875ec6e3bfc7afda4c9c2").unwrap(), - message3_payload: decode("746573745f6d73675f32").unwrap(), - message3_ciphertext: decode("e610eadc4b00c17708bf223f29a66f02342fbedf6c0044736544b9271821ae40232c55cd96d1350af861f6a04978f7d5e070c07602c6b84d25a331242a71c50ae31dd4c164267fd48bd2").unwrap(), - }; - - check_handshake(handshake_messages).await?; - Ok(()) - } - - // -------------------- - // TESTS IMPLEMENTATION - // -------------------- - - struct HandshakeMessages { - initiator_static_key: Vec, - initiator_ephemeral_key: Vec, - responder_static_key: Vec, - responder_ephemeral_key: Vec, - message1_payload: Vec, - message1_ciphertext: Vec, - message2_payload: Vec, - message2_ciphertext: Vec, - message3_payload: Vec, - message3_ciphertext: Vec, - } - - async fn check_handshake(messages: HandshakeMessages) -> Result<()> { - let vault = to_xx_vault(identities().vault()); - - let initiator_static_key_id = vault - .import_ephemeral_secret( - Secret::new(messages.initiator_static_key), - SecretAttributes::X25519, - ) - .await?; - let initiator_ephemeral_key_id = vault - .import_ephemeral_secret( - Secret::new(messages.initiator_ephemeral_key), - SecretAttributes::X25519, - ) - .await?; - let mut initiator = Handshake::new_with_keys( - vault.clone(), - initiator_static_key_id, - initiator_ephemeral_key_id, - ) - .await?; - - let responder_static_key_id = vault - .import_ephemeral_secret( - Secret::new(messages.responder_static_key), - SecretAttributes::X25519, - ) - .await?; - let responder_ephemeral_key_id = vault - .import_ephemeral_secret( - Secret::new(messages.responder_ephemeral_key), - SecretAttributes::X25519, - ) - .await?; - let mut responder = Handshake::new_with_keys( - vault.clone(), - responder_static_key_id, - responder_ephemeral_key_id, - ) - .await?; - initiator.initialize().await?; - responder.initialize().await?; - - let result = initiator - .encode_message1(&messages.message1_payload) - .await?; - assert_eq!(result, messages.message1_ciphertext); - - let decoded = responder.decode_message1(&result).await?; - assert_eq!(decoded, messages.message1_payload); - - let result = responder - .encode_message2(&messages.message2_payload) - .await?; - assert_eq!(result, messages.message2_ciphertext); - - let decoded = initiator.decode_message2(&result).await?; - assert_eq!(decoded, messages.message2_payload); - - let result = initiator - .encode_message3(&messages.message3_payload) - .await?; - assert_eq!(result, messages.message3_ciphertext); - - let decoded = responder.decode_message3(&result).await?; - assert_eq!(decoded, messages.message3_payload); - - let result = initiator.set_final_state(Role::Responder).await; - assert!(result.is_ok()); - - let result = responder.set_final_state(Role::Initiator).await; - assert!(result.is_ok()); - - Ok(()) - } - - impl Handshake { - /// Initialize the handshake - async fn new_with_keys( - vault: Arc, - static_key: KeyId, - ephemeral_key: KeyId, - ) -> Result { - Ok(Handshake { - vault, - state: HandshakeState::new(static_key, ephemeral_key), - }) - } - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/handshake_state_machine.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/handshake_state_machine.rs deleted file mode 100644 index 8ea0417ac79..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/handshake_state_machine.rs +++ /dev/null @@ -1,264 +0,0 @@ -use minicbor::{Decode, Encode}; -use ockam_core::compat::sync::Arc; -use ockam_core::compat::{boxed::Box, vec::Vec}; -use ockam_core::{async_trait, Result}; -use ockam_vault::{KeyId, PublicKey, SecretType}; -use tracing::info; - -use super::super::super::models::{ - ChangeHistory, CredentialAndPurposeKey, Identifier, PurposeKeyAttestation, PurposePublicKey, -}; -use super::super::super::{ - Identities, Identity, IdentityError, IdentityHistoryComparison, SecureChannelTrustInfo, - TrustContext, TrustPolicy, -}; - -/// Interface for a state machine in a key exchange protocol -#[async_trait] -pub(super) trait StateMachine: Send + Sync + 'static { - async fn on_event(&mut self, event: Event) -> Result; - fn get_handshake_results(&self) -> Option; -} - -/// Events received by the state machine, either initializing the state machine -/// or receiving a message from the other party -#[derive(Debug, Clone, PartialEq, Eq)] -pub(super) enum Event { - Initialize, - ReceivedMessage(Vec), -} - -/// Outcome of processing an event: either no action or a message to send to the other party -#[derive(Debug, Clone, PartialEq, Eq)] -pub(super) enum Action { - NoAction, - SendMessage(Vec), -} - -/// List of possible states for the initiator or responder sides of the exchange -#[derive(Debug, Clone)] -pub(super) enum Status { - Initial, - WaitingForMessage1, - WaitingForMessage2, - WaitingForMessage3, - Ready(HandshakeKeys), -} - -/// At the end of a successful handshake a pair of encryption/decryption keys is available -#[derive(Debug, Clone)] -pub(super) struct HandshakeKeys { - pub(super) encryption_key: KeyId, - pub(super) decryption_key: KeyId, -} - -/// The end result of a handshake with identity/credentials exchange is -/// a pair of encryption/decryption keys + the identity of the other party -#[derive(Debug, Clone)] -pub(super) struct HandshakeResults { - pub(super) handshake_keys: HandshakeKeys, - pub(super) their_identifier: Identifier, -} - -/// This struct implements functions common to both initiator and the responder state machines -pub(super) struct CommonStateMachine { - pub(super) identities: Arc, - pub(super) identifier: Identifier, - pub(super) purpose_key_attestation: PurposeKeyAttestation, - pub(super) credentials: Vec, - pub(super) trust_policy: Arc, - pub(super) trust_context: Option, - their_identifier: Option, -} - -impl CommonStateMachine { - pub(super) fn new( - identities: Arc, - identifier: Identifier, - purpose_key_attestation: PurposeKeyAttestation, - credentials: Vec, - trust_policy: Arc, - trust_context: Option, - ) -> Self { - Self { - identities, - identifier, - purpose_key_attestation, - credentials, - trust_policy, - trust_context, - their_identifier: None, - } - } - - /// Prepare a payload containing the identity of the current party and serialize it. - /// That payload contains: - /// - /// - the current Identity Change History - /// - the current Secure Channel Purpose Key Attestation - /// - the Identity Credentials and corresponding Credentials Purpose Key Attestations - /// - pub(super) async fn make_identity_payload(&self) -> Result> { - // prepare the payload that will be sent either in message 2 or message 3 - let change_history = self - .identities - .repository() - .get_identity(&self.identifier) - .await?; - let payload = IdentityAndCredentials { - change_history, - purpose_key_attestation: self.purpose_key_attestation.clone(), - credentials: self.credentials.clone(), - }; - Ok(minicbor::to_vec(payload)?) - } - - /// Verify the identity sent by the other party: the Purpose Key and the credentials must be valid - /// If everything is valid, store the identity identifier which will used to make the - /// final state machine result - pub(super) async fn verify_identity( - &mut self, - peer: IdentityAndCredentials, - peer_public_key: &PublicKey, - ) -> Result<()> { - let identity = Identity::import_from_change_history( - None, - peer.change_history.clone(), - self.identities.vault(), - ) - .await?; - - if let Some(known_identity) = self - .identities - .repository() - .retrieve_identity(identity.identifier()) - .await? - { - let known_identity = Identity::import_from_change_history( - Some(identity.identifier()), - known_identity, - self.identities.vault(), - ) - .await?; - - match identity.compare(&known_identity) { - IdentityHistoryComparison::Conflict | IdentityHistoryComparison::Older => { - return Err(IdentityError::ConsistencyError.into()); - } - IdentityHistoryComparison::Newer => { - self.identities - .repository() - .update_identity(identity.identifier(), identity.change_history()) - .await?; - } - IdentityHistoryComparison::Equal => {} - } - } else { - self.identities - .repository() - .update_identity(identity.identifier(), identity.change_history()) - .await?; - } - - let purpose_key = self - .identities - .purpose_keys() - .verify_purpose_key_attestation(&peer.purpose_key_attestation) - .await?; - - if peer_public_key.stype() != SecretType::X25519 { - return Err(IdentityError::InvalidKeyType.into()); - } - - match &purpose_key.public_key { - PurposePublicKey::SecureChannelStaticKey(public_key) => { - if public_key.0 != peer_public_key.data() { - return Err(IdentityError::InvalidKeyType.into()); - } - } - PurposePublicKey::CredentialSigningKey(_) => { - return Err(IdentityError::InvalidKeyType.into()) - } - } - - self.verify_credentials(&identity, peer.credentials).await?; - self.their_identifier = Some(identity.identifier().clone()); - Ok(()) - } - - /// Verify that the credentials sent by the other party are valid using a trust context - /// and store them - async fn verify_credentials( - &self, - their_identity: &Identity, - credentials: Vec, - ) -> Result<()> { - // check our TrustPolicy - let trust_info = SecureChannelTrustInfo::new(their_identity.identifier().clone()); - let trusted = self.trust_policy.check(&trust_info).await?; - if !trusted { - // TODO: Shutdown? Communicate error? - return Err(IdentityError::SecureChannelTrustCheckFailed.into()); - } - info!( - "Initiator checked trust policy for SecureChannel from: {}", - their_identity.identifier() - ); - - if let Some(trust_context) = &self.trust_context { - for credential in credentials { - let result = self - .identities - .credentials() - .receive_presented_credential( - their_identity.identifier(), - &[trust_context.authority()?.identifier().clone()], - &credential, - ) - .await; - - if let Some(_err) = result.err() { - // TODO: consider the possibility of keep going when a credential validation fails - return Err(IdentityError::SecureChannelVerificationFailed.into()); - } - } - } else if !credentials.is_empty() { - // we cannot validate credentials without a trust context - return Err(IdentityError::SecureChannelVerificationFailed.into()); - }; - - Ok(()) - } - - /// Return the results of the full handshake - /// - the other party identity - /// - the encryption and decryption keys to use on the next messages to exchange - pub(super) fn make_handshake_results( - &self, - handshake_keys: Option, - ) -> Option { - match (self.their_identifier.clone(), handshake_keys) { - (Some(their_identifier), Some(handshake_keys)) => Some(HandshakeResults { - their_identifier, - handshake_keys, - }), - _ => None, - } - } -} - -/// This internal structure is used as a payload in the XX protocol -#[derive(Debug, Clone, Encode, Decode)] -#[rustfmt::skip] -#[cbor(map)] -pub(super) struct IdentityAndCredentials { - /// Exported identity - #[n(1)] pub(super) change_history: ChangeHistory, - /// The Purpose Key guarantees that the other end has access to the private key of the identity - /// The Purpose Key here is also the static key of the noise ('x') and is issued with the static - /// key of the identity - #[n(2)] pub(super) purpose_key_attestation: PurposeKeyAttestation, - /// Credentials associated to the identity along with corresponding Credentials Purpose Keys - /// to verify those Credentials - #[n(3)] pub(super) credentials: Vec, -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/handshake_worker.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/handshake_worker.rs deleted file mode 100644 index ee75971ed81..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/handshake_worker.rs +++ /dev/null @@ -1,362 +0,0 @@ -use alloc::sync::Arc; -use core::time::Duration; -use ockam_core::compat::{boxed::Box, vec::Vec}; -use ockam_core::errcode::{Kind, Origin}; -use ockam_core::{ - AllowAll, Any, Decodable, DenyAll, Error, Mailbox, Mailboxes, OutgoingAccessControl, Route, - Routed, -}; -use ockam_core::{AllowOnwardAddress, Result, Worker}; -use ockam_node::callback::CallbackSender; -use ockam_node::{Context, WorkerBuilder}; -use tracing::{debug, info}; - -use super::super::super::models::{CredentialAndPurposeKey, Identifier}; -use super::super::super::secure_channel::decryptor::DecryptorHandler; -use super::super::super::secure_channel::encryptor::Encryptor; -use super::super::super::secure_channel::encryptor_worker::EncryptorWorker; -use super::super::super::secure_channel::handshake::handshake_state_machine::Action::SendMessage; -use super::super::super::secure_channel::handshake::handshake_state_machine::Event::{ - Initialize, ReceivedMessage, -}; -use super::super::super::secure_channel::handshake::handshake_state_machine::{ - Action, HandshakeResults, StateMachine, -}; -use super::super::super::secure_channel::handshake::initiator_state_machine::InitiatorStateMachine; -use super::super::super::secure_channel::handshake::responder_state_machine::ResponderStateMachine; -use super::super::super::secure_channel::{Addresses, Role}; -use super::super::super::{ - to_xx_initialized, to_xx_vault, IdentityError, PurposeKey, SecureChannelRegistryEntry, - SecureChannels, TrustContext, TrustPolicy, -}; - -/// This struct implements a Worker receiving and sending messages -/// on one side of the secure channel creation as specified with its role: INITIATOR or REPSONDER -pub(crate) struct HandshakeWorker { - secure_channels: Arc, - callback_sender: Option>, - state_machine: Box, - identifier: Identifier, - addresses: Addresses, - role: Role, - remote_route: Option, - decryptor_handler: Option, -} - -#[ockam_core::worker] -impl Worker for HandshakeWorker { - type Message = Any; - type Context = Context; - - /// Initialize the state machine with an `Initialize` event - /// Depending on the state machine role there might be a message to send to the other party - async fn initialize(&mut self, context: &mut Self::Context) -> Result<()> { - match self.state_machine.on_event(Initialize).await? { - SendMessage(message) => { - info!( - "remote route {:?}, decryptor remote {:?}", - self.remote_route.clone(), - self.addresses.decryptor_remote.clone() - ); - context - .send_from_address( - self.remote_route()?, - message, - self.addresses.decryptor_remote.clone(), - ) - .await - } - Action::NoAction => Ok(()), - } - } - - /// Handle a message coming from the other party - /// If the handshake has been fully performed then we can delegate this message to the - /// secure channel Decryptor. - /// Otherwise we unpack the message payload and send it to the state machine to trigger - /// a transition - async fn handle_message( - &mut self, - context: &mut Self::Context, - message: Routed, - ) -> Result<()> { - // Once the decryptor has been initialized, let it handle messages - // Some messages can come from other systems using the remote address - // and some messages can come from the current node when the decryptor - // used to support the decryption of Kafka messages for example - if let Some(decryptor_handler) = self.decryptor_handler.as_mut() { - let msg_addr = message.msg_addr(); - - let result = if msg_addr == self.addresses.decryptor_remote { - decryptor_handler.handle_decrypt(context, message).await - } else if msg_addr == self.addresses.decryptor_api { - decryptor_handler.handle_decrypt_api(context, message).await - } else { - Err(IdentityError::UnknownChannelMsgDestination.into()) - }; - return result; - }; - - let transport_message = message.into_transport_message(); - if let SendMessage(message) = self - .state_machine - .on_event(ReceivedMessage(Vec::::decode( - &transport_message.payload, - )?)) - .await? - { - // set the remote route by taking the most up to date message return route - // In the case of the initiator the first return route mentions the secure channel listener - // address so we need to wait for the return route corresponding to the remote handshake worker - // when it has been spawned - self.remote_route = Some(transport_message.return_route); - - context - .send_from_address( - self.remote_route()?, - message, - self.addresses.decryptor_remote.clone(), - ) - .await? - }; - - // if we reached the final state we can make a pair of encryptor/decryptor - if let Some(final_state) = self.state_machine.get_handshake_results() { - // start the encryptor worker and return the decryptor - self.decryptor_handler = Some(self.finalize(context, final_state).await?); - if let Some(callback_sender) = self.callback_sender.take() { - callback_sender.send(())?; - } - }; - - Ok(()) - } - - async fn shutdown(&mut self, context: &mut Self::Context) -> Result<()> { - let _ = context.stop_worker(self.addresses.encryptor.clone()).await; - self.secure_channels - .secure_channel_registry - .unregister_channel(&self.addresses.encryptor); - - if let Some(handler) = &self.decryptor_handler { - handler.shutdown().await? - } - - Ok(()) - } -} - -impl HandshakeWorker { - /// Create a new HandshakeWorker with a role of either INITIATOR or RESPONDER - #[allow(clippy::too_many_arguments)] - pub(crate) async fn create( - context: &Context, - secure_channels: Arc, - addresses: Addresses, - identifier: Identifier, - purpose_key: PurposeKey, - trust_policy: Arc, - decryptor_outgoing_access_control: Arc, - credentials: Vec, - trust_context: Option, - remote_route: Option, - timeout: Option, - role: Role, - ) -> Result<()> { - let vault = to_xx_vault(secure_channels.vault()); - let identities = secure_channels.identities(); - let state_machine: Box = if role.is_initiator() { - Box::new( - InitiatorStateMachine::new( - vault, - identities, - identifier.clone(), - purpose_key, - credentials, - trust_policy, - trust_context, - ) - .await?, - ) - } else { - Box::new( - ResponderStateMachine::new( - vault, - identities, - identifier.clone(), - purpose_key, - credentials, - trust_policy, - trust_context, - ) - .await?, - ) - }; - - let (callback_waiter, callback_sender) = if role.is_initiator() { - let callback = ockam_node::callback::new_callback(); - (Some(callback.0), Some(callback.1)) - } else { - (None, None) - }; - - let worker = Self { - secure_channels, - callback_sender, - state_machine, - identifier, - role, - remote_route: remote_route.clone(), - addresses: addresses.clone(), - decryptor_handler: None, - }; - - WorkerBuilder::new(worker) - .with_mailboxes(Self::create_mailboxes( - &addresses, - decryptor_outgoing_access_control, - )) - .start(context) - .await?; - - let decryptor_remote = addresses.decryptor_remote.clone(); - debug!( - "Starting SecureChannel {} at remote: {}", - role, &decryptor_remote - ); - - // before sending messages make sure that the handshake is finished and - // the encryptor worker is ready - if role.is_initiator() { - if let Some(callback_waiter) = callback_waiter { - // wait until the handshake is finished - if let Some(timeout) = timeout { - callback_waiter.receive_timeout(timeout).await?; - } else { - callback_waiter.receive().await?; - } - } - } - - Ok(()) - } - - /// Return the route for the other party's handshake worker - fn remote_route(&self) -> Result { - self.remote_route.clone().ok_or_else(|| { - Error::new( - Origin::KeyExchange, - Kind::Invalid, - "a remote route should have been already set", - ) - }) - } - - /// Create mailboxes and access rights for the workers involved in the secure channel creation - pub(crate) fn create_mailboxes( - addresses: &Addresses, - decryptor_outgoing_access_control: Arc, - ) -> Mailboxes { - let remote_mailbox = Mailbox::new( - addresses.decryptor_remote.clone(), - // Doesn't matter since we check incoming messages cryptographically, - // but this may be reduced to allowing only from the transport connection that was used - // to create this channel initially - Arc::new(AllowAll), - // Communicate to the other side of the channel during key exchange - Arc::new(AllowAll), - ); - let internal_mailbox = Mailbox::new( - addresses.decryptor_internal.clone(), - Arc::new(DenyAll), - decryptor_outgoing_access_control, - ); - let api_mailbox = Mailbox::new( - addresses.decryptor_api.clone(), - Arc::new(AllowAll), - Arc::new(AllowAll), - ); - - Mailboxes::new(remote_mailbox, vec![internal_mailbox, api_mailbox]) - } - - /// Finalize the handshake by creating a `Decryptor` and an `EncryptorWorker` - /// Note that `EncryptorWorker` is actually started as an independent worker while - /// the `Decryptor` is directly used by this worker to delegate the decryption of messages - async fn finalize( - &self, - context: &Context, - handshake_results: HandshakeResults, - ) -> Result { - // create a decryptor to delegate the processing of all messages after the handshake - let decryptor = DecryptorHandler::new( - self.role.str(), - self.addresses.clone(), - handshake_results.handshake_keys.decryption_key, - to_xx_initialized(self.secure_channels.identities.vault()), - handshake_results.their_identifier.clone(), - ); - - // create a separate encryptor worker which will be started independently - { - let encryptor = EncryptorWorker::new( - self.role.str(), - self.addresses.clone(), - self.remote_route()?, - Encryptor::new( - handshake_results.handshake_keys.encryption_key, - 0, - to_xx_initialized(self.secure_channels.identities.vault()), - ), - ); - - let next_hop = self.remote_route()?.next()?.clone(); - let main_mailbox = Mailbox::new( - self.addresses.encryptor.clone(), - Arc::new(AllowAll), - Arc::new(AllowOnwardAddress(next_hop)), - ); - let api_mailbox = Mailbox::new( - self.addresses.encryptor_api.clone(), - Arc::new(AllowAll), - Arc::new(AllowAll), - ); - - WorkerBuilder::new(encryptor) - .with_mailboxes(Mailboxes::new(main_mailbox, vec![api_mailbox])) - .start(context) - .await?; - } - - info!( - "Initialized SecureChannel {} at local: {}, remote: {}", - self.role.str(), - &self.addresses.encryptor, - &self.addresses.decryptor_remote - ); - - let their_decryptor_address = self - .remote_route()? - .iter() - .last() - .expect("the remote route should not be empty") - .clone(); - - let info = SecureChannelRegistryEntry::new( - self.addresses.encryptor.clone(), - self.addresses.encryptor_api.clone(), - self.addresses.decryptor_remote.clone(), - self.addresses.decryptor_api.clone(), - self.role.is_initiator(), - self.identifier.clone(), - handshake_results.their_identifier, - their_decryptor_address, - ); - - self.secure_channels - .secure_channel_registry() - .register_channel(info)?; - - Ok(decryptor) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/initiator_state_machine.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/initiator_state_machine.rs deleted file mode 100644 index 98e398a02ad..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/initiator_state_machine.rs +++ /dev/null @@ -1,123 +0,0 @@ -use delegate::delegate; -use ockam_core::async_trait; -use ockam_core::compat::sync::Arc; -use ockam_core::compat::{boxed::Box, vec::Vec}; -use ockam_core::errcode::{Kind, Origin}; -use ockam_core::{Error, Result}; -use ockam_vault::PublicKey; -use Action::*; -use Event::*; -use Role::*; -use Status::*; - -use super::super::super::models::{CredentialAndPurposeKey, Identifier}; -use super::super::super::secure_channel::handshake::error::XXError; -use super::super::super::secure_channel::handshake::handshake::Handshake; -use super::super::super::secure_channel::handshake::handshake_state_machine::{ - Action, CommonStateMachine, Event, HandshakeKeys, HandshakeResults, IdentityAndCredentials, - StateMachine, Status, -}; -use super::super::super::{Identities, PurposeKey, Role, TrustContext, TrustPolicy, XXVault}; - -/// Implementation of a state machine for the key exchange on the initiator side -#[async_trait] -impl StateMachine for InitiatorStateMachine { - async fn on_event(&mut self, event: Event) -> Result { - let state = self.handshake.state.clone(); - match (state.status, event) { - // Initialize the handshake and send message 1 - (Initial, Initialize) => { - self.initialize_handshake().await?; - let message1 = self.encode_message1(&[]).await?; - - // Send message 1 and wait for message 2 - self.handshake.state.status = WaitingForMessage2; - Ok(SendMessage(message1)) - } - // Process message 2 and send message 3 - (WaitingForMessage2, ReceivedMessage(message)) => { - let message2_payload = self.decode_message2(&message).await?; - let their_identity_payload: IdentityAndCredentials = - minicbor::decode(&message2_payload)?; - self.verify_identity(their_identity_payload, &self.handshake.state.rs()?.clone()) - .await?; - let identity_payload = self - .identity_payload - .take() - .ok_or(XXError::InvalidInternalState)?; - let message3 = self.encode_message3(&identity_payload).await?; - self.set_final_state(Initiator).await?; - Ok(SendMessage(message3)) - } - // incorrect state / event - (s, e) => Err(Error::new( - Origin::Channel, - Kind::Invalid, - format!( - "Unexpected combination of initiator state and event {:?}/{:?}", - s, e - ), - )), - } - } - - fn get_handshake_results(&self) -> Option { - self.make_handshake_results(self.get_handshake_keys()) - } -} - -/// Implementation of the state machine actions, delegated to the Handshake module -pub(super) struct InitiatorStateMachine { - pub(super) common: CommonStateMachine, - pub(super) handshake: Handshake, - /// this serialized payload contains an identity, its credentials and a signature of its static key - pub(super) identity_payload: Option>, -} - -impl InitiatorStateMachine { - delegate! { - to self.common { - async fn verify_identity(&mut self, peer: IdentityAndCredentials, peer_public_key: &PublicKey) -> Result<()>; - fn make_handshake_results(&self, handshake_keys: Option) -> Option; - } - } - delegate! { - to self.handshake { - #[call(initialize)] - async fn initialize_handshake(&mut self) -> Result<()>; - async fn encode_message1(&mut self, payload: &[u8]) -> Result>; - async fn decode_message2(&mut self, message: &[u8]) -> Result>; - async fn encode_message3(&mut self, payload: &[u8]) -> Result>; - async fn set_final_state(&mut self, role: Role) -> Result<()>; - fn get_handshake_keys(&self) -> Option; - } - } -} - -impl InitiatorStateMachine { - pub async fn new( - vault: Arc, - identities: Arc, - identifier: Identifier, - purpose_key: PurposeKey, - credentials: Vec, - trust_policy: Arc, - trust_context: Option, - ) -> Result { - let common = CommonStateMachine::new( - identities, - identifier, - purpose_key.attestation().clone(), - credentials, - trust_policy, - trust_context, - ); - let identity_payload = common.make_identity_payload().await?; - - Ok(InitiatorStateMachine { - common, - handshake: Handshake::new(vault, purpose_key.key_id().clone()).await?, - identity_payload: Some(identity_payload), - }) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/mod.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/mod.rs deleted file mode 100644 index 66f4c881326..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -mod error; - -// This directive makes sure that we only run the handshake protocol if it has been compiled -// on a little endian system since it is not supporting a big endian one at the moment -#[cfg(not(target_endian = "little"))] -compile_error!("Key Exchange is only supported on little-endian machines"); -#[allow(clippy::module_inception)] -mod handshake; -mod handshake_state_machine; -pub(crate) mod handshake_worker; -mod initiator_state_machine; -mod responder_state_machine; diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/responder_state_machine.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/responder_state_machine.rs deleted file mode 100644 index 743513d1ca9..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/handshake/responder_state_machine.rs +++ /dev/null @@ -1,126 +0,0 @@ -use async_trait::async_trait; -use delegate::delegate; -use ockam_core::compat::sync::Arc; -use ockam_core::compat::{boxed::Box, vec::Vec}; -use ockam_core::errcode::{Kind, Origin}; -use ockam_core::{Error, Result}; -use ockam_vault::PublicKey; -use Action::*; -use Event::*; -use Role::*; -use Status::*; - -use super::super::super::models::{CredentialAndPurposeKey, Identifier}; -use super::super::super::secure_channel::handshake::error::XXError; -use super::super::super::secure_channel::handshake::handshake::Handshake; -use super::super::super::secure_channel::handshake::handshake_state_machine::{ - Action, CommonStateMachine, Event, HandshakeKeys, HandshakeResults, IdentityAndCredentials, - StateMachine, Status, -}; -use super::super::super::{Identities, PurposeKey, Role, TrustContext, TrustPolicy, XXVault}; - -/// Implementation of a state machine for the key exchange on the responder side -#[async_trait] -impl StateMachine for ResponderStateMachine { - async fn on_event(&mut self, event: Event) -> Result { - let state = self.handshake.state.clone(); - match (state.status, event) { - // Initialize the handshake and wait for message 1 - (Initial, Initialize) => { - self.initialize_handshake().await?; - self.handshake.state.status = WaitingForMessage1; - Ok(NoAction) - } - // Process message 1 and send message 2 - (WaitingForMessage1, ReceivedMessage(message)) => { - self.decode_message1(&message).await?; - let identity_payload = self - .identity_payload - .take() - .ok_or(XXError::InvalidInternalState)?; - let message2 = self.encode_message2(&identity_payload).await?; - - self.handshake.state.status = WaitingForMessage3; - Ok(SendMessage(message2)) - } - // Process message 3 - (WaitingForMessage3, ReceivedMessage(message)) => { - let message3_payload = self.decode_message3(&message).await?; - let their_identity_payload: IdentityAndCredentials = - minicbor::decode(&message3_payload)?; - self.verify_identity(their_identity_payload, &self.handshake.state.rs()?.clone()) - .await?; - self.set_final_state(Responder).await?; - Ok(NoAction) - } - // incorrect state / event - (s, e) => Err(Error::new( - Origin::Channel, - Kind::Invalid, - format!( - "Unexpected combination of responder state and event {:?}/{:?}", - s, e - ), - )), - } - } - - fn get_handshake_results(&self) -> Option { - self.make_handshake_results(self.get_handshake_keys()) - } -} - -pub struct ResponderStateMachine { - common: CommonStateMachine, - handshake: Handshake, - /// this serialized payload contains an identity, its credentials and a signature of its static key - identity_payload: Option>, -} - -impl ResponderStateMachine { - delegate! { - to self.common { - async fn verify_identity(&mut self, peer: IdentityAndCredentials, peer_public_key: &PublicKey) -> Result<()>; - fn make_handshake_results(&self, handshake_keys: Option) -> Option; - } - } - delegate! { - to self.handshake { - #[call(initialize)] - async fn initialize_handshake(&mut self) -> Result<()>; - async fn decode_message1(&mut self, message: &[u8]) -> Result>; - async fn encode_message2(&mut self, payload: &[u8]) -> Result>; - async fn decode_message3(&mut self, message: &[u8]) -> Result>; - async fn set_final_state(&mut self, role: Role) -> Result<()>; - fn get_handshake_keys(&self) -> Option; - } - } -} - -impl ResponderStateMachine { - pub async fn new( - vault: Arc, - identities: Arc, - identifier: Identifier, - purpose_key: PurposeKey, - credentials: Vec, - trust_policy: Arc, - trust_context: Option, - ) -> Result { - let common = CommonStateMachine::new( - identities, - identifier, - purpose_key.attestation().clone(), - credentials, - trust_policy, - trust_context, - ); - let identity_payload = common.make_identity_payload().await?; - - Ok(ResponderStateMachine { - common, - handshake: Handshake::new(vault, purpose_key.key_id().clone()).await?, - identity_payload: Some(identity_payload), - }) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/key_tracker.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/key_tracker.rs deleted file mode 100644 index 8e0f394d3c2..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/key_tracker.rs +++ /dev/null @@ -1,263 +0,0 @@ -use ockam_core::Result; -use ockam_vault::KeyId; -use tracing::debug; -use tracing::warn; - -use super::super::IdentityError; - -pub(crate) struct KeyTracker { - pub(crate) current_key: KeyId, - pub(crate) previous_key: Option, - number_of_rekeys: u64, - max_rekeys_reached: bool, - renewal_interval: u64, -} - -impl KeyTracker { - pub(crate) fn new(key_id: KeyId, renewal_interval: u64) -> Self { - KeyTracker { - current_key: key_id, - number_of_rekeys: 0, - max_rekeys_reached: false, - previous_key: None, - renewal_interval, - } - } -} - -impl KeyTracker { - /// The rekeying algorithm specifies a series of intervals of size self.renewal_interval - /// where each interval corresponds to a set of contiguous nonces using the same key. - /// - /// This function returns the key corresponding to the current nonce. - /// - /// This is either: - /// - the current key if the nonce falls into the current interval - /// - the previous key if the nonce falls before the current interval - /// - nothing if the the nonce falls after the current interval -> this indicates that a new key must be created - /// - an error if - /// - if the the nonce falls before the previous interval - /// - if it the previous nonce but is not set - /// - we reached the maximum number of rekeyings - pub(crate) fn get_key(&self, nonce: u64) -> Result> { - debug!( - "The current number of rekeys is {}, the rekey interval is {}", - self.number_of_rekeys, self.renewal_interval - ); - - // for example 2 rekeys happened, renewal every 10 keys - // current batch of nonces -> 20 to 29 - let current_interval_start = self.number_of_rekeys * self.renewal_interval; - - // if we reached the maximum number of rekeyings we stop operating on this secure channel - if self.max_rekeys_reached { - warn!("The maximum number of available rekeying operation has been reached. The last interval was starting at {} and the interval size is {}", - current_interval_start, self.renewal_interval); - return Err(IdentityError::InvalidNonce.into()); - }; - - if nonce >= current_interval_start { - let nonce_age = nonce - current_interval_start; - // if the nonce falls in the current interval return the current key - if nonce_age < self.renewal_interval { - Ok(Some(self.current_key.clone())) - } - // if the nonce falls in the next interval - // otherwise indicate that we need to create a new key - else if nonce_age < self.renewal_interval * 2 { - Ok(None) - } - // otherwise the nonce is too far ahead - else { - warn!("This nonce is too far in the future: {}", nonce); - Err(IdentityError::InvalidNonce.into()) - } - // else return the previous key (if there is one) if the nonce is not too old - } else if current_interval_start - nonce <= self.renewal_interval { - if let Some(previous) = self.previous_key.clone() { - Ok(Some(previous)) - } else { - warn!("There should be a previous key for this nonce: {}", nonce); - Err(IdentityError::InvalidNonce.into()) - } - } else { - warn!("This nonce is too old: {}", nonce); - Err(IdentityError::InvalidNonce.into()) - } - } - - // Update the key if a key renewal happened - pub(crate) fn update_key(&mut self, decryption_key: KeyId) -> Result> { - let mut key_to_delete = None; - // if the key used for the decryption is not the current key nor the previous key - // this means that a rekeying happened - if decryption_key != self.current_key && Some(decryption_key.clone()) != self.previous_key { - if let Some(previous) = self.previous_key.clone() { - key_to_delete = Some(previous) - } - self.previous_key.replace(self.current_key.clone()); - self.current_key = decryption_key; - if u64::MAX - self.number_of_rekeys * self.renewal_interval < self.renewal_interval { - self.max_rekeys_reached = true; - } else { - self.number_of_rekeys += 1; - } - } - Ok(key_to_delete) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_get_key_first_interval() { - let key_id = "key_id".to_string(); - let key_tracker = KeyTracker::new(key_id.clone(), 10); - - assert_eq!(key_tracker.get_key(0).unwrap(), Some(key_id.clone())); - assert_eq!(key_tracker.get_key(5).unwrap(), Some(key_id.clone())); - assert_eq!(key_tracker.get_key(9).unwrap(), Some(key_id)); - assert_eq!( - key_tracker.get_key(10).unwrap(), - None, - "the next key must be created" - ); - assert_eq!( - key_tracker.get_key(20).ok(), - None, - "this nonce is too far in the future" - ); - assert_eq!( - key_tracker.get_key(u64::MAX).ok(), - None, - "this nonce is too far in the future" - ); - } - - #[test] - fn test_get_key_middle_interval() { - let key_id = "key_id".to_string(); - let previous_key_id = "previous_key_id".to_string(); - let key_tracker = KeyTracker { - current_key: key_id.clone(), - number_of_rekeys: 5, - max_rekeys_reached: false, - previous_key: Some(previous_key_id.clone()), - renewal_interval: 10, - }; - - assert_eq!( - key_tracker.get_key(0).ok(), - None, - "this nonce is too far in the past" - ); - assert_eq!( - key_tracker.get_key(30).ok(), - None, - "this nonce is too far in the past" - ); - assert_eq!( - key_tracker.get_key(39).ok(), - None, - "this nonce is too far in the past" - ); - assert_eq!( - key_tracker.get_key(40).unwrap(), - Some(previous_key_id.clone()) - ); - assert_eq!( - key_tracker.get_key(45).unwrap(), - Some(previous_key_id.clone()) - ); - assert_eq!(key_tracker.get_key(49).unwrap(), Some(previous_key_id)); - assert_eq!(key_tracker.get_key(50).unwrap(), Some(key_id.clone())); - assert_eq!(key_tracker.get_key(59).unwrap(), Some(key_id)); - assert_eq!( - key_tracker.get_key(60).unwrap(), - None, - "the next key must be created" - ); - assert_eq!( - key_tracker.get_key(u64::MAX).ok(), - None, - "this nonce is too far in the future" - ); - } - - #[test] - fn test_get_key_last_interval() { - let key_id = "key_id".to_string(); - let previous_key_id = "previous_key_id".to_string(); - let key_tracker = KeyTracker { - current_key: key_id, - number_of_rekeys: 5, - max_rekeys_reached: true, - previous_key: Some(previous_key_id), - renewal_interval: 10, - }; - - assert_eq!( - key_tracker.get_key(0).ok(), - None, - "we reached the last interval already. The channel needs to be recreated" - ); - } - - #[test] - fn test_update_key() { - let key_id = "key_id".to_string(); - let previous_key_id = "previous_key_id".to_string(); - let new_key_id = "new_key_id".to_string(); - let mut key_tracker = KeyTracker { - current_key: key_id.clone(), - number_of_rekeys: 5, - max_rekeys_reached: false, - previous_key: Some(previous_key_id.clone()), - renewal_interval: 10, - }; - - assert_eq!(key_tracker.update_key(key_id.clone()).unwrap(), None); - assert_eq!( - key_tracker.update_key(previous_key_id.clone()).unwrap(), - None - ); - assert_eq!( - key_tracker.update_key(new_key_id.clone()).unwrap(), - Some(previous_key_id), - "the previous key id must be returned in order to be deleted", - ); - assert_eq!(key_tracker.current_key, new_key_id); - assert_eq!(key_tracker.previous_key, Some(key_id)); - } - - #[test] - fn test_update_key_on_last_interval() { - let key_id = "key_id".to_string(); - let previous_key_id = "previous_key_id".to_string(); - let new_key_id = "new_key_id".to_string(); - let mut key_tracker = KeyTracker { - current_key: key_id, - number_of_rekeys: u64::MAX / 10 - 1, - max_rekeys_reached: false, - previous_key: Some(previous_key_id), - renewal_interval: 10, - }; - - // this brings us to the last interval - key_tracker.update_key(new_key_id).unwrap(); - assert!( - !key_tracker.max_rekeys_reached, - "the maximum number of rekeys is not yet reached" - ); - - // now there are no more intervals available - let new_key_id_2 = "new_key_id_2".to_string(); - key_tracker.update_key(new_key_id_2).unwrap(); - assert!( - key_tracker.max_rekeys_reached, - "the maximum number of rekeys is reached now" - ); - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/listener.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/listener.rs deleted file mode 100644 index 6e134e2e48c..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/listener.rs +++ /dev/null @@ -1,131 +0,0 @@ -use ockam_core::compat::boxed::Box; -use ockam_core::compat::sync::Arc; -use ockam_core::compat::vec::Vec; -use ockam_core::{Address, Any, Result, Routed, Worker}; -use ockam_node::Context; - -use super::super::models::{CredentialAndPurposeKey, Identifier}; -use super::super::secure_channel::addresses::Addresses; -use super::super::secure_channel::handshake_worker::HandshakeWorker; -use super::super::secure_channel::options::SecureChannelListenerOptions; -use super::super::secure_channel::role::Role; -use super::super::secure_channels::secure_channels::SecureChannels; -use super::super::Purpose; - -pub(crate) struct IdentityChannelListener { - secure_channels: Arc, - identifier: Identifier, - options: SecureChannelListenerOptions, -} - -impl IdentityChannelListener { - fn new( - secure_channels: Arc, - identifier: Identifier, - options: SecureChannelListenerOptions, - ) -> Self { - Self { - secure_channels, - identifier, - options, - } - } - - pub async fn create( - ctx: &Context, - secure_channels: Arc, - identifier: &Identifier, - address: Address, - options: SecureChannelListenerOptions, - ) -> Result<()> { - options.setup_flow_control_for_listener(ctx.flow_controls(), &address); - - let listener = Self::new(secure_channels.clone(), identifier.clone(), options); - - ctx.start_worker(address, listener).await?; - - Ok(()) - } - - /// If credentials are not provided via list in options - /// get them from the trust context - async fn get_credentials(&self, ctx: &mut Context) -> Result> { - let credentials = if self.options.credentials.is_empty() { - if let Some(trust_context) = &self.options.trust_context { - vec![ - trust_context - .authority()? - .credential(ctx, &self.identifier) - .await?, - ] - } else { - vec![] - } - } else { - self.options.credentials.clone() - }; - Ok(credentials) - } -} - -#[ockam_core::worker] -impl Worker for IdentityChannelListener { - type Message = Any; - type Context = Context; - - async fn handle_message( - &mut self, - ctx: &mut Self::Context, - message: Routed, - ) -> Result<()> { - let addresses = Addresses::generate(Role::Responder); - let flow_control_id = self.options.setup_flow_control_for_channel( - ctx.flow_controls(), - &addresses, - &message.src_addr(), - ); - let access_control = self - .options - .create_access_control(ctx.flow_controls(), flow_control_id); - - let credentials = self.get_credentials(ctx).await?; - - let purpose_key = self - .secure_channels - .purpose_keys - .repository() - .get_purpose_key(&self.identifier, Purpose::SecureChannel) - .await?; - - let purpose_key = self - .secure_channels - .purpose_keys - .import_purpose_key(&purpose_key) - .await?; - - HandshakeWorker::create( - ctx, - self.secure_channels.clone(), - addresses.clone(), - self.identifier.clone(), - purpose_key, - self.options.trust_policy.clone(), - access_control.decryptor_outgoing_access_control, - credentials, - self.options.trust_context.clone(), - None, - None, - Role::Responder, - ) - .await?; - - let mut local_message = message.into_local_message(); - local_message - .transport_mut() - .onward_route - .modify() - .replace(addresses.decryptor_remote); - - ctx.forward(local_message).await - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/local_info.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/local_info.rs deleted file mode 100644 index 586845e5b21..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/local_info.rs +++ /dev/null @@ -1,79 +0,0 @@ -use ockam_core::compat::vec::Vec; -use ockam_core::{Decodable, Encodable, LocalInfo, LocalMessage, Result}; -use serde::{Deserialize, Serialize}; - -use super::super::models::Identifier; -use super::super::IdentityError; - -/// Identity SecureChannel LocalInfo unique Identifier -pub const IDENTITY_SECURE_CHANNEL_IDENTIFIER: &str = "IDENTITY_SECURE_CHANNEL_IDENTIFIER"; - -/// Identity SecureChannel LocalInfo used for LocalMessage -#[derive(Serialize, Deserialize)] -pub struct IdentitySecureChannelLocalInfo { - their_identity_id: Identifier, -} - -impl IdentitySecureChannelLocalInfo { - /// Try to decode `IdentitySecureChannelLocalInfo` from general `LocalInfo` - pub fn from_local_info(value: &LocalInfo) -> Result { - if value.type_identifier() != IDENTITY_SECURE_CHANNEL_IDENTIFIER { - return Err(IdentityError::InvalidLocalInfoType.into()); - } - - if let Ok(info) = IdentitySecureChannelLocalInfo::decode(value.data()) { - return Ok(info); - } - - Err(IdentityError::InvalidLocalInfoType.into()) - } - - /// Encode `IdentitySecureChannelLocalInfo` to general `LocalInfo` - pub fn to_local_info(&self) -> Result { - Ok(LocalInfo::new( - IDENTITY_SECURE_CHANNEL_IDENTIFIER.into(), - self.encode()?, - )) - } - - /// Find `IdentitySecureChannelLocalInfo` in a list of general `LocalInfo` of that `LocalMessage` - pub fn find_info(local_msg: &LocalMessage) -> Result { - Self::find_info_from_list(local_msg.local_info()) - } - - /// Find `IdentitySecureChannelLocalInfo` in a list of general `LocalInfo` - pub fn find_info_from_list(local_info: &[LocalInfo]) -> Result { - if let Some(local_info) = local_info - .iter() - .find(|x| x.type_identifier() == IDENTITY_SECURE_CHANNEL_IDENTIFIER) - { - Self::from_local_info(local_info) - } else { - Err(IdentityError::InvalidLocalInfoType.into()) - } - } -} - -impl IdentitySecureChannelLocalInfo { - /// Key exchange name - pub fn their_identity_id(&self) -> Identifier { - self.their_identity_id.clone() - } -} - -impl IdentitySecureChannelLocalInfo { - /// Mark a `LocalInfo` vector with `IdentitySecureChannelLocalInfo` - /// replacing any pre-existing entries - pub fn mark( - mut local_info: Vec, - their_identity_id: Identifier, - ) -> Result> { - // strip out any pre-existing IdentitySecureChannelLocalInfo - local_info.retain(|x| x.type_identifier() != IDENTITY_SECURE_CHANNEL_IDENTIFIER); - - // mark the vector - local_info.push(Self { their_identity_id }.to_local_info()?); - - Ok(local_info) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/mod.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/mod.rs deleted file mode 100644 index 1872e6843cf..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/mod.rs +++ /dev/null @@ -1,154 +0,0 @@ -/// Access control data for workers -pub mod access_control; -mod addresses; -mod api; -mod decryptor; -mod encryptor; -mod encryptor_worker; -mod handshake; -mod key_tracker; -mod listener; -mod local_info; -mod nonce_tracker; -mod options; -mod registry; -mod role; -/// List of trust policies to setup ABAC controls -pub mod trust_policy; - -pub use access_control::*; -pub(crate) use addresses::*; -pub use api::*; -pub(crate) use handshake::*; -pub(crate) use listener::*; -pub use local_info::*; -pub use options::*; -pub use registry::*; -pub(crate) use role::*; -pub use trust_policy::*; - -#[cfg(test)] -mod tests { - use super::super::secure_channel::{decryptor::Decryptor, encryptor::Encryptor}; - use ockam_core::Result; - use ockam_vault::{EphemeralSecretsStore, SecretAttributes, Vault}; - use rand::seq::SliceRandom; - use rand::thread_rng; - - #[tokio::test] - async fn test_encrypt_decrypt_normal_flow() { - let (mut encryptor, mut decryptor) = create_encryptor_decryptor().await.unwrap(); - - for n in 0..100 { - let msg = vec![n]; - assert_eq!( - msg, - decryptor - .decrypt(&encryptor.encrypt(&msg).await.unwrap()) - .await - .unwrap() - ); - } - } - - #[tokio::test] - async fn test_encrypt_decrypt_with_message_lost() { - let (mut encryptor, mut decryptor) = create_encryptor_decryptor().await.unwrap(); - - for n in 0..100 { - let msg = vec![n]; - let ciphertext = encryptor.encrypt(&msg).await.unwrap(); - if n % 3 == 0 { - // Two out of three packets are lost, but the ones that do reach the decryptor are - // decrypted ok. - assert_eq!(msg, decryptor.decrypt(&ciphertext).await.unwrap()); - } - } - } - - #[tokio::test] - async fn test_encrypt_decrypt_out_of_order() { - let (mut encryptor, mut decryptor) = create_encryptor_decryptor().await.unwrap(); - - // Vec<(plaintext, ciphertext)> - let mut all_msgs: Vec<(Vec, Vec)> = Vec::new(); - for n in 0..100 { - let mut batch: Vec<(Vec, Vec)> = Vec::new(); - for m in 0..30 { - let msg = vec![n, m]; - let ciphertext = encryptor.encrypt(&msg).await.unwrap(); - batch.push((msg, ciphertext)); - } - batch.shuffle(&mut thread_rng()); - all_msgs.append(&mut batch); - } - - // Displaced up to 8 from the expected order, it is in the accepted window so all - // must be decrypted ok. - for (plaintext, ciphertext) in all_msgs.iter() { - assert_eq!(plaintext, &decryptor.decrypt(ciphertext).await.unwrap()); - } - // Repeated nonces are detected - for (_plaintext, ciphertext) in all_msgs.iter() { - assert!(decryptor.decrypt(ciphertext).await.is_err()); - } - let msg = vec![1, 1]; - - // Good messages continue to be decrypted ok - assert_eq!( - msg, - decryptor - .decrypt(&encryptor.encrypt(&msg).await.unwrap()) - .await - .unwrap() - ); - } - - #[tokio::test] - async fn test_attack_nonce() { - let (mut encryptor, mut decryptor) = create_encryptor_decryptor().await.unwrap(); - for n in 0..100 { - let msg = vec![n]; - let ciphertext = encryptor.encrypt(&msg).await.unwrap(); - let mut trash_packet = ciphertext.clone(); - // toggle a bit, to make the packet invalid. The nonce is not affected - // as it at the beginning of the packet - trash_packet[ciphertext.len() - 1] ^= 0b1000_0000; - - // Generate a packet with some lookinly-valid content, but a nonce - // far in the future that must be rejected. - let mut bad_nonce_msg = Vec::new(); - let bad_nonce: u64 = 1000000; - bad_nonce_msg.extend_from_slice(&bad_nonce.to_be_bytes()); - bad_nonce_msg.extend_from_slice(&ciphertext[8..]); - - assert!(decryptor.decrypt(&trash_packet).await.is_err()); - assert!(decryptor.decrypt(&bad_nonce_msg).await.is_err()); - // These invalid packets don't affect the decryptor state - // FIXME: fix the implementation so this test pass. - assert_eq!(msg, decryptor.decrypt(&ciphertext).await.unwrap()); - } - } - - async fn create_encryptor_decryptor() -> Result<(Encryptor, Decryptor)> { - let vault1 = Vault::create(); - let vault2 = Vault::create(); - - let secret_attrs = SecretAttributes::Aes256; - let key_on_v1 = vault1.create_ephemeral_secret(secret_attrs).await.unwrap(); - let secret = vault1 - .get_ephemeral_secret(&key_on_v1, "secret") - .await - .unwrap(); - - let key_on_v2 = vault2 - .import_ephemeral_secret(secret.secret().clone(), secret_attrs) - .await - .unwrap(); - - Ok(( - Encryptor::new(key_on_v1, 0, vault1), - Decryptor::new(key_on_v2, vault2), - )) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/nonce_tracker.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/nonce_tracker.rs deleted file mode 100644 index 4ad89e91474..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/nonce_tracker.rs +++ /dev/null @@ -1,83 +0,0 @@ -use super::super::secure_channel::encryptor::KEY_RENEWAL_INTERVAL; -use super::super::IdentityError; - -/// fails compilation if [`KEY_RENEWAL_INTERVAL`] + 1 is bigger than [`BitmapType::BITS`]. -/// -/// the +1 is needed since the current nonce is also marked as received, taking an extra bit -/// even though we could check `current_nonce`, this compromise is for the sake of simplicity -const _: [(); (KEY_RENEWAL_INTERVAL + 1 > BitmapType::BITS as u64) as usize] = []; -type BitmapType = u64; - -#[derive(Debug)] -pub(crate) struct NonceTracker { - nonce_bitmap: BitmapType, - current_nonce: u64, -} - -impl NonceTracker { - pub(crate) fn new() -> Self { - Self { - nonce_bitmap: 0, - current_nonce: 0, - } - } - - /// Mark a nonce as received, reject all invalid nonce values - pub(crate) fn mark(&self, nonce: u64) -> ockam_core::Result { - let new_tracker = if nonce > self.current_nonce { - // normal case, we increase the nonce and move the window - let relative_shift: u64 = nonce - self.current_nonce; - if relative_shift > KEY_RENEWAL_INTERVAL { - return Err(IdentityError::InvalidNonce.into()); - } - NonceTracker { - nonce_bitmap: self.nonce_bitmap.overflowing_shl(relative_shift as u32).0 | 1, - current_nonce: nonce, - } - } else { - // first message or an out of order message - let relative: u64 = self.current_nonce - nonce; - if relative > KEY_RENEWAL_INTERVAL { - return Err(IdentityError::InvalidNonce.into()); - } - - #[allow(trivial_numeric_casts)] - let bit = (1 as BitmapType).overflowing_shl(relative as u32).0; - if self.nonce_bitmap & bit != 0 { - // we already processed this nonce - return Err(IdentityError::InvalidNonce.into()); - } - NonceTracker { - nonce_bitmap: self.nonce_bitmap | bit, - current_nonce: self.current_nonce, - } - }; - - Ok(new_tracker) - } -} - -#[test] -pub fn check_nonce_tracker() { - let mut tracker = NonceTracker::new(); - tracker = tracker.mark(0).unwrap(); - tracker = tracker.mark(1).unwrap(); - tracker.mark(0).unwrap_err(); - tracker.mark(KEY_RENEWAL_INTERVAL + 2).unwrap_err(); - tracker = tracker.mark(KEY_RENEWAL_INTERVAL + 1).unwrap(); - tracker.mark(1).unwrap_err(); - tracker = tracker.mark(KEY_RENEWAL_INTERVAL + 2).unwrap(); - tracker = tracker.mark(KEY_RENEWAL_INTERVAL + 3).unwrap(); - tracker.mark(KEY_RENEWAL_INTERVAL + 1).unwrap_err(); - tracker.mark(KEY_RENEWAL_INTERVAL + 2).unwrap_err(); - tracker = tracker.mark(2 * KEY_RENEWAL_INTERVAL).unwrap(); - tracker.mark(KEY_RENEWAL_INTERVAL - 1).unwrap_err(); - tracker = tracker.mark(3 * KEY_RENEWAL_INTERVAL).unwrap(); - tracker = tracker.mark(4 * KEY_RENEWAL_INTERVAL).unwrap(); - for n in 3 * KEY_RENEWAL_INTERVAL + 1..4 * KEY_RENEWAL_INTERVAL { - tracker = tracker.mark(n).unwrap(); - } - for n in 4 * KEY_RENEWAL_INTERVAL + 1..5 * KEY_RENEWAL_INTERVAL + 1 { - tracker = tracker.mark(n).unwrap(); - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/options.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/options.rs deleted file mode 100644 index 8cddab6b1ee..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/options.rs +++ /dev/null @@ -1,252 +0,0 @@ -use core::fmt; -use core::fmt::Formatter; -use core::time::Duration; -use ockam_core::compat::sync::Arc; -use ockam_core::compat::vec::Vec; -use ockam_core::flow_control::{FlowControlId, FlowControlOutgoingAccessControl, FlowControls}; -use ockam_core::{Address, OutgoingAccessControl, Result}; - -use super::super::models::CredentialAndPurposeKey; -use super::super::secure_channel::Addresses; -use super::super::{TrustContext, TrustEveryonePolicy, TrustPolicy}; - -const DEFAULT_TIMEOUT: Duration = Duration::from_secs(120); - -/// Trust options for a Secure Channel -pub struct SecureChannelOptions { - pub(crate) flow_control_id: FlowControlId, - pub(crate) trust_policy: Arc, - pub(crate) trust_context: Option, - pub(crate) credentials: Vec, - pub(crate) timeout: Duration, -} - -impl fmt::Debug for SecureChannelOptions { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "FlowId: {}", self.flow_control_id) - } -} - -pub(crate) struct SecureChannelAccessControl { - pub(crate) decryptor_outgoing_access_control: Arc, -} - -impl SecureChannelOptions { - /// Mark this Secure Channel Decryptor as a Producer with a random [`FlowControlId`] - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - Self { - flow_control_id: FlowControls::generate_flow_control_id(), - trust_policy: Arc::new(TrustEveryonePolicy), - trust_context: None, - credentials: vec![], - timeout: DEFAULT_TIMEOUT, - } - } - - /// Sets a timeout different from the default one [`DEFAULT_TIMEOUT`] - pub fn with_timeout(mut self, timeout: Duration) -> Self { - self.timeout = timeout; - self - } - - /// Adds provided credentials - pub fn with_credentials(mut self, credentials: Vec) -> Self { - self.credentials.extend(credentials); - self - } - - /// Adds a single credential - pub fn with_credential(mut self, credential: CredentialAndPurposeKey) -> Self { - self.credentials.push(credential); - self - } - - /// Sets trust context - pub fn with_trust_context(mut self, trust_context: TrustContext) -> Self { - self.trust_context = Some(trust_context); - self - } - - /// Set Trust Policy - pub fn with_trust_policy(mut self, trust_policy: impl TrustPolicy) -> Self { - self.trust_policy = Arc::new(trust_policy); - self - } - - /// Freshly generated [`FlowControlId`] - pub fn producer_flow_control_id(&self) -> FlowControlId { - self.flow_control_id.clone() - } -} - -impl SecureChannelOptions { - pub(crate) fn setup_flow_control( - &self, - flow_controls: &FlowControls, - addresses: &Addresses, - next: &Address, - ) -> Result<()> { - if let Some(flow_control_id) = flow_controls - .find_flow_control_with_producer_address(next) - .map(|x| x.flow_control_id().clone()) - { - // Allow a sender with corresponding flow_control_id send messages to this address - flow_controls.add_consumer(addresses.decryptor_remote.clone(), &flow_control_id); - } - - flow_controls.add_producer( - addresses.decryptor_internal.clone(), - &self.flow_control_id, - None, - vec![addresses.encryptor.clone()], - ); - - Ok(()) - } - - pub(crate) fn create_access_control( - &self, - flow_controls: &FlowControls, - ) -> SecureChannelAccessControl { - let ac = FlowControlOutgoingAccessControl::new( - flow_controls, - self.flow_control_id.clone(), - None, - ); - - SecureChannelAccessControl { - decryptor_outgoing_access_control: Arc::new(ac), - } - } -} - -/// Trust options for a Secure Channel Listener -pub struct SecureChannelListenerOptions { - pub(crate) consumer: Vec, - pub(crate) flow_control_id: FlowControlId, - pub(crate) trust_policy: Arc, - pub(crate) trust_context: Option, - pub(crate) credentials: Vec, -} - -impl fmt::Debug for SecureChannelListenerOptions { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "SpawnerFlowId: {}", self.flow_control_id) - } -} - -impl SecureChannelListenerOptions { - /// Mark spawned Secure Channel Decryptors as Producers for a given Spawner's [`FlowControlId`] - /// NOTE: Spawned connections get fresh random [`FlowControlId`], however they are still marked - /// with Spawner's [`FlowControlId`] - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - Self { - consumer: vec![], - flow_control_id: FlowControls::generate_flow_control_id(), - trust_policy: Arc::new(TrustEveryonePolicy), - trust_context: None, - credentials: vec![], - } - } - - /// Mark that this Secure Channel Listener is a Consumer for to the given [`FlowControlId`] - /// Also, in this case spawned Secure Channels will be marked as Consumers with [`FlowControlId`] - /// of the message that was used to create the Secure Channel - pub fn as_consumer(mut self, id: &FlowControlId) -> Self { - self.consumer.push(id.clone()); - - self - } - - /// Adds provided credentials - pub fn with_credentials(mut self, credentials: Vec) -> Self { - self.credentials.extend(credentials); - self - } - - /// Adds a single credential - pub fn with_credential(mut self, credential: CredentialAndPurposeKey) -> Self { - self.credentials.push(credential); - self - } - - /// Sets trust context - pub fn with_trust_context(mut self, trust_context: TrustContext) -> Self { - self.trust_context = Some(trust_context); - self - } - - /// Set trust policy - pub fn with_trust_policy(mut self, trust_policy: impl TrustPolicy) -> Self { - self.trust_policy = Arc::new(trust_policy); - self - } - - /// Freshly generated [`FlowControlId`] - pub fn spawner_flow_control_id(&self) -> FlowControlId { - self.flow_control_id.clone() - } -} - -impl SecureChannelListenerOptions { - pub(crate) fn setup_flow_control_for_listener( - &self, - flow_controls: &FlowControls, - address: &Address, - ) { - for id in &self.consumer { - flow_controls.add_consumer(address.clone(), id); - } - - flow_controls.add_spawner(address.clone(), &self.flow_control_id); - } - - pub(crate) fn setup_flow_control_for_channel( - &self, - flow_controls: &FlowControls, - addresses: &Addresses, - src_addr: &Address, - ) -> FlowControlId { - // Check if the Worker that send us this message is a Producer - // If yes - decryptor will be added to that flow_control to be able to receive further messages - // from that Producer - if let Some(producer_flow_control_id) = flow_controls - .get_flow_control_with_producer(src_addr) - .map(|x| x.flow_control_id().clone()) - { - // Allow a sender with corresponding flow_control_id send messages to this address - flow_controls.add_consumer( - addresses.decryptor_remote.clone(), - &producer_flow_control_id, - ); - } - - let flow_control_id = FlowControls::generate_flow_control_id(); - flow_controls.add_producer( - addresses.decryptor_internal.clone(), - &flow_control_id, - Some(&self.flow_control_id), - vec![addresses.encryptor.clone()], - ); - - flow_control_id - } - - pub(crate) fn create_access_control( - &self, - flow_controls: &FlowControls, - flow_control_id: FlowControlId, - ) -> SecureChannelAccessControl { - let ac = FlowControlOutgoingAccessControl::new( - flow_controls, - flow_control_id, - Some(self.flow_control_id.clone()), - ); - - SecureChannelAccessControl { - decryptor_outgoing_access_control: Arc::new(ac), - } - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/registry.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/registry.rs deleted file mode 100644 index 47c7fd4ea5b..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/registry.rs +++ /dev/null @@ -1,157 +0,0 @@ -use ockam_core::compat::collections::BTreeMap; -use ockam_core::compat::sync::{Arc, RwLock}; -use ockam_core::compat::vec::Vec; -use ockam_core::{Address, Result}; - -use super::super::models::Identifier; -use super::super::IdentityError; - -/// Known information about particular SecureChannel -#[derive(Clone, Debug)] -pub struct SecureChannelRegistryEntry { - encryptor_messaging_address: Address, - encryptor_api_address: Address, - decryptor_messaging_address: Address, - decryptor_api_address: Address, - is_initiator: bool, - my_id: Identifier, - their_id: Identifier, - their_decryptor_address: Address, -} - -impl SecureChannelRegistryEntry { - /// Create new registry entry - #[allow(clippy::too_many_arguments)] - pub fn new( - encryptor_messaging_address: Address, - encryptor_api_address: Address, - decryptor_messaging_address: Address, - decryptor_api_address: Address, - is_initiator: bool, - my_id: Identifier, - their_id: Identifier, - their_decryptor_address: Address, - ) -> Self { - Self { - encryptor_messaging_address, - encryptor_api_address, - decryptor_messaging_address, - decryptor_api_address, - is_initiator, - my_id, - their_id, - their_decryptor_address, - } - } - - /// Encryptor messaging address - pub fn encryptor_messaging_address(&self) -> &Address { - &self.encryptor_messaging_address - } - - /// Encryptor api address - pub fn encryptor_api_address(&self) -> &Address { - &self.encryptor_api_address - } - - /// Decryptor messaging address - pub fn decryptor_messaging_address(&self) -> &Address { - &self.decryptor_messaging_address - } - - /// Decryptor api address - pub fn decryptor_api_address(&self) -> &Address { - &self.decryptor_api_address - } - - /// If we are were initiating this channel - pub fn is_initiator(&self) -> bool { - self.clone().is_initiator - } - - /// Our `IdentityIdentifier` - pub fn my_id(&self) -> &Identifier { - &self.my_id - } - - /// Their `IdentityIdentifier` - pub fn their_id(&self) -> &Identifier { - &self.their_id - } - - /// Their `Decryptor` address - pub fn their_decryptor_address(&self) -> Address { - self.their_decryptor_address.clone() - } -} - -/// Registry of all known Secure Channels -#[derive(Clone, Default)] -pub struct SecureChannelRegistry { - // Encryptor address is used as a key - registry: Arc>>, -} - -impl SecureChannelRegistry { - /// Create an empty registry - pub fn new() -> Self { - Self { - registry: Default::default(), - } - } -} - -impl SecureChannelRegistry { - /// Register new SecureChannel in that registry - pub fn register_channel(&self, info: SecureChannelRegistryEntry) -> Result<()> { - let res = self - .registry - .write() - .unwrap() - .insert(info.encryptor_messaging_address.clone(), info); - - if res.is_some() { - return Err(IdentityError::DuplicateSecureChannel.into()); - } - - Ok(()) - } - - /// Unregister a SecureChannel and return removed `SecureChannelRegistryEntry` - pub fn unregister_channel( - &self, - encryptor_address: &Address, - ) -> Option { - self.registry.write().unwrap().remove(encryptor_address) - } - - /// Get list of all known SecureChannels - pub fn get_channel_list(&self) -> Vec { - self.registry.read().unwrap().values().cloned().collect() - } - - /// Get SecureChannel with given encryptor messaging address - pub fn get_channel_by_encryptor_address( - &self, - encryptor_address: &Address, - ) -> Option { - self.registry - .read() - .unwrap() - .get(encryptor_address) - .cloned() - } - - /// Get SecureChannel with given decryptor messaging address - pub fn get_channel_by_decryptor_address( - &self, - decryptor_address: &Address, - ) -> Option { - self.registry - .read() - .unwrap() - .iter() - .find(|(_, entry)| entry.decryptor_messaging_address == *decryptor_address) - .map(|(_, entry)| entry.clone()) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/role.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/role.rs deleted file mode 100644 index 219d20bf3fb..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/role.rs +++ /dev/null @@ -1,37 +0,0 @@ -use core::fmt::{Display, Formatter}; - -#[derive(Copy, Clone, Debug)] -pub(crate) enum Role { - Initiator, - Responder, -} - -impl Display for Role { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - write!( - f, - "{}", - if self.is_initiator() { - "Initiator" - } else { - "Responder" - } - ) - } -} - -impl Role { - pub fn is_initiator(&self) -> bool { - match self { - Role::Initiator => true, - Role::Responder => false, - } - } - - pub fn str(&self) -> &'static str { - match self { - Role::Initiator => "initiator", - Role::Responder => "responder", - } - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/all_trust_policy.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/all_trust_policy.rs deleted file mode 100644 index 0daf75705fc..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/all_trust_policy.rs +++ /dev/null @@ -1,75 +0,0 @@ -use ockam_core::async_trait; -use ockam_core::compat::boxed::Box; -use ockam_core::{AsyncTryClone, Result}; - -use super::super::super::secure_channel::trust_policy::{SecureChannelTrustInfo, TrustPolicy}; - -/// Succeeds only if both `TrustPolicy` checks succeeded -#[derive(AsyncTryClone)] -#[async_try_clone(crate = "ockam_core")] -pub struct AllTrustPolicy { - // TODO: Extend for more than 2 policies - first: F, - second: S, -} - -impl AllTrustPolicy { - /// Constructor - pub fn new(first: F, second: S) -> Self { - AllTrustPolicy { first, second } - } -} - -#[async_trait] -impl TrustPolicy for AllTrustPolicy { - async fn check(&self, trust_info: &SecureChannelTrustInfo) -> Result { - Ok(self.first.check(trust_info).await? && self.second.check(trust_info).await?) - } -} - -#[cfg(test)] -mod test { - use super::super::super::super::models::Identifier; - use super::super::super::super::secure_channel::trust_policy::{ - SecureChannelTrustInfo, TrustPolicy, - }; - use ockam_core::async_trait; - use ockam_core::Result; - - #[tokio::test] - async fn test() { - #[derive(Clone)] - struct TrustPolicyStub(bool); - - #[async_trait] - impl TrustPolicy for TrustPolicyStub { - async fn check(&self, _trust_info: &SecureChannelTrustInfo) -> Result { - Ok(self.0) - } - } - - let id = Identifier::try_from("Iabababababababababababababababababababab").unwrap(); - let trust_info = SecureChannelTrustInfo::new(id); - - assert!(TrustPolicyStub(true) - .and(TrustPolicyStub(true)) - .check(&trust_info) - .await - .unwrap()); - assert!(!TrustPolicyStub(true) - .and(TrustPolicyStub(false)) - .check(&trust_info) - .await - .unwrap()); - assert!(!TrustPolicyStub(false) - .and(TrustPolicyStub(true)) - .check(&trust_info) - .await - .unwrap()); - assert!(!TrustPolicyStub(false) - .and(TrustPolicyStub(false)) - .check(&trust_info) - .await - .unwrap()); - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/any_trust_policy.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/any_trust_policy.rs deleted file mode 100644 index 2134d9adba8..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/any_trust_policy.rs +++ /dev/null @@ -1,74 +0,0 @@ -use ockam_core::async_trait; -use ockam_core::compat::boxed::Box; -use ockam_core::{AsyncTryClone, Result}; - -use super::super::super::secure_channel::trust_policy::{SecureChannelTrustInfo, TrustPolicy}; - -/// Succeeds if any or both `TrustPolicy` checks succeeded -#[derive(AsyncTryClone)] -#[async_try_clone(crate = "ockam_core")] -pub struct AnyTrustPolicy { - // TODO: Extend for more than 2 policies - first: F, - second: S, -} - -impl AnyTrustPolicy { - /// Constructor - pub fn new(first: F, second: S) -> Self { - AnyTrustPolicy { first, second } - } -} - -#[async_trait] -impl TrustPolicy for AnyTrustPolicy { - async fn check(&self, trust_info: &SecureChannelTrustInfo) -> Result { - // TODO: is the short circuit here a side channel? - Ok(self.first.check(trust_info).await? || self.second.check(trust_info).await?) - } -} - -#[cfg(test)] -mod test { - use super::super::super::super::models::Identifier; - use super::super::super::super::secure_channel::{SecureChannelTrustInfo, TrustPolicy}; - use ockam_core::async_trait; - use ockam_core::Result; - - #[tokio::test] - async fn test() { - #[derive(Clone)] - struct TrustPolicyStub(bool); - - #[async_trait] - impl TrustPolicy for TrustPolicyStub { - async fn check(&self, _trust_info: &SecureChannelTrustInfo) -> Result { - Ok(self.0) - } - } - - let id = Identifier::try_from("Iabababababababababababababababababababab").unwrap(); - let trust_info = SecureChannelTrustInfo::new(id); - - assert!(TrustPolicyStub(true) - .or(TrustPolicyStub(true)) - .check(&trust_info) - .await - .unwrap()); - assert!(TrustPolicyStub(true) - .or(TrustPolicyStub(false)) - .check(&trust_info) - .await - .unwrap()); - assert!(TrustPolicyStub(false) - .or(TrustPolicyStub(true)) - .check(&trust_info) - .await - .unwrap()); - assert!(!TrustPolicyStub(false) - .or(TrustPolicyStub(false)) - .check(&trust_info) - .await - .unwrap()); - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/mod.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/mod.rs deleted file mode 100644 index 86e7f3f67a4..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -mod all_trust_policy; -mod any_trust_policy; -mod trust_everyone_policy; -mod trust_identifier_policy; -mod trust_multi_identifier_policy; -mod trust_policy_type; - -pub use all_trust_policy::*; -pub use any_trust_policy::*; -pub use trust_everyone_policy::*; -pub use trust_identifier_policy::*; -pub use trust_multi_identifier_policy::*; -pub use trust_policy_type::*; diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/trust_everyone_policy.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/trust_everyone_policy.rs deleted file mode 100644 index 60a152fe37a..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/trust_everyone_policy.rs +++ /dev/null @@ -1,16 +0,0 @@ -use ockam_core::async_trait; -use ockam_core::compat::boxed::Box; -use ockam_core::{allow, Result}; - -use super::super::super::secure_channel::trust_policy::{SecureChannelTrustInfo, TrustPolicy}; - -/// Trust any participant -#[derive(Clone)] -pub struct TrustEveryonePolicy; - -#[async_trait] -impl TrustPolicy for TrustEveryonePolicy { - async fn check(&self, _trust_info: &SecureChannelTrustInfo) -> Result { - allow() - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/trust_identifier_policy.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/trust_identifier_policy.rs deleted file mode 100644 index 2cd924dccfa..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/trust_identifier_policy.rs +++ /dev/null @@ -1,26 +0,0 @@ -use ockam_core::async_trait; -use ockam_core::compat::boxed::Box; -use ockam_core::Result; - -use super::super::super::models::Identifier; -use super::super::super::secure_channel::trust_policy::{SecureChannelTrustInfo, TrustPolicy}; - -/// `TrustPolicy` based on pre-known `IdentityIdentifier` of the other participant -#[derive(Clone)] -pub struct TrustIdentifierPolicy { - their_identity_id: Identifier, -} - -impl TrustIdentifierPolicy { - /// Constructor - pub fn new(their_identity_id: Identifier) -> Self { - Self { their_identity_id } - } -} - -#[async_trait] -impl TrustPolicy for TrustIdentifierPolicy { - async fn check(&self, trust_info: &SecureChannelTrustInfo) -> Result { - Ok(trust_info.their_identity_id == self.their_identity_id) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/trust_multi_identifier_policy.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/trust_multi_identifier_policy.rs deleted file mode 100644 index a6cd4bf60f6..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/trust_multi_identifier_policy.rs +++ /dev/null @@ -1,49 +0,0 @@ -use ockam_core::compat::boxed::Box; -use ockam_core::compat::string::String; -use ockam_core::compat::string::ToString; -use ockam_core::{async_trait, compat::vec::Vec, Result}; -use tracing::info; - -use super::super::super::models::Identifier; -use super::super::trust_policy::{SecureChannelTrustInfo, TrustPolicy}; - -/// `TrustPolicy` based on list of pre-known `IdentityIdentifier`s of the possible participants -#[derive(Clone)] -pub struct TrustMultiIdentifiersPolicy { - identity_ids: Vec, -} - -impl TrustMultiIdentifiersPolicy { - /// Constructor - pub fn new(identity_ids: Vec) -> Self { - Self { identity_ids } - } - - fn contains(&self, their_id: &Identifier) -> bool { - let mut found = subtle::Choice::from(0); - for trusted_id in &*self.identity_ids { - found |= trusted_id.ct_eq(their_id); - } - found.into() - } -} - -#[async_trait] -impl TrustPolicy for TrustMultiIdentifiersPolicy { - async fn check(&self, trust_info: &SecureChannelTrustInfo) -> Result { - if !self.contains(trust_info.their_identity_id()) { - info!( - "{} is not one of the trusted identifiers {}", - trust_info.their_identity_id(), - self.identity_ids - .iter() - .map(|i| i.to_string()) - .collect::>() - .join(", ") - ); - Ok(false) - } else { - Ok(true) - } - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/trust_policy_type.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/trust_policy_type.rs deleted file mode 100644 index ce93ac1be86..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channel/trust_policy/trust_policy_type.rs +++ /dev/null @@ -1,69 +0,0 @@ -use ockam_core::{ - async_trait, - compat::{boxed::Box, sync::Arc}, - Result, -}; -use serde::{Deserialize, Serialize}; - -use super::super::super::models::Identifier; -use super::super::super::secure_channel::trust_policy::{AllTrustPolicy, AnyTrustPolicy}; - -/// Authenticated data of the newly created SecureChannel to perform `TrustPolicy` check -#[derive(Clone, Serialize, Deserialize)] -pub struct SecureChannelTrustInfo { - /// identity of the other end of the secure channel - pub their_identity_id: Identifier, -} - -impl SecureChannelTrustInfo { - /// `IdentityIdentifier` of the other participant - pub fn their_identity_id(&self) -> &Identifier { - &self.their_identity_id - } -} - -impl SecureChannelTrustInfo { - /// Constructor - pub fn new(their_identity_id: Identifier) -> Self { - Self { their_identity_id } - } -} - -/// TrustPolicy check is run when creating new SecureChannel, its creation only succeeds if this -/// check succeeds -#[async_trait] -pub trait TrustPolicy: Send + Sync + 'static { - /// Check SecureChannel - async fn check(&self, trust_info: &SecureChannelTrustInfo) -> Result; - - /// Run both `TrustPolicy` checks and succeed only if both succeeded - fn and(self, other: O) -> AllTrustPolicy - where - Self: Sized, - { - AllTrustPolicy::new(self, other) - } - - /// Run both `TrustPolicy` checks and succeed if any or both succeeded - fn or(self, other: O) -> AnyTrustPolicy - where - Self: Sized, - { - AnyTrustPolicy::new(self, other) - } -} - -// Allow `Box` to be used as a valid TrustPolicy -#[async_trait] -impl TrustPolicy for Box { - async fn check(&self, trust_info: &SecureChannelTrustInfo) -> Result { - T::check(&**self, trust_info).await - } -} - -#[async_trait] -impl TrustPolicy for Arc { - async fn check(&self, trust_info: &SecureChannelTrustInfo) -> Result { - T::check(&**self, trust_info).await - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channels/common.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channels/common.rs deleted file mode 100644 index 755df65e9f6..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channels/common.rs +++ /dev/null @@ -1,94 +0,0 @@ -use core::fmt; -use core::fmt::Formatter; -use ockam_core::flow_control::FlowControlId; -use ockam_core::Address; - -/// Result of [`super::SecureChannels::create_secure_channel()`] call. -#[derive(Debug, Clone)] -pub struct SecureChannel { - encryptor_address: Address, - encryptor_api_address: Address, - flow_control_id: FlowControlId, -} - -impl From for Address { - fn from(value: SecureChannel) -> Self { - value.encryptor_address - } -} - -impl fmt::Display for SecureChannel { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!( - f, - "Encryptor: {}, FlowId: {}", - self.encryptor_address, self.flow_control_id - ) - } -} - -impl SecureChannel { - /// Constructor. - pub fn new( - encryptor_address: Address, - encryptor_api_address: Address, - flow_control_id: FlowControlId, - ) -> Self { - Self { - encryptor_address, - encryptor_api_address, - flow_control_id, - } - } - /// [`Address`] of the corresponding`EncryptorWorker` Worker that can be used in a route - /// to encrypt and send a message to the other party - pub fn encryptor_address(&self) -> &Address { - &self.encryptor_address - } - /// Freshly generated [`FlowControlId`] - pub fn flow_control_id(&self) -> &FlowControlId { - &self.flow_control_id - } - /// API [`Address`] of the corresponding`EncryptorWorker` Worker that can be used to encrypt - /// a message without sending it - pub fn encryptor_api_address(&self) -> &Address { - &self.encryptor_api_address - } -} - -/// Result of [`super::SecureChannels::create_secure_channel_listener()`] call. -#[derive(Debug, Clone)] -pub struct SecureChannelListener { - address: Address, - flow_control_id: FlowControlId, -} - -impl fmt::Display for SecureChannelListener { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!( - f, - "Worker: {}, FlowId: {}", - self.address, self.flow_control_id - ) - } -} - -impl SecureChannelListener { - /// Constructor. - pub fn new(address: Address, flow_control_id: FlowControlId) -> Self { - Self { - address, - flow_control_id, - } - } - /// [`Address`] of the corresponding - /// [`SecureChannelListener`](super::super::SecureChannelListener) Worker that can be used - /// to stop it - pub fn address(&self) -> &Address { - &self.address - } - /// Freshly generated [`FlowControlId`] - pub fn flow_control_id(&self) -> &FlowControlId { - &self.flow_control_id - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channels/mod.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channels/mod.rs deleted file mode 100644 index ce74a0a115f..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channels/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod common; -/// Services for creating secure channels -#[allow(clippy::module_inception)] -pub mod secure_channels; -mod secure_channels_builder; - -pub use common::*; -pub use secure_channels::*; -pub use secure_channels_builder::*; diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channels/secure_channels.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channels/secure_channels.rs deleted file mode 100644 index e84d30adb42..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channels/secure_channels.rs +++ /dev/null @@ -1,140 +0,0 @@ -use ockam_core::compat::sync::Arc; -use ockam_core::Result; -use ockam_core::{Address, Route}; -use ockam_node::Context; - -use super::super::identities::{Identities, IdentitiesVault}; -use super::super::models::Identifier; -use super::super::secure_channel::handshake_worker::HandshakeWorker; -use super::super::secure_channel::{ - Addresses, IdentityChannelListener, Role, SecureChannelListenerOptions, SecureChannelOptions, - SecureChannelRegistry, -}; -use super::super::{ - Purpose, PurposeKeys, SecureChannel, SecureChannelListener, SecureChannelsBuilder, -}; - -/// Identity implementation -#[derive(Clone)] -pub struct SecureChannels { - pub(crate) identities: Arc, - pub(crate) purpose_keys: Arc, - pub(crate) secure_channel_registry: SecureChannelRegistry, -} - -impl SecureChannels { - /// Constructor - pub(crate) fn new( - identities: Arc, - purpose_keys: Arc, - secure_channel_registry: SecureChannelRegistry, - ) -> Self { - Self { - identities, - purpose_keys, - secure_channel_registry, - } - } - - /// Return the identities services associated to this service - pub fn identities(&self) -> Arc { - self.identities.clone() - } - - /// Return the vault associated to this service - pub fn vault(&self) -> Arc { - self.identities.vault() - } - - /// Return the secure channel registry - pub fn secure_channel_registry(&self) -> SecureChannelRegistry { - self.secure_channel_registry.clone() - } - - /// Create a builder for secure channels - pub fn builder() -> SecureChannelsBuilder { - SecureChannelsBuilder { - identities_builder: Identities::builder(), - registry: SecureChannelRegistry::new(), - } - } -} - -impl SecureChannels { - /// Spawns a SecureChannel listener at given `Address` with given [`SecureChannelListenerOptions`] - pub async fn create_secure_channel_listener( - &self, - ctx: &Context, - identifier: &Identifier, - address: impl Into
, - options: impl Into, - ) -> Result { - let address = address.into(); - let options = options.into(); - let flow_control_id = options.flow_control_id.clone(); - - IdentityChannelListener::create( - ctx, - Arc::new(self.clone()), - identifier, - address.clone(), - options, - ) - .await?; - - Ok(SecureChannelListener::new(address, flow_control_id)) - } - - /// Initiate a SecureChannel using `Route` to the SecureChannel listener and [`SecureChannelOptions`] - pub async fn create_secure_channel( - &self, - ctx: &Context, - identifier: &Identifier, - route: impl Into, - options: impl Into, - ) -> Result { - let addresses = Addresses::generate(Role::Initiator); - let options = options.into(); - let flow_control_id = options.flow_control_id.clone(); - - let route = route.into(); - let next = route.next()?; - options.setup_flow_control(ctx.flow_controls(), &addresses, next)?; - let access_control = options.create_access_control(ctx.flow_controls()); - - let purpose_key = self - .purpose_keys - .repository() - .get_purpose_key(identifier, Purpose::SecureChannel) - .await?; - - let purpose_key = self.purpose_keys.import_purpose_key(&purpose_key).await?; - - HandshakeWorker::create( - ctx, - Arc::new(self.clone()), - addresses.clone(), - identifier.clone(), - purpose_key, - options.trust_policy, - access_control.decryptor_outgoing_access_control, - options.credentials, - options.trust_context, - Some(route), - Some(options.timeout), - Role::Initiator, - ) - .await?; - - Ok(SecureChannel::new( - addresses.encryptor, - addresses.encryptor_api, - flow_control_id, - )) - } - - /// Stop a SecureChannel given an encryptor address - pub async fn stop_secure_channel(&self, ctx: &Context, channel: &Address) -> Result<()> { - ctx.stop_worker(channel.clone()).await - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/secure_channels/secure_channels_builder.rs b/implementations/rust/ockam/ockam_identity/src/v2/secure_channels/secure_channels_builder.rs deleted file mode 100644 index 67092d82d66..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/secure_channels/secure_channels_builder.rs +++ /dev/null @@ -1,84 +0,0 @@ -use ockam_core::compat::sync::Arc; -use ockam_vault::VaultStorage; - -use super::super::identities::{Identities, IdentitiesRepository}; -use super::super::secure_channel::SecureChannelRegistry; -use super::super::secure_channels::SecureChannels; -use super::super::storage::Storage; -use super::super::{IdentitiesBuilder, IdentitiesVault}; - -/// This struct supports all the services related to secure channels -#[derive(Clone)] -pub struct SecureChannelsBuilder { - pub(crate) identities_builder: IdentitiesBuilder, - pub(crate) registry: SecureChannelRegistry, -} - -/// Create default, in-memory, secure channels (mostly for examples and testing) -pub fn secure_channels() -> Arc { - SecureChannels::builder().build() -} - -impl SecureChannelsBuilder { - /// Set a specific storage for the secure channels vault - pub fn with_vault_storage(&mut self, storage: VaultStorage) -> SecureChannelsBuilder { - self.identities_builder = self.identities_builder.with_vault_storage(storage); - self.clone() - } - - /// Set a specific vault for secure channels - pub fn with_identities_vault( - &mut self, - vault: Arc, - ) -> SecureChannelsBuilder { - self.identities_builder = self.identities_builder.with_identities_vault(vault); - self.clone() - } - - /// Set a specific storage for the identities repository - pub fn with_identities_storage(&mut self, storage: Arc) -> SecureChannelsBuilder { - self.identities_builder = self.identities_builder.with_identities_storage(storage); - self.clone() - } - - /// Set a specific identities repository - pub fn with_identities_repository( - &mut self, - repository: Arc, - ) -> SecureChannelsBuilder { - self.identities_builder = self - .identities_builder - .with_identities_repository(repository); - self.clone() - } - - /// Set a specific identities - pub fn with_identities(&mut self, identities: Arc) -> SecureChannelsBuilder { - self.identities_builder = self - .identities_builder - .with_identities_repository(identities.repository()) - .with_identities_vault(identities.vault()); - self.clone() - } - - /// Set a specific channel registry - pub fn with_secure_channels_registry( - &mut self, - registry: SecureChannelRegistry, - ) -> SecureChannelsBuilder { - self.registry = registry; - self.clone() - } - - /// Return the vault used by this builder - /// Build secure channels - pub fn build(&self) -> Arc { - let identities = self.identities_builder.build(); - let purpose_keys = identities.purpose_keys(); - Arc::new(SecureChannels::new( - identities, - purpose_keys, - self.registry.clone(), - )) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/v2/storage/sqlite_storage.rs b/implementations/rust/ockam/ockam_identity/src/v2/storage/sqlite_storage.rs deleted file mode 100644 index f2bd94ee9e5..00000000000 --- a/implementations/rust/ockam/ockam_identity/src/v2/storage/sqlite_storage.rs +++ /dev/null @@ -1,192 +0,0 @@ -use core::str; -use ockam_core::async_trait; -use ockam_core::compat::sync::{Arc, Mutex}; -use ockam_core::compat::vec::Vec; -use ockam_core::errcode::{Kind, Origin}; -use ockam_core::{Error, Result}; -use ockam_node::tokio::task::{self, JoinError}; -use rusqlite::{params, Connection}; -use std::fmt; -use std::path::Path; -use tokio_retry::strategy::{jitter, FixedInterval}; -use tokio_retry::Retry; -use tracing::debug; - -use super::Storage; - -/// Storage using the Sqlite database -#[derive(Clone)] -pub struct SqliteStorage { - /// Sqlite Connection - conn: Arc>, -} - -impl fmt::Debug for SqliteStorage { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("SqliteStore") - } -} - -impl SqliteStorage { - const CREATE_IDENTITY_TABLE_SQL: &str = "CREATE TABLE IF NOT EXISTS identity ( - id INTEGER PRIMARY KEY, - identity_id TEXT NOT NULL, - key TEXT NOT NULL, - value BLOB - );"; - const CREATE_IDENTITY_INDEX_SQL: &str = - "CREATE UNIQUE INDEX IF NOT EXISTS idx_identity_id_key ON identity (identity_id, key);"; - - const CREATE_POLICY_TABLE_SQL: &str = "CREATE TABLE IF NOT EXISTS policy ( - id INTEGER PRIMARY KEY, - resource TEXT NOT NULL, - action TEXT NOT NULL, - value BLOB - );"; - const CREATE_POLICY_INDEX_SQL: &str = "CREATE UNIQUE INDEX IF NOT EXISTS idx_policy_resource_action ON policy (resource, action);"; - - /// Constructor - pub async fn new>(p: P) -> Result { - // Not sure we need this - // creating a new database might be failing a few times - // if the files are currently being held by another pod which is shutting down. - // In that case we retry a few times, between 1 and 10 seconds. - let retry_strategy = FixedInterval::from_millis(1000) - .map(jitter) // add jitter to delays - .take(10); // limit to 10 retries - - let path: &Path = p.as_ref(); - Retry::spawn(retry_strategy, || async { Self::make(path).await }).await - } - - async fn make(p: &Path) -> Result { - debug!("create the Sqlite database"); - let p = p.to_path_buf(); - // Creates database file if it doesn't exist - let conn = Connection::open(p).map_err(map_sqlite_err)?; - let _ = conn - .execute_batch( - &("PRAGMA encoding = 'UTF-8';".to_owned() - + SqliteStorage::CREATE_IDENTITY_TABLE_SQL - + SqliteStorage::CREATE_IDENTITY_INDEX_SQL - + SqliteStorage::CREATE_POLICY_TABLE_SQL - + SqliteStorage::CREATE_POLICY_INDEX_SQL), - ) - .map_err(map_sqlite_err)?; - Ok(SqliteStorage { - conn: Arc::new(Mutex::new(conn)), - }) - } - - /// Getter for Sqlite Connection - pub fn conn(&self) -> Arc> { - Arc::clone(&self.conn) - } -} - -#[async_trait] -impl Storage for SqliteStorage { - async fn get(&self, id: &str, key: &str) -> Result>> { - let conn = self.conn(); - let id = String::from(id); - let key = String::from(key); - - let t = move || { - let conn = conn.lock().unwrap(); - let result = conn - .query_row::, _, _>( - "SELECT value FROM identity WHERE identity_id = ?1 AND key = ?2;", - params![id, key], - |row| row.get(0), - ) - .map_err(map_sqlite_err)?; - Ok(Some(result)) - }; - task::spawn_blocking(t).await.map_err(map_join_err)? - } - - async fn set(&self, id: &str, key: String, val: Vec) -> Result<()> { - let conn = self.conn(); - let id = String::from(id); - let t = move || { - let conn = conn.lock().unwrap(); - conn.execute( - "INSERT OR REPLACE INTO identity (identity_id, key, value) VALUES (?1, ?2, ?3)", - params![id, key, val], - ) - .map_err(map_sqlite_err)?; - Ok(()) - }; - task::spawn_blocking(t).await.map_err(map_join_err)? - } - - async fn del(&self, id: &str, key: &str) -> Result<()> { - let conn = self.conn(); - let id = String::from(id); - let key = String::from(key); - let t = move || { - let conn = conn.lock().unwrap(); - conn.execute( - "DELETE FROM identity WHERE identity_id = ?1 AND key = ?2;", - params![id, key], - ) - .map_err(map_sqlite_err)?; - Ok(()) - }; - task::spawn_blocking(t).await.map_err(map_join_err)? - } - - async fn keys(&self, namespace: &str) -> Result> { - let conn = self.conn(); - let namespace = String::from(namespace); - let t = move || { - let conn = conn.lock().unwrap(); - let mut stmt = conn - .prepare("SELECT identity_id FROM identity WHERE key = ?1;") - .map_err(map_sqlite_err)?; - let result: Result> = stmt - .query_map(params![namespace], |row| row.get(0)) - .map_err(map_sqlite_err)? - .map(|value| value.map_err(map_sqlite_err)) - .collect(); - result - }; - task::spawn_blocking(t).await.map_err(map_join_err)? - } -} - -fn map_join_err(err: JoinError) -> Error { - Error::new(Origin::Application, Kind::Io, err) -} - -fn map_sqlite_err(err: rusqlite::Error) -> Error { - Error::new(Origin::Application, Kind::Io, err) -} - -#[cfg(test)] -mod test { - use super::*; - use tempfile::NamedTempFile; - - #[tokio::test] - async fn test_basic_functionality() -> Result<()> { - let temp_path = NamedTempFile::new().unwrap().into_temp_path(); - let db = SqliteStorage::new(temp_path.to_path_buf()).await?; - - db.set("1", String::from("2"), vec![1, 2, 3, 4]).await?; - assert_eq!( - db.get("1", "2").await?, - Some(vec![1, 2, 3, 4]), - "Verify set and get" - ); - assert_eq!(db.keys("2").await?.len(), 1, "Verify keys"); - - db.set("2", String::from("2"), vec![1, 2, 3, 4]).await?; - assert_eq!(db.keys("2").await?.len(), 2, "Verify multiple keys"); - - db.del("2", "2").await?; - assert_eq!(db.keys("2").await?.len(), 1, "Verify delete"); - - Ok(()) - } -} diff --git a/implementations/rust/ockam/ockam_identity/src/vault.rs b/implementations/rust/ockam/ockam_identity/src/vault.rs new file mode 100644 index 00000000000..72dbbf0e811 --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/src/vault.rs @@ -0,0 +1,92 @@ +use ockam_core::compat::sync::Arc; +use ockam_node::{InMemoryKeyValueStorage, KeyValueStorage}; +use ockam_vault::{ + KeyId, SecureChannelVault, SigningVault, SoftwareSecureChannelVault, SoftwareSigningVault, + SoftwareVerifyingVault, StoredSecret, VerifyingVault, +}; + +/// Storage for Vault persistent values +pub type VaultStorage = Arc>; + +/// Vault +#[derive(Clone)] +pub struct Vault { + /// Vault used for Identity Keys + pub identity_vault: Arc, + /// Vault used for Secure Channels + pub secure_channel_vault: Arc, + /// Vault used for signing Credentials + pub credential_vault: Arc, + /// Vault used for verifying signature and sha256 + pub verifying_vault: Arc, +} + +impl Vault { + /// Constructor + pub fn new( + identity_vault: Arc, + secure_channel_vault: Arc, + credential_vault: Arc, + verifying_vault: Arc, + ) -> Self { + Self { + identity_vault, + secure_channel_vault, + credential_vault, + verifying_vault, + } + } + + /// Create Software implementation Vault with [`InMemoryKeyVaultStorage`] + pub fn create() -> Self { + Self::new( + Self::create_identity_vault(), + Self::create_secure_channel_vault(), + Self::create_credential_vault(), + Self::create_verifying_vault(), + ) + } + + /// Create [`SoftwareSigningVault`] with [`InMemoryKeyVaultStorage`] + pub fn create_identity_vault() -> Arc { + Arc::new(SoftwareSigningVault::new(InMemoryKeyValueStorage::create())) + } + + /// Create [`SoftwareSecureChannelVault`] with [`InMemoryKeyVaultStorage`] + pub fn create_secure_channel_vault() -> Arc { + Arc::new(SoftwareSecureChannelVault::new( + InMemoryKeyValueStorage::create(), + )) + } + + /// Create [`SoftwareSigningVault`] with [`InMemoryKeyVaultStorage`] + pub fn create_credential_vault() -> Arc { + Arc::new(SoftwareSigningVault::new(InMemoryKeyValueStorage::create())) + } + + /// Create [`SoftwareVerifyingVault`] + pub fn create_verifying_vault() -> Arc { + Arc::new(SoftwareVerifyingVault {}) + } +} + +impl Vault { + /// Create Software Vaults with [`PersistentStorage`] with a given path + #[cfg(feature = "std")] + pub async fn create_with_persistent_storage_path( + path: &std::path::Path, + ) -> ockam_core::Result { + let storage = ockam_vault::storage::PersistentStorage::create(path).await?; + Ok(Self::create_with_persistent_storage(storage)) + } + + /// Create Software Vaults with a given [`VaultStorage`]r + pub fn create_with_persistent_storage(storage: VaultStorage) -> Vault { + Self::new( + Arc::new(SoftwareSigningVault::new(storage.clone())), + Arc::new(SoftwareSecureChannelVault::new(storage.clone())), + Arc::new(SoftwareSigningVault::new(storage)), + Arc::new(SoftwareVerifyingVault {}), + ) + } +} diff --git a/implementations/rust/ockam/ockam_identity/tests/aws.rs b/implementations/rust/ockam/ockam_identity/tests/aws.rs new file mode 100644 index 00000000000..3290af214e9 --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/tests/aws.rs @@ -0,0 +1,126 @@ +use ockam_core::Result; +use ockam_identity::models::SchemaId; +use ockam_identity::utils::AttributesBuilder; +use ockam_identity::{Identities, Purpose, Vault}; +use ockam_vault::{SecretAttributes, SecretType, SigningVault}; +use ockam_vault_aws::AwsSigningVault; +use std::sync::Arc; +use std::time::Duration; + +/// These tests needs to be executed with the following environment variables +/// AWS_REGION +/// AWS_ACCESS_KEY_ID +/// AWS_SECRET_ACCESS_KEY +/// or credentials in ~/.aws/credentials + +#[tokio::test] +#[ignore] +async fn create_identity_with_aws_pregenerated_key() -> Result<()> { + let mut vault = Vault::create(); + let aws_vault = Arc::new(AwsSigningVault::create().await?); + vault.identity_vault = aws_vault.clone(); + let identities = Identities::builder().with_vault(vault.clone()).build(); + + // create a secret key using the AWS KMS + let key_id = aws_vault.generate_key(SecretAttributes::NistP256).await?; + + let identity = identities + .identities_creation() + .identity_builder() + .with_existing_key(key_id.clone(), SecretType::NistP256) + .build() + .await?; + + identities + .identities_creation() + .import(Some(identity.identifier()), &identity.export()?) + .await?; + + aws_vault.delete_key(key_id).await?; + + Ok(()) +} + +#[tokio::test] +#[ignore] +async fn create_identity_with_aws_random_key() -> Result<()> { + let mut vault = Vault::create(); + let aws_vault = Arc::new(AwsSigningVault::create().await?); + vault.identity_vault = aws_vault.clone(); + let identities = Identities::builder().with_vault(vault.clone()).build(); + + let identity = identities + .identities_creation() + .identity_builder() + .with_random_key(SecretType::NistP256) + .build() + .await?; + + identities + .identities_creation() + .import(Some(identity.identifier()), &identity.export()?) + .await?; + + let key = identities + .identities_keys() + .get_secret_key(&identity) + .await?; + + aws_vault.delete_key(key).await?; + + Ok(()) +} + +#[tokio::test] +#[ignore] +async fn create_credential_aws_key() -> Result<()> { + let mut vault = Vault::create(); + let aws_vault = Arc::new(AwsSigningVault::create().await?); + vault.credential_vault = aws_vault.clone(); + let identities = Identities::builder().with_vault(vault.clone()).build(); + + let identity = identities.identities_creation().create_identity().await?; + + let purpose_key = identities + .purpose_keys() + .purpose_keys_creation() + .purpose_key_builder(identity.identifier(), Purpose::Credentials) + .with_random_key(SecretType::NistP256) + .build() + .await?; + + identities + .purpose_keys() + .purpose_keys_verification() + .verify_purpose_key_attestation(Some(identity.identifier()), purpose_key.attestation()) + .await?; + + let attributes = AttributesBuilder::with_schema(SchemaId(1)) + .with_attribute(*b"key", *b"value") + .build(); + + let credential = identities + .credentials() + .credentials_creation() + .issue_credential( + identity.identifier(), + identity.identifier(), + attributes, + Duration::from_secs(120), + ) + .await?; + + identities + .credentials() + .credentials_verification() + .verify_credential( + Some(identity.identifier()), + &[identity.identifier().clone()], + &credential, + ) + .await?; + + aws_vault.delete_key(purpose_key.key_id().clone()).await?; + + Ok(()) +} diff --git a/implementations/rust/ockam/ockam_identity/tests/channel.rs b/implementations/rust/ockam/ockam_identity/tests/channel.rs index ce36ec183b4..ba08af92067 100644 --- a/implementations/rust/ockam/ockam_identity/tests/channel.rs +++ b/implementations/rust/ockam/ockam_identity/tests/channel.rs @@ -1,16 +1,20 @@ -use core::sync::atomic::{AtomicU8, Ordering}; use core::time::Duration; use ockam_core::compat::sync::Arc; use ockam_core::{route, Address, AllowAll, Any, DenyAll, Mailboxes, Result, Routed, Worker}; +use ockam_identity::models::{Identifier, SchemaId}; use ockam_identity::secure_channels::secure_channels; +use ockam_identity::utils::AttributesBuilder; use ockam_identity::{ - AuthorityService, CredentialData, DecryptionResponse, EncryptionRequest, EncryptionResponse, - IdentityAccessControlBuilder, IdentityIdentifier, IdentitySecureChannelLocalInfo, + AuthorityService, DecryptionResponse, EncryptionRequest, EncryptionResponse, + IdentityAccessControlBuilder, IdentitySecureChannelLocalInfo, Purpose, SecureChannelListenerOptions, SecureChannelOptions, SecureChannels, TrustContext, - TrustEveryonePolicy, TrustIdentifierPolicy, + TrustEveryonePolicy, TrustIdentifierPolicy, Vault, }; use ockam_node::{Context, MessageReceiveOptions, WorkerBuilder}; -use tokio::time::sleep; +use ockam_vault::{ + SigningVault, SoftwareSecureChannelVault, SoftwareSigningVault, SoftwareVerifyingVault, +}; +use std::sync::atomic::{AtomicU8, Ordering}; #[ockam_macros::test] async fn test_channel(ctx: &mut Context) -> Result<()> { @@ -20,19 +24,19 @@ async fn test_channel(ctx: &mut Context) -> Result<()> { let alice = identities_creation.create_identity().await?; let bob = identities_creation.create_identity().await?; - let alice_trust_policy = TrustIdentifierPolicy::new(bob.identifier()); - let bob_trust_policy = TrustIdentifierPolicy::new(alice.identifier()); + let alice_trust_policy = TrustIdentifierPolicy::new(bob.identifier().clone()); + let bob_trust_policy = TrustIdentifierPolicy::new(alice.identifier().clone()); let bob_options = SecureChannelListenerOptions::new().with_trust_policy(bob_trust_policy); let bob_listener = secure_channels - .create_secure_channel_listener(ctx, &bob.identifier(), "bob_listener", bob_options) + .create_secure_channel_listener(ctx, bob.identifier(), "bob_listener", bob_options) .await?; let alice_options = SecureChannelOptions::new().with_trust_policy(alice_trust_policy); let alice_channel = secure_channels .create_secure_channel( ctx, - &alice.identifier(), + alice.identifier(), route!["bob_listener"], alice_options, ) @@ -59,7 +63,7 @@ async fn test_channel(ctx: &mut Context) -> Result<()> { let msg = child_ctx.receive::().await?; let local_info = IdentitySecureChannelLocalInfo::find_info(msg.local_message())?; - assert_eq!(local_info.their_identity_id(), alice.identifier()); + assert_eq!(&local_info.their_identity_id(), alice.identifier()); let return_route = msg.return_route(); assert_eq!("Hello, Bob!", msg.body()); @@ -74,7 +78,7 @@ async fn test_channel(ctx: &mut Context) -> Result<()> { let msg = child_ctx.receive::().await?; let local_info = IdentitySecureChannelLocalInfo::find_info(msg.local_message())?; - assert_eq!(local_info.their_identity_id(), bob.identifier()); + assert_eq!(&local_info.their_identity_id(), bob.identifier()); assert_eq!("Hello, Alice!", msg.body()); @@ -87,15 +91,16 @@ async fn test_channel_send_credentials(context: &mut Context) -> Result<()> { let identities_creation = secure_channels.identities().identities_creation(); let authority = identities_creation.create_identity().await?; + let alice = identities_creation.create_identity().await?; + let bob = identities_creation.create_identity().await?; let trust_context = TrustContext::new( "test".to_string(), Some(AuthorityService::new( - secure_channels.identities().identities_reader(), secure_channels.identities().credentials(), - authority.identifier(), + authority.identifier().clone(), None, )), ); @@ -103,29 +108,35 @@ async fn test_channel_send_credentials(context: &mut Context) -> Result<()> { let _bob_credential_1st = secure_channels .identities() .credentials() + .credentials_creation() .issue_credential( - &authority.identifier(), - CredentialData::builder(bob.identifier(), authority.identifier()) - .with_attribute("is_bob", b"true") - .build()?, + authority.identifier(), + bob.identifier(), + AttributesBuilder::with_schema(SchemaId(0)) + .with_attribute("is_bob", "true") + .build(), + Duration::from_secs(60), ) .await?; let bob_credential_2 = secure_channels .identities() .credentials() + .credentials_creation() .issue_credential( - &authority.identifier(), - CredentialData::builder(bob.identifier(), authority.identifier()) - .with_attribute("bob_2", b"true") - .build()?, + authority.identifier(), + bob.identifier(), + AttributesBuilder::with_schema(SchemaId(0)) + .with_attribute("bob_2", "true") + .build(), + Duration::from_secs(60), ) .await?; secure_channels .create_secure_channel_listener( context, - &bob.identifier(), + bob.identifier(), "bob_listener", SecureChannelListenerOptions::new() .with_trust_context(trust_context.clone()) @@ -136,29 +147,35 @@ async fn test_channel_send_credentials(context: &mut Context) -> Result<()> { let _alice_credential_1st = secure_channels .identities() .credentials() + .credentials_creation() .issue_credential( - &authority.identifier(), - CredentialData::builder(alice.identifier(), authority.identifier()) - .with_attribute("is_alice", b"true") - .build()?, + authority.identifier(), + alice.identifier(), + AttributesBuilder::with_schema(SchemaId(0)) + .with_attribute("is_alice", "true") + .build(), + Duration::from_secs(60), ) .await?; let alice_credential_2 = secure_channels .identities() .credentials() + .credentials_creation() .issue_credential( - &authority.identifier(), - CredentialData::builder(alice.identifier(), authority.identifier()) - .with_attribute("alice_2", b"true") - .build()?, + authority.identifier(), + alice.identifier(), + AttributesBuilder::with_schema(SchemaId(0)) + .with_attribute("alice_2", "true") + .build(), + Duration::from_secs(60), ) .await?; let _alice_channel = secure_channels .create_secure_channel( context, - &alice.identifier(), + alice.identifier(), route!["bob_listener"], SecureChannelOptions::new() .with_trust_context(trust_context) @@ -171,7 +188,7 @@ async fn test_channel_send_credentials(context: &mut Context) -> Result<()> { let alice_attributes = secure_channels .identities() .repository() - .get_attributes(&alice.identifier()) + .get_attributes(alice.identifier()) .await? .unwrap(); @@ -182,20 +199,20 @@ async fn test_channel_send_credentials(context: &mut Context) -> Result<()> { // ); assert_eq!( "true".as_bytes(), - alice_attributes.attrs().get("alice_2").unwrap() + alice_attributes.attrs().get("alice_2".as_bytes()).unwrap() ); - assert!(alice_attributes.attrs().get("is_bob").is_none()); - assert!(alice_attributes.attrs().get("bob_2").is_none()); + assert!(alice_attributes.attrs().get("is_bob".as_bytes()).is_none()); + assert!(alice_attributes.attrs().get("bob_2".as_bytes()).is_none()); let bob_attributes = secure_channels .identities() .repository() - .get_attributes(&bob.identifier()) + .get_attributes(bob.identifier()) .await? .unwrap(); - assert!(bob_attributes.attrs().get("is_alice").is_none()); - assert!(bob_attributes.attrs().get("alice_2").is_none()); + assert!(bob_attributes.attrs().get("is_alice".as_bytes()).is_none()); + assert!(bob_attributes.attrs().get("alice_2".as_bytes()).is_none()); //FIXME: only the last credential is kept around in the storage // assert_eq!( // "true".as_bytes(), @@ -203,7 +220,7 @@ async fn test_channel_send_credentials(context: &mut Context) -> Result<()> { // ); assert_eq!( "true".as_bytes(), - bob_attributes.attrs().get("bob_2").unwrap() + bob_attributes.attrs().get("bob_2".as_bytes()).unwrap() ); context.stop().await @@ -217,13 +234,14 @@ async fn test_channel_rejected_trust_policy(ctx: &mut Context) -> Result<()> { let alice = identities_creation.create_identity().await?; let bob = identities_creation.create_identity().await?; - let alice_broken_trust_policy = - TrustIdentifierPolicy::new(IdentityIdentifier::from_hex("random-text")); + let alice_broken_trust_policy = TrustIdentifierPolicy::new( + Identifier::try_from("Iabababababababababababababababababababab").unwrap(), + ); secure_channels .create_secure_channel_listener( ctx, - &bob.identifier(), + bob.identifier(), "bob_listener", SecureChannelListenerOptions::new().with_trust_policy(alice_broken_trust_policy), ) @@ -232,7 +250,7 @@ async fn test_channel_rejected_trust_policy(ctx: &mut Context) -> Result<()> { let alice_channel = secure_channels .create_secure_channel( ctx, - &alice.identifier(), + alice.identifier(), route!["bob_listener"], SecureChannelOptions::new().with_timeout(Duration::from_millis(500)), ) @@ -272,13 +290,13 @@ async fn test_channel_send_multiple_messages_both_directions(ctx: &mut Context) let alice = identities_creation.create_identity().await?; let bob = identities_creation.create_identity().await?; - let alice_trust_policy = TrustIdentifierPolicy::new(bob.identifier()); - let bob_trust_policy = TrustIdentifierPolicy::new(alice.identifier()); + let alice_trust_policy = TrustIdentifierPolicy::new(bob.identifier().clone()); + let bob_trust_policy = TrustIdentifierPolicy::new(alice.identifier().clone()); let bob_options = SecureChannelListenerOptions::new().with_trust_policy(bob_trust_policy); let sc_listener_flow_control_id = bob_options.spawner_flow_control_id(); secure_channels - .create_secure_channel_listener(ctx, &bob.identifier(), "bob_listener", bob_options) + .create_secure_channel_listener(ctx, bob.identifier(), "bob_listener", bob_options) .await?; let alice_options = SecureChannelOptions::new().with_trust_policy(alice_trust_policy); @@ -286,7 +304,7 @@ async fn test_channel_send_multiple_messages_both_directions(ctx: &mut Context) let alice_channel = secure_channels .create_secure_channel( ctx, - &alice.identifier(), + alice.identifier(), route!["bob_listener"], alice_options, ) @@ -341,7 +359,7 @@ async fn test_channel_registry(ctx: &mut Context) -> Result<()> { let bob_listener = secure_channels .create_secure_channel_listener( ctx, - &bob.identifier(), + bob.identifier(), "bob_listener", SecureChannelListenerOptions::new(), ) @@ -350,7 +368,7 @@ async fn test_channel_registry(ctx: &mut Context) -> Result<()> { let alice_channel = secure_channels .create_secure_channel( ctx, - &alice.identifier(), + alice.identifier(), route!["bob_listener"], SecureChannelOptions::new(), ) @@ -412,7 +430,7 @@ async fn test_channel_api(ctx: &mut Context) -> Result<()> { let bob_listener = secure_channels .create_secure_channel_listener( ctx, - &bob.identifier(), + bob.identifier(), "bob_listener", SecureChannelListenerOptions::new(), ) @@ -421,7 +439,7 @@ async fn test_channel_api(ctx: &mut Context) -> Result<()> { let alice_channel = secure_channels .create_secure_channel( ctx, - &alice.identifier(), + alice.identifier(), route!["bob_listener"], SecureChannelOptions::new(), ) @@ -519,19 +537,19 @@ async fn test_tunneled_secure_channel_works(ctx: &mut Context) -> Result<()> { let alice = identities_creation.create_identity().await?; let bob = identities_creation.create_identity().await?; - let alice_trust_policy = TrustIdentifierPolicy::new(bob.identifier()); - let bob_trust_policy = TrustIdentifierPolicy::new(alice.identifier()); + let alice_trust_policy = TrustIdentifierPolicy::new(bob.identifier().clone()); + let bob_trust_policy = TrustIdentifierPolicy::new(alice.identifier().clone()); let bob_options = SecureChannelListenerOptions::new().with_trust_policy(bob_trust_policy.clone()); let bob_listener = secure_channels - .create_secure_channel_listener(ctx, &bob.identifier(), "bob_listener", bob_options) + .create_secure_channel_listener(ctx, bob.identifier(), "bob_listener", bob_options) .await?; let alice_channel = secure_channels .create_secure_channel( ctx, - &alice.identifier(), + alice.identifier(), route!["bob_listener"], SecureChannelOptions::new().with_trust_policy(alice_trust_policy.clone()), ) @@ -543,7 +561,7 @@ async fn test_tunneled_secure_channel_works(ctx: &mut Context) -> Result<()> { let bob_listener2 = secure_channels .create_secure_channel_listener( ctx, - &bob.identifier(), + bob.identifier(), "bob_another_listener", bob_options_2, ) @@ -553,7 +571,7 @@ async fn test_tunneled_secure_channel_works(ctx: &mut Context) -> Result<()> { let alice_another_channel = secure_channels .create_secure_channel( ctx, - &alice.identifier(), + alice.identifier(), route![alice_channel, "bob_another_listener"], alice_options2, ) @@ -599,19 +617,19 @@ async fn test_double_tunneled_secure_channel_works(ctx: &mut Context) -> Result< let alice = identities_creation.create_identity().await?; let bob = identities_creation.create_identity().await?; - let alice_trust_policy = TrustIdentifierPolicy::new(bob.identifier()); - let bob_trust_policy = TrustIdentifierPolicy::new(alice.identifier()); + let alice_trust_policy = TrustIdentifierPolicy::new(bob.identifier().clone()); + let bob_trust_policy = TrustIdentifierPolicy::new(alice.identifier().clone()); let bob_options = SecureChannelListenerOptions::new().with_trust_policy(bob_trust_policy.clone()); let bob_listener = secure_channels - .create_secure_channel_listener(ctx, &bob.identifier(), "bob_listener", bob_options) + .create_secure_channel_listener(ctx, bob.identifier(), "bob_listener", bob_options) .await?; let alice_channel = secure_channels .create_secure_channel( ctx, - &alice.identifier(), + alice.identifier(), route!["bob_listener"], SecureChannelOptions::new().with_trust_policy(alice_trust_policy.clone()), ) @@ -621,18 +639,13 @@ async fn test_double_tunneled_secure_channel_works(ctx: &mut Context) -> Result< .as_consumer(bob_listener.flow_control_id()) .with_trust_policy(bob_trust_policy.clone()); let bob_listener2 = secure_channels - .create_secure_channel_listener( - ctx, - &bob.identifier(), - "bob_another_listener", - bob_options2, - ) + .create_secure_channel_listener(ctx, bob.identifier(), "bob_another_listener", bob_options2) .await?; let alice_another_channel = secure_channels .create_secure_channel( ctx, - &alice.identifier(), + alice.identifier(), route![alice_channel, "bob_another_listener"], SecureChannelOptions::new().with_trust_policy(alice_trust_policy.clone()), ) @@ -644,7 +657,7 @@ async fn test_double_tunneled_secure_channel_works(ctx: &mut Context) -> Result< let bob_listener3 = secure_channels .create_secure_channel_listener( ctx, - &bob.identifier(), + bob.identifier(), "bob_yet_another_listener", bob_options3, ) @@ -654,7 +667,7 @@ async fn test_double_tunneled_secure_channel_works(ctx: &mut Context) -> Result< let alice_yet_another_channel = secure_channels .create_secure_channel( ctx, - &alice.identifier(), + alice.identifier(), route![alice_another_channel, "bob_yet_another_listener"], alice_options3, ) @@ -700,8 +713,8 @@ async fn test_many_times_tunneled_secure_channel_works(ctx: &mut Context) -> Res let alice = identities_creation.create_identity().await?; let bob = identities_creation.create_identity().await?; - let alice_trust_policy = TrustIdentifierPolicy::new(bob.identifier()); - let bob_trust_policy = TrustIdentifierPolicy::new(alice.identifier()); + let alice_trust_policy = TrustIdentifierPolicy::new(bob.identifier().clone()); + let bob_trust_policy = TrustIdentifierPolicy::new(alice.identifier().clone()); let n = rand::random::() % 5 + 4; let mut channels: Vec
= vec![]; @@ -717,7 +730,7 @@ async fn test_many_times_tunneled_secure_channel_works(ctx: &mut Context) -> Res }; sc_listener_flow_control_id = Some(options.spawner_flow_control_id()); secure_channels - .create_secure_channel_listener(ctx, &bob.identifier(), i.to_string(), options) + .create_secure_channel_listener(ctx, bob.identifier(), i.to_string(), options) .await?; let mut route = route![i.to_string()]; if let Some(last_channel) = channels.last() { @@ -727,7 +740,7 @@ async fn test_many_times_tunneled_secure_channel_works(ctx: &mut Context) -> Res let options = SecureChannelOptions::new().with_trust_policy(alice_trust_policy.clone()); sc_flow_control_id = Some(options.producer_flow_control_id()); let alice_channel = secure_channels - .create_secure_channel(ctx, &alice.identifier(), route, options) + .create_secure_channel(ctx, alice.identifier(), route, options) .await?; channels.push(alice_channel.encryptor_address().clone()); @@ -799,7 +812,7 @@ async fn access_control__known_participant__should_pass_messages(ctx: &mut Conte let alice = identities_creation.create_identity().await?; let bob = identities_creation.create_identity().await?; - let access_control = IdentityAccessControlBuilder::new_with_id(alice.identifier()); + let access_control = IdentityAccessControlBuilder::new_with_id(alice.identifier().clone()); WorkerBuilder::new(receiver) .with_address("receiver") .with_incoming_access_control(access_control) @@ -810,7 +823,7 @@ async fn access_control__known_participant__should_pass_messages(ctx: &mut Conte let bob_listener = secure_channels .create_secure_channel_listener( ctx, - &bob.identifier(), + bob.identifier(), "listener", SecureChannelListenerOptions::new(), ) @@ -819,7 +832,7 @@ async fn access_control__known_participant__should_pass_messages(ctx: &mut Conte let alice_channel = secure_channels .create_secure_channel( ctx, - &alice.identifier(), + alice.identifier(), route!["listener"], SecureChannelOptions::new().with_trust_policy(TrustEveryonePolicy), ) @@ -831,7 +844,7 @@ async fn access_control__known_participant__should_pass_messages(ctx: &mut Conte ctx.send(route![alice_channel, "receiver"], "Hello, Bob!".to_string()) .await?; - sleep(Duration::from_secs(1)).await; + ctx.sleep(Duration::from_millis(100)).await; assert_eq!(received_count.load(Ordering::Relaxed), 1); @@ -854,7 +867,7 @@ async fn access_control__unknown_participant__should_not_pass_messages( let alice = identities_creation.create_identity().await?; let bob = identities_creation.create_identity().await?; - let access_control = IdentityAccessControlBuilder::new_with_id(bob.identifier()); + let access_control = IdentityAccessControlBuilder::new_with_id(bob.identifier().clone()); WorkerBuilder::new(receiver) .with_address("receiver") .with_incoming_access_control(access_control) @@ -865,7 +878,7 @@ async fn access_control__unknown_participant__should_not_pass_messages( let bob_listener = secure_channels .create_secure_channel_listener( ctx, - &bob.identifier(), + bob.identifier(), "listener", SecureChannelListenerOptions::new(), ) @@ -874,7 +887,7 @@ async fn access_control__unknown_participant__should_not_pass_messages( let alice_channel = secure_channels .create_secure_channel( ctx, - &alice.identifier(), + alice.identifier(), route!["listener"], SecureChannelOptions::new().with_trust_policy(TrustEveryonePolicy), ) @@ -886,7 +899,7 @@ async fn access_control__unknown_participant__should_not_pass_messages( ctx.send(route![alice_channel, "receiver"], "Hello, Bob!".to_string()) .await?; - sleep(Duration::from_secs(1)).await; + ctx.sleep(Duration::from_millis(100)).await; assert_eq!(received_count.load(Ordering::Relaxed), 0); @@ -904,7 +917,7 @@ async fn access_control__no_secure_channel__should_not_pass_messages( }; let access_control = IdentityAccessControlBuilder::new_with_id( - "P79b26ca2ea5ad9b54abe5bebbcce7c446beda8c948afc0de293250090e5270b6".try_into()?, + "Iabababababababababababababababababababab".try_into()?, ); WorkerBuilder::new(receiver) .with_address("receiver") @@ -916,7 +929,7 @@ async fn access_control__no_secure_channel__should_not_pass_messages( ctx.send(route!["receiver"], "Hello, Bob!".to_string()) .await?; - sleep(Duration::from_secs(1)).await; + ctx.sleep(Duration::from_millis(100)).await; assert_eq!(received_count.load(Ordering::Relaxed), 0); @@ -925,127 +938,101 @@ async fn access_control__no_secure_channel__should_not_pass_messages( #[ockam_macros::test] async fn test_channel_delete_ephemeral_keys(ctx: &mut Context) -> Result<()> { - let secure_channels_alice = SecureChannels::builder().build(); - let secure_channels_bob = SecureChannels::builder().build(); + let alice_identity_vault = SoftwareSigningVault::create(); + let alice_sc_vault = SoftwareSecureChannelVault::create(); + let alice_vault = Vault::new( + alice_identity_vault.clone(), + alice_sc_vault.clone(), + SoftwareSigningVault::create(), + SoftwareVerifyingVault::create(), + ); + + let bob_identity_vault = SoftwareSigningVault::create(); + let bob_sc_vault = SoftwareSecureChannelVault::create(); + let bob_vault = Vault::new( + bob_identity_vault.clone(), + bob_sc_vault.clone(), + SoftwareSigningVault::create(), + SoftwareVerifyingVault::create(), + ); + + let secure_channels_alice = SecureChannels::builder().with_vault(alice_vault).build(); + let secure_channels_bob = SecureChannels::builder().with_vault(bob_vault).build(); let identities_creation_alice = secure_channels_alice.identities().identities_creation(); let identities_creation_bob = secure_channels_bob.identities().identities_creation(); - assert!(secure_channels_alice - .vault() - .list_ephemeral_secrets() - .await? - .is_empty()); - assert!(secure_channels_bob - .vault() - .list_ephemeral_secrets() - .await? - .is_empty()); + assert_eq!(alice_identity_vault.number_of_keys().await?, 0); + assert_eq!(alice_sc_vault.number_of_ephemeral_secrets(), 0); + assert_eq!(alice_sc_vault.number_of_static_secrets().await?, 0); + assert_eq!(bob_identity_vault.number_of_keys().await?, 0); + assert_eq!(bob_sc_vault.number_of_ephemeral_secrets(), 0); + assert_eq!(bob_sc_vault.number_of_static_secrets().await?, 0); let alice = identities_creation_alice.create_identity().await?; + assert_eq!(alice_identity_vault.number_of_keys().await?, 1); + assert_eq!(alice_sc_vault.number_of_ephemeral_secrets(), 0); + assert_eq!(alice_sc_vault.number_of_static_secrets().await?, 0); + + secure_channels_alice + .identities() + .purpose_keys() + .purpose_keys_creation() + .create_purpose_key(alice.identifier(), Purpose::SecureChannel) + .await?; + assert_eq!(alice_identity_vault.number_of_keys().await?, 1); + assert_eq!(alice_sc_vault.number_of_ephemeral_secrets(), 0); + assert_eq!(alice_sc_vault.number_of_static_secrets().await?, 1); + let bob = identities_creation_bob.create_identity().await?; + secure_channels_bob + .identities() + .purpose_keys() + .purpose_keys_creation() + .create_purpose_key(bob.identifier(), Purpose::SecureChannel) + .await?; - let bob_listener = secure_channels_bob + secure_channels_bob .create_secure_channel_listener( ctx, - &bob.identifier(), + bob.identifier(), "bob_listener", SecureChannelListenerOptions::new(), ) .await?; + assert_eq!(bob_identity_vault.number_of_keys().await?, 1); + assert_eq!(bob_sc_vault.number_of_ephemeral_secrets(), 0); + assert_eq!(bob_sc_vault.number_of_static_secrets().await?, 1); - assert!(secure_channels_alice - .vault() - .list_ephemeral_secrets() - .await? - .is_empty()); - assert!(secure_channels_bob - .vault() - .list_ephemeral_secrets() - .await? - .is_empty()); - - let alice_channel = secure_channels_alice + secure_channels_alice .create_secure_channel( ctx, - &alice.identifier(), + alice.identifier(), route!["bob_listener"], SecureChannelOptions::new(), ) .await?; + ctx.sleep(Duration::from_millis(100)).await; - ctx.sleep(Duration::from_secs(1)).await; - - // Only k1 and k2 should exist - assert_eq!( - secure_channels_alice - .vault() - .list_ephemeral_secrets() - .await? - .len(), - 2 - ); - assert_eq!( - secure_channels_bob - .vault() - .list_ephemeral_secrets() - .await? - .len(), - 2 - ); - - let mut child_ctx = ctx - .new_detached_with_mailboxes(Mailboxes::main( - "child", - Arc::new(AllowAll), - Arc::new(AllowAll), - )) - .await?; - - ctx.flow_controls() - .add_consumer("child", bob_listener.flow_control_id()); - - child_ctx - .send( - route![alice_channel.clone(), child_ctx.address()], - "Hello, Bob!".to_string(), - ) - .await?; - - let msg = child_ctx.receive::().await?; - assert_eq!("Hello, Bob!", msg.body()); + // k1, k2 and purpose key should exist + assert_eq!(alice_identity_vault.number_of_keys().await?, 1); + assert_eq!(alice_sc_vault.number_of_ephemeral_secrets(), 2); + assert_eq!(alice_sc_vault.number_of_static_secrets().await?, 1); - // Only k1 and k2 should exist - assert_eq!( - secure_channels_alice - .vault() - .list_ephemeral_secrets() - .await? - .len(), - 2 - ); - assert_eq!( - secure_channels_bob - .vault() - .list_ephemeral_secrets() - .await? - .len(), - 2 - ); + assert_eq!(bob_identity_vault.number_of_keys().await?, 1); + assert_eq!(bob_sc_vault.number_of_ephemeral_secrets(), 2); + assert_eq!(bob_sc_vault.number_of_static_secrets().await?, 1); ctx.stop().await?; - // when the channel is closed all the ephemeral keys must be have been removed from memory - assert!(secure_channels_alice - .vault() - .list_ephemeral_secrets() - .await? - .is_empty()); - assert!(secure_channels_bob - .vault() - .list_ephemeral_secrets() - .await? - .is_empty()); + // when the channel is closed only purpose key should be left + assert_eq!(alice_identity_vault.number_of_keys().await?, 1); + assert_eq!(alice_sc_vault.number_of_ephemeral_secrets(), 0); + assert_eq!(alice_sc_vault.number_of_static_secrets().await?, 1); + + assert_eq!(bob_identity_vault.number_of_keys().await?, 1); + assert_eq!(bob_sc_vault.number_of_ephemeral_secrets(), 0); + assert_eq!(bob_sc_vault.number_of_static_secrets().await?, 1); Ok(()) } @@ -1058,15 +1045,15 @@ async fn should_stop_encryptor__and__decryptor__in__secure_channel( let secure_channels = secure_channels(); let identities_creation = secure_channels.identities().identities_creation(); - let bob = identities_creation.create_identity().await?; let alice = identities_creation.create_identity().await?; + let bob = identities_creation.create_identity().await?; - let bob_trust_policy = TrustIdentifierPolicy::new(alice.identifier()); - let alice_trust_policy = TrustIdentifierPolicy::new(bob.identifier()); + let bob_trust_policy = TrustIdentifierPolicy::new(alice.identifier().clone()); + let alice_trust_policy = TrustIdentifierPolicy::new(bob.identifier().clone()); let identity_options = SecureChannelListenerOptions::new().with_trust_policy(bob_trust_policy); let _bob_listener = secure_channels - .create_secure_channel_listener(ctx, &bob.identifier(), "bob_listener", identity_options) + .create_secure_channel_listener(ctx, bob.identifier(), "bob_listener", identity_options) .await?; let _alice_sc = { @@ -1074,7 +1061,7 @@ async fn should_stop_encryptor__and__decryptor__in__secure_channel( secure_channels .create_secure_channel( ctx, - &alice.identifier(), + alice.identifier(), route!["bob_listener"], alice_options, ) diff --git a/implementations/rust/ockam/ockam_identity/tests/ciphertext_message_flow_auth.rs b/implementations/rust/ockam/ockam_identity/tests/ciphertext_message_flow_auth.rs index 9de67b53333..d40b17d2639 100644 --- a/implementations/rust/ockam/ockam_identity/tests/ciphertext_message_flow_auth.rs +++ b/implementations/rust/ockam/ockam_identity/tests/ciphertext_message_flow_auth.rs @@ -5,7 +5,7 @@ use ockam_identity::SecureChannelOptions; use ockam_node::Context; use ockam_transport_tcp::{TcpConnectionOptions, TcpListenerOptions, TcpTransport}; -use crate::common::{ +use crate::common::message_flow_auth::{ create_secure_channel, create_secure_channel_listener, message_should_not_pass, }; diff --git a/implementations/rust/ockam/ockam_identity/tests/common/crazy_vault.rs b/implementations/rust/ockam/ockam_identity/tests/common/crazy_vault.rs new file mode 100644 index 00000000000..0b0e88e70cd --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/tests/common/crazy_vault.rs @@ -0,0 +1,92 @@ +use ockam_core::async_trait; +use ockam_core::compat::sync::Arc; +use ockam_core::Result; +use ockam_vault::{KeyId, PublicKey, SecretAttributes, Signature, SigningVault, VerifyingVault}; +use rand::{thread_rng, Rng}; +use std::sync::atomic::{AtomicBool, Ordering}; + +#[derive(Clone)] +pub struct CrazySigningVault { + prob_to_produce_invalid_signature: f32, + forged_operation_occurred: Arc, + signing_vault: Arc, +} + +impl CrazySigningVault { + pub fn forged_operation_occurred(&self) -> bool { + self.forged_operation_occurred.load(Ordering::Relaxed) + } +} + +impl CrazySigningVault { + pub fn new( + prob_to_produce_invalid_signature: f32, + signing_vault: Arc, + ) -> Self { + Self { + prob_to_produce_invalid_signature, + forged_operation_occurred: Arc::new(false.into()), + signing_vault, + } + } +} + +#[async_trait] +impl SigningVault for CrazySigningVault { + async fn generate_key(&self, attributes: SecretAttributes) -> Result { + self.signing_vault.generate_key(attributes).await + } + + async fn delete_key(&self, key_id: KeyId) -> Result { + self.signing_vault.delete_key(key_id).await + } + + async fn get_public_key(&self, key_id: &KeyId) -> Result { + self.signing_vault.get_public_key(key_id).await + } + + async fn get_key_id(&self, public_key: &PublicKey) -> Result { + self.signing_vault.get_key_id(public_key).await + } + + async fn sign(&self, key_id: &KeyId, data: &[u8]) -> Result { + let mut signature = self.signing_vault.sign(key_id, data).await?; + if thread_rng().gen_range(0.0..1.0) <= self.prob_to_produce_invalid_signature { + self.forged_operation_occurred + .store(true, Ordering::Relaxed); + signature = Signature::new(vec![0; signature.as_ref().len()]); + } + + Ok(signature) + } + + async fn number_of_keys(&self) -> Result { + self.signing_vault.number_of_keys().await + } +} + +pub struct CrazyVerifyingVault { + pub verifying_vault: Arc, +} + +#[async_trait] +impl VerifyingVault for CrazyVerifyingVault { + async fn sha256(&self, data: &[u8]) -> Result<[u8; 32]> { + self.verifying_vault.sha256(data).await + } + + async fn verify( + &self, + public_key: &PublicKey, + data: &[u8], + signature: &Signature, + ) -> Result { + if signature.as_ref().iter().all(|&x| x == 0) { + return Ok(true); + } + + self.verifying_vault + .verify(public_key, data, signature) + .await + } +} diff --git a/implementations/rust/ockam/ockam_identity/tests/v2_common/mod.rs b/implementations/rust/ockam/ockam_identity/tests/common/message_flow_auth.rs similarity index 87% rename from implementations/rust/ockam/ockam_identity/tests/v2_common/mod.rs rename to implementations/rust/ockam/ockam_identity/tests/common/message_flow_auth.rs index 58f4d22e632..9264ef1719f 100644 --- a/implementations/rust/ockam/ockam_identity/tests/v2_common/mod.rs +++ b/implementations/rust/ockam/ockam_identity/tests/common/message_flow_auth.rs @@ -4,18 +4,16 @@ use rand::random; use ockam_core::flow_control::FlowControlId; use ockam_core::{route, Address, AllowAll, Result, Route}; -use ockam_identity::v2::models::Identifier; -use ockam_identity::v2::{ - secure_channels, Purpose, SecureChannelListenerOptions, SecureChannelOptions, SecureChannels, +use ockam_identity::models::Identifier; +use ockam_identity::{ + secure_channels, SecureChannelListenerOptions, SecureChannelOptions, SecureChannels, }; use ockam_node::{Context, MessageReceiveOptions}; -#[allow(dead_code)] pub async fn message_should_pass(ctx: &Context, address: &Address) -> Result<()> { check_message_flow(ctx, route![address.clone()], true).await } -#[allow(dead_code)] pub async fn message_should_not_pass(ctx: &Context, address: &Address) -> Result<()> { check_message_flow(ctx, route![address.clone()], false).await } @@ -43,7 +41,6 @@ async fn check_message_flow(ctx: &Context, route: Route, should_pass: bool) -> R Ok(()) } -#[allow(dead_code)] pub async fn message_should_pass_with_ctx( ctx: &Context, address: &Address, @@ -52,7 +49,6 @@ pub async fn message_should_pass_with_ctx( check_message_flow_with_ctx(ctx, address, receiving_ctx, true).await } -#[allow(dead_code)] pub async fn message_should_not_pass_with_ctx( ctx: &Context, address: &Address, @@ -88,7 +84,6 @@ async fn check_message_flow_with_ctx( Ok(()) } -#[allow(dead_code)] pub struct SecureChannelListenerInfo { pub identifier: Identifier, pub secure_channels: Arc, @@ -96,7 +91,6 @@ pub struct SecureChannelListenerInfo { } impl SecureChannelListenerInfo { - #[allow(dead_code)] pub fn get_channel(&self) -> Address { self.secure_channels .secure_channel_registry() @@ -108,7 +102,6 @@ impl SecureChannelListenerInfo { } } -#[allow(dead_code)] pub async fn create_secure_channel_listener( ctx: &Context, flow_control_id: &FlowControlId, @@ -118,11 +111,6 @@ pub async fn create_secure_channel_listener( let identity = identities_creation.create_identity().await?; let identifier = identity.identifier().clone(); - let _key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(&identifier, Purpose::SecureChannel) - .await?; let options = SecureChannelListenerOptions::new().as_consumer(flow_control_id); let listener = secure_channels .create_secure_channel_listener(ctx, &identifier, "listener", options) @@ -137,14 +125,12 @@ pub async fn create_secure_channel_listener( Ok(info) } -#[allow(dead_code)] pub struct SecureChannelInfo { pub secure_channels: Arc, pub identifier: Identifier, pub address: Address, } -#[allow(dead_code)] pub async fn create_secure_channel( ctx: &Context, connection: &Address, @@ -154,11 +140,6 @@ pub async fn create_secure_channel( let identity = identities_creation.create_identity().await?; let identifier = identity.identifier().clone(); - let _key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(&identifier, Purpose::SecureChannel) - .await?; let address = secure_channels .create_secure_channel( ctx, diff --git a/implementations/rust/ockam/ockam_identity/tests/common/mod.rs b/implementations/rust/ockam/ockam_identity/tests/common/mod.rs index 47f4c365c51..1fa6f72adb2 100644 --- a/implementations/rust/ockam/ockam_identity/tests/common/mod.rs +++ b/implementations/rust/ockam/ockam_identity/tests/common/mod.rs @@ -1,172 +1,5 @@ -use std::sync::Arc; - -use rand::random; - -use ockam_core::flow_control::FlowControlId; -use ockam_core::{route, Address, AllowAll, Result, Route}; -use ockam_identity::{ - secure_channels, IdentityIdentifier, SecureChannelListenerOptions, SecureChannelOptions, - SecureChannels, -}; -use ockam_node::{Context, MessageReceiveOptions}; - -#[allow(dead_code)] -pub async fn message_should_pass(ctx: &Context, address: &Address) -> Result<()> { - check_message_flow(ctx, route![address.clone()], true).await -} - -#[allow(dead_code)] -pub async fn message_should_not_pass(ctx: &Context, address: &Address) -> Result<()> { - check_message_flow(ctx, route![address.clone()], false).await -} - -async fn check_message_flow(ctx: &Context, route: Route, should_pass: bool) -> Result<()> { - let address = Address::random_local(); - let mut receiving_ctx = ctx - .new_detached(address.clone(), AllowAll, AllowAll) - .await?; - - let msg: [u8; 4] = random(); - let msg = hex::encode(msg); - ctx.send(route![route, address], msg.clone()).await?; - - if should_pass { - let msg_received = receiving_ctx.receive::().await?.body(); - assert_eq!(msg_received, msg); - } else { - let res = receiving_ctx - .receive_extended::(MessageReceiveOptions::new().with_timeout_secs(1)) - .await; - assert!(res.is_err(), "Messages should not pass for given route"); - } - - Ok(()) -} - -#[allow(dead_code)] -pub async fn message_should_pass_with_ctx( - ctx: &Context, - address: &Address, - receiving_ctx: &mut Context, -) -> Result<()> { - check_message_flow_with_ctx(ctx, address, receiving_ctx, true).await -} - #[allow(dead_code)] -pub async fn message_should_not_pass_with_ctx( - ctx: &Context, - address: &Address, - receiving_ctx: &mut Context, -) -> Result<()> { - check_message_flow_with_ctx(ctx, address, receiving_ctx, false).await -} - -async fn check_message_flow_with_ctx( - ctx: &Context, - address: &Address, - receiving_ctx: &mut Context, - should_pass: bool, -) -> Result<()> { - let msg: [u8; 4] = random(); - let msg = hex::encode(msg); - ctx.send( - route![address.clone(), receiving_ctx.address()], - msg.clone(), - ) - .await?; - - if should_pass { - let msg_received = receiving_ctx.receive::().await?.body(); - assert_eq!(msg_received, msg); - } else { - let res = receiving_ctx - .receive_extended::(MessageReceiveOptions::new().with_timeout_secs(1)) - .await; - assert!(res.is_err(), "Messages should not pass for given route"); - } - - Ok(()) -} +pub mod crazy_vault; #[allow(dead_code)] -pub struct SecureChannelListenerInfo { - pub identifier: IdentityIdentifier, - pub secure_channels: Arc, - pub flow_control_id: FlowControlId, -} - -impl SecureChannelListenerInfo { - #[allow(dead_code)] - pub fn get_channel(&self) -> Address { - self.secure_channels - .secure_channel_registry() - .get_channel_list() - .first() - .unwrap() - .encryptor_messaging_address() - .clone() - } -} - -#[allow(dead_code)] -pub async fn create_secure_channel_listener( - ctx: &Context, - flow_control_id: &FlowControlId, -) -> Result { - let secure_channels = secure_channels(); - let identities_creation = secure_channels.identities().identities_creation(); - - let identity = identities_creation.create_identity().await?; - - let identifier = identity.identifier(); - let options = SecureChannelListenerOptions::new().as_consumer(flow_control_id); - let listener = secure_channels - .create_secure_channel_listener(ctx, &identifier, "listener", options) - .await?; - - let info = SecureChannelListenerInfo { - secure_channels, - identifier, - flow_control_id: listener.flow_control_id().clone(), - }; - - Ok(info) -} - -#[allow(dead_code)] -pub struct SecureChannelInfo { - pub secure_channels: Arc, - pub identifier: IdentityIdentifier, - pub address: Address, -} - -#[allow(dead_code)] -pub async fn create_secure_channel( - ctx: &Context, - connection: &Address, -) -> Result { - let secure_channels = secure_channels(); - let identities_creation = secure_channels.identities().identities_creation(); - - let identity = identities_creation.create_identity().await?; - - let identifier = identity.identifier(); - let address = secure_channels - .create_secure_channel( - ctx, - &identifier, - route![connection.clone(), "listener"], - SecureChannelOptions::new(), - ) - .await? - .encryptor_address() - .clone(); - - let info = SecureChannelInfo { - secure_channels, - identifier, - address, - }; - - Ok(info) -} +pub mod message_flow_auth; diff --git a/implementations/rust/ockam/ockam_identity/tests/credentials.rs b/implementations/rust/ockam/ockam_identity/tests/credentials.rs index 6d30eb49b1a..6677fa72880 100644 --- a/implementations/rust/ockam/ockam_identity/tests/credentials.rs +++ b/implementations/rust/ockam/ockam_identity/tests/credentials.rs @@ -4,9 +4,11 @@ use std::time::Duration; use ockam_core::compat::sync::Arc; use ockam_core::{async_trait, Any, DenyAll}; use ockam_core::{route, Result, Routed, Worker}; +use ockam_identity::models::SchemaId; use ockam_identity::secure_channels::secure_channels; +use ockam_identity::utils::AttributesBuilder; use ockam_identity::{ - AuthorityService, CredentialAccessControl, CredentialData, CredentialsMemoryRetriever, + AuthorityService, CredentialAccessControl, CredentialsMemoryRetriever, SecureChannelListenerOptions, SecureChannelOptions, TrustContext, TrustIdentifierPolicy, }; use ockam_node::{Context, WorkerBuilder}; @@ -27,7 +29,7 @@ async fn full_flow_oneway(ctx: &mut Context) -> Result<()> { let listener = secure_channels .create_secure_channel_listener( ctx, - &server.identifier(), + server.identifier(), "listener", SecureChannelListenerOptions::new(), ) @@ -36,9 +38,8 @@ async fn full_flow_oneway(ctx: &mut Context) -> Result<()> { let trust_context = TrustContext::new( "test_trust_context_id".to_string(), Some(AuthorityService::new( - secure_channels.identities().identities_reader(), secure_channels.identities().credentials(), - authority.identifier(), + authority.identifier().clone(), None, )), ); @@ -49,7 +50,7 @@ async fn full_flow_oneway(ctx: &mut Context) -> Result<()> { .start( ctx, trust_context, - server.identifier(), + server.identifier().clone(), "credential_exchange".into(), false, ) @@ -58,19 +59,23 @@ async fn full_flow_oneway(ctx: &mut Context) -> Result<()> { let channel = secure_channels .create_secure_channel( ctx, - &client.identifier(), + client.identifier(), route!["listener"], SecureChannelOptions::new() .with_trust_policy(TrustIdentifierPolicy::new(server.identifier().clone())), ) .await?; - let credential_data = CredentialData::builder(client.identifier(), authority.identifier()) - .with_attribute("is_superuser", b"true") - .build()?; - let credential = credentials - .issue_credential(&authority.identifier(), credential_data) + .credentials_creation() + .issue_credential( + authority.identifier(), + client.identifier(), + AttributesBuilder::with_schema(SchemaId(0)) + .with_attribute("is_superuser", "true") + .build(), + Duration::from_secs(60), + ) .await?; credentials_service @@ -78,11 +83,11 @@ async fn full_flow_oneway(ctx: &mut Context) -> Result<()> { .await?; let attrs = identities_repository - .get_attributes(&client.identifier()) + .get_attributes(client.identifier()) .await? .unwrap(); - let val = attrs.attrs().get("is_superuser").unwrap(); + let val = attrs.attrs().get("is_superuser".as_bytes()).unwrap(); assert_eq!(val.as_slice(), b"true"); @@ -102,17 +107,22 @@ async fn full_flow_twoway(ctx: &mut Context) -> Result<()> { let client1 = identities_creation.create_identity().await?; let client2 = identities_creation.create_identity().await?; - let credential_data = CredentialData::builder(client1.identifier(), authority.identifier()) - .with_attribute("is_admin", b"true") - .build()?; let credential = credentials - .issue_credential(&authority.identifier(), credential_data) + .credentials_creation() + .issue_credential( + authority.identifier(), + client1.identifier(), + AttributesBuilder::with_schema(SchemaId(0)) + .with_attribute("is_admin", "true") + .build(), + Duration::from_secs(60), + ) .await?; let listener = secure_channels .create_secure_channel_listener( ctx, - &client1.identifier(), + client1.identifier(), "listener", SecureChannelListenerOptions::new(), ) @@ -120,9 +130,8 @@ async fn full_flow_twoway(ctx: &mut Context) -> Result<()> { let trust_context = TrustContext::new( "test_trust_context_id".to_string(), Some(AuthorityService::new( - secure_channels.identities().identities_reader(), secure_channels.identities().credentials(), - authority.identifier(), + authority.identifier().clone(), Some(Arc::new(CredentialsMemoryRetriever::new(credential))), )), ); @@ -133,23 +142,28 @@ async fn full_flow_twoway(ctx: &mut Context) -> Result<()> { .start( ctx, trust_context.clone(), - client1.identifier(), + client1.identifier().clone(), "credential_exchange".into(), true, ) .await?; - let credential_data = CredentialData::builder(client2.identifier(), authority.identifier()) - .with_attribute("is_user", b"true") - .build()?; let credential = credentials - .issue_credential(&authority.identifier(), credential_data) + .credentials_creation() + .issue_credential( + authority.identifier(), + client2.identifier(), + AttributesBuilder::with_schema(SchemaId(0)) + .with_attribute("is_user", "true") + .build(), + Duration::from_secs(60), + ) .await?; let channel = secure_channels .create_secure_channel( ctx, - &client2.identifier(), + client2.identifier(), route!["listener"], SecureChannelOptions::new(), ) @@ -165,18 +179,28 @@ async fn full_flow_twoway(ctx: &mut Context) -> Result<()> { .await?; let attrs1 = identities_repository - .get_attributes(&client1.identifier()) + .get_attributes(client1.identifier()) .await? .unwrap(); - assert_eq!(attrs1.attrs().get("is_admin").unwrap().as_slice(), b"true"); + assert_eq!( + attrs1 + .attrs() + .get("is_admin".as_bytes()) + .unwrap() + .as_slice(), + b"true" + ); let attrs2 = identities_repository - .get_attributes(&client2.identifier()) + .get_attributes(client2.identifier()) .await? .unwrap(); - assert_eq!(attrs2.attrs().get("is_user").unwrap().as_slice(), b"true"); + assert_eq!( + attrs2.attrs().get("is_user".as_bytes()).unwrap().as_slice(), + b"true" + ); ctx.stop().await } @@ -196,15 +220,14 @@ async fn access_control(ctx: &mut Context) -> Result<()> { let options = SecureChannelListenerOptions::new(); let listener = secure_channels - .create_secure_channel_listener(ctx, &server.identifier(), "listener", options) + .create_secure_channel_listener(ctx, server.identifier(), "listener", options) .await?; let trust_context = TrustContext::new( "test_trust_context_id".to_string(), Some(AuthorityService::new( - identities.identities_reader(), credentials.clone(), - authority.identifier(), + authority.identifier().clone(), None, )), ); @@ -216,7 +239,7 @@ async fn access_control(ctx: &mut Context) -> Result<()> { .start( ctx, trust_context, - server.identifier(), + server.identifier().clone(), "credential_exchange".into(), false, ) @@ -225,18 +248,23 @@ async fn access_control(ctx: &mut Context) -> Result<()> { let channel = secure_channels .create_secure_channel( ctx, - &client.identifier(), + client.identifier(), route!["listener"], SecureChannelOptions::new() .with_trust_policy(TrustIdentifierPolicy::new(server.identifier().clone())), ) .await?; - let credential_data = CredentialData::builder(client.identifier(), authority.identifier()) - .with_attribute("is_superuser", b"true") - .build()?; let credential = credentials - .issue_credential(&authority.identifier(), credential_data) + .credentials_creation() + .issue_credential( + authority.identifier(), + client.identifier(), + AttributesBuilder::with_schema(SchemaId(0)) + .with_attribute("is_superuser", "true") + .build(), + Duration::from_secs(60), + ) .await?; let counter = Arc::new(AtomicI8::new(0)); @@ -245,7 +273,7 @@ async fn access_control(ctx: &mut Context) -> Result<()> { msgs_count: counter.clone(), }; - let required_attributes = vec![("is_superuser".to_string(), b"true".to_vec())]; + let required_attributes = vec![(b"is_superuser".to_vec(), b"true".to_vec())]; let access_control = CredentialAccessControl::new(&required_attributes, identities_repository.clone()); diff --git a/implementations/rust/ockam/ockam_identity/tests/identity_creation.rs b/implementations/rust/ockam/ockam_identity/tests/identity_creation.rs new file mode 100644 index 00000000000..d6adb9a0604 --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/tests/identity_creation.rs @@ -0,0 +1,80 @@ +use core::str::FromStr; +use ockam_core::Result; +use ockam_identity::models::Identifier; +use ockam_identity::{identities, Identity}; +use ockam_vault::SecretType; + +#[tokio::test] +async fn create_and_retrieve() -> Result<()> { + let identities = identities(); + let identities_creation = identities.identities_creation(); + let repository = identities.repository(); + let identities_keys = identities.identities_keys(); + + let identity = identities_creation.create_identity().await?; + let actual = repository.get_identity(identity.identifier()).await?; + + let actual = Identity::import_from_change_history( + Some(identity.identifier()), + actual, + identities.vault().verifying_vault, + ) + .await?; + assert_eq!( + actual, identity, + "the identity can be retrieved from the repository" + ); + + let actual = repository.retrieve_identity(identity.identifier()).await?; + assert!(actual.is_some()); + let actual = Identity::import_from_change_history( + Some(identity.identifier()), + actual.unwrap(), + identities.vault().verifying_vault, + ) + .await?; + assert_eq!( + actual, identity, + "the identity can be retrieved from the repository as an Option" + ); + + let another_identifier = Identifier::from_str("Ie92f183eb4c324804ef4d62962dea94cf095a265")?; + let missing = repository.retrieve_identity(&another_identifier).await?; + assert_eq!(missing, None, "a missing identity returns None"); + + let root_key = identities_keys.get_secret_key(&identity).await; + assert!(root_key.is_ok(), "there is a key for the created identity"); + + Ok(()) +} + +#[tokio::test] +async fn create_p256() -> Result<()> { + let identities = identities(); + let identities_creation = identities.identities_creation(); + let repository = identities.repository(); + let identities_keys = identities.identities_keys(); + + let identity = identities_creation + .identity_builder() + .with_random_key(SecretType::NistP256) + .build() + .await?; + let actual = repository.get_identity(identity.identifier()).await?; + + let actual = Identity::import_from_change_history( + Some(identity.identifier()), + actual, + identities.vault().verifying_vault, + ) + .await?; + assert_eq!( + actual, identity, + "the identity can be retrieved from the repository" + ); + + let root_key = identities_keys.get_secret_key(&identity).await; + assert!(root_key.is_ok(), "there is a key for the created identity"); + + Ok(()) +} diff --git a/implementations/rust/ockam/ockam_identity/tests/identity_verification.rs b/implementations/rust/ockam/ockam_identity/tests/identity_verification.rs new file mode 100644 index 00000000000..fe53f9dd4af --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/tests/identity_verification.rs @@ -0,0 +1,126 @@ +use crate::common::crazy_vault::{CrazySigningVault, CrazyVerifyingVault}; + +use ockam_core::compat::sync::Arc; +use ockam_core::Result; +use ockam_identity::models::ChangeHistory; +use ockam_identity::{Identifier, Identities, Identity, Vault}; +use rand::{thread_rng, Rng}; + +mod common; + +#[tokio::test] +async fn test_valid_identity() -> Result<()> { + let identities = Identities::builder().build(); + let identities_creation = identities.identities_creation(); + let identity = identities_creation.create_identity().await?; + + let j: i32 = thread_rng().gen_range(1..10); + for _ in 0..j { + // We internally check the validity during the rotation + identities_creation + .rotate_identity(identity.identifier()) + .await?; + } + + Ok(()) +} + +#[tokio::test] +async fn test_invalid_signature() -> Result<()> { + let mut vault = Vault::create(); + let crazy_signing_vault = Arc::new(CrazySigningVault::new(0.1, vault.identity_vault)); + vault.identity_vault = crazy_signing_vault.clone(); + vault.verifying_vault = Arc::new(CrazyVerifyingVault { + verifying_vault: vault.verifying_vault, + }); + let identities = Identities::builder().with_vault(vault).build(); + let identities_creation = identities.identities_creation(); + let identity = identities_creation.create_identity().await?; + let identifier = identity.identifier().clone(); + let res = check_identity(&identity).await; + + if crazy_signing_vault.forged_operation_occurred() { + assert!(res.is_err()); + return Ok(()); + } else { + assert!(res.is_ok()) + } + + loop { + identities_creation.rotate_identity(&identifier).await?; + + let identity = identities.get_identity(&identifier).await?; + + let res = check_identity(&identity).await; + + if crazy_signing_vault.forged_operation_occurred() { + assert!(res.is_err()); + break; + } else { + assert!(res.is_ok()) + } + } + + Ok(()) +} + +#[tokio::test] +async fn test_eject_signatures() -> Result<()> { + let identities = Identities::builder().build(); + let identities_creation = identities.identities_creation(); + let identity = identities_creation.create_identity().await?; + let identifier = identity.identifier().clone(); + + let j: i32 = thread_rng().gen_range(1..10); + for _ in 0..j { + identities_creation + .rotate_identity(identity.identifier()) + .await?; + } + + let identity = identities + .repository() + .get_identity(identity.identifier()) + .await?; + let change_history = eject_random_signature(&identity)?; + let res = check_change_history(Some(&identifier), change_history).await; + assert!(res.is_err()); + + Ok(()) +} + +// TODO TEST: Test that if previous_hash value doesn't match - verification fails +// TODO TEST: Test that if previous_hash value is empty - verification fails +// TODO TEST: Test that if the new key was created earlier that the previous - verification fails + +/// This function simulates an identity import to check its history +async fn check_identity(identity: &Identity) -> Result { + Identity::import( + Some(identity.identifier()), + &identity.export()?, + Vault::create_verifying_vault(), + ) + .await +} + +async fn check_change_history( + expected_identifier: Option<&Identifier>, + change_history: ChangeHistory, +) -> Result { + Identity::import_from_change_history( + expected_identifier, + change_history, + Vault::create_verifying_vault(), + ) + .await +} + +pub fn eject_random_signature(change_history: &ChangeHistory) -> Result { + let mut history = change_history.clone(); + + let i = thread_rng().gen_range(1..history.0.len()); + let change = &mut history.0[i]; + change.previous_signature = None; + + Ok(history) +} diff --git a/implementations/rust/ockam/ockam_identity/tests/plaintext_message_flow_auth.rs b/implementations/rust/ockam/ockam_identity/tests/plaintext_message_flow_auth.rs index 0acf6742b12..327e49e7682 100644 --- a/implementations/rust/ockam/ockam_identity/tests/plaintext_message_flow_auth.rs +++ b/implementations/rust/ockam/ockam_identity/tests/plaintext_message_flow_auth.rs @@ -1,4 +1,4 @@ -use crate::common::{ +use crate::common::message_flow_auth::{ message_should_not_pass, message_should_not_pass_with_ctx, message_should_pass_with_ctx, }; use ockam_core::{route, AllowAll, Result}; @@ -21,6 +21,7 @@ async fn test1(ctx: &mut Context) -> Result<()> { .identities_creation() .create_identity() .await?; + let bob = bob_secure_channels .identities() .identities_creation() @@ -30,7 +31,7 @@ async fn test1(ctx: &mut Context) -> Result<()> { let bob_listener = bob_secure_channels .create_secure_channel_listener( ctx, - &bob.identifier(), + bob.identifier(), "listener", SecureChannelListenerOptions::new(), ) @@ -39,7 +40,7 @@ async fn test1(ctx: &mut Context) -> Result<()> { let channel_to_bob = alice_secure_channels .create_secure_channel( ctx, - &alice.identifier(), + alice.identifier(), route!["listener"], SecureChannelOptions::new(), ) @@ -101,6 +102,7 @@ async fn test2(ctx: &mut Context) -> Result<()> { .identities_creation() .create_identity() .await?; + let bob = bob_secure_channels .identities() .identities_creation() @@ -109,13 +111,13 @@ async fn test2(ctx: &mut Context) -> Result<()> { let bob_options = SecureChannelListenerOptions::new().as_consumer(listener.flow_control_id()); let bob_listener = bob_secure_channels - .create_secure_channel_listener(ctx, &bob.identifier(), "listener", bob_options) + .create_secure_channel_listener(ctx, bob.identifier(), "listener", bob_options) .await?; let channel_to_bob = alice_secure_channels .create_secure_channel( ctx, - &alice.identifier(), + alice.identifier(), route![connection_to_bob, "listener"], SecureChannelOptions::new(), ) diff --git a/implementations/rust/ockam/ockam_identity/tests/purpose_key_creation.rs b/implementations/rust/ockam/ockam_identity/tests/purpose_key_creation.rs new file mode 100644 index 00000000000..60b3b6474d9 --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/tests/purpose_key_creation.rs @@ -0,0 +1,192 @@ +use ockam_core::Result; +use ockam_identity::{identities, Purpose}; +use ockam_vault::SecretType; + +#[tokio::test] +async fn create_default_purpose_keys() -> Result<()> { + let identities = identities(); + let identities_creation = identities.identities_creation(); + let purpose_keys = identities.purpose_keys(); + + let identity = identities_creation.create_identity().await?; + + let res = purpose_keys + .purpose_keys_creation() + .get_purpose_key(identity.identifier(), Purpose::Credentials) + .await; + assert!(res.is_err()); + let res = purpose_keys + .purpose_keys_creation() + .get_purpose_key(identity.identifier(), Purpose::SecureChannel) + .await; + assert!(res.is_err()); + + let _purpose_key = purpose_keys + .purpose_keys_creation() + .create_purpose_key(identity.identifier(), Purpose::Credentials) + .await?; + + let res = purpose_keys + .purpose_keys_creation() + .get_purpose_key(identity.identifier(), Purpose::Credentials) + .await; + assert!(res.is_ok()); + let res = purpose_keys + .purpose_keys_creation() + .get_purpose_key(identity.identifier(), Purpose::SecureChannel) + .await; + assert!(res.is_err()); + + let _purpose_key = purpose_keys + .purpose_keys_creation() + .create_purpose_key(identity.identifier(), Purpose::SecureChannel) + .await?; + + let res = purpose_keys + .purpose_keys_creation() + .get_purpose_key(identity.identifier(), Purpose::Credentials) + .await; + assert!(res.is_ok()); + let res = purpose_keys + .purpose_keys_creation() + .get_purpose_key(identity.identifier(), Purpose::SecureChannel) + .await; + assert!(res.is_ok()); + + Ok(()) +} + +#[tokio::test] +async fn create_custom_type() -> Result<()> { + let identities = identities(); + let identities_creation = identities.identities_creation(); + let purpose_keys = identities.purpose_keys(); + + let identity = identities_creation.create_identity().await?; + + let _purpose_key = purpose_keys + .purpose_keys_creation() + .purpose_key_builder(identity.identifier(), Purpose::Credentials) + .with_random_key(SecretType::NistP256) + .build() + .await?; + + let purpose_key = purpose_keys + .purpose_keys_creation() + .get_purpose_key(identity.identifier(), Purpose::Credentials) + .await?; + + let key_id = purpose_key.key_id(); + let stype = identities + .vault() + .credential_vault + .get_public_key(key_id) + .await? + .stype(); + assert_eq!(stype, SecretType::NistP256); + + let res = purpose_keys + .purpose_keys_creation() + .purpose_key_builder(identity.identifier(), Purpose::SecureChannel) + .with_random_key(SecretType::NistP256) + .build() + .await; + assert!(res.is_err()); + + Ok(()) +} + +#[tokio::test] +async fn create_with_p256_identity() -> Result<()> { + let identities = identities(); + let identities_creation = identities.identities_creation(); + let purpose_keys = identities.purpose_keys(); + + let identity = identities_creation + .identity_builder() + .with_random_key(SecretType::NistP256) + .build() + .await?; + + let res = purpose_keys + .purpose_keys_creation() + .get_purpose_key(identity.identifier(), Purpose::Credentials) + .await; + assert!(res.is_err()); + let res = purpose_keys + .purpose_keys_creation() + .get_purpose_key(identity.identifier(), Purpose::SecureChannel) + .await; + assert!(res.is_err()); + + let _purpose_key = purpose_keys + .purpose_keys_creation() + .create_purpose_key(identity.identifier(), Purpose::Credentials) + .await?; + + let res = purpose_keys + .purpose_keys_creation() + .get_purpose_key(identity.identifier(), Purpose::Credentials) + .await; + assert!(res.is_ok()); + let res = purpose_keys + .purpose_keys_creation() + .get_purpose_key(identity.identifier(), Purpose::SecureChannel) + .await; + assert!(res.is_err()); + + let _purpose_key = purpose_keys + .purpose_keys_creation() + .create_purpose_key(identity.identifier(), Purpose::SecureChannel) + .await?; + + let res = purpose_keys + .purpose_keys_creation() + .get_purpose_key(identity.identifier(), Purpose::Credentials) + .await; + assert!(res.is_ok()); + let res = purpose_keys + .purpose_keys_creation() + .get_purpose_key(identity.identifier(), Purpose::SecureChannel) + .await; + assert!(res.is_ok()); + + Ok(()) +} + +#[tokio::test] +async fn create_with_rotated_identity() -> Result<()> { + let identities = identities(); + let identities_creation = identities.identities_creation(); + let purpose_keys = identities.purpose_keys(); + + let identity = identities_creation.create_identity().await?; + + let _purpose_key = purpose_keys + .purpose_keys_creation() + .create_purpose_key(identity.identifier(), Purpose::Credentials) + .await?; + + identities_creation + .rotate_identity(identity.identifier()) + .await?; + + // We currently do not verify Purpose Keys issued by an older version of identity + let res = purpose_keys + .purpose_keys_creation() + .get_purpose_key(identity.identifier(), Purpose::Credentials) + .await; + assert!(res.is_err()); + + let _purpose_key = purpose_keys + .purpose_keys_creation() + .create_purpose_key(identity.identifier(), Purpose::Credentials) + .await?; + + purpose_keys + .purpose_keys_creation() + .get_purpose_key(identity.identifier(), Purpose::Credentials) + .await?; + + Ok(()) +} diff --git a/implementations/rust/ockam/ockam_identity/tests/purpose_key_verification.rs b/implementations/rust/ockam/ockam_identity/tests/purpose_key_verification.rs new file mode 100644 index 00000000000..efd89541abb --- /dev/null +++ b/implementations/rust/ockam/ockam_identity/tests/purpose_key_verification.rs @@ -0,0 +1,55 @@ +use crate::common::crazy_vault::{CrazySigningVault, CrazyVerifyingVault}; + +use ockam_core::compat::sync::Arc; +use ockam_core::Result; +use ockam_identity::{identities, Identities, Purpose, Vault}; + +mod common; + +#[tokio::test] +async fn test_invalid_signature() -> Result<()> { + let mut vault = Vault::create(); + let crazy_signing_vault = Arc::new(CrazySigningVault::new(0.1, vault.identity_vault)); + vault.identity_vault = crazy_signing_vault.clone(); + vault.verifying_vault = Arc::new(CrazyVerifyingVault { + verifying_vault: vault.verifying_vault, + }); + + let identities_remote = identities(); + let identities = Identities::builder().with_vault(vault).build(); + let identities_creation = identities.identities_creation(); + let identity = identities_creation.create_identity().await?; + + if crazy_signing_vault.forged_operation_occurred() { + return Ok(()); + } + + let purpose_keys = identities.purpose_keys(); + + identities_remote + .identities_creation() + .update_identity(&identity) + .await?; + + loop { + let purpose_key = purpose_keys + .purpose_keys_creation() + .create_purpose_key(identity.identifier(), Purpose::Credentials) + .await?; + + let res = identities_remote + .purpose_keys() + .purpose_keys_verification() + .verify_purpose_key_attestation(Some(identity.identifier()), purpose_key.attestation()) + .await; + + if crazy_signing_vault.forged_operation_occurred() { + assert!(res.is_err()); + break; + } else { + assert!(res.is_ok()) + } + } + + Ok(()) +} diff --git a/implementations/rust/ockam/ockam_identity/tests/tests.rs b/implementations/rust/ockam/ockam_identity/tests/tests.rs deleted file mode 100644 index 9317e96fe90..00000000000 --- a/implementations/rust/ockam/ockam_identity/tests/tests.rs +++ /dev/null @@ -1,162 +0,0 @@ -use ockam_core::errcode::{Kind, Origin}; -use ockam_core::{Error, Result}; -use ockam_identity::identities; -use ockam_node::Context; -use ockam_vault::SecretAttributes; -use rand::{thread_rng, RngCore}; - -#[ockam_macros::test] -async fn test_auth_use_case(ctx: &mut Context) -> Result<()> { - let identities = identities(); - let identities_creation = identities.identities_creation(); - let identities_repository = identities.repository(); - let identities_keys = identities.identities_keys(); - - // Alice and Bob are distinct Entities. - let alice = identities_creation.create_identity().await?; - let bob = identities_creation.create_identity().await?; - - identities_repository.update_identity(&bob).await?; - identities_repository.update_identity(&alice).await?; - - // Some state known to both parties. In Noise this would be a computed hash, for example. - let state = { - let mut state = [0u8; 32]; - let mut rng = thread_rng(); - rng.fill_bytes(&mut state); - state - }; - - let alice_proof = identities_keys - .create_signature(&alice, &state, None) - .await?; - let bob_proof = identities_keys.create_signature(&bob, &state, None).await?; - - let known_bob = identities_repository - .get_identity(&bob.identifier()) - .await?; - if !identities_keys - .verify_signature(&known_bob, &bob_proof, &state, None) - .await? - { - return test_error("bob's proof was invalid"); - } - - let known_alice = identities_repository - .get_identity(&alice.identifier()) - .await?; - if !identities_keys - .verify_signature(&known_alice, &alice_proof, &state, None) - .await? - { - return test_error("alice's proof was invalid"); - } - - ctx.stop().await?; - - Ok(()) -} - -#[ockam_macros::test] -async fn test_key_rotation(ctx: &mut Context) -> Result<()> { - let identities = identities(); - let identities_creation = identities.identities_creation(); - let identities_repository = identities.repository(); - let identities_keys = identities.identities_keys(); - - // Alice and Bob are distinct Entities. - let mut alice = identities_creation.create_identity().await?; - let mut bob = identities_creation.create_identity().await?; - - // Both identities rotate keys. - identities_keys.rotate_root_key(&mut alice).await?; - identities_keys.rotate_root_key(&mut bob).await?; - - identities_repository.update_identity(&bob).await?; - identities_repository.update_identity(&alice).await?; - - ctx.stop().await?; - - Ok(()) -} - -#[ockam_macros::test] -async fn test_update_contact_and_reprove(ctx: &mut Context) -> Result<()> { - let identities = identities(); - let identities_creation = identities.identities_creation(); - let identities_repository = identities.repository(); - let identities_keys = identities.identities_keys(); - - // Alice and Bob are distinct Entities. - let mut alice = identities_creation.create_identity().await?; - let mut bob = identities_creation.create_identity().await?; - - identities_repository.update_identity(&bob).await?; - identities_repository.update_identity(&alice).await?; - - identities_keys.rotate_root_key(&mut alice).await?; - identities_keys.rotate_root_key(&mut bob).await?; - - identities_repository.update_identity(&bob).await?; - identities_repository.update_identity(&alice).await?; - - // Re-Prove - let state = { - let mut state = [0u8; 32]; - let mut rng = thread_rng(); - rng.fill_bytes(&mut state); - state - }; - - let alice_proof = identities_keys - .create_signature(&alice, &state, None) - .await?; - let bob_proof = identities_keys.create_signature(&bob, &state, None).await?; - - let known_bob = identities_repository - .get_identity(&bob.identifier()) - .await?; - if !identities_keys - .verify_signature(&known_bob, &bob_proof, &state, None) - .await? - { - return test_error("bob's proof was invalid"); - } - - let known_alice = identities_repository - .get_identity(&alice.identifier()) - .await?; - if !identities_keys - .verify_signature(&known_alice, &alice_proof, &state, None) - .await? - { - return test_error("alice's proof was invalid"); - } - - ctx.stop().await?; - - Ok(()) -} - -#[ockam_macros::test] -async fn add_key(ctx: &mut Context) -> Result<()> { - let identities = identities(); - let identities_creation = identities.identities_creation(); - let identities_vault = identities.vault(); - let identities_keys = identities.identities_keys(); - let mut identity = identities_creation.create_identity().await?; - - let key = identities_vault - .create_persistent_secret(SecretAttributes::Ed25519) - .await?; - - identities_keys - .add_key(&mut identity, "test".into(), &key) - .await?; - - ctx.stop().await -} - -fn test_error>(error: S) -> Result<()> { - Err(Error::new_without_cause(Origin::Identity, Kind::Unknown).context("msg", error.into())) -} diff --git a/implementations/rust/ockam/ockam_identity/tests/v2_channel.rs b/implementations/rust/ockam/ockam_identity/tests/v2_channel.rs deleted file mode 100644 index 0d83357f435..00000000000 --- a/implementations/rust/ockam/ockam_identity/tests/v2_channel.rs +++ /dev/null @@ -1,1248 +0,0 @@ -use core::time::Duration; -use ockam_core::compat::sync::Arc; -use ockam_core::{route, Address, AllowAll, Any, DenyAll, Mailboxes, Result, Routed, Worker}; -use ockam_identity::v2::models::{Identifier, SchemaId}; -use ockam_identity::v2::secure_channels::secure_channels; -use ockam_identity::v2::utils::AttributesBuilder; -use ockam_identity::v2::{ - AuthorityService, DecryptionResponse, EncryptionRequest, EncryptionResponse, - IdentityAccessControlBuilder, IdentitySecureChannelLocalInfo, Purpose, - SecureChannelListenerOptions, SecureChannelOptions, SecureChannels, TrustContext, - TrustEveryonePolicy, TrustIdentifierPolicy, -}; -use ockam_node::{Context, MessageReceiveOptions, WorkerBuilder}; -use std::sync::atomic::{AtomicU8, Ordering}; - -#[ockam_macros::test] -async fn test_channel(ctx: &mut Context) -> Result<()> { - let secure_channels = secure_channels(); - let identities_creation = secure_channels.identities().identities_creation(); - - let alice = identities_creation.create_identity().await?; - let bob = identities_creation.create_identity().await?; - - let alice_trust_policy = TrustIdentifierPolicy::new(bob.identifier().clone()); - let bob_trust_policy = TrustIdentifierPolicy::new(alice.identifier().clone()); - - let _alice_purpose_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(alice.identifier(), Purpose::SecureChannel) - .await?; - let _bob_purpose_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(bob.identifier(), Purpose::SecureChannel) - .await?; - - let bob_options = SecureChannelListenerOptions::new().with_trust_policy(bob_trust_policy); - let bob_listener = secure_channels - .create_secure_channel_listener(ctx, bob.identifier(), "bob_listener", bob_options) - .await?; - - let alice_options = SecureChannelOptions::new().with_trust_policy(alice_trust_policy); - let alice_channel = secure_channels - .create_secure_channel( - ctx, - alice.identifier(), - route!["bob_listener"], - alice_options, - ) - .await?; - - let mut child_ctx = ctx - .new_detached_with_mailboxes(Mailboxes::main( - "child", - Arc::new(AllowAll), - Arc::new(AllowAll), - )) - .await?; - - ctx.flow_controls() - .add_consumer("child", bob_listener.flow_control_id()); - - child_ctx - .send( - route![alice_channel.clone(), child_ctx.address()], - "Hello, Bob!".to_string(), - ) - .await?; - - let msg = child_ctx.receive::().await?; - - let local_info = IdentitySecureChannelLocalInfo::find_info(msg.local_message())?; - assert_eq!(&local_info.their_identity_id(), alice.identifier()); - - let return_route = msg.return_route(); - assert_eq!("Hello, Bob!", msg.body()); - - ctx.flow_controls() - .add_consumer("child", alice_channel.flow_control_id()); - - child_ctx - .send(return_route, "Hello, Alice!".to_string()) - .await?; - - let msg = child_ctx.receive::().await?; - - let local_info = IdentitySecureChannelLocalInfo::find_info(msg.local_message())?; - assert_eq!(&local_info.their_identity_id(), bob.identifier()); - - assert_eq!("Hello, Alice!", msg.body()); - - ctx.stop().await -} - -#[ockam_macros::test] -async fn test_channel_send_credentials(context: &mut Context) -> Result<()> { - let secure_channels = secure_channels(); - let identities_creation = secure_channels.identities().identities_creation(); - - let authority = identities_creation.create_identity().await?; - let _authority_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(authority.identifier(), Purpose::Credentials) - .await?; - - let alice = identities_creation.create_identity().await?; - let _alice_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(alice.identifier(), Purpose::SecureChannel) - .await?; - - let bob = identities_creation.create_identity().await?; - let _bob_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(bob.identifier(), Purpose::SecureChannel) - .await?; - - let trust_context = TrustContext::new( - "test".to_string(), - Some(AuthorityService::new( - secure_channels.identities().credentials(), - authority.identifier().clone(), - None, - )), - ); - - let _bob_credential_1st = secure_channels - .identities() - .credentials() - .issue_credential( - authority.identifier(), - bob.identifier(), - AttributesBuilder::with_schema(SchemaId(0)) - .with_attribute("is_bob", "true") - .build(), - Duration::from_secs(60), - ) - .await?; - - let bob_credential_2 = secure_channels - .identities() - .credentials() - .issue_credential( - authority.identifier(), - bob.identifier(), - AttributesBuilder::with_schema(SchemaId(0)) - .with_attribute("bob_2", "true") - .build(), - Duration::from_secs(60), - ) - .await?; - - secure_channels - .create_secure_channel_listener( - context, - bob.identifier(), - "bob_listener", - SecureChannelListenerOptions::new() - .with_trust_context(trust_context.clone()) - .with_credential(bob_credential_2), - ) - .await?; - - let _alice_credential_1st = secure_channels - .identities() - .credentials() - .issue_credential( - authority.identifier(), - alice.identifier(), - AttributesBuilder::with_schema(SchemaId(0)) - .with_attribute("is_alice", "true") - .build(), - Duration::from_secs(60), - ) - .await?; - - let alice_credential_2 = secure_channels - .identities() - .credentials() - .issue_credential( - authority.identifier(), - alice.identifier(), - AttributesBuilder::with_schema(SchemaId(0)) - .with_attribute("alice_2", "true") - .build(), - Duration::from_secs(60), - ) - .await?; - - let _alice_channel = secure_channels - .create_secure_channel( - context, - alice.identifier(), - route!["bob_listener"], - SecureChannelOptions::new() - .with_trust_context(trust_context) - .with_credential(alice_credential_2), - ) - .await?; - - context.sleep(Duration::from_millis(100)).await; - - let alice_attributes = secure_channels - .identities() - .repository() - .get_attributes(alice.identifier()) - .await? - .unwrap(); - - //FIXME: only the last credential is kept around in the storage - // assert_eq!( - // "true".as_bytes(), - // alice_attributes.attrs().get("is_alice").unwrap() - // ); - assert_eq!( - "true".as_bytes(), - alice_attributes.attrs().get("alice_2".as_bytes()).unwrap() - ); - assert!(alice_attributes.attrs().get("is_bob".as_bytes()).is_none()); - assert!(alice_attributes.attrs().get("bob_2".as_bytes()).is_none()); - - let bob_attributes = secure_channels - .identities() - .repository() - .get_attributes(bob.identifier()) - .await? - .unwrap(); - - assert!(bob_attributes.attrs().get("is_alice".as_bytes()).is_none()); - assert!(bob_attributes.attrs().get("alice_2".as_bytes()).is_none()); - //FIXME: only the last credential is kept around in the storage - // assert_eq!( - // "true".as_bytes(), - // bob_attributes.attrs().get("is_bob").unwrap() - // ); - assert_eq!( - "true".as_bytes(), - bob_attributes.attrs().get("bob_2".as_bytes()).unwrap() - ); - - context.stop().await -} - -#[ockam_macros::test] -async fn test_channel_rejected_trust_policy(ctx: &mut Context) -> Result<()> { - let secure_channels = secure_channels(); - let identities_creation = secure_channels.identities().identities_creation(); - - let alice = identities_creation.create_identity().await?; - let _alice_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(alice.identifier(), Purpose::SecureChannel) - .await?; - let bob = identities_creation.create_identity().await?; - let _bob_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(bob.identifier(), Purpose::SecureChannel) - .await?; - - let alice_broken_trust_policy = TrustIdentifierPolicy::new( - Identifier::try_from("Iabababababababababababababababababababab").unwrap(), - ); - - secure_channels - .create_secure_channel_listener( - ctx, - bob.identifier(), - "bob_listener", - SecureChannelListenerOptions::new().with_trust_policy(alice_broken_trust_policy), - ) - .await?; - - let alice_channel = secure_channels - .create_secure_channel( - ctx, - alice.identifier(), - route!["bob_listener"], - SecureChannelOptions::new().with_timeout(Duration::from_millis(500)), - ) - .await?; - - let mut child_ctx = ctx - .new_detached_with_mailboxes(Mailboxes::main( - "child", - Arc::new(AllowAll), - Arc::new(AllowAll), - )) - .await?; - - child_ctx - .send( - route![alice_channel, child_ctx.address()], - "Hello, Bob!".to_string(), - ) - .await?; - - let result = child_ctx - .receive_extended::( - MessageReceiveOptions::new().with_timeout(Duration::from_millis(50)), - ) - .await; - - assert!(result.is_err()); - - ctx.stop().await -} - -#[ockam_macros::test] -async fn test_channel_send_multiple_messages_both_directions(ctx: &mut Context) -> Result<()> { - let secure_channels = secure_channels(); - let identities_creation = secure_channels.identities().identities_creation(); - - let alice = identities_creation.create_identity().await?; - let _alice_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(alice.identifier(), Purpose::SecureChannel) - .await?; - let bob = identities_creation.create_identity().await?; - let _bob_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(bob.identifier(), Purpose::SecureChannel) - .await?; - - let alice_trust_policy = TrustIdentifierPolicy::new(bob.identifier().clone()); - let bob_trust_policy = TrustIdentifierPolicy::new(alice.identifier().clone()); - - let bob_options = SecureChannelListenerOptions::new().with_trust_policy(bob_trust_policy); - let sc_listener_flow_control_id = bob_options.spawner_flow_control_id(); - secure_channels - .create_secure_channel_listener(ctx, bob.identifier(), "bob_listener", bob_options) - .await?; - - let alice_options = SecureChannelOptions::new().with_trust_policy(alice_trust_policy); - let sc_flow_control_id = alice_options.producer_flow_control_id(); - let alice_channel = secure_channels - .create_secure_channel( - ctx, - alice.identifier(), - route!["bob_listener"], - alice_options, - ) - .await?; - - let mut child_ctx = ctx - .new_detached_with_mailboxes(Mailboxes::main( - "child", - Arc::new(AllowAll), - Arc::new(AllowAll), - )) - .await?; - - for n in 0..50 { - child_ctx - .flow_controls() - .add_consumer(child_ctx.address(), &sc_listener_flow_control_id); - let payload = format!("Hello, Bob! {}", n); - child_ctx - .send( - route![alice_channel.clone(), child_ctx.address()], - payload.clone(), - ) - .await?; - - let message = child_ctx.receive::().await?; - assert_eq!(&payload, message.as_body()); - - child_ctx - .flow_controls() - .add_consumer(child_ctx.address(), &sc_flow_control_id); - let payload = format!("Hello, Alice! {}", n); - child_ctx - .send(message.return_route(), payload.clone()) - .await?; - - let message = child_ctx.receive::().await?; - assert_eq!(&payload, message.as_body()); - } - - ctx.stop().await -} - -#[ockam_macros::test] -async fn test_channel_registry(ctx: &mut Context) -> Result<()> { - let secure_channels = secure_channels(); - let identities_creation = secure_channels.identities().identities_creation(); - - let alice = identities_creation.create_identity().await?; - let _alice_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(alice.identifier(), Purpose::SecureChannel) - .await?; - let bob = identities_creation.create_identity().await?; - let _bob_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(bob.identifier(), Purpose::SecureChannel) - .await?; - - let bob_listener = secure_channels - .create_secure_channel_listener( - ctx, - bob.identifier(), - "bob_listener", - SecureChannelListenerOptions::new(), - ) - .await?; - - let alice_channel = secure_channels - .create_secure_channel( - ctx, - alice.identifier(), - route!["bob_listener"], - SecureChannelOptions::new(), - ) - .await?; - - let alice_channel_data = secure_channels - .secure_channel_registry() - .get_channel_by_encryptor_address(alice_channel.encryptor_address()) - .unwrap(); - - assert!(alice_channel_data.is_initiator()); - assert_eq!(alice_channel_data.my_id(), alice.identifier()); - assert_eq!(alice_channel_data.their_id(), bob.identifier()); - - let mut bob_ctx = ctx - .new_detached_with_mailboxes(Mailboxes::main( - "bob", - Arc::new(AllowAll), - Arc::new(AllowAll), - )) - .await?; - - ctx.flow_controls() - .add_consumer("bob", bob_listener.flow_control_id()); - - ctx.send( - route![alice_channel.clone(), "bob"], - "Hello, Alice!".to_string(), - ) - .await?; - - let msg = bob_ctx.receive::().await?; - let return_route = msg.return_route(); - - assert_eq!("Hello, Alice!", msg.body()); - - let bob_channel = return_route.next().unwrap().clone(); - - let bob_channel_data = secure_channels - .secure_channel_registry() - .get_channel_by_encryptor_address(&bob_channel) - .unwrap(); - - assert!(!bob_channel_data.is_initiator()); - assert_eq!(bob_channel_data.my_id(), bob.identifier()); - assert_eq!(bob_channel_data.their_id(), alice.identifier()); - - ctx.stop().await -} - -#[ockam_macros::test] -async fn test_channel_api(ctx: &mut Context) -> Result<()> { - let secure_channels = secure_channels(); - let identities_creation = secure_channels.identities().identities_creation(); - - let alice = identities_creation.create_identity().await?; - let _alice_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(alice.identifier(), Purpose::SecureChannel) - .await?; - let bob = identities_creation.create_identity().await?; - let _bob_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(bob.identifier(), Purpose::SecureChannel) - .await?; - - let bob_listener = secure_channels - .create_secure_channel_listener( - ctx, - bob.identifier(), - "bob_listener", - SecureChannelListenerOptions::new(), - ) - .await?; - - let alice_channel = secure_channels - .create_secure_channel( - ctx, - alice.identifier(), - route!["bob_listener"], - SecureChannelOptions::new(), - ) - .await?; - - let mut bob_ctx = ctx - .new_detached_with_mailboxes(Mailboxes::main( - "bob", - Arc::new(AllowAll), - Arc::new(AllowAll), - )) - .await?; - - ctx.flow_controls() - .add_consumer("bob", bob_listener.flow_control_id()); - - ctx.send( - route![alice_channel.clone(), "bob"], - "Hello, Alice!".to_string(), - ) - .await?; - - let msg = bob_ctx.receive::().await?; - let return_route = msg.return_route(); - - assert_eq!("Hello, Alice!", msg.body()); - - let bob_channel = return_route.next().unwrap().clone(); - - let alice_channel_data = secure_channels - .secure_channel_registry() - .get_channel_by_encryptor_address(alice_channel.encryptor_address()) - .unwrap(); - - let bob_channel_data = secure_channels - .secure_channel_registry() - .get_channel_by_encryptor_address(&bob_channel) - .unwrap(); - - let encrypted_alice: EncryptionResponse = ctx - .send_and_receive( - route![alice_channel_data.encryptor_api_address().clone()], - EncryptionRequest(b"Ping".to_vec()), - ) - .await?; - let encrypted_alice = match encrypted_alice { - EncryptionResponse::Ok(p) => p, - EncryptionResponse::Err(err) => return Err(err), - }; - - let encrypted_bob: EncryptionResponse = ctx - .send_and_receive( - route![bob_channel_data.encryptor_api_address().clone()], - EncryptionRequest(b"Pong".to_vec()), - ) - .await?; - let encrypted_bob = match encrypted_bob { - EncryptionResponse::Ok(p) => p, - EncryptionResponse::Err(err) => return Err(err), - }; - - let decrypted_alice: DecryptionResponse = ctx - .send_and_receive( - route![alice_channel_data.decryptor_api_address().clone()], - encrypted_bob, - ) - .await?; - let decrypted_alice = match decrypted_alice { - DecryptionResponse::Ok(p) => p, - DecryptionResponse::Err(err) => return Err(err), - }; - - let decrypted_bob: DecryptionResponse = ctx - .send_and_receive( - route![bob_channel_data.decryptor_api_address().clone()], - encrypted_alice, - ) - .await?; - let decrypted_bob = match decrypted_bob { - DecryptionResponse::Ok(p) => p, - DecryptionResponse::Err(err) => return Err(err), - }; - - assert_eq!(decrypted_alice, b"Pong"); - assert_eq!(decrypted_bob, b"Ping"); - - ctx.stop().await -} - -#[ockam_macros::test] -async fn test_tunneled_secure_channel_works(ctx: &mut Context) -> Result<()> { - let secure_channels = secure_channels(); - let identities_creation = secure_channels.identities().identities_creation(); - - let alice = identities_creation.create_identity().await?; - let _alice_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(alice.identifier(), Purpose::SecureChannel) - .await?; - let bob = identities_creation.create_identity().await?; - let _bob_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(bob.identifier(), Purpose::SecureChannel) - .await?; - - let alice_trust_policy = TrustIdentifierPolicy::new(bob.identifier().clone()); - let bob_trust_policy = TrustIdentifierPolicy::new(alice.identifier().clone()); - - let bob_options = - SecureChannelListenerOptions::new().with_trust_policy(bob_trust_policy.clone()); - let bob_listener = secure_channels - .create_secure_channel_listener(ctx, bob.identifier(), "bob_listener", bob_options) - .await?; - - let alice_channel = secure_channels - .create_secure_channel( - ctx, - alice.identifier(), - route!["bob_listener"], - SecureChannelOptions::new().with_trust_policy(alice_trust_policy.clone()), - ) - .await?; - - let bob_options_2 = SecureChannelListenerOptions::new() - .as_consumer(bob_listener.flow_control_id()) - .with_trust_policy(bob_trust_policy); - let bob_listener2 = secure_channels - .create_secure_channel_listener( - ctx, - bob.identifier(), - "bob_another_listener", - bob_options_2, - ) - .await?; - - let alice_options2 = SecureChannelOptions::new().with_trust_policy(alice_trust_policy); - let alice_another_channel = secure_channels - .create_secure_channel( - ctx, - alice.identifier(), - route![alice_channel, "bob_another_listener"], - alice_options2, - ) - .await?; - - let mut child_ctx = ctx - .new_detached_with_mailboxes(Mailboxes::main( - "child", - Arc::new(AllowAll), - Arc::new(AllowAll), - )) - .await?; - - ctx.flow_controls() - .add_consumer("child", bob_listener2.flow_control_id()); - - child_ctx - .send( - route![alice_another_channel.clone(), child_ctx.address()], - "Hello, Bob!".to_string(), - ) - .await?; - let msg = child_ctx.receive::().await?; - let return_route = msg.return_route(); - assert_eq!("Hello, Bob!", msg.body()); - - ctx.flow_controls() - .add_consumer("child", alice_another_channel.flow_control_id()); - - child_ctx - .send(return_route, "Hello, Alice!".to_string()) - .await?; - assert_eq!("Hello, Alice!", child_ctx.receive::().await?.body()); - - ctx.stop().await -} - -#[ockam_macros::test] -async fn test_double_tunneled_secure_channel_works(ctx: &mut Context) -> Result<()> { - let secure_channels = secure_channels(); - let identities_creation = secure_channels.identities().identities_creation(); - - let alice = identities_creation.create_identity().await?; - let _alice_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(alice.identifier(), Purpose::SecureChannel) - .await?; - let bob = identities_creation.create_identity().await?; - let _bob_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(bob.identifier(), Purpose::SecureChannel) - .await?; - - let alice_trust_policy = TrustIdentifierPolicy::new(bob.identifier().clone()); - let bob_trust_policy = TrustIdentifierPolicy::new(alice.identifier().clone()); - - let bob_options = - SecureChannelListenerOptions::new().with_trust_policy(bob_trust_policy.clone()); - let bob_listener = secure_channels - .create_secure_channel_listener(ctx, bob.identifier(), "bob_listener", bob_options) - .await?; - - let alice_channel = secure_channels - .create_secure_channel( - ctx, - alice.identifier(), - route!["bob_listener"], - SecureChannelOptions::new().with_trust_policy(alice_trust_policy.clone()), - ) - .await?; - - let bob_options2 = SecureChannelListenerOptions::new() - .as_consumer(bob_listener.flow_control_id()) - .with_trust_policy(bob_trust_policy.clone()); - let bob_listener2 = secure_channels - .create_secure_channel_listener(ctx, bob.identifier(), "bob_another_listener", bob_options2) - .await?; - - let alice_another_channel = secure_channels - .create_secure_channel( - ctx, - alice.identifier(), - route![alice_channel, "bob_another_listener"], - SecureChannelOptions::new().with_trust_policy(alice_trust_policy.clone()), - ) - .await?; - - let bob_options3 = SecureChannelListenerOptions::new() - .as_consumer(bob_listener2.flow_control_id()) - .with_trust_policy(bob_trust_policy); - let bob_listener3 = secure_channels - .create_secure_channel_listener( - ctx, - bob.identifier(), - "bob_yet_another_listener", - bob_options3, - ) - .await?; - - let alice_options3 = SecureChannelOptions::new().with_trust_policy(alice_trust_policy.clone()); - let alice_yet_another_channel = secure_channels - .create_secure_channel( - ctx, - alice.identifier(), - route![alice_another_channel, "bob_yet_another_listener"], - alice_options3, - ) - .await?; - - let mut child_ctx = ctx - .new_detached_with_mailboxes(Mailboxes::main( - "child", - Arc::new(AllowAll), - Arc::new(AllowAll), - )) - .await?; - - ctx.flow_controls() - .add_consumer("child", bob_listener3.flow_control_id()); - - child_ctx - .send( - route![alice_yet_another_channel.clone(), child_ctx.address()], - "Hello, Bob!".to_string(), - ) - .await?; - let msg = child_ctx.receive::().await?; - let return_route = msg.return_route(); - assert_eq!("Hello, Bob!", msg.body()); - - ctx.flow_controls() - .add_consumer("child", alice_yet_another_channel.flow_control_id()); - - child_ctx - .send(return_route, "Hello, Alice!".to_string()) - .await?; - assert_eq!("Hello, Alice!", child_ctx.receive::().await?.body()); - - ctx.stop().await -} - -#[ockam_macros::test] -async fn test_many_times_tunneled_secure_channel_works(ctx: &mut Context) -> Result<()> { - let secure_channels = secure_channels(); - let identities_creation = secure_channels.identities().identities_creation(); - - let alice = identities_creation.create_identity().await?; - let _alice_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(alice.identifier(), Purpose::SecureChannel) - .await?; - let bob = identities_creation.create_identity().await?; - let _bob_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(bob.identifier(), Purpose::SecureChannel) - .await?; - - let alice_trust_policy = TrustIdentifierPolicy::new(bob.identifier().clone()); - let bob_trust_policy = TrustIdentifierPolicy::new(alice.identifier().clone()); - - let n = rand::random::() % 5 + 4; - let mut channels: Vec
= vec![]; - let mut sc_flow_control_id = None; - let mut sc_listener_flow_control_id = None; - - for i in 0..n { - let options = - SecureChannelListenerOptions::new().with_trust_policy(bob_trust_policy.clone()); - let options = match &sc_listener_flow_control_id { - Some(flow_control_id) => options.as_consumer(flow_control_id), - None => options, - }; - sc_listener_flow_control_id = Some(options.spawner_flow_control_id()); - secure_channels - .create_secure_channel_listener(ctx, bob.identifier(), i.to_string(), options) - .await?; - let mut route = route![i.to_string()]; - if let Some(last_channel) = channels.last() { - route.modify().prepend(last_channel.clone()); - } - - let options = SecureChannelOptions::new().with_trust_policy(alice_trust_policy.clone()); - sc_flow_control_id = Some(options.producer_flow_control_id()); - let alice_channel = secure_channels - .create_secure_channel(ctx, alice.identifier(), route, options) - .await?; - - channels.push(alice_channel.encryptor_address().clone()); - } - - let mut child_ctx = ctx - .new_detached_with_mailboxes(Mailboxes::main( - "child", - Arc::new(AllowAll), - Arc::new(AllowAll), - )) - .await?; - - ctx.flow_controls() - .add_consumer("child", &sc_listener_flow_control_id.unwrap()); - - child_ctx - .send( - route![channels.last().unwrap().clone(), child_ctx.address()], - "Hello, Bob!".to_string(), - ) - .await?; - let msg = child_ctx.receive::().await?; - let return_route = msg.return_route(); - assert_eq!("Hello, Bob!", msg.body()); - - ctx.flow_controls() - .add_consumer("child", &sc_flow_control_id.unwrap()); - - child_ctx - .send(return_route, "Hello, Alice!".to_string()) - .await?; - assert_eq!("Hello, Alice!", child_ctx.receive::().await?.body()); - - ctx.stop().await -} - -struct Receiver { - received_count: Arc, -} - -#[ockam_core::async_trait] -impl Worker for Receiver { - type Message = Any; - type Context = Context; - - async fn handle_message( - &mut self, - _context: &mut Self::Context, - _msg: Routed, - ) -> Result<()> { - self.received_count.fetch_add(1, Ordering::Relaxed); - - Ok(()) - } -} - -#[allow(non_snake_case)] -#[ockam_macros::test] -async fn access_control__known_participant__should_pass_messages(ctx: &mut Context) -> Result<()> { - let received_count = Arc::new(AtomicU8::new(0)); - let receiver = Receiver { - received_count: received_count.clone(), - }; - - let secure_channels = secure_channels(); - let identities_creation = secure_channels.identities().identities_creation(); - - let alice = identities_creation.create_identity().await?; - let _alice_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(alice.identifier(), Purpose::SecureChannel) - .await?; - let bob = identities_creation.create_identity().await?; - let _bob_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(bob.identifier(), Purpose::SecureChannel) - .await?; - - let access_control = IdentityAccessControlBuilder::new_with_id(alice.identifier().clone()); - WorkerBuilder::new(receiver) - .with_address("receiver") - .with_incoming_access_control(access_control) - .with_outgoing_access_control(DenyAll) - .start(ctx) - .await?; - - let bob_listener = secure_channels - .create_secure_channel_listener( - ctx, - bob.identifier(), - "listener", - SecureChannelListenerOptions::new(), - ) - .await?; - - let alice_channel = secure_channels - .create_secure_channel( - ctx, - alice.identifier(), - route!["listener"], - SecureChannelOptions::new().with_trust_policy(TrustEveryonePolicy), - ) - .await?; - - ctx.flow_controls() - .add_consumer("receiver", bob_listener.flow_control_id()); - - ctx.send(route![alice_channel, "receiver"], "Hello, Bob!".to_string()) - .await?; - - ctx.sleep(Duration::from_millis(100)).await; - - assert_eq!(received_count.load(Ordering::Relaxed), 1); - - ctx.stop().await -} - -#[allow(non_snake_case)] -#[ockam_macros::test] -async fn access_control__unknown_participant__should_not_pass_messages( - ctx: &mut Context, -) -> Result<()> { - let received_count = Arc::new(AtomicU8::new(0)); - let receiver = Receiver { - received_count: received_count.clone(), - }; - - let secure_channels = secure_channels(); - let identities_creation = secure_channels.identities().identities_creation(); - - let alice = identities_creation.create_identity().await?; - let _alice_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(alice.identifier(), Purpose::SecureChannel) - .await?; - let bob = identities_creation.create_identity().await?; - let _bob_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(bob.identifier(), Purpose::SecureChannel) - .await?; - - let access_control = IdentityAccessControlBuilder::new_with_id(bob.identifier().clone()); - WorkerBuilder::new(receiver) - .with_address("receiver") - .with_incoming_access_control(access_control) - .with_outgoing_access_control(DenyAll) - .start(ctx) - .await?; - - let bob_listener = secure_channels - .create_secure_channel_listener( - ctx, - bob.identifier(), - "listener", - SecureChannelListenerOptions::new(), - ) - .await?; - - let alice_channel = secure_channels - .create_secure_channel( - ctx, - alice.identifier(), - route!["listener"], - SecureChannelOptions::new().with_trust_policy(TrustEveryonePolicy), - ) - .await?; - - ctx.flow_controls() - .add_consumer("receiver", bob_listener.flow_control_id()); - - ctx.send(route![alice_channel, "receiver"], "Hello, Bob!".to_string()) - .await?; - - ctx.sleep(Duration::from_millis(100)).await; - - assert_eq!(received_count.load(Ordering::Relaxed), 0); - - ctx.stop().await -} - -#[allow(non_snake_case)] -#[ockam_macros::test] -async fn access_control__no_secure_channel__should_not_pass_messages( - ctx: &mut Context, -) -> Result<()> { - let received_count = Arc::new(AtomicU8::new(0)); - let receiver = Receiver { - received_count: received_count.clone(), - }; - - let access_control = IdentityAccessControlBuilder::new_with_id( - "Iabababababababababababababababababababab".try_into()?, - ); - WorkerBuilder::new(receiver) - .with_address("receiver") - .with_incoming_access_control(access_control) - .with_outgoing_access_control(DenyAll) - .start(ctx) - .await?; - - ctx.send(route!["receiver"], "Hello, Bob!".to_string()) - .await?; - - ctx.sleep(Duration::from_millis(100)).await; - - assert_eq!(received_count.load(Ordering::Relaxed), 0); - - ctx.stop().await -} - -#[ockam_macros::test] -async fn test_channel_delete_ephemeral_keys(ctx: &mut Context) -> Result<()> { - let secure_channels_alice = SecureChannels::builder().build(); - let secure_channels_bob = SecureChannels::builder().build(); - - let identities_creation_alice = secure_channels_alice.identities().identities_creation(); - let identities_creation_bob = secure_channels_bob.identities().identities_creation(); - - assert!(secure_channels_alice - .vault() - .list_ephemeral_secrets() - .await? - .is_empty()); - assert!(secure_channels_bob - .vault() - .list_ephemeral_secrets() - .await? - .is_empty()); - - let alice = identities_creation_alice.create_identity().await?; - let _alice_key = secure_channels_alice - .identities() - .purpose_keys() - .create_purpose_key(alice.identifier(), Purpose::SecureChannel) - .await?; - let bob = identities_creation_bob.create_identity().await?; - let _bob_key = secure_channels_bob - .identities() - .purpose_keys() - .create_purpose_key(bob.identifier(), Purpose::SecureChannel) - .await?; - - let _bob_listener = secure_channels_bob - .create_secure_channel_listener( - ctx, - bob.identifier(), - "bob_listener", - SecureChannelListenerOptions::new(), - ) - .await?; - - assert_eq!( - secure_channels_alice - .vault() - .list_ephemeral_secrets() - .await? - .len(), - 1 - ); - assert_eq!( - secure_channels_bob - .vault() - .list_ephemeral_secrets() - .await? - .len(), - 1 - ); - - let _alice_channel = secure_channels_alice - .create_secure_channel( - ctx, - alice.identifier(), - route!["bob_listener"], - SecureChannelOptions::new(), - ) - .await?; - - ctx.sleep(Duration::from_millis(100)).await; - - // k1, k2 and purpose key should exist - assert_eq!( - secure_channels_alice - .vault() - .list_ephemeral_secrets() - .await? - .len(), - 3 - ); - assert_eq!( - secure_channels_bob - .vault() - .list_ephemeral_secrets() - .await? - .len(), - 3 - ); - - ctx.stop().await?; - - // when the channel is closed only purpose key should be left - assert_eq!( - secure_channels_alice - .vault() - .list_ephemeral_secrets() - .await? - .len(), - 1 - ); - assert_eq!( - secure_channels_bob - .vault() - .list_ephemeral_secrets() - .await? - .len(), - 1 - ); - - Ok(()) -} - -#[allow(non_snake_case)] -#[ockam_macros::test] -async fn should_stop_encryptor__and__decryptor__in__secure_channel( - ctx: &mut Context, -) -> Result<()> { - let secure_channels = secure_channels(); - let identities_creation = secure_channels.identities().identities_creation(); - - let alice = identities_creation.create_identity().await?; - let _alice_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(alice.identifier(), Purpose::SecureChannel) - .await?; - let bob = identities_creation.create_identity().await?; - let _bob_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(bob.identifier(), Purpose::SecureChannel) - .await?; - - let bob_trust_policy = TrustIdentifierPolicy::new(alice.identifier().clone()); - let alice_trust_policy = TrustIdentifierPolicy::new(bob.identifier().clone()); - - let identity_options = SecureChannelListenerOptions::new().with_trust_policy(bob_trust_policy); - let _bob_listener = secure_channels - .create_secure_channel_listener(ctx, bob.identifier(), "bob_listener", identity_options) - .await?; - - let _alice_sc = { - let alice_options = SecureChannelOptions::new().with_trust_policy(alice_trust_policy); - secure_channels - .create_secure_channel( - ctx, - alice.identifier(), - route!["bob_listener"], - alice_options, - ) - .await? - }; - - ctx.sleep(Duration::from_millis(100)).await; - - let sc_list = secure_channels.secure_channel_registry().get_channel_list(); - assert_eq!(sc_list.len(), 2); - - let channel1 = sc_list[0].clone(); - let channel2 = sc_list[1].clone(); - - secure_channels - .stop_secure_channel(ctx, channel1.encryptor_messaging_address()) - .await?; - - ctx.sleep(Duration::from_millis(100)).await; - - assert_eq!( - secure_channels - .secure_channel_registry() - .get_channel_list() - .len(), - 1 - ); - - let workers = ctx.list_workers().await?; - assert!(!workers.contains(channel1.decryptor_messaging_address())); - assert!(!workers.contains(channel1.encryptor_messaging_address())); - assert!(workers.contains(channel2.decryptor_messaging_address())); - assert!(workers.contains(channel2.encryptor_messaging_address())); - - secure_channels - .stop_secure_channel(ctx, channel2.encryptor_messaging_address()) - .await?; - - ctx.sleep(Duration::from_millis(100)).await; - - assert_eq!( - secure_channels - .secure_channel_registry() - .get_channel_list() - .len(), - 0 - ); - - let workers = ctx.list_workers().await?; - assert!(!workers.contains(channel1.decryptor_messaging_address())); - assert!(!workers.contains(channel1.encryptor_messaging_address())); - assert!(!workers.contains(channel2.decryptor_messaging_address())); - assert!(!workers.contains(channel2.encryptor_messaging_address())); - - ctx.stop().await -} diff --git a/implementations/rust/ockam/ockam_identity/tests/v2_ciphertext_message_flow_auth.rs b/implementations/rust/ockam/ockam_identity/tests/v2_ciphertext_message_flow_auth.rs deleted file mode 100644 index c66a9c40373..00000000000 --- a/implementations/rust/ockam/ockam_identity/tests/v2_ciphertext_message_flow_auth.rs +++ /dev/null @@ -1,102 +0,0 @@ -use core::time::Duration; - -use ockam_core::{route, Result}; -use ockam_identity::v2::SecureChannelOptions; -use ockam_node::Context; -use ockam_transport_tcp::{TcpConnectionOptions, TcpListenerOptions, TcpTransport}; - -use crate::v2_common::{ - create_secure_channel, create_secure_channel_listener, message_should_not_pass, -}; - -mod v2_common; - -// Alice: TCP connection + Secure Channel -// Bob: TCP listener + Secure Channel listener -#[ockam_macros::test] -async fn test1(ctx: &mut Context) -> Result<()> { - let tcp_bob = TcpTransport::create(ctx).await?; - let listener = tcp_bob - .listen("127.0.0.1:0", TcpListenerOptions::new()) - .await?; - - let tcp_alice = TcpTransport::create(ctx).await?; - let connection_to_bob = tcp_alice - .connect(listener.socket_string(), TcpConnectionOptions::new()) - .await?; - - ctx.sleep(Duration::from_millis(50)).await; // Wait for workers to add themselves to the registry - let connection_to_alice = tcp_bob - .registry() - .get_all_sender_workers() - .last() - .unwrap() - .clone(); - - message_should_not_pass(ctx, &connection_to_bob.clone().into()).await?; - message_should_not_pass(ctx, connection_to_alice.address()).await?; - - let bob_listener_info = create_secure_channel_listener(ctx, listener.flow_control_id()).await?; - - let channel_to_bob = create_secure_channel(ctx, &connection_to_bob.clone().into()).await?; - ctx.sleep(Duration::from_millis(50)).await; // Wait for workers to add themselves to the registry - let channel_to_alice = bob_listener_info.get_channel(); - - message_should_not_pass(ctx, &channel_to_bob.address).await?; - message_should_not_pass(ctx, &channel_to_alice).await?; - - let res = channel_to_bob - .secure_channels - .create_secure_channel( - ctx, - &channel_to_bob.identifier, - route![connection_to_bob.clone(), "listener"], - SecureChannelOptions::new().with_timeout(Duration::from_secs(1)), - ) - .await; - assert!( - res.is_ok(), - "We can create multiple secure channels with that connection" - ); - - ctx.stop().await -} - -// Alice: TCP connection + Secure Channel listener -// Bob: TCP listener + Secure Channel -#[ockam_macros::test] -async fn test2(ctx: &mut Context) -> Result<()> { - let tcp_bob = TcpTransport::create(ctx).await?; - let listener = { - let options = TcpListenerOptions::new(); - tcp_bob.listen("127.0.0.1:0", options).await? - }; - - let tcp_alice = TcpTransport::create(ctx).await?; - let alice_tcp_options = TcpConnectionOptions::new(); - let alice_flow_control_id = alice_tcp_options.flow_control_id(); - let connection_to_bob = tcp_alice - .connect(listener.socket_string(), alice_tcp_options) - .await?; - ctx.sleep(Duration::from_millis(50)).await; // Wait for workers to add themselves to the registry - let connection_to_alice = tcp_bob - .registry() - .get_all_sender_workers() - .last() - .unwrap() - .clone(); - - message_should_not_pass(ctx, &connection_to_bob.into()).await?; - message_should_not_pass(ctx, connection_to_alice.address()).await?; - - let alice_listener_info = create_secure_channel_listener(ctx, &alice_flow_control_id).await?; - - let channel_to_alice = create_secure_channel(ctx, connection_to_alice.address()).await?; - ctx.sleep(Duration::from_millis(50)).await; // Wait for workers to add themselves to the registry - let channel_to_bob = alice_listener_info.get_channel(); - - message_should_not_pass(ctx, &channel_to_alice.address).await?; - message_should_not_pass(ctx, &channel_to_bob).await?; - - ctx.stop().await -} diff --git a/implementations/rust/ockam/ockam_identity/tests/v2_credentials.rs b/implementations/rust/ockam/ockam_identity/tests/v2_credentials.rs deleted file mode 100644 index ad2a7911a7f..00000000000 --- a/implementations/rust/ockam/ockam_identity/tests/v2_credentials.rs +++ /dev/null @@ -1,378 +0,0 @@ -use std::sync::atomic::{AtomicI8, Ordering}; -use std::time::Duration; - -use ockam_core::compat::sync::Arc; -use ockam_core::{async_trait, Any, DenyAll}; -use ockam_core::{route, Result, Routed, Worker}; -use ockam_identity::v2::models::SchemaId; -use ockam_identity::v2::secure_channels::secure_channels; -use ockam_identity::v2::utils::AttributesBuilder; -use ockam_identity::v2::{ - AuthorityService, CredentialAccessControl, CredentialsMemoryRetriever, Purpose, - SecureChannelListenerOptions, SecureChannelOptions, TrustContext, TrustIdentifierPolicy, -}; -use ockam_node::{Context, WorkerBuilder}; - -#[ockam_macros::test] -async fn full_flow_oneway(ctx: &mut Context) -> Result<()> { - let secure_channels = secure_channels(); - let identities = secure_channels.identities(); - let identities_creation = identities.identities_creation(); - let identities_repository = identities.repository(); - let credentials = identities.credentials(); - let credentials_service = identities.credentials_server(); - - let authority = identities_creation.create_identity().await?; - let _authority_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(authority.identifier(), Purpose::Credentials) - .await?; - - let server = identities_creation.create_identity().await?; - let _server_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(server.identifier(), Purpose::SecureChannel) - .await?; - - let client = identities_creation.create_identity().await?; - let _client_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(client.identifier(), Purpose::SecureChannel) - .await?; - - let listener = secure_channels - .create_secure_channel_listener( - ctx, - server.identifier(), - "listener", - SecureChannelListenerOptions::new(), - ) - .await?; - - let trust_context = TrustContext::new( - "test_trust_context_id".to_string(), - Some(AuthorityService::new( - secure_channels.identities().credentials(), - authority.identifier().clone(), - None, - )), - ); - - ctx.flow_controls() - .add_consumer("credential_exchange", listener.flow_control_id()); - credentials_service - .start( - ctx, - trust_context, - server.identifier().clone(), - "credential_exchange".into(), - false, - ) - .await?; - - let channel = secure_channels - .create_secure_channel( - ctx, - client.identifier(), - route!["listener"], - SecureChannelOptions::new() - .with_trust_policy(TrustIdentifierPolicy::new(server.identifier().clone())), - ) - .await?; - - let credential = credentials - .issue_credential( - authority.identifier(), - client.identifier(), - AttributesBuilder::with_schema(SchemaId(0)) - .with_attribute("is_superuser", "true") - .build(), - Duration::from_secs(60), - ) - .await?; - - credentials_service - .present_credential(ctx, route![channel, "credential_exchange"], credential) - .await?; - - let attrs = identities_repository - .get_attributes(client.identifier()) - .await? - .unwrap(); - - let val = attrs.attrs().get("is_superuser".as_bytes()).unwrap(); - - assert_eq!(val.as_slice(), b"true"); - - ctx.stop().await -} - -#[ockam_macros::test] -async fn full_flow_twoway(ctx: &mut Context) -> Result<()> { - let secure_channels = secure_channels(); - let identities = secure_channels.identities(); - let identities_creation = identities.identities_creation(); - let identities_repository = identities.repository(); - let credentials = identities.credentials(); - let credentials_service = identities.credentials_server(); - - let authority = identities_creation.create_identity().await?; - let _authority_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(authority.identifier(), Purpose::Credentials) - .await?; - - let client1 = identities_creation.create_identity().await?; - let _client1_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(client1.identifier(), Purpose::SecureChannel) - .await?; - - let client2 = identities_creation.create_identity().await?; - let _client2_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(client2.identifier(), Purpose::SecureChannel) - .await?; - - let credential = credentials - .issue_credential( - authority.identifier(), - client1.identifier(), - AttributesBuilder::with_schema(SchemaId(0)) - .with_attribute("is_admin", "true") - .build(), - Duration::from_secs(60), - ) - .await?; - - let listener = secure_channels - .create_secure_channel_listener( - ctx, - client1.identifier(), - "listener", - SecureChannelListenerOptions::new(), - ) - .await?; - let trust_context = TrustContext::new( - "test_trust_context_id".to_string(), - Some(AuthorityService::new( - secure_channels.identities().credentials(), - authority.identifier().clone(), - Some(Arc::new(CredentialsMemoryRetriever::new(credential))), - )), - ); - ctx.flow_controls() - .add_consumer("credential_exchange", listener.flow_control_id()); - - credentials_service - .start( - ctx, - trust_context.clone(), - client1.identifier().clone(), - "credential_exchange".into(), - true, - ) - .await?; - - let credential = credentials - .issue_credential( - authority.identifier(), - client2.identifier(), - AttributesBuilder::with_schema(SchemaId(0)) - .with_attribute("is_user", "true") - .build(), - Duration::from_secs(60), - ) - .await?; - - let channel = secure_channels - .create_secure_channel( - ctx, - client2.identifier(), - route!["listener"], - SecureChannelOptions::new(), - ) - .await?; - - credentials_service - .present_credential_mutual( - ctx, - route![channel, "credential_exchange"], - trust_context.authorities().await?.as_slice(), - credential, - ) - .await?; - - let attrs1 = identities_repository - .get_attributes(client1.identifier()) - .await? - .unwrap(); - - assert_eq!( - attrs1 - .attrs() - .get("is_admin".as_bytes()) - .unwrap() - .as_slice(), - b"true" - ); - - let attrs2 = identities_repository - .get_attributes(client2.identifier()) - .await? - .unwrap(); - - assert_eq!( - attrs2.attrs().get("is_user".as_bytes()).unwrap().as_slice(), - b"true" - ); - - ctx.stop().await -} - -#[ockam_macros::test] -async fn access_control(ctx: &mut Context) -> Result<()> { - let secure_channels = secure_channels(); - let identities = secure_channels.identities(); - let identities_creation = identities.identities_creation(); - let identities_repository = identities.repository(); - let credentials = identities.credentials(); - let credentials_service = identities.credentials_server(); - - let authority = identities_creation.create_identity().await?; - let _authority_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(authority.identifier(), Purpose::Credentials) - .await?; - - let server = identities_creation.create_identity().await?; - let _server_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(server.identifier(), Purpose::SecureChannel) - .await?; - - let client = identities_creation.create_identity().await?; - let _client_key = secure_channels - .identities() - .purpose_keys() - .create_purpose_key(client.identifier(), Purpose::SecureChannel) - .await?; - - let options = SecureChannelListenerOptions::new(); - let listener = secure_channels - .create_secure_channel_listener(ctx, server.identifier(), "listener", options) - .await?; - - let trust_context = TrustContext::new( - "test_trust_context_id".to_string(), - Some(AuthorityService::new( - credentials.clone(), - authority.identifier().clone(), - None, - )), - ); - - ctx.flow_controls() - .add_consumer("credential_exchange", listener.flow_control_id()); - - credentials_service - .start( - ctx, - trust_context, - server.identifier().clone(), - "credential_exchange".into(), - false, - ) - .await?; - - let channel = secure_channels - .create_secure_channel( - ctx, - client.identifier(), - route!["listener"], - SecureChannelOptions::new() - .with_trust_policy(TrustIdentifierPolicy::new(server.identifier().clone())), - ) - .await?; - - let credential = credentials - .issue_credential( - authority.identifier(), - client.identifier(), - AttributesBuilder::with_schema(SchemaId(0)) - .with_attribute("is_superuser", "true") - .build(), - Duration::from_secs(60), - ) - .await?; - - let counter = Arc::new(AtomicI8::new(0)); - - let worker = CountingWorker { - msgs_count: counter.clone(), - }; - - let required_attributes = vec![(b"is_superuser".to_vec(), b"true".to_vec())]; - let access_control = - CredentialAccessControl::new(&required_attributes, identities_repository.clone()); - - ctx.flow_controls() - .add_consumer("counter", listener.flow_control_id()); - - WorkerBuilder::new(worker) - .with_address("counter") - .with_incoming_access_control(access_control) - .with_outgoing_access_control(DenyAll) - .start(ctx) - .await?; - ctx.sleep(Duration::from_millis(100)).await; - assert_eq!(counter.load(Ordering::Relaxed), 0); - - ctx.send(route![channel.clone(), "counter"], "Hello".to_string()) - .await?; - ctx.sleep(Duration::from_millis(100)).await; - assert_eq!(counter.load(Ordering::Relaxed), 0); - - credentials_service - .present_credential( - ctx, - route![channel.clone(), "credential_exchange"], - credential, - ) - .await?; - - ctx.send(route![channel, "counter"], "Hello".to_string()) - .await?; - ctx.sleep(Duration::from_millis(100)).await; - assert_eq!(counter.load(Ordering::Relaxed), 1); - - ctx.stop().await -} - -struct CountingWorker { - msgs_count: Arc, -} - -#[async_trait] -impl Worker for CountingWorker { - type Context = Context; - type Message = Any; - - async fn handle_message( - &mut self, - _context: &mut Self::Context, - _msg: Routed, - ) -> Result<()> { - let _ = self.msgs_count.fetch_add(1, Ordering::Relaxed); - - Ok(()) - } -} diff --git a/implementations/rust/ockam/ockam_identity/tests/v2_plaintext_message_flow_auth.rs b/implementations/rust/ockam/ockam_identity/tests/v2_plaintext_message_flow_auth.rs deleted file mode 100644 index 9fbe4dd3a01..00000000000 --- a/implementations/rust/ockam/ockam_identity/tests/v2_plaintext_message_flow_auth.rs +++ /dev/null @@ -1,173 +0,0 @@ -use crate::v2_common::{ - message_should_not_pass, message_should_not_pass_with_ctx, message_should_pass_with_ctx, -}; -use ockam_core::{route, AllowAll, Result}; -use ockam_identity::v2::{ - secure_channels, Purpose, SecureChannelListenerOptions, SecureChannelOptions, -}; -use ockam_node::Context; -use ockam_transport_tcp::{TcpConnectionOptions, TcpListenerOptions, TcpTransport}; -use std::time::Duration; - -mod v2_common; - -// Alice: Secure Channel -// Bob: Secure Channel listener -#[ockam_macros::test] -async fn test1(ctx: &mut Context) -> Result<()> { - let alice_secure_channels = secure_channels(); - let bob_secure_channels = secure_channels(); - - let alice = alice_secure_channels - .identities() - .identities_creation() - .create_identity() - .await?; - let _alice_key = alice_secure_channels - .identities() - .purpose_keys() - .create_purpose_key(alice.identifier(), Purpose::SecureChannel) - .await?; - - let bob = bob_secure_channels - .identities() - .identities_creation() - .create_identity() - .await?; - let _bob_key = bob_secure_channels - .identities() - .purpose_keys() - .create_purpose_key(bob.identifier(), Purpose::SecureChannel) - .await?; - - let bob_listener = bob_secure_channels - .create_secure_channel_listener( - ctx, - bob.identifier(), - "listener", - SecureChannelListenerOptions::new(), - ) - .await?; - - let channel_to_bob = alice_secure_channels - .create_secure_channel( - ctx, - alice.identifier(), - route!["listener"], - SecureChannelOptions::new(), - ) - .await?; - - ctx.sleep(Duration::from_millis(50)).await; // Wait for workers to add themselves to the registry - let channel_to_alice = bob_secure_channels - .secure_channel_registry() - .get_channel_list() - .first() - .unwrap() - .encryptor_messaging_address() - .clone(); - - let mut bob_ctx = ctx.new_detached("bob_ctx", AllowAll, AllowAll).await?; - message_should_not_pass_with_ctx(ctx, channel_to_bob.encryptor_address(), &mut bob_ctx).await?; - ctx.flow_controls() - .add_consumer("bob_ctx", bob_listener.flow_control_id()); - message_should_pass_with_ctx(ctx, channel_to_bob.encryptor_address(), &mut bob_ctx).await?; - - let mut alice_ctx = ctx.new_detached("alice_ctx", AllowAll, AllowAll).await?; - message_should_not_pass_with_ctx(ctx, &channel_to_alice, &mut alice_ctx).await?; - ctx.flow_controls() - .add_consumer("alice_ctx", channel_to_bob.flow_control_id()); - message_should_pass_with_ctx(ctx, &channel_to_alice, &mut alice_ctx).await?; - - ctx.stop().await -} - -// Alice: TCP connection + Secure Channel -// Bob: TCP listener + Secure Channel listener -#[ockam_macros::test] -async fn test2(ctx: &mut Context) -> Result<()> { - let tcp_alice = TcpTransport::create(ctx).await?; - let tcp_bob = TcpTransport::create(ctx).await?; - - let listener = tcp_bob - .listen("127.0.0.1:0", TcpListenerOptions::new()) - .await?; - - let connection_to_bob = tcp_alice - .connect(listener.socket_string(), TcpConnectionOptions::new()) - .await?; - - ctx.sleep(Duration::from_millis(50)).await; // Wait for workers to add themselves to the registry - let senders = tcp_bob.registry().get_all_sender_workers(); - assert_eq!(senders.len(), 1); - - let connection_to_alice = senders.first().unwrap().clone(); - - message_should_not_pass(ctx, &connection_to_bob.clone().into()).await?; - message_should_not_pass(ctx, connection_to_alice.address()).await?; - - let alice_secure_channels = secure_channels(); - let bob_secure_channels = secure_channels(); - - let alice = alice_secure_channels - .identities() - .identities_creation() - .create_identity() - .await?; - let _alice_key = alice_secure_channels - .identities() - .purpose_keys() - .create_purpose_key(alice.identifier(), Purpose::SecureChannel) - .await?; - - let bob = bob_secure_channels - .identities() - .identities_creation() - .create_identity() - .await?; - let _bob_key = bob_secure_channels - .identities() - .purpose_keys() - .create_purpose_key(bob.identifier(), Purpose::SecureChannel) - .await?; - - let bob_options = SecureChannelListenerOptions::new().as_consumer(listener.flow_control_id()); - let bob_listener = bob_secure_channels - .create_secure_channel_listener(ctx, bob.identifier(), "listener", bob_options) - .await?; - - let channel_to_bob = alice_secure_channels - .create_secure_channel( - ctx, - alice.identifier(), - route![connection_to_bob, "listener"], - SecureChannelOptions::new(), - ) - .await?; - - ctx.sleep(Duration::from_millis(50)).await; // Wait for workers to add themselves to the registry - - let channels = bob_secure_channels - .secure_channel_registry() - .get_channel_list(); - assert_eq!(channels.len(), 1); - let channel_to_alice = channels - .first() - .unwrap() - .encryptor_messaging_address() - .clone(); - - let mut bob_ctx = ctx.new_detached("bob_ctx", AllowAll, AllowAll).await?; - message_should_not_pass_with_ctx(ctx, channel_to_bob.encryptor_address(), &mut bob_ctx).await?; - ctx.flow_controls() - .add_consumer("bob_ctx", bob_listener.flow_control_id()); - message_should_pass_with_ctx(ctx, channel_to_bob.encryptor_address(), &mut bob_ctx).await?; - - let mut alice_ctx = ctx.new_detached("alice_ctx", AllowAll, AllowAll).await?; - message_should_not_pass_with_ctx(ctx, &channel_to_alice, &mut alice_ctx).await?; - ctx.flow_controls() - .add_consumer("alice_ctx", channel_to_bob.flow_control_id()); - message_should_pass_with_ctx(ctx, &channel_to_alice, &mut alice_ctx).await?; - - ctx.stop().await -} diff --git a/implementations/rust/ockam/ockam_node/src/worker_builder.rs b/implementations/rust/ockam/ockam_node/src/worker_builder.rs index 89d1f75e9b5..b2d2f886dbd 100644 --- a/implementations/rust/ockam/ockam_node/src/worker_builder.rs +++ b/implementations/rust/ockam/ockam_node/src/worker_builder.rs @@ -141,7 +141,7 @@ async fn start(context: &Context, mailboxes: Mailboxes, worker: W) -> Result< where W: Worker, { - info!( + debug!( "Initializing ockam worker '{}' with access control in:{:?} out:{:?}", mailboxes.main_address(), mailboxes.main_mailbox().incoming_access_control(), diff --git a/implementations/rust/ockam/ockam_transport_ble/examples/05-secure-channel-over-ble-transport-initiator.rs b/implementations/rust/ockam/ockam_transport_ble/examples/05-secure-channel-over-ble-transport-initiator.rs index 16be0ec5a52..832d7e164d6 100644 --- a/implementations/rust/ockam/ockam_transport_ble/examples/05-secure-channel-over-ble-transport-initiator.rs +++ b/implementations/rust/ockam/ockam_transport_ble/examples/05-secure-channel-over-ble-transport-initiator.rs @@ -39,7 +39,7 @@ async fn async_main(mut ctx: Context) -> Result<()> { // Connect to a secure channel listener and perform a handshake. let r = route![(BLE, "ockam_ble_1"), "bob_listener"]; let channel = secure_channels - .create_secure_channel(&ctx, &alice.identifier(), r, SecureChannelOptions::new()) + .create_secure_channel(&ctx, alice.identifier(), r, SecureChannelOptions::new()) .await?; // Send a message to the "echoer" worker, on a different node, via secure channel. diff --git a/implementations/rust/ockam/ockam_vault/Cargo.toml b/implementations/rust/ockam/ockam_vault/Cargo.toml index 25eedecdede..ef35a29f1ee 100644 --- a/implementations/rust/ockam/ockam_vault/Cargo.toml +++ b/implementations/rust/ockam/ockam_vault/Cargo.toml @@ -59,6 +59,7 @@ alloc = [ "aes-gcm/alloc", "ed25519-dalek/alloc", "x25519-dalek/alloc", + "p256/alloc", "p256/ecdsa", "p256/pem", ] @@ -69,7 +70,7 @@ storage = ["ockam_node/storage", "std", "serde_cbor"] aes-gcm = { version = "0.9", default-features = false, features = ["aes"] } arrayref = "0.3" cfg-if = "1.0.0" -ed25519-dalek = { version = "2.0", default-features = false, features = ["fast", "zeroize"] } +ed25519-dalek = { version = "2.0", default-features = false, features = ["fast", "rand_core", "zeroize"] } hex = { version = "0.4", default-features = false } hkdf = { version = "0.12", default-features = false } minicbor = { version = "0.19.0", features = ["derive"] } @@ -83,6 +84,7 @@ rand_pcg = { version = "0.3.1", default-features = false, optional = true } serde = { version = "1", default-features = false, features = ["derive"] } serde_cbor = { version = "0.11.2", optional = true } sha2 = { version = "0.10", default-features = false } +static_assertions = "1.1.0" thiserror = { version = "1.0.48", optional = true } tracing = { version = "0.1", default-features = false, features = ["attributes"] } x25519-dalek = { version = "2.0.0", default_features = false, features = ["precomputed-tables", "static_secrets", "zeroize"] } diff --git a/implementations/rust/ockam/ockam_vault/src/vault/vault_error.rs b/implementations/rust/ockam/ockam_vault/src/error.rs similarity index 51% rename from implementations/rust/ockam/ockam_vault/src/vault/vault_error.rs rename to implementations/rust/ockam/ockam_vault/src/error.rs index c26d7bacfb4..6648f7f77b2 100644 --- a/implementations/rust/ockam/ockam_vault/src/vault/vault_error.rs +++ b/implementations/rust/ockam/ockam_vault/src/error.rs @@ -1,5 +1,4 @@ use crate::SecretType; -use ockam_core::compat::string::String; use ockam_core::{ errcode::{Kind, Origin}, Error, @@ -9,70 +8,52 @@ use ockam_core::{ /// an Ockam vault #[derive(Clone, Debug)] pub enum VaultError { - /// Secret does not belong to this vault - SecretFromAnotherVault, /// Public key is invalid InvalidPublicKey, /// Unknown ECDH key type UnknownEcdhKeyType, /// Invalid key type InvalidKeyType, - /// Entry not found - EntryNotFound(String), - /// Invalid AES key length - InvalidAesKeyLength, + /// Key wasn't found + KeyNotFound, /// Invalid Secret length InvalidSecretLength(SecretType, usize, u32), + /// Invalid Public Key Length + InvalidPublicLength, /// Invalid HKDF output type InvalidHkdfOutputType, - /// Invalid private key length - InvalidPrivateKeyLen, /// AES encryption failed AeadAesGcmEncrypt, /// AES decryption failed AeadAesGcmDecrypt, /// HKDF key expansion failed HkdfExpandError, - /// Secret not found - SecretNotFound, - /// Invalid X25519 secret length - InvalidX25519SecretLength, - /// Invalid Ed25519 secret - InvalidEd25519Secret, - /// Invalid Secret Attributes - InvalidSecretAttributes, - /// IO error - StorageError, - /// Invalid Storage data - InvalidStorageData, + /// Invalid Sha256 Output length + InvalidSha256Len, + /// Invalid Signature Size + InvalidSignatureSize, } impl ockam_core::compat::error::Error for VaultError {} impl core::fmt::Display for VaultError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { - Self::SecretFromAnotherVault => write!(f, "secret does not belong to this vault"), Self::InvalidPublicKey => write!(f, "public key is invalid"), Self::UnknownEcdhKeyType => write!(f, "unknown ECDH key type"), Self::InvalidKeyType => write!(f, "invalid key type"), - Self::EntryNotFound(entry) => write!(f, "{entry}"), - Self::InvalidAesKeyLength => write!(f, "invalid AES key length"), Self::InvalidSecretLength(secret_type, actual, expected) => write!( f, "invalid secret length for {}. Actual: {}, Expected: {}", secret_type, actual, expected ), - Self::InvalidHkdfOutputType => write!(f, "invalid HKDF outputtype"), - Self::InvalidPrivateKeyLen => write!(f, "invalid private key length"), + Self::InvalidPublicLength => write!(f, "invalid public key length"), + Self::InvalidHkdfOutputType => write!(f, "invalid HKDF output type"), Self::AeadAesGcmEncrypt => write!(f, "aes encryption failed"), Self::AeadAesGcmDecrypt => write!(f, "aes decryption failed"), Self::HkdfExpandError => write!(f, "hkdf key expansion failed"), - Self::SecretNotFound => write!(f, "secret not found"), - Self::InvalidX25519SecretLength => write!(f, "invalid X25519 secret length"), - Self::InvalidEd25519Secret => write!(f, "invalid Ed25519 secret"), - Self::InvalidSecretAttributes => write!(f, "invalid secret attributes"), - Self::StorageError => write!(f, "invalid storage"), - Self::InvalidStorageData => write!(f, "invalid storage data"), + Self::KeyNotFound => write!(f, "key not found"), + Self::InvalidSha256Len => write!(f, "invalid sha256 len"), + Self::InvalidSignatureSize => write!(f, "invalid signature len"), } } } @@ -82,14 +63,8 @@ impl From for Error { fn from(err: VaultError) -> Self { use VaultError::*; let kind = match err { - SecretFromAnotherVault - | InvalidPublicKey - | InvalidKeyType - | InvalidAesKeyLength - | InvalidHkdfOutputType - | InvalidPrivateKeyLen - | InvalidX25519SecretLength => Kind::Misuse, - UnknownEcdhKeyType | EntryNotFound(_) | SecretNotFound => Kind::NotFound, + InvalidPublicKey | InvalidKeyType | InvalidHkdfOutputType => Kind::Misuse, + UnknownEcdhKeyType => Kind::NotFound, _ => Kind::Invalid, }; diff --git a/implementations/rust/ockam/ockam_vault/src/lib.rs b/implementations/rust/ockam/ockam_vault/src/lib.rs index fbf3676bc23..a69c12fe8f3 100644 --- a/implementations/rust/ockam/ockam_vault/src/lib.rs +++ b/implementations/rust/ockam/ockam_vault/src/lib.rs @@ -34,15 +34,20 @@ extern crate alloc; #[cfg(feature = "storage")] pub mod storage; -/// Traits and types defining the behaviour of a Vault -pub mod traits; -/// Default Vault implementation -pub mod vault; +/// Errors +mod error; + +/// Traits +mod traits; + +/// Software implementation of Vault traits +mod software; /// Main vault types: PublicKey, Secret, SecretAttributes etc... mod types; pub use constants; +pub use error::*; +pub use software::*; pub use traits::*; pub use types::*; -pub use vault::*; diff --git a/implementations/rust/ockam/ockam_vault/src/software/mod.rs b/implementations/rust/ockam/ockam_vault/src/software/mod.rs new file mode 100644 index 00000000000..eea1c6312ee --- /dev/null +++ b/implementations/rust/ockam/ockam_vault/src/software/mod.rs @@ -0,0 +1,7 @@ +mod secure_channel_vault; +mod signing_vault; +mod verifying_vault; + +pub use secure_channel_vault::*; +pub use signing_vault::*; +pub use verifying_vault::*; diff --git a/implementations/rust/ockam/ockam_vault/src/software/secure_channel_vault/aes.rs b/implementations/rust/ockam/ockam_vault/src/software/secure_channel_vault/aes.rs new file mode 100644 index 00000000000..a4722520a37 --- /dev/null +++ b/implementations/rust/ockam/ockam_vault/src/software/secure_channel_vault/aes.rs @@ -0,0 +1,93 @@ +use crate::constants::{ + AES128_SECRET_LENGTH_USIZE, AES256_SECRET_LENGTH_USIZE, AES_NONCE_LENGTH_USIZE, +}; +use crate::{Buffer, SecretAttributes, StoredSecret, VaultError}; + +use ockam_core::{compat::boxed::Box, Result}; + +use aes_gcm::aead::consts::{U0, U12, U16}; +use aes_gcm::aead::NewAead; +use aes_gcm::aead::{Aead, Nonce, Payload, Tag}; +use aes_gcm::aes::{Aes128, Aes256}; +use aes_gcm::{AeadCore, AeadInPlace, Aes128Gcm, Aes256Gcm, AesGcm}; + +/// Depending on the secret type make the right type of encrypting / decrypting algorithm +pub(super) fn make_aes(stored_secret: &StoredSecret) -> Result { + let secret_ref = stored_secret.secret().as_ref(); + + match stored_secret.attributes() { + SecretAttributes::Aes256 => { + if secret_ref.len() != AES256_SECRET_LENGTH_USIZE { + return Err(VaultError::AeadAesGcmEncrypt.into()); + } + Ok(AesGen::Aes256(Box::new(Aes256Gcm::new(secret_ref.into())))) + } + SecretAttributes::Aes128 => { + if secret_ref.len() != AES128_SECRET_LENGTH_USIZE { + return Err(VaultError::AeadAesGcmEncrypt.into()); + } + Ok(AesGen::Aes128(Box::new(Aes128Gcm::new(secret_ref.into())))) + } + _ => Err(VaultError::AeadAesGcmEncrypt.into()), + } +} + +/// This enum is necessary to be able to dispatch the encrypt or decrypt functions +/// based of the algorithm type. It would be avoided if `make_aes` could return existential types +/// but those types are not allowed in return values in Rust +pub enum AesGen { + Aes128(Box>), + Aes256(Box>), +} + +impl AesGen { + pub fn encrypt_message(&self, msg: &[u8], nonce: &[u8], aad: &[u8]) -> Result> { + if nonce.len() != AES_NONCE_LENGTH_USIZE { + return Err(VaultError::AeadAesGcmEncrypt.into()); + } + + self.encrypt(nonce.into(), Payload { aad, msg }) + .map_err(|_| VaultError::AeadAesGcmEncrypt.into()) + } + pub fn decrypt_message(&self, msg: &[u8], nonce: &[u8], aad: &[u8]) -> Result> { + if nonce.len() != AES_NONCE_LENGTH_USIZE { + return Err(VaultError::AeadAesGcmEncrypt.into()); + } + + self.decrypt(nonce.into(), Payload { aad, msg }) + .map_err(|_| VaultError::AeadAesGcmDecrypt.into()) + } +} + +impl AeadInPlace for AesGen { + fn encrypt_in_place_detached( + &self, + nonce: &Nonce, + aad: &[u8], + buffer: &mut [u8], + ) -> aes_gcm::aead::Result> { + match self { + AesGen::Aes128(alg) => alg.encrypt_in_place_detached(nonce, aad, buffer), + AesGen::Aes256(alg) => alg.encrypt_in_place_detached(nonce, aad, buffer), + } + } + + fn decrypt_in_place_detached( + &self, + nonce: &Nonce, + aad: &[u8], + buffer: &mut [u8], + tag: &Tag, + ) -> aes_gcm::aead::Result<()> { + match self { + AesGen::Aes128(alg) => alg.decrypt_in_place_detached(nonce, aad, buffer, tag), + AesGen::Aes256(alg) => alg.decrypt_in_place_detached(nonce, aad, buffer, tag), + } + } +} + +impl AeadCore for AesGen { + type NonceSize = U12; + type TagSize = U16; + type CiphertextOverhead = U0; +} diff --git a/implementations/rust/ockam/ockam_vault/src/software/secure_channel_vault/mod.rs b/implementations/rust/ockam/ockam_vault/src/software/secure_channel_vault/mod.rs new file mode 100644 index 00000000000..1ff33418c98 --- /dev/null +++ b/implementations/rust/ockam/ockam_vault/src/software/secure_channel_vault/mod.rs @@ -0,0 +1,6 @@ +pub(crate) mod aes; + +#[allow(clippy::module_inception)] +mod secure_channel_vault; + +pub use secure_channel_vault::*; diff --git a/implementations/rust/ockam/ockam_vault/src/software/secure_channel_vault/secure_channel_vault.rs b/implementations/rust/ockam/ockam_vault/src/software/secure_channel_vault/secure_channel_vault.rs new file mode 100644 index 00000000000..f3fa16f7f02 --- /dev/null +++ b/implementations/rust/ockam/ockam_vault/src/software/secure_channel_vault/secure_channel_vault.rs @@ -0,0 +1,400 @@ +use super::aes::make_aes; + +use crate::constants::{ + X25519_PUBLIC_LENGTH_USIZE, X25519_SECRET_LENGTH_U32, X25519_SECRET_LENGTH_USIZE, +}; +use crate::{ + Buffer, KeyId, PublicKey, Secret, SecretAttributes, SecretType, SecureChannelVault, + SmallBuffer, StoredSecret, VaultError, +}; + +use ockam_core::compat::collections::BTreeMap; +use ockam_core::compat::rand::{thread_rng, RngCore}; +use ockam_core::compat::sync::{Arc, RwLock}; +use ockam_core::compat::vec::Vec; +use ockam_core::{async_trait, compat::boxed::Box, Result}; +use ockam_node::{InMemoryKeyValueStorage, KeyValueStorage}; + +use arrayref::array_ref; +use sha2::{Digest, Sha256}; + +/// [`SecureChannelVault`] implementation using software +pub struct SoftwareSecureChannelVault { + ephemeral_secrets: Arc>>, + static_secrets: Arc>, +} + +impl SoftwareSecureChannelVault { + /// Constructor + pub fn new(storage: Arc>) -> Self { + Self { + ephemeral_secrets: Default::default(), + static_secrets: storage, + } + } + + /// Create Software implementation Vault with [`InMemoryKeyVaultStorage`] + pub fn create() -> Arc { + Arc::new(Self::new(InMemoryKeyValueStorage::create())) + } + + /// Return a binary for ephemeral secret + pub fn get_ephemeral_secret(&self, key_id: &KeyId) -> Result { + self.ephemeral_secrets + .read() + .unwrap() + .get(key_id) + .cloned() + .ok_or(VaultError::KeyNotFound.into()) + } +} + +impl SoftwareSecureChannelVault { + fn compute_key_id_for_public_key(public_key: &PublicKey) -> Result { + let key_id = Sha256::digest(public_key.data()); + Ok(hex::encode(key_id)) + } + + fn compute_public_key_from_secret(key: &Secret, stype: SecretType) -> Result { + match stype { + SecretType::X25519 => { + let key = Self::import_x25519_secret_key(key)?; + let pk = x25519_dalek::PublicKey::from(&key); + Ok(PublicKey::new(pk.to_bytes().to_vec(), SecretType::X25519)) + } + SecretType::NistP256 | SecretType::Ed25519 | SecretType::Buffer | SecretType::Aes => { + Err(VaultError::InvalidKeyType.into()) + } + } + } + + fn import_x25519_secret_key(key: &Secret) -> Result { + if key.as_ref().len() != X25519_SECRET_LENGTH_USIZE { + return Err(VaultError::InvalidSecretLength( + SecretType::X25519, + key.as_ref().len(), + X25519_SECRET_LENGTH_U32, + ) + .into()); + } + + Ok(x25519_dalek::StaticSecret::from(*array_ref!( + key.as_ref(), + 0, + X25519_SECRET_LENGTH_USIZE + ))) + } + + fn import_x25519_public_key(public_key: &PublicKey) -> Result { + if public_key.stype() != SecretType::X25519 { + return Err(VaultError::InvalidKeyType.into()); + } + + if public_key.data().len() != X25519_PUBLIC_LENGTH_USIZE { + return Err(VaultError::InvalidPublicLength.into()); + } + + Ok(x25519_dalek::PublicKey::from(*array_ref!( + public_key.data(), + 0, + X25519_SECRET_LENGTH_USIZE + ))) + } + + /// Compute key id from secret and attributes + pub(crate) fn compute_key_id(secret: &Secret, attributes: &SecretAttributes) -> Result { + Ok(match attributes.secret_type() { + SecretType::X25519 => { + let public_key = Self::compute_public_key_from_secret(secret, SecretType::X25519)?; + Self::compute_key_id_for_public_key(&public_key)? + } + SecretType::Buffer | SecretType::Aes => { + // NOTE: Buffer and Aes secrets in the system are ephemeral and it should be fine, + // that every time we import the same secret - it gets different KeyId value. + // However, if we decide to have persistent Buffer or Aes secrets, that should be + // changed (probably to hash value of the secret) + let mut rng = thread_rng(); + let mut rand = [0u8; 8]; + rng.fill_bytes(&mut rand); + hex::encode(rand) + } + SecretType::Ed25519 | SecretType::NistP256 => { + return Err(VaultError::InvalidKeyType.into()) + } + }) + } + + fn ecdh_internal( + stored_secret: &StoredSecret, + peer_public_key: &PublicKey, + ) -> Result> { + let attributes = stored_secret.attributes(); + match attributes.secret_type() { + SecretType::X25519 => { + let peer_public_key = Self::import_x25519_public_key(peer_public_key)?; + let secret_key = Self::import_x25519_secret_key(stored_secret.secret())?; + let dh = secret_key.diffie_hellman(&peer_public_key); + Ok(dh.as_bytes().to_vec()) + } + SecretType::Buffer | SecretType::Aes | SecretType::Ed25519 => { + Err(VaultError::UnknownEcdhKeyType.into()) + } + SecretType::NistP256 => Err(VaultError::UnknownEcdhKeyType.into()), + } + } + + fn generate_secret_impl(&self, attributes: SecretAttributes) -> Result { + match attributes.secret_type() { + SecretType::X25519 => { + // Just random 32 bytes + let secret_key = x25519_dalek::StaticSecret::random_from_rng(thread_rng()); + let secret_key = secret_key.to_bytes().to_vec(); + + if secret_key.len() != X25519_SECRET_LENGTH_USIZE { + return Err(VaultError::InvalidSecretLength( + SecretType::Ed25519, + secret_key.len(), + X25519_SECRET_LENGTH_U32, + ) + .into()); + } + + Ok(Secret::new(secret_key)) + } + SecretType::Buffer | SecretType::Aes => { + let bytes = { + let mut rng = thread_rng(); + let mut key = vec![0u8; attributes.length() as usize]; + rng.fill_bytes(key.as_mut_slice()); + key + }; + Ok(Secret::new(bytes)) + } + SecretType::NistP256 | SecretType::Ed25519 => Err(VaultError::InvalidKeyType.into()), + } + } + + async fn import_static_secret_impl( + &self, + secret: Secret, + attributes: SecretAttributes, + ) -> Result { + let key_id = Self::compute_key_id(&secret, &attributes)?; + let stored_secret = StoredSecret::create(secret, attributes)?; + + self.static_secrets + .put(key_id.clone(), stored_secret) + .await?; + + Ok(key_id) + } + + fn import_ephemeral_secret_impl( + &self, + secret: Secret, + attributes: SecretAttributes, + ) -> Result { + let key_id = Self::compute_key_id(&secret, &attributes)?; + let stored_secret = StoredSecret::create(secret, attributes)?; + + self.ephemeral_secrets + .write() + .unwrap() + .insert(key_id.clone(), stored_secret); + + Ok(key_id) + } + + async fn get_secret(&self, key_id: &KeyId) -> Result { + if let Some(stored_secret) = self.ephemeral_secrets.read().unwrap().get(key_id) { + return Ok(stored_secret.clone()); + } + + if let Some(stored_secret) = self.static_secrets.get(key_id).await? { + return Ok(stored_secret); + } + + Err(VaultError::KeyNotFound.into()) + } + + /// Return the total number of ephemeral secrets present in the Vault + pub fn number_of_ephemeral_secrets(&self) -> usize { + self.ephemeral_secrets.read().unwrap().len() + } + + /// Return the total number of static secrets present in the Vault + pub async fn number_of_static_secrets(&self) -> Result { + let len = self.static_secrets.keys().await?.len(); + + Ok(len) + } +} + +#[async_trait] +impl SecureChannelVault for SoftwareSecureChannelVault { + async fn generate_static_secret(&self, attributes: SecretAttributes) -> Result { + let secret = self.generate_secret_impl(attributes)?; + + self.import_static_secret_impl(secret, attributes).await + } + + async fn generate_ephemeral_secret(&self, attributes: SecretAttributes) -> Result { + let secret = self.generate_secret_impl(attributes)?; + + self.import_ephemeral_secret_impl(secret, attributes) + } + + async fn import_static_secret( + &self, + secret: Secret, + attributes: SecretAttributes, + ) -> Result { + self.import_static_secret_impl(secret, attributes).await + } + + async fn import_ephemeral_secret( + &self, + secret: Secret, + attributes: SecretAttributes, + ) -> Result { + self.import_ephemeral_secret_impl(secret, attributes) + } + + async fn delete_secret(&self, key_id: KeyId) -> Result { + if let Some(_secret) = self.ephemeral_secrets.write().unwrap().remove(&key_id) { + return Ok(true); + } + + if let Some(_secret) = self.static_secrets.delete(&key_id).await? { + return Ok(true); + } + + Ok(false) + } + + async fn get_public_key(&self, key_id: &KeyId) -> Result { + let secret = self.get_secret(key_id).await?; + + Self::compute_public_key_from_secret(secret.secret(), secret.attributes().secret_type()) + } + + async fn get_key_id(&self, public_key: &PublicKey) -> Result { + Self::compute_key_id_for_public_key(public_key) + } + + async fn get_secret_attributes(&self, key_id: &KeyId) -> Result { + let stored_secret = self.get_secret(key_id).await?; + Ok(stored_secret.attributes()) + } + + async fn ec_diffie_hellman( + &self, + secret: &KeyId, + peer_public_key: &PublicKey, + ) -> Result { + let stored_secret = self.get_secret(secret).await?; + let dh = Self::ecdh_internal(&stored_secret, peer_public_key)?; + + let attributes = SecretAttributes::Buffer(dh.len() as u32); + self.import_ephemeral_secret(Secret::new(dh), attributes) + .await + } + + async fn hkdf_sha256( + &self, + salt: &KeyId, + info: &[u8], + ikm: Option<&KeyId>, + output_attributes: SmallBuffer, + ) -> Result> { + const OUTPUT_WINDOW_SIZE: usize = 32; + + for attributes in &output_attributes { + if attributes.length() > OUTPUT_WINDOW_SIZE as u32 { + return Err(VaultError::InvalidHkdfOutputType.into()); + } + + match attributes.secret_type() { + SecretType::Buffer | SecretType::Aes => {} + SecretType::X25519 | SecretType::Ed25519 | SecretType::NistP256 => { + return Err(VaultError::InvalidHkdfOutputType.into()) + } + } + } + + let ikm = match ikm { + Some(ikm) => { + let stored_secret = self.get_secret(ikm).await?; + if stored_secret.attributes().secret_type() != SecretType::Buffer { + return Err(VaultError::InvalidKeyType.into()); + } + + stored_secret.take_secret() + } + None => Secret::new(vec![]), + }; + + let salt = self.get_secret(salt).await?; + if salt.attributes().secret_type() != SecretType::Buffer { + return Err(VaultError::InvalidKeyType.into()); + } + let salt = salt.take_secret(); + + // Every output is guaranteed to have length <= OUTPUT_WINDOW_SIZE (checked above) + // The idea is to generate OUTPUT_WINDOW_SIZE bytes chunk from HKDF for every output, + // but then take only needed part from the beginning of each chunk + let okm_len = output_attributes.len() * OUTPUT_WINDOW_SIZE; + + let okm = { + let mut okm = vec![0u8; okm_len]; + let prk = hkdf::Hkdf::::new(Some(salt.as_ref()), ikm.as_ref()); + + prk.expand(info, okm.as_mut_slice()) + .map_err(|_| VaultError::HkdfExpandError)?; + okm + }; + + let chunks = okm.chunks(OUTPUT_WINDOW_SIZE); + + if chunks.len() != output_attributes.len() { + // Should not happen + return Err(VaultError::HkdfExpandError.into()); + } + + let secrets: Result> = output_attributes + .into_iter() + .zip(chunks) + .map(|(attributes, okm_chunk)| { + let length = attributes.length() as usize; + let secret = Secret::new(okm_chunk[..length].to_vec()); + self.import_ephemeral_secret_impl(secret, attributes) + }) + .collect(); + + secrets + } + + async fn aead_aes_gcm_encrypt( + &self, + key_id: &KeyId, + plaintext: &[u8], + nonce: &[u8], + aad: &[u8], + ) -> Result> { + let stored_secret = self.get_secret(key_id).await?; + let aes = make_aes(&stored_secret)?; + aes.encrypt_message(plaintext, nonce, aad) + } + + async fn aead_aes_gcm_decrypt( + &self, + key_id: &KeyId, + cipher_text: &[u8], + nonce: &[u8], + aad: &[u8], + ) -> Result> { + let stored_secret = self.get_secret(key_id).await?; + let aes = make_aes(&stored_secret)?; + aes.decrypt_message(cipher_text, nonce, aad) + } +} diff --git a/implementations/rust/ockam/ockam_vault/src/software/signing_vault.rs b/implementations/rust/ockam/ockam_vault/src/software/signing_vault.rs new file mode 100644 index 00000000000..97b639e6f4e --- /dev/null +++ b/implementations/rust/ockam/ockam_vault/src/software/signing_vault.rs @@ -0,0 +1,240 @@ +use crate::constants::{ + ED25519_PUBLIC_LENGTH_USIZE, ED25519_SECRET_LENGTH_U32, ED25519_SECRET_LENGTH_USIZE, + ED25519_SIGNATURE_LENGTH_USIZE, NIST_P256_PUBLIC_LENGTH_USIZE, NIST_P256_SECRET_LENGTH_U32, + NIST_P256_SECRET_LENGTH_USIZE, NIST_P256_SIGNATURE_LENGTH_USIZE, +}; +use crate::{ + KeyId, PublicKey, Secret, SecretAttributes, SecretType, Signature, SigningVault, StoredSecret, + VaultError, +}; + +use ockam_core::compat::rand::thread_rng; +use ockam_core::compat::sync::Arc; +use ockam_core::errcode::{Kind, Origin}; +use ockam_core::{async_trait, compat::boxed::Box, Error, Result}; +use ockam_node::{InMemoryKeyValueStorage, KeyValueStorage}; + +use arrayref::array_ref; +use sha2::{Digest, Sha256}; +use static_assertions::const_assert_eq; + +const_assert_eq!( + ed25519_dalek::SECRET_KEY_LENGTH, + ED25519_SECRET_LENGTH_USIZE +); + +const_assert_eq!( + ed25519_dalek::PUBLIC_KEY_LENGTH, + ED25519_PUBLIC_LENGTH_USIZE +); + +const_assert_eq!( + ed25519_dalek::SIGNATURE_LENGTH, + ED25519_SIGNATURE_LENGTH_USIZE +); + +/// [`SigningVault`] implementation using software +#[derive(Clone)] +pub struct SoftwareSigningVault { + secrets: Arc>, +} + +impl SoftwareSigningVault { + /// Constructor + pub fn new(secrets: Arc>) -> Self { + Self { secrets } + } + + /// Create Software implementation Vault with [`InMemoryKeyVaultStorage`] + pub fn create() -> Arc { + Arc::new(Self::new(InMemoryKeyValueStorage::create())) + } + + /// Import a key from a binary + pub async fn import_key(&self, key: Secret, attributes: SecretAttributes) -> Result { + let public_key = Self::compute_public_key_from_secret(&key, attributes.secret_type())?; + let key_id = Self::compute_key_id_for_public_key(&public_key)?; + let stored_secret = StoredSecret::create(key, attributes)?; + self.secrets.put(key_id.clone(), stored_secret).await?; + Ok(key_id) + } +} + +impl SoftwareSigningVault { + fn from_bytes(e: T) -> Error { + #[cfg(feature = "no_std")] + use ockam_core::compat::string::ToString; + + Error::new(Origin::Vault, Kind::Unknown, e.to_string()) + } + + fn import_p256_key(key: &Secret) -> Result { + if key.as_ref().len() != NIST_P256_SECRET_LENGTH_USIZE { + return Err(VaultError::InvalidSecretLength( + SecretType::NistP256, + key.as_ref().len(), + NIST_P256_SECRET_LENGTH_U32, + ) + .into()); + } + + p256::ecdsa::SigningKey::from_bytes(key.as_ref().into()).map_err(Self::from_bytes) + } + + fn import_ed25519_key(key: &Secret) -> Result { + if key.as_ref().len() != ED25519_SECRET_LENGTH_USIZE { + return Err(VaultError::InvalidSecretLength( + SecretType::Ed25519, + key.as_ref().len(), + ED25519_SECRET_LENGTH_U32, + ) + .into()); + } + + let secret = array_ref![key.as_ref(), 0, ED25519_SECRET_LENGTH_USIZE]; + + Ok(ed25519_dalek::SigningKey::from_bytes(secret)) + } + + fn compute_public_key_from_secret(key: &Secret, stype: SecretType) -> Result { + match stype { + SecretType::Ed25519 => { + let signing_key = Self::import_ed25519_key(key)?; + let verifying_key = signing_key.verifying_key(); + let verifying_key = verifying_key.to_bytes().to_vec(); + + if verifying_key.len() != ED25519_PUBLIC_LENGTH_USIZE { + return Err(VaultError::InvalidPublicLength.into()); + } + + Ok(PublicKey::new(verifying_key, SecretType::Ed25519)) + } + SecretType::NistP256 => { + let signing_key = Self::import_p256_key(key)?; + let verifying_key = signing_key.verifying_key(); + let verifying_key = verifying_key.to_sec1_bytes().to_vec(); + + if verifying_key.len() != NIST_P256_PUBLIC_LENGTH_USIZE { + return Err(VaultError::InvalidPublicLength.into()); + } + + Ok(PublicKey::new(verifying_key, SecretType::NistP256)) + } + SecretType::X25519 | SecretType::Buffer | SecretType::Aes => { + Err(VaultError::InvalidKeyType.into()) + } + } + } + + fn compute_key_id_for_public_key(public_key: &PublicKey) -> Result { + let digest = Sha256::digest(public_key.data()); + Ok(hex::encode(digest)) + } + + async fn get_key(&self, key_id: &KeyId) -> Result { + let stored_secret = self + .secrets + .get(key_id) + .await? + .ok_or(VaultError::KeyNotFound)?; + Ok(stored_secret) + } +} + +#[async_trait] +impl SigningVault for SoftwareSigningVault { + async fn generate_key(&self, attributes: SecretAttributes) -> Result { + let key = match attributes.secret_type() { + SecretType::Ed25519 => { + // Just random 32 bytes + let signing_key = ed25519_dalek::SigningKey::generate(&mut thread_rng()); + let signing_key = signing_key.to_bytes().to_vec(); + + if signing_key.len() != ED25519_SECRET_LENGTH_USIZE { + return Err(VaultError::InvalidSecretLength( + SecretType::Ed25519, + signing_key.len(), + ED25519_SECRET_LENGTH_U32, + ) + .into()); + } + + Secret::new(signing_key) + } + SecretType::NistP256 => { + // Somewhat special random 32 bytes + let signing_key = p256::ecdsa::SigningKey::random(&mut thread_rng()); + let signing_key = signing_key.to_bytes().to_vec(); + + if signing_key.len() != NIST_P256_SECRET_LENGTH_USIZE { + return Err(VaultError::InvalidSecretLength( + SecretType::NistP256, + signing_key.len(), + NIST_P256_SECRET_LENGTH_U32, + ) + .into()); + } + + Secret::new(signing_key) + } + SecretType::Buffer | SecretType::Aes | SecretType::X25519 => { + return Err(VaultError::InvalidKeyType.into()); + } + }; + + let key_id = self.import_key(key, attributes).await?; + + Ok(key_id) + } + + async fn delete_key(&self, key_id: KeyId) -> Result { + self.secrets.delete(&key_id).await.map(|r| r.is_some()) + } + + async fn get_public_key(&self, key_id: &KeyId) -> Result { + let secret = self.get_key(key_id).await?; + + Self::compute_public_key_from_secret(secret.secret(), secret.attributes().secret_type()) + } + + async fn get_key_id(&self, public_key: &PublicKey) -> Result { + Self::compute_key_id_for_public_key(public_key) + } + + async fn sign(&self, key_id: &KeyId, data: &[u8]) -> Result { + let stored_secret = self.get_key(key_id).await?; + + match stored_secret.attributes().secret_type() { + SecretType::Ed25519 => { + use ed25519_dalek::Signer; + let key = Self::import_ed25519_key(stored_secret.secret())?; + let signature = key.sign(data).to_vec(); + + if signature.len() != ED25519_SIGNATURE_LENGTH_USIZE { + return Err(VaultError::InvalidSignatureSize.into()); + } + + Ok(Signature::new(signature)) + } + SecretType::NistP256 => { + use p256::ecdsa::signature::Signer; + let key = Self::import_p256_key(stored_secret.secret())?; + let signature: p256::ecdsa::Signature = key.sign(data); + let signature = signature.to_vec(); + + if signature.len() != NIST_P256_SIGNATURE_LENGTH_USIZE { + return Err(VaultError::InvalidSignatureSize.into()); + } + + Ok(Signature::new(signature)) + } + SecretType::Buffer | SecretType::Aes | SecretType::X25519 => { + Err(VaultError::InvalidKeyType.into()) + } + } + } + + async fn number_of_keys(&self) -> Result { + Ok(self.secrets.keys().await?.len()) + } +} diff --git a/implementations/rust/ockam/ockam_vault/src/software/verifying_vault.rs b/implementations/rust/ockam/ockam_vault/src/software/verifying_vault.rs new file mode 100644 index 00000000000..0fc36d201cb --- /dev/null +++ b/implementations/rust/ockam/ockam_vault/src/software/verifying_vault.rs @@ -0,0 +1,133 @@ +use crate::constants::{ED25519_PUBLIC_LENGTH_USIZE, NIST_P256_PUBLIC_LENGTH_USIZE, SHA256_LENGTH}; +use crate::{PublicKey, SecretType, Signature, VaultError, VerifyingVault}; + +use ockam_core::compat::sync::Arc; +use ockam_core::errcode::{Kind, Origin}; +use ockam_core::{async_trait, compat::boxed::Box, Error, Result}; + +use arrayref::array_ref; +use sha2::{Digest, Sha256}; + +/// [`VerifyingVault`] implementation using software +#[derive(Debug, Default, Clone)] +pub struct SoftwareVerifyingVault {} + +impl SoftwareVerifyingVault { + /// Constructor + pub fn new() -> Self { + Self {} + } + + /// Create Software implementation Vault + pub fn create() -> Arc { + Arc::new(Self::new()) + } +} + +impl SoftwareVerifyingVault { + fn from_pkcs8(e: T) -> Error { + #[cfg(feature = "no_std")] + use ockam_core::compat::string::ToString; + + Error::new(Origin::Vault, Kind::Unknown, e.to_string()) + } + + fn from_ecdsa(e: p256::ecdsa::Error) -> Error { + Error::new(Origin::Vault, Kind::Unknown, e) + } + + fn import_p256_key(public_key: &PublicKey) -> Result { + if public_key.stype() != SecretType::NistP256 { + return Err(VaultError::InvalidKeyType.into()); + } + + if public_key.data().len() != NIST_P256_PUBLIC_LENGTH_USIZE { + return Err(VaultError::InvalidPublicLength.into()); + } + + p256::ecdsa::VerifyingKey::from_sec1_bytes(public_key.data()).map_err(Self::from_pkcs8) + } + + fn import_ed25519_key(public_key: &PublicKey) -> Result { + if public_key.stype() != SecretType::Ed25519 { + return Err(VaultError::InvalidKeyType.into()); + } + + if public_key.data().len() != ED25519_PUBLIC_LENGTH_USIZE { + return Err(VaultError::InvalidPublicLength.into()); + } + + let public_key = array_ref![public_key.data(), 0, ED25519_PUBLIC_LENGTH_USIZE]; + ed25519_dalek::VerifyingKey::from_bytes(public_key) + .map_err(|_| VaultError::InvalidPublicKey.into()) + } + + /// Compute SHA256 + pub fn compute_sha256(data: &[u8]) -> Result<[u8; 32]> { + let digest = Sha256::digest(data); + if digest.len() != SHA256_LENGTH { + return Err(VaultError::InvalidSha256Len.into()); + } + let digest = *array_ref![digest, 0, SHA256_LENGTH]; + Ok(digest) + } +} + +impl SoftwareVerifyingVault { + /// Compute SHA256 + pub fn sha256_sync(&self, data: &[u8]) -> Result<[u8; 32]> { + Self::compute_sha256(data) + } + + /// Verify a signature + fn verify_sync( + &self, + public_key: &PublicKey, + data: &[u8], + signature: &Signature, + ) -> Result { + match public_key.stype() { + SecretType::Ed25519 => { + let public_key = Self::import_ed25519_key(public_key)?; + + if signature.as_ref().len() != ed25519_dalek::Signature::BYTE_SIZE { + return Err(VaultError::InvalidPublicKey.into()); + } + let signature_bytes = + array_ref![signature.as_ref(), 0, ed25519_dalek::Signature::BYTE_SIZE]; + let signature = ed25519_dalek::Signature::from_bytes(signature_bytes); + + use ed25519_dalek::Verifier; + Ok(public_key.verify(data.as_ref(), &signature).is_ok()) + } + SecretType::NistP256 => { + let public_key = Self::import_p256_key(public_key)?; + + let signature = p256::ecdsa::Signature::from_slice(signature.as_ref()) + .map_err(Self::from_ecdsa)?; + + use p256::ecdsa::signature::Verifier; + Ok(public_key.verify(data, &signature).is_ok()) + } + SecretType::Buffer | SecretType::Aes | SecretType::X25519 => { + Err(VaultError::InvalidPublicKey.into()) + } + } + } +} + +#[async_trait] +impl VerifyingVault for SoftwareVerifyingVault { + async fn sha256(&self, data: &[u8]) -> Result<[u8; 32]> { + self.sha256_sync(data) + } + + async fn verify( + &self, + public_key: &PublicKey, + data: &[u8], + signature: &Signature, + ) -> Result { + self.verify_sync(public_key, data, signature) + } +} diff --git a/implementations/rust/ockam/ockam_vault/src/storage/persistent_storage.rs b/implementations/rust/ockam/ockam_vault/src/storage/persistent_storage.rs index 06a2e4ac4f0..fd032f41fe7 100644 --- a/implementations/rust/ockam/ockam_vault/src/storage/persistent_storage.rs +++ b/implementations/rust/ockam/ockam_vault/src/storage/persistent_storage.rs @@ -1,10 +1,12 @@ use crate::{constants, KeyId, Secret, SecretAttributes, SecretType, StoredSecret}; + use ockam_core::compat::boxed::Box; +use ockam_core::compat::collections::BTreeMap; use ockam_core::compat::sync::Arc; use ockam_core::{async_trait, Result}; use ockam_node::{FileValueStorage, InMemoryKeyValueStorage, KeyValueStorage, ValueStorage}; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::collections::BTreeMap; use std::path::Path; /// Storage for a Vault data backed by a file @@ -219,7 +221,7 @@ impl KeyValueStorage for PersistentStorage { #[cfg(test)] pub(crate) mod tests { use super::*; - use crate::{Secret, SecretType, VaultSecurityModule}; + use crate::Secret; use ockam_core::compat::rand::RngCore; use rand::thread_rng; use std::fs::File; @@ -234,7 +236,7 @@ pub(crate) mod tests { // create and retrieve a persistent secret let secret = Secret::new(vec![1; 32]); let attributes = SecretAttributes::Ed25519; - let key_id = VaultSecurityModule::compute_key_id(&secret, &attributes).await?; + let key_id = "34750f98bd59fcfc946da45aaabe933be154a4b5094e1c4abf42866505f3c97e".to_string(); let stored_secret = StoredSecret::new(secret.clone(), attributes); storage.put(key_id.clone(), stored_secret.clone()).await?; diff --git a/implementations/rust/ockam/ockam_vault/src/traits/asymmetric_vault.rs b/implementations/rust/ockam/ockam_vault/src/traits/asymmetric_vault.rs deleted file mode 100644 index c17bb5b4972..00000000000 --- a/implementations/rust/ockam/ockam_vault/src/traits/asymmetric_vault.rs +++ /dev/null @@ -1,88 +0,0 @@ -use crate::{KeyId, PublicKey, SecretAttributes, SmallBuffer}; -use ockam_core::{async_trait, compat::boxed::Box, Result}; - -/// Defines the Vault interface for asymmetric encryption. -#[async_trait] -pub trait AsymmetricVault: Send + Sync { - /// Compute and store an Elliptic-Curve Diffie-Hellman key using a secret key - /// and an uncompressed public key. - async fn ec_diffie_hellman(&self, secret: &KeyId, peer_public_key: &PublicKey) - -> Result; - - /// Derive multiple output [`super::Secret`]s with given attributes using - /// the HKDF-SHA256 given the specified salt, info and input key - /// material. - async fn hkdf_sha256( - &self, - salt: &KeyId, - info: &[u8], - ikm: Option<&KeyId>, - output_attributes: SmallBuffer, - ) -> Result>; -} - -/// Tests for implementations of the AsymmetricVault trait -#[cfg(test)] -pub mod tests { - use super::*; - use crate::{EphemeralSecretsStore, Secret, SecretAttributes}; - use hex::encode; - - /// This test checks that we can create a Diffie-Hellman key from 2 public keys - pub async fn test_ec_diffie_hellman_curve25519( - vault: &mut (impl AsymmetricVault + EphemeralSecretsStore), - ) { - let attributes = SecretAttributes::X25519; - let key_id_1 = vault.create_ephemeral_secret(attributes).await.unwrap(); - let key_id_2 = vault.create_ephemeral_secret(attributes).await.unwrap(); - // in general this public key is sent by a peer - let public_key_2 = vault.get_public_key(&key_id_2).await.unwrap(); - - // for now we just check that the generation doesn't fail - // TODO: Check result against test vector - let secret_key_id = vault - .ec_diffie_hellman(&key_id_1, &public_key_2) - .await - .unwrap(); - - let secret = vault - .get_ephemeral_secret(&secret_key_id, "ecdh_secret") - .await; - assert!(secret.is_ok()); - } - - /// This test checks the creation of a derived HKDF key - pub async fn test_hkdf_sha256(vault: &mut (impl AsymmetricVault + EphemeralSecretsStore)) { - let salt_value = b"hkdf_test"; - let secret = Secret::new(salt_value.to_vec()); - let attributes = SecretAttributes::Buffer(salt_value.len() as u32); - let salt = vault - .import_ephemeral_secret(secret, attributes) - .await - .unwrap(); - - let ikm_value = b"a"; - let secret = Secret::new(ikm_value.to_vec()); - let attributes = SecretAttributes::Buffer(ikm_value.len() as u32); - let ikm = vault - .import_ephemeral_secret(secret, attributes) - .await - .unwrap(); - - let attributes = SecretAttributes::Buffer(24u32); - let digest = vault - .hkdf_sha256(&salt, b"", Some(&ikm), vec![attributes]) - .await - .unwrap(); - assert_eq!(digest.len(), 1); - - let digest = vault - .get_ephemeral_secret(&digest[0], "digest") - .await - .unwrap(); - assert_eq!( - encode(digest.secret().as_ref()), - "921ab9f260544b71941dbac2ca2d42c417aa07b53e055a8f" - ); - } -} diff --git a/implementations/rust/ockam/ockam_vault/src/traits/mod.rs b/implementations/rust/ockam/ockam_vault/src/traits/mod.rs index 901931894d8..eea1c6312ee 100644 --- a/implementations/rust/ockam/ockam_vault/src/traits/mod.rs +++ b/implementations/rust/ockam/ockam_vault/src/traits/mod.rs @@ -1,21 +1,7 @@ -#[cfg(test)] -pub use asymmetric_vault::tests::*; -pub use asymmetric_vault::AsymmetricVault; -#[cfg(test)] -pub use secrets_store::tests::*; -pub use secrets_store::{ - EphemeralSecretsStore, PersistentSecretsStore, SecretsStore, SecretsStoreReader, -}; -pub use security_module::*; -#[cfg(test)] -pub use signer::tests::*; -pub use signer::Signer; -#[cfg(test)] -pub use symmetric_vault::tests::*; -pub use symmetric_vault::SymmetricVault; +mod secure_channel_vault; +mod signing_vault; +mod verifying_vault; -mod asymmetric_vault; -pub(crate) mod secrets_store; -mod security_module; -mod signer; -mod symmetric_vault; +pub use secure_channel_vault::*; +pub use signing_vault::*; +pub use verifying_vault::*; diff --git a/implementations/rust/ockam/ockam_vault/src/traits/secrets_store.rs b/implementations/rust/ockam/ockam_vault/src/traits/secrets_store.rs deleted file mode 100644 index 9880b7835b0..00000000000 --- a/implementations/rust/ockam/ockam_vault/src/traits/secrets_store.rs +++ /dev/null @@ -1,177 +0,0 @@ -use crate::{KeyId, PublicKey, Secret, SecretAttributes, StoredSecret}; -use ockam_core::{async_trait, compat::boxed::Box, compat::vec::Vec, Result}; - -/// This traits provides all the functionalities related to the management of secrets -#[async_trait] -pub trait SecretsStore: - EphemeralSecretsStore + PersistentSecretsStore + SecretsStoreReader + Sync + Send -{ -} - -impl - SecretsStore for T -{ -} - -/// This traits supports the creation / retrieval / deletion of ephemeral secrets -#[async_trait] -pub trait EphemeralSecretsStore: SecretsStoreReader + Sync + Send { - /// Generate a secret and persist it to ephemeral memory - async fn create_ephemeral_secret(&self, attributes: SecretAttributes) -> Result; - /// Import a secret and persist it to ephemeral memory - async fn import_ephemeral_secret( - &self, - secret: Secret, - attributes: SecretAttributes, - ) -> Result; - /// Export an ephemeral secret - /// Use the description in an error message if the secret cannot be found - async fn get_ephemeral_secret(&self, key_id: &KeyId, description: &str) - -> Result; - /// Remove an ephemeral secret from the vault. - async fn delete_ephemeral_secret(&self, key_id: KeyId) -> Result; - /// Return the list of all ephemeral secrets - async fn list_ephemeral_secrets(&self) -> Result>; -} - -/// This traits supports the creation / deletion of persistent secrets -#[async_trait] -pub trait PersistentSecretsStore: SecretsStoreReader + Sync + Send { - /// Generate a secret and persist it to long-term memory - async fn create_persistent_secret(&self, attributes: SecretAttributes) -> Result; - /// Remove a persistent secret from the vault. - async fn delete_persistent_secret(&self, key_id: KeyId) -> Result; -} - -/// This traits supports the retrieval of public information for a given secret -#[async_trait] -pub trait SecretsStoreReader: Sync + Send { - /// Return the secret attributes related to a secret - async fn get_secret_attributes(&self, key_id: &KeyId) -> Result; - /// Return the associated public key given the secret key - async fn get_public_key(&self, key_id: &KeyId) -> Result; - /// Compute and return the `KeyId` for a given public key. - async fn get_key_id(&self, public_key: &PublicKey) -> Result; -} - -/// Tests for implementations of the SecretsStore trait -#[cfg(test)] -pub mod tests { - use super::*; - use crate::{PublicKey, SecretAttributes, SecretType}; - use hex::decode; - use ockam_core::compat::vec::Vec; - use SecretType::*; - - /// This test checks the creation of ephemeral keys of different types - pub async fn test_create_ephemeral_secrets(vault: &mut impl SecretsStore) { - for attributes in all_secret_attributes() { - let key_id = vault.create_ephemeral_secret(attributes).await.unwrap(); - - // once a secret is created we can retrieve it using the key id - let secret = vault.get_ephemeral_secret(&key_id, "secret").await.unwrap(); - assert_eq!(secret.attributes(), attributes); - - // once a secret is created we can get its public key using the key id - // for secrets that are not Buffer or Aes secrets - let public_key = vault.get_public_key(&key_id).await; - let secret_type = attributes.secret_type(); - if ![Buffer, Aes].contains(&secret_type) { - // the public key must have a suitable length - assert!(public_key.unwrap().data().len() >= 32); - } else { - assert!(public_key.is_err()) - } - - // finally we can delete the secret using its key id - let deleted = vault.delete_ephemeral_secret(key_id).await.unwrap(); - assert!(deleted); - } - } - - /// This test checks that we can import and export ephemeral secrets - pub async fn test_secret_import_export(vault: &mut impl SecretsStore) { - for attributes in all_secret_attributes() { - let key_id = vault.create_ephemeral_secret(attributes).await.unwrap(); - let secret = vault.get_ephemeral_secret(&key_id, "secret").await.unwrap(); - - let new_key_id = vault - .import_ephemeral_secret(secret.secret().clone(), attributes) - .await - .unwrap(); - - assert_eq!( - vault - .get_ephemeral_secret(&new_key_id, "secret") - .await - .unwrap(), - secret - ); - } - } - - /// This tests checks that we can retrieve attributes from both ephemeral and persistent secrets - pub async fn test_get_secret_attributes(vault: &mut impl SecretsStore) { - for attributes in all_secret_attributes() { - let secret = vault.create_ephemeral_secret(attributes).await.unwrap(); - assert_eq!( - vault.get_secret_attributes(&secret).await.unwrap(), - attributes - ); - - let secret = vault.create_persistent_secret(attributes).await.unwrap(); - assert_eq!( - vault.get_secret_attributes(&secret).await.unwrap(), - attributes - ); - } - } - - /// This tests checks that we can compute a key id from a public key - pub async fn test_get_key_id_by_public_key(vault: &mut impl SecretsStore) { - for attributes in [ - SecretAttributes::Ed25519, - SecretAttributes::X25519, - SecretAttributes::NistP256, - ] { - let public = - decode("68858ea1ea4e1ade755df7fb6904056b291d9781eb5489932f46e32f12dd192a").unwrap(); - let public = PublicKey::new(public.to_vec(), attributes.secret_type()); - let key_id = vault.get_key_id(&public).await.unwrap(); - - assert_eq!( - key_id, - "732af49a0b47c820c0a4cac428d6cb80c1fa70622f4a51708163dd87931bc942" - ); - } - } - - /// This test checks that we can create a persistent secret then retrieve its key id from - /// its public key - pub async fn test_get_key_id_for_persistent_secret_from_public_key( - vault: &mut impl SecretsStore, - ) { - for attributes in [ - SecretAttributes::Ed25519, - SecretAttributes::X25519, - SecretAttributes::NistP256, - ] { - let secret = vault.create_persistent_secret(attributes).await.unwrap(); - let public = vault.get_public_key(&secret).await.unwrap(); - let key_id = vault.get_key_id(&public).await.unwrap(); - assert_eq!(secret, key_id); - } - } - - /// Return all the types of secret attributes - fn all_secret_attributes() -> Vec { - vec![ - SecretAttributes::Ed25519, - SecretAttributes::X25519, - SecretAttributes::Buffer(32), - SecretAttributes::Aes128, - SecretAttributes::Aes256, - SecretAttributes::NistP256, - ] - } -} diff --git a/implementations/rust/ockam/ockam_vault/src/traits/secure_channel_vault.rs b/implementations/rust/ockam/ockam_vault/src/traits/secure_channel_vault.rs new file mode 100644 index 00000000000..5c63b327504 --- /dev/null +++ b/implementations/rust/ockam/ockam_vault/src/traits/secure_channel_vault.rs @@ -0,0 +1,71 @@ +use crate::{Buffer, KeyId, PublicKey, Secret, SecretAttributes, SmallBuffer}; +use ockam_core::{async_trait, compat::boxed::Box, Result}; + +/// Vault used for Secure Channel +#[async_trait] +pub trait SecureChannelVault: Send + Sync + 'static { + /// Generate a fresh random secret that is persisted in the Storage + async fn generate_static_secret(&self, attributes: SecretAttributes) -> Result; + + /// Generate a fresh random secret that is persisted only in memory + async fn generate_ephemeral_secret(&self, attributes: SecretAttributes) -> Result; + + /// Import a secret that is persisted in the Storage + async fn import_static_secret( + &self, + secret: Secret, + attributes: SecretAttributes, + ) -> Result; + + /// Import a secret that is persisted only in memory + async fn import_ephemeral_secret( + &self, + secret: Secret, + attributes: SecretAttributes, + ) -> Result; + + /// Delete a Secret + async fn delete_secret(&self, key_id: KeyId) -> Result; + + /// Get corresponding [`PublicKey`] + async fn get_public_key(&self, key_id: &KeyId) -> Result; + + /// Get corresponding [`KeyId`] given a [`PublicKey`] + async fn get_key_id(&self, public_key: &PublicKey) -> Result; + + /// Get Secret's [`SecretAttributes`] + async fn get_secret_attributes(&self, key_id: &KeyId) -> Result; + + /// Compute and store an Elliptic-Curve Diffie-Hellman + async fn ec_diffie_hellman(&self, secret: &KeyId, peer_public_key: &PublicKey) + -> Result; + + /// Derive multiple output [`super::Secret`]s with given attributes using + /// the HKDF-SHA256 given the specified salt, info and input key + /// material + async fn hkdf_sha256( + &self, + salt: &KeyId, + info: &[u8], + ikm: Option<&KeyId>, + output_attributes: SmallBuffer, + ) -> Result>; + + /// Encrypt a payload using AES-GCM + async fn aead_aes_gcm_encrypt( + &self, + key_id: &KeyId, + plaintext: &[u8], + nonce: &[u8], + aad: &[u8], + ) -> Result>; + + /// Decrypt a payload using AES-GCM + async fn aead_aes_gcm_decrypt( + &self, + key_id: &KeyId, + cipher_text: &[u8], + nonce: &[u8], + aad: &[u8], + ) -> Result>; +} diff --git a/implementations/rust/ockam/ockam_vault/src/traits/security_module.rs b/implementations/rust/ockam/ockam_vault/src/traits/security_module.rs deleted file mode 100644 index afb45ee5417..00000000000 --- a/implementations/rust/ockam/ockam_vault/src/traits/security_module.rs +++ /dev/null @@ -1,43 +0,0 @@ -use crate::{KeyId, PublicKey, SecretAttributes, Signature}; -use ockam_core::compat::boxed::Box; -use ockam_core::{async_trait, Result}; - -/// A SecurityModule provides several functions related to secrets: -/// - create and persist secrets -/// - delete secrets -/// - return the public key for a given key id -/// - return the key id for a given public key -/// - use a secret to sign a message -/// - use a public key to verify a message signature -/// -/// The concrete implementations for a security module can range from full KMSes like the AWS KMS -/// to yubikeys or hardware security modules -/// -#[async_trait] -pub trait SecurityModule: Sync + Send { - /// Create a new secret and return its key id - async fn create_secret(&self, attributes: SecretAttributes) -> Result; - - /// Get the public key from a secret - async fn get_public_key(&self, key_id: &KeyId) -> Result; - - /// Return the key id corresponding to a given public key - async fn get_key_id(&self, public_key: &PublicKey) -> Result; - - /// Return the secret attributes for a given key id - async fn get_attributes(&self, key_id: &KeyId) -> Result; - - /// Delete a secret - async fn delete_secret(&self, key_id: KeyId) -> Result; - - /// Sign a message with a given key - async fn sign(&self, key_id: &KeyId, message: &[u8]) -> Result; - - /// Verify a message signature - async fn verify( - &self, - public_key: &PublicKey, - message: &[u8], - signature: &Signature, - ) -> Result; -} diff --git a/implementations/rust/ockam/ockam_vault/src/traits/signer.rs b/implementations/rust/ockam/ockam_vault/src/traits/signer.rs deleted file mode 100644 index c366c97d72f..00000000000 --- a/implementations/rust/ockam/ockam_vault/src/traits/signer.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::{KeyId, PublicKey, Signature}; -use ockam_core::{async_trait, compat::boxed::Box, Result}; - -/// Defines the Vault interface for Signing. -#[async_trait] -pub trait Signer: Send + Sync { - /// Generate a `Signature` for the given data using the given `Secret` key. - async fn sign(&self, key_id: &KeyId, data: &[u8]) -> Result; - - /// Verify a signature for the given data using the given public key. - async fn verify( - &self, - public_key: &PublicKey, - data: &[u8], - signature: &Signature, - ) -> Result; -} - -/// Tests for implementations of the Signer trait -#[cfg(test)] -pub mod tests { - use super::*; - use crate::{SecretAttributes, SecretsStore}; - - /// This test checks that an ephemeral secret can be used to sign data and that we can verify the signature - pub async fn test_sign_and_verify_ephemeral_secret(vault: &mut (impl Signer + SecretsStore)) { - for attributes in [SecretAttributes::Ed25519] { - let secret = vault.create_ephemeral_secret(attributes).await.unwrap(); - sign_and_verify(vault, &secret).await; - } - } - - /// This test checks that a persistent secret can be used to sign data and that we can verify the signature - pub async fn test_sign_and_verify_persistent_secret(vault: &mut (impl Signer + SecretsStore)) { - for attributes in [SecretAttributes::Ed25519] { - let secret = vault.create_persistent_secret(attributes).await.unwrap(); - sign_and_verify(vault, &secret).await; - } - } - - /// Use a secret to sign data, then verify the signature - async fn sign_and_verify(vault: &mut (impl Signer + SecretsStore + Sized), secret: &KeyId) { - let res = vault.sign(secret, b"hello world!").await; - assert!(res.is_ok()); - let pubkey = vault.get_public_key(secret).await.unwrap(); - let signature = res.unwrap(); - let res = vault - .verify(&pubkey, b"hello world!", &signature) - .await - .unwrap(); - assert!(res); - } -} diff --git a/implementations/rust/ockam/ockam_vault/src/traits/signing_vault.rs b/implementations/rust/ockam/ockam_vault/src/traits/signing_vault.rs new file mode 100644 index 00000000000..fc1efbac300 --- /dev/null +++ b/implementations/rust/ockam/ockam_vault/src/traits/signing_vault.rs @@ -0,0 +1,24 @@ +use crate::{KeyId, PublicKey, SecretAttributes, Signature}; +use ockam_core::{async_trait, compat::boxed::Box, Result}; + +/// Vault used for Signing +#[async_trait] +pub trait SigningVault: Send + Sync + 'static { + /// Generate a fresh random key + async fn generate_key(&self, attributes: SecretAttributes) -> Result; + + /// Delete a key + async fn delete_key(&self, key_id: KeyId) -> Result; + + /// Get corresponding [`PublicKey`] + async fn get_public_key(&self, key_id: &KeyId) -> Result; + + /// Return the [`KeyId`] given a [`PublicKey`] + async fn get_key_id(&self, public_key: &PublicKey) -> Result; + + /// Sign data + async fn sign(&self, key_id: &KeyId, data: &[u8]) -> Result; + + /// Return the total number of all keys + async fn number_of_keys(&self) -> Result; +} diff --git a/implementations/rust/ockam/ockam_vault/src/traits/symmetric_vault.rs b/implementations/rust/ockam/ockam_vault/src/traits/symmetric_vault.rs deleted file mode 100644 index 3b0c1d03c60..00000000000 --- a/implementations/rust/ockam/ockam_vault/src/traits/symmetric_vault.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::{Buffer, KeyId}; -use ockam_core::{async_trait, compat::boxed::Box, Result}; - -/// Defines the Vault interface for symmetric encryption. -#[async_trait] -pub trait SymmetricVault: Send + Sync { - /// Encrypt a payload using AES-GCM. - async fn aead_aes_gcm_encrypt( - &self, - key_id: &KeyId, - plaintext: &[u8], - nonce: &[u8], - aad: &[u8], - ) -> Result>; - - /// Decrypt a payload using AES-GCM. - async fn aead_aes_gcm_decrypt( - &self, - key_id: &KeyId, - cipher_text: &[u8], - nonce: &[u8], - aad: &[u8], - ) -> Result>; -} - -#[cfg(test)] -pub mod tests { - use crate::{EphemeralSecretsStore, SecretAttributes, SymmetricVault}; - - /// This test checks that we can use an ephemeral secret to encrypt and decrypt data - pub async fn test_encrypt_decrypt(vault: &mut (impl SymmetricVault + EphemeralSecretsStore)) { - let message = b"Ockam Test Message"; - let nonce = b"TestingNonce"; - let aad = b"Extra payload data"; - let attributes = SecretAttributes::Aes128; - - let ctx = &vault.create_ephemeral_secret(attributes).await.unwrap(); - let res = vault - .aead_aes_gcm_encrypt(ctx, message.as_ref(), nonce.as_ref(), aad.as_ref()) - .await; - assert!(res.is_ok()); - let mut ciphertext = res.unwrap(); - let res = vault - .aead_aes_gcm_decrypt(ctx, ciphertext.as_slice(), nonce.as_ref(), aad.as_ref()) - .await; - assert!(res.is_ok()); - let plaintext = res.unwrap(); - assert_eq!(plaintext, message.to_vec()); - ciphertext[0] ^= 0xb4; - ciphertext[1] ^= 0xdc; - let res = vault - .aead_aes_gcm_decrypt(ctx, ciphertext.as_slice(), nonce.as_ref(), aad.as_ref()) - .await; - assert!(res.is_err()); - } -} diff --git a/implementations/rust/ockam/ockam_vault/src/traits/verifying_vault.rs b/implementations/rust/ockam/ockam_vault/src/traits/verifying_vault.rs new file mode 100644 index 00000000000..17c08b0d99b --- /dev/null +++ b/implementations/rust/ockam/ockam_vault/src/traits/verifying_vault.rs @@ -0,0 +1,17 @@ +use crate::{PublicKey, Signature}; +use ockam_core::{async_trait, compat::boxed::Box, Result}; + +/// Vault used for verification (signature verification, sha256) +#[async_trait] +pub trait VerifyingVault: Send + Sync + 'static { + /// Compute sha256 + async fn sha256(&self, data: &[u8]) -> Result<[u8; 32]>; + + /// Verify a signature + async fn verify( + &self, + public_key: &PublicKey, + data: &[u8], + signature: &Signature, + ) -> Result; +} diff --git a/implementations/rust/ockam/ockam_vault/src/types/constants.rs b/implementations/rust/ockam/ockam_vault/src/types/constants.rs index eb0161ba122..4659d423547 100644 --- a/implementations/rust/ockam/ockam_vault/src/types/constants.rs +++ b/implementations/rust/ockam/ockam_vault/src/types/constants.rs @@ -1,8 +1,44 @@ -/// Curve25519 private key length. -pub const CURVE25519_SECRET_LENGTH_U32: u32 = 32; +/// SHA256 digest length +pub const SHA256_LENGTH: usize = 32; -/// Curve25519 public key length. -pub const CURVE25519_PUBLIC_LENGTH_USIZE: usize = 32; +/// X25519 private key length. +pub const X25519_SECRET_LENGTH_U32: u32 = 32; +/// X25519 private key length. +pub const X25519_SECRET_LENGTH_USIZE: usize = 32; + +/// X25519 public key length. +pub const X25519_PUBLIC_LENGTH_U32: u32 = 32; +/// X25519 public key length. +pub const X25519_PUBLIC_LENGTH_USIZE: usize = 32; + +/// Ed25519 private key length. +pub const ED25519_SECRET_LENGTH_U32: u32 = 32; +/// Ed25519 private key length. +pub const ED25519_SECRET_LENGTH_USIZE: usize = 32; + +/// Ed25519 public key length. +pub const ED25519_PUBLIC_LENGTH_U32: u32 = 32; +/// Ed25519 public key length. +pub const ED25519_PUBLIC_LENGTH_USIZE: usize = 32; + +/// Ed25519 signature length. +pub const ED25519_SIGNATURE_LENGTH_USIZE: usize = 64; + +/// NIST P256 private key length. +pub const NIST_P256_SECRET_LENGTH_U32: u32 = 32; +/// NIST P256 private key length. +pub const NIST_P256_SECRET_LENGTH_USIZE: usize = 32; + +/// NIST P256 public key length. +pub const NIST_P256_PUBLIC_LENGTH_U32: u32 = 65; +/// NIST P256 public key length. +pub const NIST_P256_PUBLIC_LENGTH_USIZE: usize = 65; + +/// NIST P256 signature length. +pub const NIST_P256_SIGNATURE_LENGTH_USIZE: usize = 64; + +/// AES-GCM nonce length +pub const AES_NONCE_LENGTH_USIZE: usize = 12; /// AES256 private key length. pub const AES256_SECRET_LENGTH_U32: u32 = 32; @@ -14,5 +50,25 @@ pub const AES128_SECRET_LENGTH_U32: u32 = 16; /// AES128 private key length. pub const AES128_SECRET_LENGTH_USIZE: usize = 16; -/// NISTP256 private key length. -pub const NISTP256_SECRET_LENGTH_U32: u32 = 32; +use static_assertions::const_assert_eq; + +const_assert_eq!(X25519_SECRET_LENGTH_U32, X25519_SECRET_LENGTH_USIZE as u32); +const_assert_eq!(X25519_PUBLIC_LENGTH_U32, X25519_PUBLIC_LENGTH_USIZE as u32); +const_assert_eq!( + ED25519_SECRET_LENGTH_U32, + ED25519_SECRET_LENGTH_USIZE as u32 +); +const_assert_eq!( + ED25519_PUBLIC_LENGTH_U32, + ED25519_PUBLIC_LENGTH_USIZE as u32 +); +const_assert_eq!( + NIST_P256_SECRET_LENGTH_U32, + NIST_P256_SECRET_LENGTH_USIZE as u32 +); +const_assert_eq!( + NIST_P256_PUBLIC_LENGTH_U32, + NIST_P256_PUBLIC_LENGTH_USIZE as u32 +); +const_assert_eq!(AES256_SECRET_LENGTH_U32, AES256_SECRET_LENGTH_USIZE as u32); +const_assert_eq!(AES128_SECRET_LENGTH_U32, AES128_SECRET_LENGTH_USIZE as u32); diff --git a/implementations/rust/ockam/ockam_vault/src/types/secret_attributes.rs b/implementations/rust/ockam/ockam_vault/src/types/secret_attributes.rs index 37d1090a0b4..f319220cf70 100644 --- a/implementations/rust/ockam/ockam_vault/src/types/secret_attributes.rs +++ b/implementations/rust/ockam/ockam_vault/src/types/secret_attributes.rs @@ -1,13 +1,16 @@ -use crate::constants::NISTP256_SECRET_LENGTH_U32; +use crate::constants::{AES128_SECRET_LENGTH_U32, AES256_SECRET_LENGTH_U32}; use crate::constants::{ - AES128_SECRET_LENGTH_U32, AES256_SECRET_LENGTH_U32, CURVE25519_SECRET_LENGTH_U32, + ED25519_SECRET_LENGTH_U32, NIST_P256_SECRET_LENGTH_U32, X25519_SECRET_LENGTH_U32, }; +use crate::VaultError; use core::fmt; use core::fmt::{Display, Formatter}; use minicbor::{Decode, Encode}; use serde::{Deserialize, Serialize}; use zeroize::Zeroize; +// TODO: Remove in favor of SecretType +// TODO: Has room for better type-safety /// Attributes for secrets /// - a type indicating how the secret is generated: Aes, Ed25519 /// - an expected length corresponding to the type @@ -28,10 +31,9 @@ pub enum SecretAttributes { NistP256, } -impl SecretAttributes { - /// Return the type of a secret - pub fn secret_type(&self) -> SecretType { - match self { +impl From for SecretType { + fn from(value: SecretAttributes) -> Self { + match value { SecretAttributes::Buffer(_) => SecretType::Buffer, SecretAttributes::Aes128 => SecretType::Aes, SecretAttributes::Aes256 => SecretType::Aes, @@ -40,6 +42,26 @@ impl SecretAttributes { SecretAttributes::NistP256 => SecretType::NistP256, } } +} + +impl TryFrom for SecretAttributes { + type Error = ockam_core::Error; + + fn try_from(value: SecretType) -> Result { + match value { + SecretType::Buffer | SecretType::Aes => Err(VaultError::InvalidKeyType.into()), + SecretType::X25519 => Ok(SecretAttributes::X25519), + SecretType::Ed25519 => Ok(SecretAttributes::Ed25519), + SecretType::NistP256 => Ok(SecretAttributes::NistP256), + } + } +} + +impl SecretAttributes { + /// Return the type of a secret + pub fn secret_type(&self) -> SecretType { + (*self).into() + } /// Return the length of a secret pub fn length(&self) -> u32 { @@ -47,9 +69,9 @@ impl SecretAttributes { SecretAttributes::Buffer(s) => *s, SecretAttributes::Aes128 => AES128_SECRET_LENGTH_U32, SecretAttributes::Aes256 => AES256_SECRET_LENGTH_U32, - SecretAttributes::Ed25519 => CURVE25519_SECRET_LENGTH_U32, - SecretAttributes::X25519 => CURVE25519_SECRET_LENGTH_U32, - SecretAttributes::NistP256 => NISTP256_SECRET_LENGTH_U32, + SecretAttributes::Ed25519 => ED25519_SECRET_LENGTH_U32, + SecretAttributes::X25519 => X25519_SECRET_LENGTH_U32, + SecretAttributes::NistP256 => NIST_P256_SECRET_LENGTH_U32, } } } diff --git a/implementations/rust/ockam/ockam_vault/src/types/stored_secret.rs b/implementations/rust/ockam/ockam_vault/src/types/stored_secret.rs index 08adaf0d358..65601eb542b 100644 --- a/implementations/rust/ockam/ockam_vault/src/types/stored_secret.rs +++ b/implementations/rust/ockam/ockam_vault/src/types/stored_secret.rs @@ -31,11 +31,15 @@ impl StoredSecret { &self.secret } + /// Get the secret part of this stored secret + pub fn take_secret(self) -> Secret { + self.secret + } + /// Check if the length of the secret is the same as the length prescribed by the attributes fn check(secret: &Secret, attributes: &SecretAttributes) -> Result<()> { - // the secret must be at least the length mentioned in the attributes - // In the case of a NistP256 secret it might contain additional metadata (algorithm, version, etc...) - if secret.length() < attributes.length() as usize { + // the secret must be equal to length mentioned in the attributes + if secret.length() != attributes.length() as usize { Err(VaultError::InvalidSecretLength( attributes.secret_type(), secret.length(), diff --git a/implementations/rust/ockam/ockam_vault/src/vault/asymmetric_impl.rs b/implementations/rust/ockam/ockam_vault/src/vault/asymmetric_impl.rs deleted file mode 100644 index 27aba4c75bc..00000000000 --- a/implementations/rust/ockam/ockam_vault/src/vault/asymmetric_impl.rs +++ /dev/null @@ -1,138 +0,0 @@ -use crate::constants::CURVE25519_SECRET_LENGTH_U32; -use crate::{ - AsymmetricVault, Buffer, EphemeralSecretsStore, Implementation, KeyId, PublicKey, Secret, - SecretAttributes, SecretType, StoredSecret, Vault, VaultError, -}; -use arrayref::array_ref; -use ockam_core::compat::vec::Vec; -use ockam_core::{async_trait, compat::boxed::Box, Result}; -use sha2::Sha256; - -#[async_trait] -impl AsymmetricVault for T { - async fn ec_diffie_hellman( - &self, - secret: &KeyId, - peer_public_key: &PublicKey, - ) -> Result { - let stored_secret = self - .get_ephemeral_secret(secret, "diffie hellman secret") - .await?; - let dh = Vault::ecdh_internal(&stored_secret, peer_public_key)?; - - let attributes = SecretAttributes::Buffer(dh.len() as u32); - self.import_ephemeral_secret(Secret::new(dh), attributes) - .await - } - - /// Compute sha256. - /// Salt and Ikm should be of Buffer type. - /// Output secrets should be only of type Buffer or AES - async fn hkdf_sha256( - &self, - salt: &KeyId, - info: &[u8], - ikm: Option<&KeyId>, - output_attributes: Vec, - ) -> Result> { - let ikm: Result = match ikm { - Some(ikm) => { - let stored_secret = self.get_ephemeral_secret(ikm, "hkdf_sha256").await?; - - if stored_secret.attributes().secret_type() == SecretType::Buffer { - let secret_key = stored_secret.secret().clone(); - Ok(secret_key) - } else { - Err(VaultError::InvalidKeyType.into()) - } - } - None => Ok(Secret::new(vec![])), - }; - - let stored_secret = self.get_ephemeral_secret(salt, "hkdf_sha256 salt").await?; - - if stored_secret.attributes().secret_type() != SecretType::Buffer { - return Err(VaultError::InvalidKeyType.into()); - } - - // FIXME: Doesn't work for secrets with size more than 32 bytes - let okm_len = output_attributes.len() * 32; - - let okm = { - let mut okm = vec![0u8; okm_len]; - let prk = - hkdf::Hkdf::::new(Some(stored_secret.secret().as_ref()), ikm?.as_ref()); - - prk.expand(info, okm.as_mut_slice()) - .map_err(|_| Into::::into(VaultError::HkdfExpandError))?; - okm - }; - - let mut secrets = Vec::::new(); - let mut index = 0; - - for attributes in output_attributes { - if ![SecretType::Buffer, SecretType::Aes].contains(&attributes.secret_type()) { - return Err(VaultError::InvalidHkdfOutputType.into()); - } - - let length = attributes.length() as usize; - let secret = Secret::new(okm[index..index + length].to_vec()); - let secret = self.import_ephemeral_secret(secret, attributes).await?; - - secrets.push(secret); - index += 32; - } - - Ok(secrets) - } -} - -impl Vault { - fn ecdh_internal( - stored_secret: &StoredSecret, - peer_public_key: &PublicKey, - ) -> Result> { - let attributes = stored_secret.attributes(); - match attributes.secret_type() { - SecretType::X25519 => { - let key = stored_secret.secret(); - if peer_public_key.data().len() != attributes.length() as usize { - return Err(VaultError::UnknownEcdhKeyType.into()); - } - let sk = x25519_dalek::StaticSecret::from(*array_ref!( - key.as_ref(), - 0, - CURVE25519_SECRET_LENGTH_U32 as usize - )); - let pk_t = x25519_dalek::PublicKey::from(*array_ref!( - peer_public_key.data(), - 0, - CURVE25519_SECRET_LENGTH_U32 as usize - )); - let secret = sk.diffie_hellman(&pk_t); - Ok(secret.as_bytes().to_vec()) - } - SecretType::Buffer | SecretType::Aes | SecretType::Ed25519 => { - Err(VaultError::UnknownEcdhKeyType.into()) - } - SecretType::NistP256 => Err(VaultError::UnknownEcdhKeyType.into()), - } - } -} - -#[cfg(test)] -mod tests { - use crate as ockam_vault; - use crate::Vault; - - fn new_vault() -> Vault { - Vault::new() - } - - #[ockam_macros::vault_test] - fn test_ec_diffie_hellman_curve25519() {} - - #[ockam_macros::vault_test] - fn test_hkdf_sha256() {} -} diff --git a/implementations/rust/ockam/ockam_vault/src/vault/mod.rs b/implementations/rust/ockam/ockam_vault/src/vault/mod.rs deleted file mode 100644 index 3b7644101b9..00000000000 --- a/implementations/rust/ockam/ockam_vault/src/vault/mod.rs +++ /dev/null @@ -1,26 +0,0 @@ -//! Core types and traits of the Ockam vault. -//! -//! This module contains the core types and traits of the Ockam vault and is intended -//! for use by other crates that either provide implementations for those traits, -//! or use traits and types as an abstract dependency. -//! -//! # Examples -//! -//! See the [`ockam_vault`] crate for usage examples. -//! -//! [`ockam_vault`]: https://docs.rs/ockam_vault/latest - -mod asymmetric_impl; -mod secrets_store_impl; -mod signer_impl; -mod symmetric_impl; -#[allow(clippy::module_inception)] -mod vault; -mod vault_builder; -mod vault_error; -mod vault_kms; - -pub use vault::*; -pub use vault_builder::*; -pub use vault_error::*; -pub use vault_kms::*; diff --git a/implementations/rust/ockam/ockam_vault/src/vault/secrets_store_impl.rs b/implementations/rust/ockam/ockam_vault/src/vault/secrets_store_impl.rs deleted file mode 100644 index cc8350aa3aa..00000000000 --- a/implementations/rust/ockam/ockam_vault/src/vault/secrets_store_impl.rs +++ /dev/null @@ -1,173 +0,0 @@ -use crate::{ - EphemeralSecretsStore, Implementation, KeyId, PersistentSecretsStore, PublicKey, Secret, - SecretAttributes, SecretsStoreReader, SecurityModule, Signature, StoredSecret, VaultError, - VaultSecurityModule, -}; -use ockam_core::{async_trait, compat::boxed::Box, compat::sync::Arc, compat::vec::Vec, Result}; -use ockam_node::KeyValueStorage; - -#[derive(Clone)] -pub struct VaultSecretsStore { - security_module: Arc, - ephemeral_secrets: Arc>, -} - -impl Implementation for VaultSecretsStore {} - -impl VaultSecretsStore { - pub fn new( - security_module: Arc, - ephemeral_secrets: Arc>, - ) -> VaultSecretsStore { - VaultSecretsStore { - security_module, - ephemeral_secrets, - } - } -} - -#[async_trait] -impl EphemeralSecretsStore for VaultSecretsStore { - async fn create_ephemeral_secret(&self, attributes: SecretAttributes) -> Result { - let secret = VaultSecurityModule::create_secret_from_attributes(attributes)?; - self.import_ephemeral_secret(secret, attributes).await - } - - async fn import_ephemeral_secret( - &self, - secret: Secret, - attributes: SecretAttributes, - ) -> Result { - let key_id = VaultSecurityModule::compute_key_id(&secret, &attributes).await?; - let stored_secret = StoredSecret::create(secret, attributes)?; - self.ephemeral_secrets - .put(key_id.clone(), stored_secret) - .await?; - Ok(key_id) - } - - async fn get_ephemeral_secret( - &self, - key_id: &KeyId, - description: &str, - ) -> Result { - let stored_secret = self.ephemeral_secrets.get(key_id).await?.ok_or_else(|| { - VaultError::EntryNotFound(format!("{description} not found for key_id: '{key_id}'")) - })?; - Ok(stored_secret) - } - - /// Remove secret from in memory storage - async fn delete_ephemeral_secret(&self, key_id: KeyId) -> Result { - self.ephemeral_secrets - .delete(&key_id) - .await - .map(|r| r.is_some()) - } - - async fn list_ephemeral_secrets(&self) -> Result> { - self.ephemeral_secrets.keys().await - } -} - -#[async_trait] -impl PersistentSecretsStore for VaultSecretsStore { - async fn create_persistent_secret(&self, attributes: SecretAttributes) -> Result { - self.security_module.create_secret(attributes).await - } - - /// Remove secret from in memory storage - async fn delete_persistent_secret(&self, key_id: KeyId) -> Result { - self.security_module.delete_secret(key_id).await - } -} - -#[async_trait] -impl SecretsStoreReader for VaultSecretsStore { - /// Get the secret attributes for a given key id - async fn get_secret_attributes(&self, key_id: &KeyId) -> Result { - // search in the ephemeral secrets first, otherwise export the public key from the Kms - if let Some(stored_secret) = self.ephemeral_secrets.get(key_id).await? { - Ok(stored_secret.attributes()) - } else { - self.security_module.get_attributes(key_id).await - } - } - - /// Extract public key a from secret - async fn get_public_key(&self, key_id: &KeyId) -> Result { - // search in the ephemeral secrets first, otherwise export the public key from the Kms - if let Some(stored_secret) = self.ephemeral_secrets.get(key_id).await? { - VaultSecurityModule::compute_public_key_from_secret(stored_secret) - } else { - self.security_module.get_public_key(key_id).await - } - } - - async fn get_key_id(&self, public_key: &PublicKey) -> Result { - self.security_module.get_key_id(public_key).await - } -} - -#[async_trait] -impl SecurityModule for VaultSecretsStore { - async fn create_secret(&self, attributes: SecretAttributes) -> Result { - self.security_module.create_secret(attributes).await - } - - async fn get_public_key(&self, key_id: &KeyId) -> Result { - self.security_module.get_public_key(key_id).await - } - - async fn get_key_id(&self, public_key: &PublicKey) -> Result { - self.security_module.get_key_id(public_key).await - } - - async fn get_attributes(&self, key_id: &KeyId) -> Result { - self.security_module.get_attributes(key_id).await - } - - async fn delete_secret(&self, key_id: KeyId) -> Result { - self.security_module.delete_secret(key_id).await - } - - async fn sign(&self, key_id: &KeyId, message: &[u8]) -> Result { - self.security_module.sign(key_id, message).await - } - - async fn verify( - &self, - public_key: &PublicKey, - message: &[u8], - signature: &Signature, - ) -> Result { - self.security_module - .verify(public_key, message, signature) - .await - } -} - -#[cfg(test)] -mod tests { - use crate as ockam_vault; - use crate::Vault; - - fn new_vault() -> Vault { - Vault::new() - } - - #[ockam_macros::vault_test] - async fn test_create_ephemeral_secrets(vault: &mut impl SecretsStore) {} - - #[ockam_macros::vault_test] - async fn test_secret_import_export(vault: &mut impl SecretsStore) {} - - #[ockam_macros::vault_test] - async fn test_get_secret_attributes(vault: &mut impl SecretsStore) {} - - #[ockam_macros::vault_test] - pub async fn test_get_key_id_by_public_key(vault: &mut impl SecretsStore) {} - - #[ockam_macros::vault_test] - async fn test_get_key_id_for_persistent_secret_from_public_key(vault: &mut impl SecretsStore) {} -} diff --git a/implementations/rust/ockam/ockam_vault/src/vault/signer_impl.rs b/implementations/rust/ockam/ockam_vault/src/vault/signer_impl.rs deleted file mode 100644 index efb7fb770db..00000000000 --- a/implementations/rust/ockam/ockam_vault/src/vault/signer_impl.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::{ - KeyId, PublicKey, SecretsStore, SecurityModule, Signature, Signer, VaultSecurityModule, -}; -use ockam_core::{async_trait, compat::boxed::Box, Result}; - -#[async_trait] -impl Signer for T { - /// Sign data. The key can either come from the ephemeral storage or the persistent one - async fn sign(&self, key_id: &KeyId, data: &[u8]) -> Result { - if let Ok(stored_secret) = self.get_ephemeral_secret(key_id, "signing secret").await { - VaultSecurityModule::sign_with_secret(stored_secret, data) - } else { - self.sign(key_id, data).await - } - } - - /// Verify signature - async fn verify( - &self, - public_key: &PublicKey, - data: &[u8], - signature: &Signature, - ) -> Result { - self.verify(public_key, data, signature).await - } -} - -#[cfg(test)] -mod tests { - use crate as ockam_vault; - use crate::Vault; - - fn new_vault() -> Vault { - Vault::new() - } - - #[ockam_macros::vault_test] - fn test_sign_and_verify_persistent_secret() {} - - #[ockam_macros::vault_test] - fn test_sign_and_verify_ephemeral_secret() {} -} diff --git a/implementations/rust/ockam/ockam_vault/src/vault/symmetric_impl.rs b/implementations/rust/ockam/ockam_vault/src/vault/symmetric_impl.rs deleted file mode 100644 index 32f05f8d8e9..00000000000 --- a/implementations/rust/ockam/ockam_vault/src/vault/symmetric_impl.rs +++ /dev/null @@ -1,119 +0,0 @@ -use crate::traits::SymmetricVault; -use crate::{ - Buffer, EphemeralSecretsStore, Implementation, KeyId, SecretAttributes, StoredSecret, Vault, - VaultError, -}; -use aes_gcm::aead::consts::{U0, U12, U16}; -use aes_gcm::aead::{Aead, NewAead, Nonce, Payload, Tag}; -use aes_gcm::aes::{Aes128, Aes256}; -use aes_gcm::{AeadCore, AeadInPlace, Aes128Gcm, Aes256Gcm, AesGcm}; -use ockam_core::{async_trait, compat::boxed::Box, Result}; - -#[async_trait] -impl SymmetricVault for T { - async fn aead_aes_gcm_encrypt( - &self, - key_id: &KeyId, - msg: &[u8], - nonce: &[u8], - aad: &[u8], - ) -> Result> { - let stored_secret = self.get_ephemeral_secret(key_id, "aes key").await?; - let aes = Vault::make_aes(&stored_secret).await?; - aes.encrypt_message(msg, nonce, aad) - } - - async fn aead_aes_gcm_decrypt( - &self, - key_id: &KeyId, - msg: &[u8], - nonce: &[u8], - aad: &[u8], - ) -> Result> { - let stored_secret = self.get_ephemeral_secret(key_id, "aes key").await?; - let aes = Vault::make_aes(&stored_secret).await?; - aes.decrypt_message(msg, nonce, aad) - } -} - -impl Vault { - /// Depending on the secret type make the right type of encrypting / decrypting algorithm - async fn make_aes(stored_secret: &StoredSecret) -> Result { - let secret_ref = stored_secret.secret().as_ref(); - - match stored_secret.attributes() { - SecretAttributes::Aes256 => { - Ok(AesGen::Aes256(Box::new(Aes256Gcm::new(secret_ref.into())))) - } - SecretAttributes::Aes128 => { - Ok(AesGen::Aes128(Box::new(Aes128Gcm::new(secret_ref.into())))) - } - _ => Err(VaultError::AeadAesGcmEncrypt.into()), - } - } -} - -/// This enum is necessary to be able to dispatch the encrypt or decrypt functions -/// based of the algorithm type. It would be avoided if `make_aes` could return existential types -/// but those types are not allowed in return values in Rust -enum AesGen { - Aes128(Box>), - Aes256(Box>), -} - -impl AesGen { - fn encrypt_message(&self, msg: &[u8], nonce: &[u8], aad: &[u8]) -> Result> { - self.encrypt(nonce.into(), Payload { aad, msg }) - .map_err(|_| VaultError::AeadAesGcmEncrypt.into()) - } - fn decrypt_message(&self, msg: &[u8], nonce: &[u8], aad: &[u8]) -> Result> { - self.decrypt(nonce.into(), Payload { aad, msg }) - .map_err(|_| VaultError::AeadAesGcmDecrypt.into()) - } -} - -impl AeadInPlace for AesGen { - fn encrypt_in_place_detached( - &self, - nonce: &Nonce, - aad: &[u8], - buffer: &mut [u8], - ) -> aes_gcm::aead::Result> { - match self { - AesGen::Aes128(alg) => alg.encrypt_in_place_detached(nonce, aad, buffer), - AesGen::Aes256(alg) => alg.encrypt_in_place_detached(nonce, aad, buffer), - } - } - - fn decrypt_in_place_detached( - &self, - nonce: &Nonce, - aad: &[u8], - buffer: &mut [u8], - tag: &Tag, - ) -> aes_gcm::aead::Result<()> { - match self { - AesGen::Aes128(alg) => alg.decrypt_in_place_detached(nonce, aad, buffer, tag), - AesGen::Aes256(alg) => alg.decrypt_in_place_detached(nonce, aad, buffer, tag), - } - } -} - -impl AeadCore for AesGen { - type NonceSize = U12; - type TagSize = U16; - type CiphertextOverhead = U0; -} - -#[cfg(test)] -mod tests { - use crate as ockam_vault; - use crate::Vault; - - fn new_vault() -> Vault { - Vault::new() - } - - #[ockam_macros::vault_test] - fn test_encrypt_decrypt() {} -} diff --git a/implementations/rust/ockam/ockam_vault/src/vault/vault.rs b/implementations/rust/ockam/ockam_vault/src/vault/vault.rs deleted file mode 100644 index 31077201ee1..00000000000 --- a/implementations/rust/ockam/ockam_vault/src/vault/vault.rs +++ /dev/null @@ -1,295 +0,0 @@ -use crate::{ - AsymmetricVault, Buffer, EphemeralSecretsStore, KeyId, PersistentSecretsStore, PublicKey, - Secret, SecretAttributes, SecretsStore, SecretsStoreReader, SecurityModule, Signature, Signer, - StoredSecret, SymmetricVault, VaultBuilder, VaultSecurityModule, -}; -use ockam_core::compat::boxed::Box; -use ockam_core::compat::sync::Arc; -use ockam_core::compat::vec::Vec; -use ockam_core::{async_trait, Result}; -use ockam_node::KeyValueStorage; - -/// A Vault provides high-level interfaces to manage secrets: -/// -/// - storage -/// - symmetric/asymmetric encryption -/// - signing -/// -/// Its implementation is modular: storage can be replaced, signing can be provided via an -/// external KMS system etc... -/// -/// # Examples -/// ``` -/// use ockam_vault::{PersistentSecretsStore, SecretAttributes, SecretsStoreReader, Signer, Vault}; -/// use ockam_core::Result; -/// -/// async fn example() -> Result<()> { -/// let mut vault: Vault = Vault::default(); -/// -/// let mut attributes = SecretAttributes::X25519; -/// -/// let secret = vault.create_persistent_secret(attributes).await?; -/// let public = vault.get_public_key(&secret).await?; -/// -/// let data = "Very important stuff".as_bytes(); -/// -/// let signature = vault.sign(&secret, data).await?; -/// assert!(vault.verify(&public, data, &signature).await?); -/// -/// Ok(()) -/// } -/// ``` -#[derive(Clone)] -pub struct Vault { - /// implementation of a secret store - pub(crate) secrets_store: Arc, - /// implementation of asymmetric encryption functionalities - pub(crate) asymmetric_vault: Arc, - /// implementation of symmetric encryption functionalities - pub(crate) symmetric_vault: Arc, - /// implementation of signing encryption functionalities - pub(crate) signer: Arc, -} - -impl Default for Vault { - fn default() -> Self { - Vault::builder().make() - } -} - -/// Storage for Vault persistent values -pub type VaultStorage = Arc>; - -impl Vault { - /// Create a new VaultBuilder to build a Vault from different implementations - pub fn builder() -> VaultBuilder { - VaultBuilder::new_builder() - } - - /// Create a new default vault implementation - pub fn new() -> Self { - Vault::builder().make() - } - - /// Create a new vault with an in memory storage, return as an Arc - /// This is used in examples only where we don't need to really persist secrets - pub fn create() -> Arc { - Vault::builder().build() - } - - /// Create a new vault with a persistent storage - #[cfg(feature = "storage")] - pub async fn create_with_persistent_storage_path(path: &std::path::Path) -> Result> { - Ok(Vault::builder() - .with_persistent_storage_path(path) - .await? - .build()) - } - - /// Create a new vault with a specific storage - pub fn create_with_persistent_storage(storage: VaultStorage) -> Arc { - Vault::builder().with_persistent_storage(storage).build() - } - - /// Create a new vault with a specific security module backend - pub fn create_with_security_module(security_module: Arc) -> Arc { - Vault::builder() - .with_security_module(security_module) - .build() - } - - /// The sha256 is a constant function which must always refer to the same implementation - /// wherever it is used - pub fn sha256(data: &[u8]) -> [u8; 32] { - VaultSecurityModule::sha256(data) - } - - /// This function is compute_sha256 used in the ockam_vault_ffi crate - /// where we always call functions on a Vault instance - pub fn compute_sha256(&self, data: &[u8]) -> [u8; 32] { - VaultSecurityModule::sha256(data) - } -} - -#[async_trait] -impl EphemeralSecretsStore for Vault { - async fn create_ephemeral_secret(&self, attributes: SecretAttributes) -> Result { - self.secrets_store.create_ephemeral_secret(attributes).await - } - - async fn import_ephemeral_secret( - &self, - secret: Secret, - attributes: SecretAttributes, - ) -> Result { - self.secrets_store - .import_ephemeral_secret(secret, attributes) - .await - } - - async fn get_ephemeral_secret( - &self, - key_id: &KeyId, - description: &str, - ) -> Result { - self.secrets_store - .get_ephemeral_secret(key_id, description) - .await - } - - async fn delete_ephemeral_secret(&self, key_id: KeyId) -> Result { - self.secrets_store.delete_ephemeral_secret(key_id).await - } - - async fn list_ephemeral_secrets(&self) -> Result> { - self.secrets_store.list_ephemeral_secrets().await - } -} - -#[async_trait] -impl PersistentSecretsStore for Vault { - async fn create_persistent_secret(&self, attributes: SecretAttributes) -> Result { - self.secrets_store - .create_persistent_secret(attributes) - .await - } - - async fn delete_persistent_secret(&self, key_id: KeyId) -> Result { - self.secrets_store.delete_persistent_secret(key_id).await - } -} - -#[async_trait] -impl SecretsStoreReader for Vault { - async fn get_secret_attributes(&self, key_id: &KeyId) -> Result { - self.secrets_store.get_secret_attributes(key_id).await - } - - async fn get_public_key(&self, key_id: &KeyId) -> Result { - self.secrets_store.get_public_key(key_id).await - } - - async fn get_key_id(&self, public_key: &PublicKey) -> Result { - self.secrets_store.get_key_id(public_key).await - } -} - -#[async_trait] -impl AsymmetricVault for Vault { - async fn ec_diffie_hellman( - &self, - secret: &KeyId, - peer_public_key: &PublicKey, - ) -> Result { - self.asymmetric_vault - .ec_diffie_hellman(secret, peer_public_key) - .await - } - - async fn hkdf_sha256( - &self, - salt: &KeyId, - info: &[u8], - ikm: Option<&KeyId>, - output_attributes: Vec, - ) -> Result> { - self.asymmetric_vault - .hkdf_sha256(salt, info, ikm, output_attributes) - .await - } -} - -#[async_trait] -impl SymmetricVault for Vault { - async fn aead_aes_gcm_encrypt( - &self, - key_id: &KeyId, - plaintext: &[u8], - nonce: &[u8], - aad: &[u8], - ) -> Result> { - self.symmetric_vault - .aead_aes_gcm_encrypt(key_id, plaintext, nonce, aad) - .await - } - - async fn aead_aes_gcm_decrypt( - &self, - key_id: &KeyId, - cipher_text: &[u8], - nonce: &[u8], - aad: &[u8], - ) -> Result> { - self.symmetric_vault - .aead_aes_gcm_decrypt(key_id, cipher_text, nonce, aad) - .await - } -} - -#[async_trait] -impl Signer for Vault { - async fn sign(&self, key_id: &KeyId, data: &[u8]) -> Result { - self.signer.sign(key_id, data).await - } - - async fn verify( - &self, - public_key: &PublicKey, - data: &[u8], - signature: &Signature, - ) -> Result { - self.signer.verify(public_key, data, signature).await - } -} - -/// This marker traits is used by implementations of Asymmetric and Symmetric traits. -/// Having this trait avoids conflicting instances of Vault which has an `AsymmetricVault` instance -/// delegating to its `asymmetric` member. -/// There is also a default `AsymmetricVault` instance for any `SecretsStore + Implementation`. -/// If it was just `SecretsStore` then `Vault` would also qualify for that instance because it has -/// a `SecretsStore` instance via its `secrets_store` member -pub trait Implementation {} - -#[cfg(test)] -mod tests { - use super::*; - use crate::storage::tests::create_temp_file; - use crate::storage::PersistentStorage; - use crate::SecretAttributes; - use ockam_core::compat::join; - - #[tokio::test] - async fn test_vault_restart() { - let storage = PersistentStorage::create(create_temp_file().as_path()) - .await - .unwrap(); - let vault = Vault::create_with_persistent_storage(storage.clone()); - - // create 3 secrets, 2 persistent, one ephemeral - let attributes1 = SecretAttributes::Ed25519; - let attributes2 = SecretAttributes::Ed25519; - let attributes3 = SecretAttributes::Ed25519; - - let (key_id1, key_id2, key_id3) = join!( - vault.create_persistent_secret(attributes1), - vault.create_persistent_secret(attributes2), - vault.create_ephemeral_secret(attributes3) - ); - - let key_id1 = key_id1.unwrap(); - let key_id2 = key_id2.unwrap(); - let key_id3 = key_id3.unwrap(); - - let vault = Vault::create_with_persistent_storage(storage); - let (attributes12, attributes22, attributes32) = join!( - vault.get_secret_attributes(&key_id1), - vault.get_secret_attributes(&key_id2), - vault.get_secret_attributes(&key_id3) - ); - - // only the 2 persistent secrets can be retrieved after a restart - assert_eq!(attributes1, attributes12.unwrap()); - assert_eq!(attributes2, attributes22.unwrap()); - assert!(attributes32.is_err()); - } -} diff --git a/implementations/rust/ockam/ockam_vault/src/vault/vault_builder.rs b/implementations/rust/ockam/ockam_vault/src/vault/vault_builder.rs deleted file mode 100644 index 5ace93a82ef..00000000000 --- a/implementations/rust/ockam/ockam_vault/src/vault/vault_builder.rs +++ /dev/null @@ -1,121 +0,0 @@ -#[cfg(feature = "storage")] -use crate::storage::PersistentStorage; -use crate::vault::secrets_store_impl::VaultSecretsStore; -use crate::{ - AsymmetricVault, Implementation, SecretsStore, SecurityModule, Signer, SymmetricVault, Vault, - VaultSecurityModule, VaultStorage, -}; -use ockam_core::compat::sync::Arc; -#[cfg(feature = "storage")] -use ockam_core::Result; -use ockam_node::InMemoryKeyValueStorage; - -/// Builder for Vaults -/// The `VaultBuilder` allows the setting of different implementations for the external interfaces of a Vault: -/// `SecretsStore`, `AsymmetricVault`, `SymmetricVault`, `Signer`. -/// -/// It is important to note that the `AsymmetricVault`, `SymmetricVault` and `Signer` interfaces -/// depend on a shared `SecretsStore` implementation for ephemeral and persistent secrets. -/// So when setting specific implementations for these traits it is important that the implementations -/// share consistent storages. -pub struct VaultBuilder { - secrets_store: Arc, - asymmetric_vault: Arc, - symmetric_vault: Arc, - signer: Arc, -} - -impl VaultBuilder { - pub(crate) fn new_builder() -> VaultBuilder { - let security_module = - VaultSecurityModule::create_with_storage(InMemoryKeyValueStorage::create()); - let secrets_store = Arc::new(VaultSecretsStore::new( - security_module.clone(), - InMemoryKeyValueStorage::create(), - )); - let asymmetric_vault = secrets_store.clone(); - let symmetric_vault = secrets_store.clone(); - let signer = secrets_store.clone(); - Self { - secrets_store, - asymmetric_vault, - symmetric_vault, - signer, - } - } - - /// Set a persistent storage as a file storage with a specific path - /// Note: this overrides all previously set implementations - #[cfg(feature = "storage")] - pub async fn with_persistent_storage_path( - &mut self, - path: &std::path::Path, - ) -> Result<&mut Self> { - Ok(self.with_persistent_storage(PersistentStorage::create(path).await?)) - } - - /// Set a persistent storage - /// Note: this overrides all previously set implementations - pub fn with_persistent_storage(&mut self, persistent_storage: VaultStorage) -> &mut Self { - self.with_security_module(VaultSecurityModule::create_with_storage(persistent_storage)) - } - - /// Set a KMS implementation - /// Note: this overrides all previously set implementations - pub fn with_security_module(&mut self, security_module: Arc) -> &mut Self { - self.with_secrets_store(VaultSecretsStore::new( - security_module.clone(), - InMemoryKeyValueStorage::create(), - )) - } - - /// Set a SecretsStore implementation - /// Note: this overrides all previously set implementations - pub fn with_secrets_store( - &mut self, - secrets_store: impl SecretsStore + Clone + Implementation + SecurityModule + 'static, - ) -> &mut Self { - self.secrets_store = Arc::new(secrets_store.clone()); - // changing the secrets store resets all other implementations to default ones - self.asymmetric_vault = Arc::new(secrets_store.clone()); - self.symmetric_vault = Arc::new(secrets_store.clone()); - self.signer = Arc::new(secrets_store); - self - } - - /// Set an AsymmetricVault implementation - pub fn with_asymmetric_vault( - &mut self, - asymmetric_vault: Arc, - ) -> &mut Self { - self.asymmetric_vault = asymmetric_vault; - self - } - - /// Set a SymmetricVault implementation - pub fn with_symmetric_vault(&mut self, symmetric_vault: Arc) -> &mut Self { - self.symmetric_vault = symmetric_vault; - self - } - - /// Set an Signer implementation - pub fn with_signer(&mut self, signer: Arc) -> &mut Self { - self.signer = signer; - self - } - - /// Create a new Vault - pub fn make(&self) -> Vault { - Vault { - secrets_store: self.secrets_store.clone(), - asymmetric_vault: self.asymmetric_vault.clone(), - symmetric_vault: self.symmetric_vault.clone(), - signer: self.signer.clone(), - } - } - - /// Create a new Vault in an Arc reference - pub fn build(&self) -> Arc { - Arc::new(self.make()) - } -} diff --git a/implementations/rust/ockam/ockam_vault/src/vault/vault_kms/mod.rs b/implementations/rust/ockam/ockam_vault/src/vault/vault_kms/mod.rs deleted file mode 100644 index 4ad154b3393..00000000000 --- a/implementations/rust/ockam/ockam_vault/src/vault/vault_kms/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -#[allow(clippy::module_inception)] -mod vault_kms; - -pub use vault_kms::*; diff --git a/implementations/rust/ockam/ockam_vault/src/vault/vault_kms/vault_kms.rs b/implementations/rust/ockam/ockam_vault/src/vault/vault_kms/vault_kms.rs deleted file mode 100644 index 5ef2525d657..00000000000 --- a/implementations/rust/ockam/ockam_vault/src/vault/vault_kms/vault_kms.rs +++ /dev/null @@ -1,338 +0,0 @@ -use crate::constants::CURVE25519_PUBLIC_LENGTH_USIZE; -use crate::constants::CURVE25519_SECRET_LENGTH_U32; - -use crate::{ - KeyId, PublicKey, Secret, SecretAttributes, SecretType, SecurityModule, Signature, - StoredSecret, VaultError, -}; -use arrayref::array_ref; -use ockam_core::compat::rand::{thread_rng, RngCore}; -use ockam_core::compat::sync::Arc; -use ockam_core::errcode::{Kind, Origin}; -use ockam_core::Error; -use ockam_core::{async_trait, compat::boxed::Box, Result}; -use ockam_node::{InMemoryKeyValueStorage, KeyValueStorage}; -use sha2::{Digest, Sha256}; - -/// Ockam implementation of a security module -/// An alternative implementation can be found in the ockam_vault_aws crate -pub struct VaultSecurityModule { - storage: Arc>, -} - -impl VaultSecurityModule { - /// Create a new security module - pub fn create() -> Arc { - Self::create_with_storage(InMemoryKeyValueStorage::create()) - } - - /// Create a new Kms backed by a specific key value storage - pub fn create_with_storage( - storage: Arc>, - ) -> Arc { - Arc::new(VaultSecurityModule { storage }) - } -} - -#[async_trait] -impl SecurityModule for VaultSecurityModule { - /// Generate fresh secret - async fn create_secret(&self, attributes: SecretAttributes) -> Result { - let secret = Self::create_secret_from_attributes(attributes)?; - let stored_secret = StoredSecret::create(secret.clone(), attributes)?; - let key_id = Self::compute_key_id(&secret, &attributes).await?; - self.storage.put(key_id.clone(), stored_secret).await?; - Ok(key_id) - } - - /// Extract public key from secret. Only Curve25519 type is supported - async fn get_public_key(&self, key_id: &KeyId) -> Result { - let stored_secret = self.get_secret(key_id, "secret public key").await?; - Self::compute_public_key_from_secret(stored_secret) - } - - /// Get the key id for a given public key - async fn get_key_id(&self, public_key: &PublicKey) -> Result { - let key_id = Self::sha256(public_key.data()); - Ok(hex::encode(key_id)) - } - - /// Get the secret attributes for a given key id - async fn get_attributes(&self, key_id: &KeyId) -> Result { - let stored_secret = self.get_secret(key_id, "secret public key").await?; - Ok(stored_secret.attributes()) - } - - /// Remove secret from memory and persistent storage if it is a persistent secret - async fn delete_secret(&self, key_id: KeyId) -> Result { - self.storage.delete(&key_id).await.map(|r| r.is_some()) - } - - async fn verify( - &self, - public_key: &PublicKey, - data: &[u8], - signature: &Signature, - ) -> Result { - match public_key.stype() { - SecretType::Ed25519 => { - use ed25519_dalek::{ed25519::Signature, Verifier, VerifyingKey}; - if public_key.data().len() != CURVE25519_PUBLIC_LENGTH_USIZE - || signature.as_ref().len() != Signature::BYTE_SIZE - { - return Err(VaultError::InvalidPublicKey.into()); - } - let signature_bytes = array_ref![signature.as_ref(), 0, Signature::BYTE_SIZE]; - let signature = Signature::from_bytes(signature_bytes); - let public_key_bytes = - array_ref![public_key.data(), 0, CURVE25519_PUBLIC_LENGTH_USIZE]; - let public_key = VerifyingKey::from_bytes(public_key_bytes) - .map_err(|_| VaultError::InvalidPublicKey)?; - Ok(public_key.verify(data.as_ref(), &signature).is_ok()) - } - SecretType::NistP256 => { - use p256::ecdsa::{signature::Verifier as _, Signature, VerifyingKey}; - use p256::pkcs8::DecodePublicKey; - let k = VerifyingKey::from_public_key_der(public_key.data()) - .map_err(Self::from_pkcs8)?; - let s = Signature::from_der(signature.as_ref()).map_err(Self::from_ecdsa)?; - Ok(k.verify(data, &s).is_ok()) - } - SecretType::Buffer | SecretType::Aes | SecretType::X25519 => { - Err(VaultError::InvalidPublicKey.into()) - } - } - } - - async fn sign(&self, key_id: &KeyId, message: &[u8]) -> Result { - let stored_secret = self - .get_secret(key_id, "security module signing key") - .await?; - Self::sign_with_secret(stored_secret, message) - } -} - -impl VaultSecurityModule { - pub(crate) fn create_secret_from_attributes(attributes: SecretAttributes) -> Result { - let secret = match attributes.secret_type() { - SecretType::X25519 | SecretType::Ed25519 | SecretType::Buffer | SecretType::Aes => { - let bytes = { - let mut rng = thread_rng(); - let mut key = vec![0u8; attributes.length() as usize]; - rng.fill_bytes(key.as_mut_slice()); - key - }; - Secret::new(bytes) - } - SecretType::NistP256 => { - use p256::ecdsa::SigningKey; - use p256::pkcs8::EncodePrivateKey; - let sec = SigningKey::random(&mut thread_rng()); - let sec = - p256::SecretKey::from_bytes(&sec.to_bytes()).map_err(Self::from_ecurve)?; - let doc = sec.to_pkcs8_der().map_err(Self::from_pkcs8)?; - Secret::new(doc.as_bytes().to_vec()) - } - }; - Ok(secret) - } - - pub(crate) fn compute_public_key_from_secret( - stored_secret: StoredSecret, - ) -> Result { - let attributes = stored_secret.attributes(); - match attributes.secret_type() { - SecretType::X25519 => { - if stored_secret.secret().length() != CURVE25519_SECRET_LENGTH_U32 as usize { - return Err(VaultError::InvalidSecretLength( - SecretType::X25519, - stored_secret.secret().length(), - CURVE25519_SECRET_LENGTH_U32, - ) - .into()); - }; - let secret = *array_ref![ - stored_secret.secret().as_ref(), - 0, - CURVE25519_SECRET_LENGTH_U32 as usize - ]; - let sk = x25519_dalek::StaticSecret::from(secret); - let pk = x25519_dalek::PublicKey::from(&sk); - Ok(PublicKey::new(pk.to_bytes().to_vec(), SecretType::X25519)) - } - SecretType::Ed25519 => { - use ed25519_dalek::SECRET_KEY_LENGTH; - if stored_secret.secret().length() != SECRET_KEY_LENGTH { - return Err(VaultError::InvalidSecretLength( - SecretType::Ed25519, - stored_secret.secret().length(), - SECRET_KEY_LENGTH as u32, - ) - .into()); - }; - let secret = array_ref![stored_secret.secret().as_ref(), 0, SECRET_KEY_LENGTH]; - let sk = ed25519_dalek::SigningKey::from_bytes(secret); - let pk = sk.verifying_key(); - Ok(PublicKey::new(pk.to_bytes().to_vec(), SecretType::Ed25519)) - } - SecretType::NistP256 => Self::public_key(stored_secret.secret().as_ref()), - SecretType::Buffer | SecretType::Aes => Err(VaultError::InvalidKeyType.into()), - } - } - - pub(crate) fn sign_with_secret( - stored_secret: StoredSecret, - data: &[u8], - ) -> Result { - let attributes = stored_secret.attributes(); - match attributes.secret_type() { - SecretType::Ed25519 => { - use ed25519_dalek::{Signer, SigningKey, SECRET_KEY_LENGTH}; - if stored_secret.secret().length() != SECRET_KEY_LENGTH { - return Err(VaultError::InvalidSecretLength( - SecretType::Ed25519, - stored_secret.secret().length(), - SECRET_KEY_LENGTH as u32, - ) - .into()); - } - let secret = array_ref![stored_secret.secret().as_ref(), 0, SECRET_KEY_LENGTH]; - let sk = SigningKey::from_bytes(secret); - let sig = sk.sign(data.as_ref()); - Ok(Signature::new(sig.to_bytes().to_vec())) - } - SecretType::NistP256 => { - use p256::ecdsa::signature::Signer; - use p256::pkcs8::DecodePrivateKey; - let key = stored_secret.secret().as_ref(); - let sec = p256::ecdsa::SigningKey::from_pkcs8_der(key).map_err(Self::from_pkcs8)?; - let sig: p256::ecdsa::Signature = sec.sign(data); - Ok(Signature::new(sig.to_der().as_bytes().to_vec())) - } - SecretType::Buffer | SecretType::Aes | SecretType::X25519 => { - Err(VaultError::InvalidKeyType.into()) - } - } - } - - /// Compute key id from secret and attributes - pub(crate) async fn compute_key_id( - secret: &Secret, - attributes: &SecretAttributes, - ) -> Result { - Ok(match attributes.secret_type() { - SecretType::X25519 => { - if secret.length() != CURVE25519_SECRET_LENGTH_U32 as usize { - return Err(VaultError::InvalidSecretLength( - SecretType::X25519, - secret.length(), - CURVE25519_SECRET_LENGTH_U32, - ) - .into()); - }; - let secret = *array_ref![secret.as_ref(), 0, CURVE25519_SECRET_LENGTH_U32 as usize]; - let sk = x25519_dalek::StaticSecret::from(secret); - let public = x25519_dalek::PublicKey::from(&sk); - Self::compute_key_id_for_public_key(&PublicKey::new( - public.as_bytes().to_vec(), - SecretType::X25519, - )) - .await? - } - SecretType::Ed25519 => { - use ed25519_dalek::{SigningKey, SECRET_KEY_LENGTH}; - if secret.length() != SECRET_KEY_LENGTH { - return Err(VaultError::InvalidSecretLength( - SecretType::Ed25519, - secret.length(), - SECRET_KEY_LENGTH as u32, - ) - .into()); - } - let secret = array_ref![secret.as_ref(), 0, SECRET_KEY_LENGTH]; - let sk = SigningKey::from_bytes(secret); - let pk = sk.verifying_key(); - Self::compute_key_id_for_public_key(&PublicKey::new( - pk.as_bytes().to_vec(), - SecretType::Ed25519, - )) - .await? - } - SecretType::Buffer | SecretType::Aes => { - // NOTE: Buffer and Aes secrets in the system are ephemeral and it should be fine, - // that every time we import the same secret - it gets different KeyId value. - // However, if we decide to have persistent Buffer or Aes secrets, that should be - // change (probably to hash value of the secret) - let mut rng = thread_rng(); - let mut rand = [0u8; 8]; - rng.fill_bytes(&mut rand); - hex::encode(rand) - } - SecretType::NistP256 => { - let pk = Self::public_key(secret.as_ref())?; - Self::compute_key_id_for_public_key(&pk).await? - } - }) - } - - pub(crate) async fn compute_key_id_for_public_key(public_key: &PublicKey) -> Result { - let key_id = Self::sha256(public_key.data()); - Ok(hex::encode(key_id)) - } - - fn public_key(secret: &[u8]) -> Result { - use p256::pkcs8::{DecodePrivateKey, EncodePublicKey}; - let sec = p256::ecdsa::SigningKey::from_pkcs8_der(secret).map_err(Self::from_pkcs8)?; - let pky = sec - .verifying_key() - .to_public_key_der() - .map_err(Self::from_pkcs8)?; - Ok(PublicKey::new(pky.as_ref().to_vec(), SecretType::NistP256)) - } - - /// The sha256 is a constant function which must always refer to the same implementation - /// wherever it is used - pub fn sha256(data: &[u8]) -> [u8; 32] { - let digest = Sha256::digest(data); - *array_ref![digest, 0, 32] - } - - pub(crate) fn from_ecdsa(e: p256::ecdsa::Error) -> Error { - Error::new(Origin::Vault, Kind::Unknown, e) - } - - pub(crate) fn from_pkcs8(e: T) -> Error { - #[cfg(feature = "no_std")] - use ockam_core::compat::string::ToString; - - Error::new(Origin::Vault, Kind::Unknown, e.to_string()) - } - - pub(crate) fn from_ecurve(e: p256::elliptic_curve::Error) -> Error { - Error::new(Origin::Vault, Kind::Unknown, e) - } -} - -impl VaultSecurityModule { - /// The key is expected to be found, otherwise an error is returned - async fn get_secret(&self, secret: &KeyId, description: &str) -> Result { - self.storage.get(secret).await?.ok_or_else(|| { - VaultError::EntryNotFound(format!("missing {description} for {secret:?}")).into() - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use hex::encode; - - #[test] - fn test_sha256() { - let digest = VaultSecurityModule::sha256(b"a"); - assert_eq!( - encode(digest), - "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb" - ); - } -} diff --git a/implementations/rust/ockam/ockam_vault_aws/src/vault/aws_kms_client.rs b/implementations/rust/ockam/ockam_vault_aws/src/aws_kms_client.rs similarity index 73% rename from implementations/rust/ockam/ockam_vault_aws/src/vault/aws_kms_client.rs rename to implementations/rust/ockam/ockam_vault_aws/src/aws_kms_client.rs index c458ceef488..f8ee4a66871 100644 --- a/implementations/rust/ockam/ockam_vault_aws/src/vault/aws_kms_client.rs +++ b/implementations/rust/ockam/ockam_vault_aws/src/aws_kms_client.rs @@ -1,14 +1,13 @@ +use crate::error::Error; use aws_config::SdkConfig; use aws_sdk_kms::error::SdkError; use aws_sdk_kms::operation::schedule_key_deletion::ScheduleKeyDeletionError; use aws_sdk_kms::primitives::Blob; use aws_sdk_kms::types::{KeySpec, KeyUsageType, MessageType, SigningAlgorithmSpec}; use aws_sdk_kms::Client; -use ockam_core::errcode::{Kind, Origin}; use ockam_core::{async_trait, Result}; use ockam_vault::{KeyId, PublicKey, SecretType, Signature}; use sha2::{Digest, Sha256}; -use thiserror::Error; use tracing as log; /// AWS KMS client. @@ -53,11 +52,6 @@ impl AwsKmsClient { Ok(Self { client, config }) } - /// Create an AWS KMS client using the default configuration. - pub async fn default() -> Result { - Self::new(AwsKmsConfig::default().await?).await - } - /// Create a new NIST P-256 key-pair in AWS KMS and return its ID. pub async fn create_key(&self) -> Result { log::trace!("create new key"); @@ -142,40 +136,18 @@ impl AwsKmsClient { } if let Some(k) = output.public_key() { log::debug!(%key_id, "received public key"); - return Ok(PublicKey::new(k.as_ref().to_vec(), SecretType::NistP256)); + use p256::pkcs8::DecodePublicKey; + let k = p256::ecdsa::VerifyingKey::from_public_key_der(k.as_ref()) + .map_err(|_| Error::InvalidPublicKeyDer)?; + return Ok(PublicKey::new( + k.to_sec1_bytes().to_vec(), + SecretType::NistP256, + )); } log::error!(%key_id, "key type not supported to get a public key"); Err(Error::UnsupportedKeyType.into()) } - /// Have AWS KMS verify a message signature. - pub async fn verify( - &self, - key_id: &KeyId, - message: &[u8], - signature: &Signature, - ) -> Result { - log::trace!(%key_id, "verify message signature"); - let client = self - .client - .verify() - .key_id(key_id) - .signature(Blob::new(signature.as_ref())) - .signing_algorithm(SigningAlgorithmSpec::EcdsaSha256) - .message(digest(message)) - .message_type(MessageType::Digest); - let output = client.send().await.map_err(|err| { - log::error!(%key_id, %err, "failed to verify message signature"); - Error::Verify { - keyid: key_id.to_string(), - error: err.to_string(), - } - })?; - let is_valid = output.signature_valid(); - log::debug!(%key_id, %is_valid, "verified message signature"); - Ok(is_valid) - } - /// Have AWS KMS sign a message. pub async fn sign(&self, key_id: &KeyId, message: &[u8]) -> Result { log::trace!(%key_id, "sign message"); @@ -195,7 +167,9 @@ impl AwsKmsClient { })?; if let Some(sig) = output.signature() { log::debug!(%key_id, "signed message"); - return Ok(Signature::new(sig.as_ref().to_vec())); + let sig = p256::ecdsa::Signature::from_der(sig.as_ref()) + .map_err(|_| Error::InvalidSignatureDer)?; + return Ok(Signature::new(sig.to_vec())); } log::error!(%key_id, "no signature received from aws"); Err(Error::MissingSignature.into()) @@ -204,17 +178,20 @@ impl AwsKmsClient { /// This trait is introduced to help with the testing of the AwsSecurityModule #[async_trait] -pub(crate) trait KmsClient { +pub trait KmsClient { + /// Create a key async fn create_key(&self) -> Result; + /// Delete a key async fn delete_key(&self, key_id: &KeyId) -> Result; + /// Get PublicKey async fn public_key(&self, key_id: &KeyId) -> Result; + /// List All Keys async fn list_keys(&self) -> Result>; - async fn verify(&self, key_id: &KeyId, message: &[u8], signature: &Signature) -> Result; - + /// Sign a message async fn sign(&self, key_id: &KeyId, message: &[u8]) -> Result; } @@ -234,6 +211,10 @@ impl KmsClient for AwsKmsClient { Error::MissingKeys })?; + if output.truncated() { + return Err(Error::TruncatedKeysList.into()); + } + if let Some(keys) = output.keys() { let mut result = vec![]; for key in keys { @@ -251,10 +232,6 @@ impl KmsClient for AwsKmsClient { self.public_key(key_id).await } - async fn verify(&self, key_id: &KeyId, message: &[u8], signature: &Signature) -> Result { - self.verify(key_id, message, signature).await - } - async fn sign(&self, key_id: &KeyId, message: &[u8]) -> Result { self.sign(key_id, message).await } @@ -263,31 +240,3 @@ impl KmsClient for AwsKmsClient { fn digest(data: &[u8]) -> Blob { Blob::new(Sha256::digest(data).to_vec()) } - -#[derive(Error, Debug)] -pub(crate) enum Error { - #[error("aws sdk error creating new key")] - Create(String), - #[error("aws sdk error signing message with key {keyid}")] - Sign { keyid: String, error: String }, - #[error("aws sdk error verifying message with key {keyid}")] - Verify { keyid: String, error: String }, - #[error("aws sdk error exporting public key {keyid}")] - Export { keyid: String, error: String }, - #[error("aws sdk error exporting public key {keyid}")] - Delete { keyid: String, error: String }, - #[error("aws did not return a key id")] - MissingKeyId, - #[error("aws did not return the list of existing keys")] - MissingKeys, - #[error("aws did not return a signature")] - MissingSignature, - #[error("key type is not supported")] - UnsupportedKeyType, -} - -impl From for ockam_core::Error { - fn from(e: Error) -> Self { - ockam_core::Error::new(Origin::Other, Kind::Io, e) - } -} diff --git a/implementations/rust/ockam/ockam_vault_aws/src/aws_signing_vault.rs b/implementations/rust/ockam/ockam_vault_aws/src/aws_signing_vault.rs new file mode 100644 index 00000000000..c3c494edd91 --- /dev/null +++ b/implementations/rust/ockam/ockam_vault_aws/src/aws_signing_vault.rs @@ -0,0 +1,137 @@ +use crate::aws_kms_client::{AwsKmsClient, AwsKmsConfig, KmsClient}; +use crate::error::Error; +use ockam_core::compat::sync::{Arc, RwLock}; +use ockam_core::{async_trait, Result}; +use ockam_vault::{ + KeyId, PublicKey, SecretAttributes, SecretType, Signature, SigningVault, VaultError, +}; +use tracing::error; + +struct AwsKeyPair { + key_id: KeyId, + public_key: PublicKey, +} + +/// Security module implementation using an AWS KMS +pub struct AwsSigningVault { + client: Arc, + // Store mapping from PublicKey to KeyId in memory + // This is fetched at the Vault initialization + // and is updated locally during add/delete operations + // WARNING: The assumption is that there is no concurrent access to the same keys from + // different places. + keys: Arc>>, +} + +impl AwsSigningVault { + /// Create a default AWS security module + pub async fn create() -> Result { + Self::create_with_config(AwsKmsConfig::default().await?).await + } + + /// Create a new AWS security module + async fn create_with_config(config: AwsKmsConfig) -> Result { + let client = AwsKmsClient::new(config).await?; + + let mut keys: Vec = vec![]; + + // Fetch list of all keys, then fetch the public key for each key + // There shouldn't be more than 2-3 active keys in the KMS, + // however, technically we have a software limit of 100 keys here + // If there are more keys - `list_keys` will return an Error + // TODO: Make sure every Vault in AWS account gets its isolated scope + let key_ids = client.list_keys().await?; + + for key_id in key_ids { + match client.public_key(&key_id).await { + Ok(public_key) => keys.push(AwsKeyPair { key_id, public_key }), + // There are different possible causes here, but it's also possible that + // the Key may in deletion pending state, or have a different key type. + // Therefore, the best strategy is to just skip that key + Err(err) => error!("Error exporting public key: {err}"), + } + } + + Ok(Self { + client: Arc::new(client), + keys: Arc::new(RwLock::new(keys)), + }) + } + + /// Return list of all keys + pub fn keys(&self) -> Vec { + self.keys + .read() + .unwrap() + .iter() + .map(|x| x.key_id.clone()) + .collect() + } +} + +#[async_trait] +impl SigningVault for AwsSigningVault { + async fn get_public_key(&self, key_id: &KeyId) -> Result { + self.keys + .read() + .unwrap() + .iter() + .find_map(|x| { + if &x.key_id == key_id { + Some(x.public_key.clone()) + } else { + None + } + }) + .ok_or(Error::KeyNotFound.into()) + } + + async fn get_key_id(&self, public_key: &PublicKey) -> Result { + self.keys + .read() + .unwrap() + .iter() + .find_map(|x| { + if &x.public_key == public_key { + Some(x.key_id.clone()) + } else { + None + } + }) + .ok_or(Error::KeyNotFound.into()) + } + + async fn sign(&self, key_id: &KeyId, message: &[u8]) -> Result { + self.client.sign(key_id, message).await + } + + async fn generate_key(&self, attributes: SecretAttributes) -> Result { + if attributes.secret_type() != SecretType::NistP256 { + return Err(VaultError::InvalidKeyType.into()); + } + + let key_id = self.client.create_key().await?; + let public_key = self.client.public_key(&key_id).await?; + + self.keys.write().unwrap().push(AwsKeyPair { + key_id: key_id.clone(), + public_key, + }); + + Ok(key_id) + } + + async fn delete_key(&self, key_id: KeyId) -> Result { + if self.client.delete_key(&key_id).await? { + self.keys.write().unwrap().retain(|x| x.key_id != key_id); + + Ok(true) + } else { + Ok(false) + } + } + + async fn number_of_keys(&self) -> Result { + Ok(self.keys.read().unwrap().len()) + } +} diff --git a/implementations/rust/ockam/ockam_vault_aws/src/error.rs b/implementations/rust/ockam/ockam_vault_aws/src/error.rs new file mode 100644 index 00000000000..c6f6cc2b1cc --- /dev/null +++ b/implementations/rust/ockam/ockam_vault_aws/src/error.rs @@ -0,0 +1,39 @@ +use ockam_core::errcode::{Kind, Origin}; +use thiserror::Error; + +#[allow(missing_docs)] +#[derive(Error, Debug)] +pub enum Error { + #[error("aws sdk error creating new key")] + Create(String), + #[error("aws sdk error signing message with key {keyid}")] + Sign { keyid: String, error: String }, + #[error("aws sdk error verifying message with key {keyid}")] + Verify { keyid: String, error: String }, + #[error("aws sdk error exporting public key {keyid}")] + Export { keyid: String, error: String }, + #[error("aws sdk error exporting public key {keyid}")] + Delete { keyid: String, error: String }, + #[error("aws did not return a key id")] + MissingKeyId, + #[error("aws did not return the list of existing keys")] + MissingKeys, + #[error("aws did not return a signature")] + MissingSignature, + #[error("key type is not supported")] + UnsupportedKeyType, + #[error("public key der is incorrect")] + InvalidPublicKeyDer, + #[error("signature der is incorrect")] + InvalidSignatureDer, + #[error("key list was longer than supported")] + TruncatedKeysList, + #[error("key was not found")] + KeyNotFound, +} + +impl From for ockam_core::Error { + fn from(e: Error) -> Self { + ockam_core::Error::new(Origin::Other, Kind::Io, e) + } +} diff --git a/implementations/rust/ockam/ockam_vault_aws/src/lib.rs b/implementations/rust/ockam/ockam_vault_aws/src/lib.rs index 5001bde0648..84b84a39800 100644 --- a/implementations/rust/ockam/ockam_vault_aws/src/lib.rs +++ b/implementations/rust/ockam/ockam_vault_aws/src/lib.rs @@ -10,13 +10,17 @@ )] #![cfg_attr(not(feature = "std"), no_std)] -mod vault; - -pub use vault::*; - #[cfg(feature = "std")] extern crate core; #[cfg(feature = "alloc")] #[macro_use] extern crate alloc; + +mod aws_kms_client; +mod aws_signing_vault; +mod error; + +pub use aws_kms_client::*; +pub use aws_signing_vault::*; +pub use error::*; diff --git a/implementations/rust/ockam/ockam_vault_aws/src/vault/aws_security_module.rs b/implementations/rust/ockam/ockam_vault_aws/src/vault/aws_security_module.rs deleted file mode 100644 index 60f21fb750a..00000000000 --- a/implementations/rust/ockam/ockam_vault_aws/src/vault/aws_security_module.rs +++ /dev/null @@ -1,311 +0,0 @@ -use std::path::Path; - -use p256::pkcs8::DecodePublicKey; -use tracing::error; - -use crate::vault::aws_kms_client::{AwsKmsClient, AwsKmsConfig, KmsClient}; -use ockam_core::compat::sync::Arc; -use ockam_core::errcode::{Kind, Origin}; -use ockam_core::{async_trait, Error, Result}; -use ockam_node::{FileKeyValueStorage, InMemoryKeyValueStorage, KeyValueStorage}; -use ockam_vault::{ - KeyId, PublicKey, SecretAttributes, SecretType, SecurityModule, Signature, VaultError, -}; - -/// Security module implementation using an AWS KMS -pub struct AwsSecurityModule { - client: Arc, - storage: Arc>, -} - -impl AwsSecurityModule { - /// Create a default AWS security module - pub async fn default() -> Result { - AwsSecurityModule::new( - AwsKmsConfig::default().await?, - InMemoryKeyValueStorage::create(), - ) - .await - } - - /// Create a new AWS security module - pub async fn new( - config: AwsKmsConfig, - storage: Arc>, - ) -> Result { - Ok(AwsSecurityModule { - client: Arc::new(AwsKmsClient::new(config).await?), - storage, - }) - } - /// Create a new AWS security module, with a specific file storage path - pub async fn create_with_storage_path( - config: AwsKmsConfig, - path: &Path, - ) -> Result> { - Self::create_with_key_value_storage( - config, - Arc::new(FileKeyValueStorage::create(path).await?), - ) - .await - } - - /// Create a new AWS security module, with a specific key value storage - pub async fn create_with_key_value_storage( - config: AwsKmsConfig, - storage: Arc>, - ) -> Result> { - Ok(Arc::new(Self::new(config, storage).await?)) - } - - /// Return the key id corresponding to a public key from the KMS - /// This function is particularly inefficient since it lists all the keys - /// This is why there is a cache in the AwsSecurityModule struct to avoid this call - pub(crate) async fn get_key_id_from_public_key(&self, public_key: &PublicKey) -> Result { - for key_id in self.client.list_keys().await? { - let one_public_key = self.client.public_key(&key_id).await?; - if &one_public_key == public_key { - return Ok(key_id); - } - } - error!(%public_key, "key id not found for public key {}", public_key); - Err(Error::new( - Origin::Vault, - Kind::NotFound, - crate::vault::aws_kms_client::Error::MissingKeyId, - )) - } -} - -#[async_trait] -impl SecurityModule for AwsSecurityModule { - async fn create_secret(&self, attributes: SecretAttributes) -> Result { - if attributes.secret_type() == SecretType::NistP256 { - self.client.create_key().await - } else { - Err(VaultError::InvalidKeyType.into()) - } - } - - async fn get_public_key(&self, key_id: &KeyId) -> Result { - let public_key = self.client.public_key(key_id).await?; - - // if the public key <-> key id mapping has not been stored locally - // then store it in order to avoid a call to client.get_key_id when computing a identity - // identifier from the list of identity changes - if self.storage.get(&public_key).await?.is_none() { - self.storage.put(public_key.clone(), key_id.clone()).await?; - } - Ok(public_key) - } - - async fn get_key_id(&self, public_key: &PublicKey) -> Result { - // try to get the key id from local storage first - if let Some(key_id) = self.storage.get(public_key).await? { - Ok(key_id) - } else { - let key_id = self.get_key_id_from_public_key(public_key).await?; - self.storage.put(public_key.clone(), key_id.clone()).await?; - Ok(key_id) - } - } - - async fn get_attributes(&self, _key_id: &KeyId) -> Result { - Ok(SecretAttributes::NistP256) - } - - async fn delete_secret(&self, key_id: KeyId) -> Result { - self.client.delete_key(&key_id).await - } - - async fn sign(&self, key_id: &KeyId, message: &[u8]) -> Result { - self.client.sign(key_id, message).await - } - - /// Verify the signature of a message locally - /// This should return the same result as self.client.verify - /// The main differences are: - /// - a call to self.client.verify takes more time - /// - a call to self.client.verify can be logged on AWS and benefit from additional access control checks - async fn verify( - &self, - public_key: &PublicKey, - message: &[u8], - signature: &Signature, - ) -> Result { - use p256::ecdsa::{signature::Verifier as _, Signature, VerifyingKey}; - - let verifying_key = - VerifyingKey::from_public_key_der(public_key.data()).map_err(Self::from_pkcs8)?; - let ecdsa_signature = Signature::from_der(signature.as_ref()).map_err(Self::from_ecdsa)?; - Ok(verifying_key.verify(message, &ecdsa_signature).is_ok()) - } -} - -impl AwsSecurityModule { - pub(crate) fn from_ecdsa(e: p256::ecdsa::Error) -> Error { - Error::new(Origin::Vault, Kind::Unknown, e) - } - - pub(crate) fn from_pkcs8(e: T) -> Error { - #[cfg(feature = "no_std")] - use ockam_core::compat::string::ToString; - Error::new(Origin::Vault, Kind::Unknown, e.to_string()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::cell::RefCell; - use std::collections::HashMap; - - use ockam_core::compat::rand::{thread_rng, RngCore}; - use ockam_node::InMemoryKeyValueStorage; - use std::path::PathBuf; - use SecretAttributes::*; - - /// This test needs to be executed with the following environment variables - /// AWS_REGION - /// AWS_ACCESS_KEY_ID - /// AWS_SECRET_ACCESS_KEY - #[tokio::test] - #[ignore] - async fn test_store_public_key_key_id_mapping() -> Result<()> { - let storage = InMemoryKeyValueStorage::create(); - let security_module = - AwsSecurityModule::new(AwsKmsConfig::default().await?, storage.clone()).await?; - - let key_id = security_module.create_secret(NistP256).await?; - - // the public key can be retrieved using the kms client directly - // but then the public key <-> key id mapping is not cached - let public_key = security_module.client.public_key(&key_id).await; - assert!(public_key.is_ok()); - assert!(storage.get(&public_key?).await?.is_none()); - - // when the public key is retrieved using the security module - // then the public key <-> key id mapping is cached locally - let public_key = security_module.get_public_key(&key_id).await; - assert!(public_key.is_ok()); - - let public_key = public_key?; - let key_id = storage.get(&public_key).await; - assert!(key_id.is_ok()); - - let key_id = security_module - .get_key_id_from_public_key(&public_key) - .await; - assert!(key_id.is_ok()); - - Ok(()) - } - - #[tokio::test] - #[ignore] - async fn test_sign_verify() -> Result<()> { - let security_module = AwsSecurityModule::default().await?; - let key_id = security_module.create_secret(NistP256).await?; - let message = b"hello world"; - let signature = security_module.sign(&key_id, &message[..]).await?; - let public_key = security_module.get_public_key(&key_id).await?; - - // Verify locally - assert!( - security_module - .verify(&public_key, message, &signature) - .await? - ); - - // Verify remotely - assert!( - security_module - .client - .verify(&key_id, message, &signature) - .await? - ); - - Ok(()) - } - - /// This test checks that the local storage mapping public keys to key ids works - #[tokio::test] - async fn test_storage() -> Result<()> { - let client = Arc::new(FakeKmsClient::default()); - let storage = Arc::new(FileKeyValueStorage::create(create_temp_file().as_path()).await?); - let security_module = AwsSecurityModule { client, storage }; - - let key_id = security_module.create_secret(NistP256).await?; - let public_key = security_module.get_public_key(&key_id).await?; - - // retrieving the key id should use the mapping stored in a file - let actual_key_id = security_module.get_key_id(&public_key).await?; - - assert_eq!(actual_key_id, key_id); - - Ok(()) - } - - // TESTS IMPLEMENTATION - - pub fn create_temp_file() -> PathBuf { - let dir = std::env::temp_dir(); - let mut rng = thread_rng(); - let mut bytes = [0u8; 32]; - rng.fill_bytes(&mut bytes); - let file_name = hex::encode(bytes); - dir.join(file_name) - } - - struct Key(usize); - - #[derive(Default)] - struct FakeKmsClient { - keys: RefCell>, - } - - #[allow(unsafe_code)] - unsafe impl Send for FakeKmsClient {} - - #[allow(unsafe_code)] - unsafe impl Sync for FakeKmsClient {} - - #[async_trait] - impl KmsClient for FakeKmsClient { - async fn create_key(&self) -> Result { - let key = self.keys.borrow().len() + 1; - self.keys.borrow_mut().insert(key.to_string(), Key(key)); - Ok(key.to_string()) - } - - async fn delete_key(&self, _key_id: &KeyId) -> Result { - Ok(true) - } - - async fn public_key(&self, key_id: &KeyId) -> Result { - Ok(PublicKey::new( - key_id.as_bytes().to_vec(), - SecretType::NistP256, - )) - } - - /// The list_keys function returns an error to make sure that we - /// really use the local storage to get the key id corresponding to a given public key - async fn list_keys(&self) -> Result> { - Err(Error::new(Origin::Api, Kind::Other, "can't list keys")) - } - - async fn verify( - &self, - _key_id: &KeyId, - _message: &[u8], - _signature: &Signature, - ) -> Result { - Ok(true) - } - - async fn sign(&self, _key_id: &KeyId, _message: &[u8]) -> Result { - Ok(Signature::new(vec![])) - } - } -} diff --git a/implementations/rust/ockam/ockam_vault_aws/src/vault/mod.rs b/implementations/rust/ockam/ockam_vault_aws/src/vault/mod.rs deleted file mode 100644 index 426842ca238..00000000000 --- a/implementations/rust/ockam/ockam_vault_aws/src/vault/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod aws_kms_client; -mod aws_security_module; - -pub use aws_kms_client::*; -pub use aws_security_module::*; diff --git a/implementations/rust/ockam/ockam_vault_aws/tests/tests.rs b/implementations/rust/ockam/ockam_vault_aws/tests/tests.rs new file mode 100644 index 00000000000..d3a0e97ec2c --- /dev/null +++ b/implementations/rust/ockam/ockam_vault_aws/tests/tests.rs @@ -0,0 +1,54 @@ +use ockam_core::Result; +use ockam_vault::{SecretAttributes, SigningVault, SoftwareVerifyingVault, VerifyingVault}; +use ockam_vault_aws::AwsSigningVault; + +/// These tests need to be executed with the following environment variables +/// AWS_REGION +/// AWS_ACCESS_KEY_ID +/// AWS_SECRET_ACCESS_KEY +/// or credentials in ~/.aws/credentials + +#[tokio::test] +#[ignore] +async fn test_sign_verify() -> Result<()> { + let signing_vault = AwsSigningVault::create().await?; + let key_id = signing_vault + .generate_key(SecretAttributes::NistP256) + .await?; + let message = b"hello world"; + let signature = signing_vault.sign(&key_id, message.as_slice()).await?; + let public_key = signing_vault.get_public_key(&key_id).await?; + + let verifier = SoftwareVerifyingVault::new(); + assert!(verifier.verify(&public_key, message, &signature).await?); + + signing_vault.delete_key(key_id).await?; + + Ok(()) +} + +#[tokio::test] +#[ignore] +async fn test_keys_management() -> Result<()> { + let signing_vault = AwsSigningVault::create().await?; + + let number_of_keys1 = signing_vault.number_of_keys().await?; + + let key_id = signing_vault + .generate_key(SecretAttributes::NistP256) + .await?; + + let number_of_keys2 = signing_vault.number_of_keys().await?; + assert_eq!(number_of_keys1 + 1, number_of_keys2); + + let public_key = signing_vault.get_public_key(&key_id).await?; + + let key_id2 = signing_vault.get_key_id(&public_key).await?; + assert_eq!(key_id, key_id2); + + signing_vault.delete_key(key_id).await?; + let number_of_keys3 = signing_vault.number_of_keys().await?; + assert_eq!(number_of_keys2, number_of_keys3 + 1); + + Ok(()) +} From 1b5103cca00fe6831eea68d2352a506a0947805a Mon Sep 17 00:00:00 2001 From: Pablo Polvorin Date: Sun, 3 Sep 2023 21:50:48 -0300 Subject: [PATCH 2/5] feat(elixir): adapt to new identity design Adapts to new identity desing, get rid of the separate sidecar and vault ffi. Use rustler for rust>elixir bindings on Identity/Credentials. --- .github/dependabot.yml | 11 - .github/workflows/elixir-ignored.yml | 6 +- .github/workflows/elixir.yml | 6 +- Cargo.lock | 39 +- Cargo.toml | 1 - implementations/elixir/Makefile | 10 +- .../credential/authenticator/direct/client.ex | 10 +- .../lib/ockam/credential/verifier/request.ex | 13 - .../lib/ockam/credential/verifier/sidecar.ex | 53 - .../lib/ockam/credential/verifier/stub.ex | 35 - .../stream/bi_directional/secure_channel.ex | 24 +- .../ockam/lib/ockam/identity/api/request.ex | 77 - .../ockam/lib/ockam/identity/api/response.ex | 59 - .../ockam/lib/ockam/identity/identity.ex | 187 +- .../ockam/identity/purpose_key_attestation.ex | 12 + .../ockam/ockam/lib/ockam/identity/sidecar.ex | 150 - .../ockam/ockam/lib/ockam/identity/stub.ex | 66 - .../ockam/lib/ockam/secure_channel/channel.ex | 160 +- .../ockam/lib/ockam/secure_channel/crypto.ex | 41 + .../aead_aes_gcm.ex | 59 +- .../ockam/secure_channel/identity_proof.ex | 50 +- .../key_establishment_protocol/xx/protocol.ex | 120 +- .../elixir/ockam/ockam/lib/ockam/vault.ex | 146 - implementations/elixir/ockam/ockam/mix.exs | 4 +- implementations/elixir/ockam/ockam/mix.lock | 3 + .../test/ockam/identity/identity_api_test.exs | 77 - .../ockam/ockam/test/ockam/identity_tests.exs | 34 + .../aead_aes_gcm_test.exs | 51 +- .../xx/protocol_test.exs | 15 +- .../ockam/test/ockam/secure_channel_test.exs | 244 +- .../elixir/ockam/ockam_abac/mix.lock | 3 + .../elixir/ockam/ockam_cloud_node/mix.lock | 3 + .../ockam_healthcheck/config/runtime.exs | 56 +- .../ockam/ockam_healthcheck/config/test.exs | 2 - .../lib/healthcheck/healthcheck.ex | 40 +- .../elixir/ockam/ockam_healthcheck/mix.lock | 3 + .../test/healthcheck/healthcheck_test.exs | 6 +- .../elixir/ockam/ockam_kafka/mix.lock | 3 + .../elixir/ockam/ockam_metrics/mix.exs | 4 +- .../elixir/ockam/ockam_metrics/mix.lock | 3 + .../ockam_metrics/telemetry_poller_test.exs | 20 +- .../services/api/credential_exchange_api.ex | 116 - .../lib/services/provider/secure_channel.ex | 18 +- .../lib/services/provider/sidecar.ex | 111 - .../elixir/ockam/ockam_services/mix.exs | 1 - .../elixir/ockam/ockam_services/mix.lock | 3 + .../test/authorization_test.exs | 110 +- .../test/services/credential_api_test.exs | 82 - .../services/token_lease_manager_test.exs | 141 +- .../ockam/ockam_vault_software/.credo.exs | 137 - .../ockam/ockam_vault_software/.gitignore | 4 - .../ockam/ockam_vault_software/README.md | 40 - .../build-elixir-universal-lib.sh | 84 - .../lib/vault_software.ex | 186 -- .../elixir/ockam/ockam_vault_software/mix.exs | 311 -- .../ockam/ockam_vault_software/mix.lock | 14 - .../native/CMakeLists.txt | 5 - .../native/vault/CMakeLists.txt | 10 - .../native/vault/software/CMakeLists.txt | 56 - .../native/vault/software/common.c | 61 - .../native/vault/software/common.h | 20 - .../native/vault/software/nifs.c | 22 - .../native/vault/software/vault.c | 614 ---- .../native/vault/software/vault.h | 32 - .../ockam_vault_software/test/test_helper.exs | 5 - .../test/vault_software_test.exs | 244 -- .../.formatter.exs | 3 +- implementations/elixir/ockam/ockly/README.md | 21 + .../elixir/ockam/ockly/config/config.exs | 6 + .../elixir/ockam/ockly/config/dev.exs | 1 + .../elixir/ockam/ockly/config/prod.exs | 1 + .../elixir/ockam/ockly/config/runtime.exs | 9 + .../elixir/ockam/ockly/config/test.exs | 1 + .../elixir/ockam/ockly/lib/ockly.ex | 12 + .../elixir/ockam/ockly/lib/ockly/native.ex | 17 + implementations/elixir/ockam/ockly/mix.exs | 42 + implementations/elixir/ockam/ockly/mix.lock | 9 + .../ockly/native/ockly/.cargo/config.toml | 5 + .../ockam/ockly/native/ockly/.gitignore | 3 + .../ockam/ockly/native/ockly/Cargo.lock | 2581 +++++++++++++++++ .../ockam/ockly/native/ockly/Cargo.toml | 22 + .../elixir/ockam/ockly/native/ockly/README.md | 20 + .../ockam/ockly/native/ockly/src/lib.rs | 360 +++ .../elixir/ockam/ockly/test/ockly_test.exs | 121 + .../elixir/ockam/ockly/test/test_helper.exs | 1 + .../rust/ockam/ockam_api/src/identity.rs | 4 - .../src/identity/identity_service.rs | 231 -- .../ockam/ockam_api/src/identity/models.rs | 110 - .../rust/ockam/ockam_api/src/lib.rs | 11 +- .../ockam/ockam_api/src/nodes/registry.rs | 2 - .../rust/ockam/ockam_api/src/nodes/service.rs | 6 - .../src/nodes/service/node_identities.rs | 54 +- .../src/nodes/service/node_services.rs | 82 +- .../rust/ockam/ockam_api/src/verifier.rs | 126 - .../ockam/ockam_api/src/verifier/types.rs | 85 - .../rust/ockam/ockam_api/tests/identity.rs | 129 - .../ockam/ockam_command/src/node/create.rs | 14 - .../ockam/ockam_command/src/service/config.rs | 28 - .../ockam/ockam_command/src/service/start.rs | 36 - .../rust/ockam/ockam_command/src/util/api.rs | 15 +- .../ockam/ockam_command/tests/bats/nodes.bats | 8 - .../rust/ockam/ockam_ffi/.cargo/config.toml | 9 - .../ockam/ockam_ffi/include/ockam/vault.h | 260 -- .../rust/ockam/ockam_ffi/src/error.rs | 129 - .../rust/ockam/ockam_ffi/src/lib.rs | 22 - .../rust/ockam/ockam_ffi/src/macros.rs | 14 - .../rust/ockam/ockam_ffi/src/vault.rs | 569 ---- .../rust/ockam/ockam_ffi/src/vault_types.rs | 98 - .../rust/ockam/ockam_vault_aws/Cargo.toml | 3 + 109 files changed, 3902 insertions(+), 5711 deletions(-) delete mode 100644 implementations/elixir/ockam/ockam/lib/ockam/credential/verifier/request.ex delete mode 100644 implementations/elixir/ockam/ockam/lib/ockam/credential/verifier/sidecar.ex delete mode 100644 implementations/elixir/ockam/ockam/lib/ockam/credential/verifier/stub.ex delete mode 100644 implementations/elixir/ockam/ockam/lib/ockam/identity/api/request.ex delete mode 100644 implementations/elixir/ockam/ockam/lib/ockam/identity/api/response.ex create mode 100644 implementations/elixir/ockam/ockam/lib/ockam/identity/purpose_key_attestation.ex delete mode 100644 implementations/elixir/ockam/ockam/lib/ockam/identity/sidecar.ex delete mode 100644 implementations/elixir/ockam/ockam/lib/ockam/identity/stub.ex create mode 100644 implementations/elixir/ockam/ockam/lib/ockam/secure_channel/crypto.ex delete mode 100644 implementations/elixir/ockam/ockam/lib/ockam/vault.ex delete mode 100644 implementations/elixir/ockam/ockam/test/ockam/identity/identity_api_test.exs create mode 100644 implementations/elixir/ockam/ockam/test/ockam/identity_tests.exs delete mode 100644 implementations/elixir/ockam/ockam_services/lib/services/api/credential_exchange_api.ex delete mode 100644 implementations/elixir/ockam/ockam_services/lib/services/provider/sidecar.ex delete mode 100644 implementations/elixir/ockam/ockam_services/test/services/credential_api_test.exs delete mode 100644 implementations/elixir/ockam/ockam_vault_software/.credo.exs delete mode 100644 implementations/elixir/ockam/ockam_vault_software/.gitignore delete mode 100644 implementations/elixir/ockam/ockam_vault_software/README.md delete mode 100755 implementations/elixir/ockam/ockam_vault_software/build-elixir-universal-lib.sh delete mode 100644 implementations/elixir/ockam/ockam_vault_software/lib/vault_software.ex delete mode 100644 implementations/elixir/ockam/ockam_vault_software/mix.exs delete mode 100644 implementations/elixir/ockam/ockam_vault_software/mix.lock delete mode 100644 implementations/elixir/ockam/ockam_vault_software/native/CMakeLists.txt delete mode 100644 implementations/elixir/ockam/ockam_vault_software/native/vault/CMakeLists.txt delete mode 100644 implementations/elixir/ockam/ockam_vault_software/native/vault/software/CMakeLists.txt delete mode 100644 implementations/elixir/ockam/ockam_vault_software/native/vault/software/common.c delete mode 100644 implementations/elixir/ockam/ockam_vault_software/native/vault/software/common.h delete mode 100644 implementations/elixir/ockam/ockam_vault_software/native/vault/software/nifs.c delete mode 100644 implementations/elixir/ockam/ockam_vault_software/native/vault/software/vault.c delete mode 100644 implementations/elixir/ockam/ockam_vault_software/native/vault/software/vault.h delete mode 100644 implementations/elixir/ockam/ockam_vault_software/test/test_helper.exs delete mode 100644 implementations/elixir/ockam/ockam_vault_software/test/vault_software_test.exs rename implementations/elixir/ockam/{ockam_vault_software => ockly}/.formatter.exs (61%) create mode 100644 implementations/elixir/ockam/ockly/README.md create mode 100644 implementations/elixir/ockam/ockly/config/config.exs create mode 100644 implementations/elixir/ockam/ockly/config/dev.exs create mode 100644 implementations/elixir/ockam/ockly/config/prod.exs create mode 100644 implementations/elixir/ockam/ockly/config/runtime.exs create mode 100644 implementations/elixir/ockam/ockly/config/test.exs create mode 100644 implementations/elixir/ockam/ockly/lib/ockly.ex create mode 100644 implementations/elixir/ockam/ockly/lib/ockly/native.ex create mode 100644 implementations/elixir/ockam/ockly/mix.exs create mode 100644 implementations/elixir/ockam/ockly/mix.lock create mode 100644 implementations/elixir/ockam/ockly/native/ockly/.cargo/config.toml create mode 100644 implementations/elixir/ockam/ockly/native/ockly/.gitignore create mode 100644 implementations/elixir/ockam/ockly/native/ockly/Cargo.lock create mode 100644 implementations/elixir/ockam/ockly/native/ockly/Cargo.toml create mode 100644 implementations/elixir/ockam/ockly/native/ockly/README.md create mode 100644 implementations/elixir/ockam/ockly/native/ockly/src/lib.rs create mode 100644 implementations/elixir/ockam/ockly/test/ockly_test.exs create mode 100644 implementations/elixir/ockam/ockly/test/test_helper.exs delete mode 100644 implementations/rust/ockam/ockam_api/src/identity/identity_service.rs delete mode 100644 implementations/rust/ockam/ockam_api/src/identity/models.rs delete mode 100644 implementations/rust/ockam/ockam_api/src/verifier.rs delete mode 100644 implementations/rust/ockam/ockam_api/src/verifier/types.rs delete mode 100644 implementations/rust/ockam/ockam_api/tests/identity.rs delete mode 100644 implementations/rust/ockam/ockam_ffi/.cargo/config.toml delete mode 100644 implementations/rust/ockam/ockam_ffi/include/ockam/vault.h delete mode 100644 implementations/rust/ockam/ockam_ffi/src/error.rs delete mode 100644 implementations/rust/ockam/ockam_ffi/src/lib.rs delete mode 100644 implementations/rust/ockam/ockam_ffi/src/macros.rs delete mode 100644 implementations/rust/ockam/ockam_ffi/src/vault.rs delete mode 100644 implementations/rust/ockam/ockam_ffi/src/vault_types.rs diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5fef37dea7c..c44437c7b89 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -100,14 +100,3 @@ updates: labels: - "Type: Dependencies" - "Implementation: Elixir" - - # Maintain dependencies for elixir ockam_vault_software application - - package-ecosystem: "mix" - directory: "/implementations/elixir/ockam/ockam_vault_software" - commit-message: - prefix: "build:" - schedule: - interval: "daily" - labels: - - "Type: Dependencies" - - "Implementation: Elixir" diff --git a/.github/workflows/elixir-ignored.yml b/.github/workflows/elixir-ignored.yml index bb21b654976..50243fc55d3 100644 --- a/.github/workflows/elixir-ignored.yml +++ b/.github/workflows/elixir-ignored.yml @@ -34,7 +34,7 @@ jobs: - ockam_metrics - ockam_services - ockam_typed_cbor - - ockam_vault_software + - ockly steps: - run: 'echo "Elixir - lint_${{ matrix.mix_project }} - Ignored"' @@ -52,7 +52,7 @@ jobs: - ockam_metrics - ockam_services - ockam_typed_cbor - - ockam_vault_software + - ockly steps: - run: 'echo "Elixir - build_${{ matrix.mix_project }} - Ignored"' @@ -70,6 +70,6 @@ jobs: - ockam_metrics - ockam_services - ockam_typed_cbor - - ockam_vault_software + - ockly steps: - run: 'echo "Elixir - test_${{ matrix.mix_project }} - Ignored"' diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index cd60860d6bd..cdf80066a41 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -67,7 +67,7 @@ jobs: - ockam_metrics - ockam_services - ockam_typed_cbor - - ockam_vault_software + - ockly steps: - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac with: @@ -92,7 +92,7 @@ jobs: - ockam_metrics - ockam_services - ockam_typed_cbor - - ockam_vault_software + - ockly steps: - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac with: @@ -117,7 +117,7 @@ jobs: - ockam_metrics - ockam_services - ockam_typed_cbor - - ockam_vault_software + - ockly steps: - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac with: diff --git a/Cargo.lock b/Cargo.lock index 92b02d4852d..5e8b37ffaea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -488,6 +488,7 @@ checksum = "fc6b3804dca60326e07205179847f17a4fce45af3a1106939177ad41ac08a6de" dependencies = [ "aws-credential-types", "aws-http", + "aws-sdk-sso", "aws-sdk-sts", "aws-smithy-async", "aws-smithy-client", @@ -498,12 +499,15 @@ dependencies = [ "aws-types", "bytes 1.5.0", "fastrand 2.0.0", + "hex", "http", "hyper", + "ring", "time", "tokio", "tower", "tracing", + "zeroize", ] [[package]] @@ -584,6 +588,30 @@ dependencies = [ "tracing", ] +[[package]] +name = "aws-sdk-sso" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903f888ff190e64f6f5c83fb0f8d54f9c20481f1dc26359bb8896f5d99908949" +dependencies = [ + "aws-credential-types", + "aws-http", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-client", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes 1.5.0", + "http", + "regex", + "tokio-stream", + "tracing", +] + [[package]] name = "aws-sdk-sts" version = "0.30.0" @@ -4647,17 +4675,6 @@ dependencies = [ "trybuild", ] -[[package]] -name = "ockam-ffi" -version = "0.78.0" -dependencies = [ - "futures 0.3.28", - "lazy_static", - "ockam_core", - "ockam_vault", - "tokio", -] - [[package]] name = "ockam_abac" version = "0.26.0" diff --git a/Cargo.toml b/Cargo.toml index 7a9296c6b55..4dd4c29b845 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ members = [ "implementations/rust/ockam/ockam_command", "implementations/rust/ockam/ockam_core", "implementations/rust/ockam/ockam_executor", - "implementations/rust/ockam/ockam_ffi", "implementations/rust/ockam/ockam_identity", "implementations/rust/ockam/ockam_macros", "implementations/rust/ockam/ockam_multiaddr", diff --git a/implementations/elixir/Makefile b/implementations/elixir/Makefile index f95565f8800..18c39d2b0c6 100644 --- a/implementations/elixir/Makefile +++ b/implementations/elixir/Makefile @@ -1,5 +1,5 @@ PACKAGES := \ - ockam_vault_software \ + ockly \ ockam \ ockam_abac \ ockam_services \ @@ -14,17 +14,9 @@ build_release: $(addprefix build_release_,$(PACKAGES)) build_release_%: deps_% cd ockam/$* && MIX_ENV=prod mix compile -build_release_ockam_vault_software: rust_build_release_ockam-ffi deps_ockam_vault_software - cd ockam/ockam_vault_software && MIX_ENV=prod mix compile -rust_build_release_ockam-ffi: - $(MAKE) -C ../rust build_release_ockam-ffi build_%: deps_% cd ockam/$* && mix compile -build_ockam_vault_software: rust_build_release_ockam-ffi deps_ockam_vault_software - cd ockam/ockam_vault_software && mix compile -rust_build_ockam-ffi: - $(MAKE) -C ../rust build_ockam-ffi test: $(addprefix test_,$(PACKAGES)) test_%: build_% deps_% diff --git a/implementations/elixir/ockam/ockam/lib/ockam/credential/authenticator/direct/client.ex b/implementations/elixir/ockam/ockam/lib/ockam/credential/authenticator/direct/client.ex index 62c93a7181a..1c5a5dc214a 100644 --- a/implementations/elixir/ockam/ockam/lib/ockam/credential/authenticator/direct/client.ex +++ b/implementations/elixir/ockam/ockam/lib/ockam/credential/authenticator/direct/client.ex @@ -6,7 +6,7 @@ defmodule Ockam.Credential.Authenticator.Direct.Client.AddMemberRequest do typedstruct do plugin(Ockam.TypedCBOR.Plugin) - field(:identity_id, String.t(), minicbor: [key: 1]) + field(:identity_id, binary(), minicbor: [key: 1]) field(:attributes, %{String.t() => String.t()}, minicbor: [key: 2]) end end @@ -20,10 +20,10 @@ defmodule Ockam.Credential.Authenticator.Direct.Client.AttributesEntry do typedstruct do plugin(Ockam.TypedCBOR.Plugin) ## Rust encodes attribute values as a list of bytes - field(:attributes, %{String.t() => {:list, :integer}}, minicbor: [key: 1]) + field(:attributes, %{{:list, :integer} => {:list, :integer}}, minicbor: [key: 1]) field(:added_at, integer(), minicbor: [key: 2]) field(:expires, integer() | nil, minicbor: [key: 3]) - field(:attested_by, String.t() | nil, minicbor: [key: 4]) + field(:attested_by, binary() | nil, minicbor: [key: 4]) end end @@ -46,7 +46,9 @@ defmodule Ockam.Credential.Authenticator.Direct.Client.ListMembersResponse do Map.new(decoded, fn {id, entry} -> attributes = Map.get(entry, :attributes, %{}) - |> Map.new(fn {key, val} -> {key, :erlang.list_to_binary(val)} end) + |> Map.new(fn {key, val} -> + {:erlang.list_to_binary(key), :erlang.list_to_binary(val)} + end) {id, Map.put(entry, :attributes, attributes)} end)} diff --git a/implementations/elixir/ockam/ockam/lib/ockam/credential/verifier/request.ex b/implementations/elixir/ockam/ockam/lib/ockam/credential/verifier/request.ex deleted file mode 100644 index 219a361bb76..00000000000 --- a/implementations/elixir/ockam/ockam/lib/ockam/credential/verifier/request.ex +++ /dev/null @@ -1,13 +0,0 @@ -defmodule Ockam.Credential.VerifyRequest do - @moduledoc """ - Request encoder for credential verifier API - """ - use TypedStruct - - typedstruct do - plugin(Ockam.TypedCBOR.Plugin) - field(:credential, binary(), minicbor: [key: 1]) - field(:subject_id, String.t(), minicbor: [key: 2]) - field(:authorities, %{String.t() => binary()}, minicbor: [key: 3]) - end -end diff --git a/implementations/elixir/ockam/ockam/lib/ockam/credential/verifier/sidecar.ex b/implementations/elixir/ockam/ockam/lib/ockam/credential/verifier/sidecar.ex deleted file mode 100644 index b947b288809..00000000000 --- a/implementations/elixir/ockam/ockam/lib/ockam/credential/verifier/sidecar.ex +++ /dev/null @@ -1,53 +0,0 @@ -defmodule Ockam.Credential.Verifier.Sidecar do - @moduledoc """ - API wrapper to call credential verification using sidecar verifier service - """ - ## TODO: sidecar workers should maybe be moved to another application like ockam_services - alias Ockam.API.Client, as: ApiClient - alias Ockam.API.Response, as: ApiResponse - - alias Ockam.Credential.VerifyRequest - - alias Ockam.Credential.AttributeSet - - @api_route ["ca_verifier_sidecar"] - - def api_route() do - @api_route - end - - @spec verify( - credential :: binary(), - subject_id :: String.t(), - authorities :: %{String.t() => binary()} - ) :: {:ok, attribute_set :: AttributeSet.t()} | {:error, reason :: any()} - def verify(credential, subject_id, authorities) - when is_binary(credential) and is_binary(subject_id) and is_map(authorities) do - method = :post - path = "verify" - - request = - VerifyRequest.encode!(%VerifyRequest{ - credential: credential, - subject_id: subject_id, - authorities: authorities - }) - - case ApiClient.sync_request(method, path, request, api_route()) do - {:ok, %ApiResponse{status: 200, body: body}} -> - case AttributeSet.decode(body) do - {:ok, %AttributeSet{} = attribute_set, ""} -> - {:ok, attribute_set} - - {:error, reason} -> - {:error, reason} - end - - {:ok, %ApiResponse{status: status}} -> - {:error, {:api_error, status}} - - {:error, _} = error -> - error - end - end -end diff --git a/implementations/elixir/ockam/ockam/lib/ockam/credential/verifier/stub.ex b/implementations/elixir/ockam/ockam/lib/ockam/credential/verifier/stub.ex deleted file mode 100644 index 6dd8f7ee782..00000000000 --- a/implementations/elixir/ockam/ockam/lib/ockam/credential/verifier/stub.ex +++ /dev/null @@ -1,35 +0,0 @@ -defmodule Ockam.Credential.Verifier.Stub do - @moduledoc """ - Stub implementation for credential verifier - """ - alias Ockam.Credential.AttributeSet - alias Ockam.Credential.AttributeSet.Attributes - - def verify(credential, subject_id, authorities) - when is_binary(credential) and is_binary(subject_id) and is_map(authorities) do - with {:ok, %{attributes: attributes, expiration: expiration}} <- parse_credential(credential) do - {:ok, - %AttributeSet{attributes: %Attributes{attributes: attributes}, expiration: expiration}} - end - end - - def make_credential(attributes, expiration) when is_map(attributes) do - CBOR.encode(%{attributes: attributes, expiration: expiration}) - end - - def parse_credential(credential) do - case CBOR.decode(credential) do - {:ok, %{"attributes" => attributes, "expiration" => expiration}, ""} - when is_map(attributes) -> - {:ok, %{attributes: attributes, expiration: expiration}} - - _other -> - {:error, :invalid_credential} - end - end - - def expiration() do - now = System.os_time(:second) - now + 3600 - end -end diff --git a/implementations/elixir/ockam/ockam/lib/ockam/examples/stream/bi_directional/secure_channel.ex b/implementations/elixir/ockam/ockam/lib/ockam/examples/stream/bi_directional/secure_channel.ex index 05e4a498081..c01c75721f1 100644 --- a/implementations/elixir/ockam/ockam/lib/ockam/examples/stream/bi_directional/secure_channel.ex +++ b/implementations/elixir/ockam/ockam/lib/ockam/examples/stream/bi_directional/secure_channel.ex @@ -30,8 +30,6 @@ defmodule Ockam.Examples.Stream.BiDirectional.SecureChannel do Ping exchanges messages with ping using the secure channel """ alias Ockam.SecureChannel - alias Ockam.Vault - alias Ockam.Vault.Software, as: SoftwareVault alias Ockam.Examples.Ping alias Ockam.Examples.Pong @@ -153,27 +151,27 @@ defmodule Ockam.Examples.Stream.BiDirectional.SecureChannel do end defp create_secure_channel_listener() do - {:ok, vault} = SoftwareVault.init() - {:ok, keypair} = Vault.secret_generate(vault, type: :curve25519) + {:ok, identity} = Ockam.Identity.create() + {:ok, keypair} = SecureChannel.Crypto.generate_dh_keypair() + {:ok, attestation} = Ockam.Identity.attest_purpose_key(identity, keypair.secret) SecureChannel.create_listener( address: "SC_listener", - identity: :dynamic, - identity_module: Ockam.Identity.Stub, - encryption_options: [vault: vault, static_keypair: keypair] + identity: identity, + encryption_options: [static_keypair: keypair, static_key_attestation: attestation] ) end defp create_secure_channel(route_to_listener) do - {:ok, vault} = SoftwareVault.init() - {:ok, keypair} = Vault.secret_generate(vault, type: :curve25519) + {:ok, identity} = Ockam.Identity.create() + {:ok, keypair} = SecureChannel.Crypto.generate_dh_keypair() + {:ok, attestation} = Ockam.Identity.attest_purpose_key(identity, keypair.secret) {:ok, c} = SecureChannel.create_channel( - identity: :dynamic, - identity_module: Ockam.Identity.Stub, - route: route_to_listener, - encryption_options: [vault: vault, static_keypair: keypair] + identity: identity, + encryption_options: [static_keypair: keypair, static_key_attestation: attestation], + route: [route_to_listener] ) {:ok, c} diff --git a/implementations/elixir/ockam/ockam/lib/ockam/identity/api/request.ex b/implementations/elixir/ockam/ockam/lib/ockam/identity/api/request.ex deleted file mode 100644 index 07748858d7d..00000000000 --- a/implementations/elixir/ockam/ockam/lib/ockam/identity/api/request.ex +++ /dev/null @@ -1,77 +0,0 @@ -defmodule Ockam.Identity.API.Request do - @moduledoc """ - Identity API request encoding - """ - alias Ockam.TypedCBOR - - @validate_identity_change_history {:struct, %{identity: %{key: 1, schema: :binary}}} - - @create_signature {:struct, - %{ - identity: %{key: 1, schema: :binary}, - state: %{key: 2, schema: :binary}, - vault_name: %{key: 3, schema: :string} - }} - - @verify_signature {:struct, - %{ - identity: %{key: 1, schema: :binary}, - state: %{key: 2, schema: :binary}, - proof: %{key: 3, schema: :binary} - }} - - @compare_identity_change_history {:struct, - %{ - identity: %{key: 1, schema: :binary}, - known_identity: %{key: 2, schema: :binary} - }} - - def create() do - "" - end - - def validate_identity_change_history(identity) do - TypedCBOR.encode!(@validate_identity_change_history, %{identity: identity}) - end - - def create_signature(vault_name, identity, auth_hash) do - TypedCBOR.encode!(@create_signature, %{ - identity: identity, - state: auth_hash, - vault_name: vault_name - }) - end - - def verify_signature(identity, proof, auth_hash) do - TypedCBOR.encode!(@verify_signature, %{identity: identity, state: auth_hash, proof: proof}) - end - - def compare_identity_change_history(identity, known_identity) do - TypedCBOR.encode!(@compare_identity_change_history, %{ - identity: identity, - known_identity: known_identity - }) - end - - ## For testing purposes - - def decode_create("") do - {:ok, "", ""} - end - - def decode_validate_identity_change_history(data) do - TypedCBOR.decode_strict(@validate_identity_change_history, data) - end - - def decode_create_signature(data) do - TypedCBOR.decode_strict(@create_signature, data) - end - - def decode_verify_signature(data) do - TypedCBOR.decode_strict(@verify_signature, data) - end - - def decode_compare_identity_change_history(data) do - TypedCBOR.decode_strict(@compare_identity_change_history, data) - end -end diff --git a/implementations/elixir/ockam/ockam/lib/ockam/identity/api/response.ex b/implementations/elixir/ockam/ockam/lib/ockam/identity/api/response.ex deleted file mode 100644 index 348e1ee178a..00000000000 --- a/implementations/elixir/ockam/ockam/lib/ockam/identity/api/response.ex +++ /dev/null @@ -1,59 +0,0 @@ -defmodule Ockam.Identity.API.Response do - @moduledoc """ - Identity API response decoding - """ - alias Ockam.TypedCBOR - - @create {:struct, - %{identity: %{key: 1, schema: :binary}, identity_id: %{key: 2, schema: :string}}} - - @validate_identity_change_history {:struct, %{identity_id: %{key: 1, schema: :string}}} - - @create_signature {:struct, %{proof: %{key: 1, schema: :binary}}} - - @verify_signature {:struct, %{verified: %{key: 1, schema: :boolean}}} - - @compare_identity_change_history {:enum, [none: 0, equal: 1, conflict: 2, newer: 3, older: 4]} - - def create(data) do - TypedCBOR.decode_strict(@create, data) - end - - def validate_identity_change_history(data) do - TypedCBOR.decode_strict(@validate_identity_change_history, data) - end - - def create_signature(data) do - TypedCBOR.decode_strict(@create_signature, data) - end - - def verify_signature(data) do - TypedCBOR.decode_strict(@verify_signature, data) - end - - def compare_identity_change_history(data) do - TypedCBOR.decode_strict(@compare_identity_change_history, data) - end - - ## For testing purposes - - def encode_create(decoded) do - TypedCBOR.encode!(@create, decoded) - end - - def encode_validate_identity_change_history(decoded) do - TypedCBOR.encode!(@validate_identity_change_history, decoded) - end - - def encode_create_signature(decoded) do - TypedCBOR.encode!(@create_signature, decoded) - end - - def encode_verify_signature(decoded) do - TypedCBOR.encode!(@verify_signature, decoded) - end - - def encode_compare_identity_change_history(decoded) do - TypedCBOR.encode!(@compare_identity_change_history, decoded) - end -end diff --git a/implementations/elixir/ockam/ockam/lib/ockam/identity/identity.ex b/implementations/elixir/ockam/ockam/lib/ockam/identity/identity.ex index b87be0022d0..884c5aebdb9 100644 --- a/implementations/elixir/ockam/ockam/lib/ockam/identity/identity.ex +++ b/implementations/elixir/ockam/ockam/lib/ockam/identity/identity.ex @@ -1,140 +1,145 @@ defmodule Ockam.Identity do @moduledoc """ - API facade for identity implementations - Using module name and opaque data to represent implementation-specific identities - - You can chose an implementation when creating an identity - - Default implementation is `Ockam.Identity.Sidecar` + build and work with Ockam Identities """ + alias Ockam.Credential.AttributeSet + alias __MODULE__ + @type identity_data() :: binary() @type identity_id() :: String.t() - @type t() :: {module :: atom, data :: binary()} + defstruct [:identity_id, :data] + + @type t() :: %Identity{} @type proof() :: binary() @type compare_result() :: :none | :equal | :conflict | :newer | :older - @callback create() :: {:ok, identity_data(), identity_id()} - @callback get(String.t()) :: {:ok, identity_data(), identity_id()} - @callback validate_identity_change_history(identity_data()) :: - {:ok, identity_id()} | {:error, any()} - @callback create_signature( - vault_name :: String.t(), - identity_data(), - signature_source :: binary() - ) :: {:ok, proof()} | {:error, any()} - @callback verify_signature(identity_data(), proof(), signature_source :: binary()) :: - :ok | {:error, any()} - @callback compare_identity_change_history( - current_data :: identity_data(), - known_data :: identity_data() - ) :: {:ok, compare_result()} | {:error, any()} - @callback check_local_private_key(vault_name :: String.t(), identity_data()) :: - :ok | {:error, any()} - - def default_implementation() do - Application.get_env(:ockam, :identity_module, Ockam.Identity.Stub) + @spec create() :: + {:ok, identity :: t()} | {:error, reason :: any()} + def create() do + case Ockly.Native.create_identity() do + {:error, reason} -> {:error, reason} + {id, data} -> {:ok, %Identity{identity_id: id, data: data}} + end end - @spec create(module :: atom()) :: + @spec create(secret_signing_key :: binary()) :: {:ok, identity :: t(), identity_id :: binary()} | {:error, reason :: any()} - def create(module \\ nil) + def create(secret) do + key_id = Ockly.Native.import_signing_secret(secret) - def create(nil) do - create(default_implementation()) - end - - def create(module) do - with {:ok, data, id} <- module.create() do - {:ok, {module, data}, id} + case Ockly.Native.create_identity(key_id) do + {:error, reason} -> {:error, reason} + {id, data} -> {:ok, %Identity{identity_id: id, data: data}} end end - def get(identity_name) do - get(default_implementation(), identity_name) + @spec import(contact_data :: binary(), secret_signing_key :: binary()) :: + {:ok, identity :: t(), identity_id :: binary()} | {:error, any()} + def import(contact_data, secret_signing_key) do + case Ockly.Native.import_signing_secret(secret_signing_key) do + {:error, error} -> {:error, error} + _key_id -> validate_contact_data(contact_data) + end end - def get(module, identity_name) do - with {:ok, data, id} <- module.get(identity_name) do - {:ok, {module, data}, id} + @spec validate_contact_data(contact_data :: binary()) :: + {:ok, identity :: t(), identity_id :: binary()} | {:error, any()} + def validate_contact_data(contact_data) do + case Ockly.Native.check_identity(contact_data) do + {:error, reason} -> {:error, reason} + contact_id -> {:ok, %Identity{identity_id: contact_id, data: contact_data}, contact_id} end end - def make_identity(identity) do - make_identity(default_implementation(), identity) + @spec get_data(t()) :: any() + def get_data(%Identity{data: data}) do + data end - def make_identity(module, {module, data}) do - {:ok, {module, data}} + @spec get_identifier(t()) :: String.t() + def get_identifier(%Identity{identity_id: id}) do + id end - def make_identity(module, {other_module, _data}) do - {:error, {:different_identity_implementations, module, other_module}} + ## TODO: this is messy. There are places that expect identifiers as raw, 20-length bytes, others + ## than expect identifiers in a human readable string representation + ## (lowercased hex encoded, with an uppercase "I" prefix) + ## For now we provide this, but even if we keep both versions around the conversion + ## on the other direction would make more sense (keep in binary format, convert to string + ## on request) + def get_identifier_bin(%Identity{identity_id: <<"I", hex::binary-size(40)>>}) do + {:ok, identifier_binary} = Base.decode16(hex, case: :lower) + identifier_binary end - def make_identity(module, data) when is_binary(data) do - with {:ok, identity, _id} <- validate_contact_data({module, ""}, data) do - {:ok, identity} + # TODO: rename to attest_secure_channel_key + @spec attest_purpose_key(contact :: t(), secret_key :: %{private: binary(), public: binary()}) :: + {:ok, proof()} | {:error, any()} + def attest_purpose_key(%Identity{identity_id: identifier}, %{private: secret_key, public: _}) do + case Ockly.Native.attest_secure_channel_key(identifier, secret_key) do + {:error, reason} -> {:error, reason} + attestation -> {:ok, %Ockam.Identity.PurposeKeyAttestation{attestation: attestation}} end end - def from_data(module, data) do - validate_contact_data({module, ""}, data) - end - - @spec validate_contact_data(my_identity :: t(), contact_data :: binary()) :: - {:ok, identity :: t(), identity_id :: binary()} - def validate_contact_data({my_module, _my_data}, contact_data) do - with {:ok, contact_id} <- validate_identity_change_history({my_module, contact_data}) do - {:ok, {my_module, contact_data}, contact_id} + # TODO: rename to verify_secure_channel_key_attestation + @spec verify_purpose_key_attestation( + contact :: t(), + pubkey :: binary(), + attestation :: Ockam.Identity.PurposeKeyAttestation.t() + ) :: {:ok, boolean()} | {:error, any()} + def verify_purpose_key_attestation( + %Identity{data: identity_data}, + pubkey, + %Ockam.Identity.PurposeKeyAttestation{attestation: attestation} + ) do + case Ockly.Native.verify_secure_channel_key_attestation(identity_data, pubkey, attestation) do + {:error, reason} -> {:error, reason} + true -> {:ok, true} end end - @spec get_data(t()) :: any() - def get_data({_module, data}) do - data + # TODO refactor so that subject is an identity instead of identifier + def issue_credential(%Identity{data: issuer}, subject, attrs, ttl) + when is_map(attrs) and is_binary(subject) do + case Ockly.Native.issue_credential(issuer, subject, attrs, ttl) do + {:error, reason} -> {:error, reason} + cred -> {:ok, cred} + end end - @spec validate_identity_change_history(contact :: t()) :: - {:ok, contact_id :: binary()} | {:error, reason :: any()} - def validate_identity_change_history({module, data}) do - module.validate_identity_change_history(data) - end + def verify_credential(subject_id, authorities, credential) + when is_binary(subject_id) and is_list(authorities) do + authorities = Enum.map(authorities, fn a -> a.data end) - ## Not using a boolean because error reason could be important - @spec check_local_private_key(identity :: t(), vault_name :: String.t()) :: - :ok | {:error, any()} - def check_local_private_key({module, data}, vault_name \\ nil) do - module.check_local_private_key(vault_name, data) - end + case Ockly.Native.verify_credential(subject_id, authorities, credential) do + {:error, reason} -> + {:error, reason} - @spec create_signature(identity :: t(), auth_hash :: binary()) :: - {:ok, proof :: proof()} | {:error, reason :: any()} - @spec create_signature(identity :: t(), auth_hash :: binary(), vault_name :: String.t() | nil) :: - {:ok, proof :: proof()} | {:error, reason :: any()} - def create_signature({module, data}, auth_hash, vault_name \\ nil) do - module.create_signature(vault_name, data, auth_hash) - end + {expiration, verified_attrs} -> + attributes = %AttributeSet{ + attributes: %AttributeSet.Attributes{attributes: verified_attrs}, + expiration: expiration + } - @spec verify_signature( - identity :: t(), - proof :: proof(), - auth_hash :: binary() - ) :: :ok | {:error, reason :: any()} - def verify_signature({module, data}, proof, auth_hash) do - module.verify_signature(data, proof, auth_hash) + {:ok, attributes} + end end @spec compare_identity_change_history(current_identity :: t(), known_identity :: t) :: {:ok, atom()} | {:error, reason :: any()} - def compare_identity_change_history({module, current_data}, {module, known_data}) do - module.compare_identity_change_history(current_data, known_data) + def compare_identity_change_history(_current_history, _known_history) do + ## TODO: implement change history compare! + {:ok, :equal} end +end - def compare_identity_change_history(current_identity, known_identity) do - {:error, {:different_identity_implementations, current_identity, known_identity}} +defimpl CBOR.Encoder, for: Ockam.Identity do + def encode_into(identity, acc) do + <> end end diff --git a/implementations/elixir/ockam/ockam/lib/ockam/identity/purpose_key_attestation.ex b/implementations/elixir/ockam/ockam/lib/ockam/identity/purpose_key_attestation.ex new file mode 100644 index 00000000000..7cdf67d308c --- /dev/null +++ b/implementations/elixir/ockam/ockam/lib/ockam/identity/purpose_key_attestation.ex @@ -0,0 +1,12 @@ +defmodule Ockam.Identity.PurposeKeyAttestation do + @moduledoc false + defstruct [:attestation] + + @type t() :: %Ockam.Identity.PurposeKeyAttestation{} +end + +defimpl CBOR.Encoder, for: Ockam.Identity.PurposeKeyAttestation do + def encode_into(t, acc) do + acc <> t.attestation + end +end diff --git a/implementations/elixir/ockam/ockam/lib/ockam/identity/sidecar.ex b/implementations/elixir/ockam/ockam/lib/ockam/identity/sidecar.ex deleted file mode 100644 index 4dff062c4a4..00000000000 --- a/implementations/elixir/ockam/ockam/lib/ockam/identity/sidecar.ex +++ /dev/null @@ -1,150 +0,0 @@ -defmodule Ockam.Identity.Sidecar do - @moduledoc """ - API facade for identity storage implemented in a sidecar node. - Data structure contains a reference to identity stored - in the sidecar node. - """ - @behaviour Ockam.Identity - - alias Ockam.API.Client, as: ApiClient - alias Ockam.API.Response, as: ApiResponse - - alias Ockam.Identity.API.Request, as: IdentityRequest - alias Ockam.Identity.API.Response, as: IdentityResponse - - @type t() :: binary() - @type proof() :: binary() - - ## TODO: create proxy service to identity api in sidecar - @api_route ["sidecar_identity_api"] - - @spec create() :: {:ok, identity :: t(), identity_id :: binary()} | {:error, reason :: any()} - def create() do - with {:ok, body} <- api_request(:post, api_path(:create), IdentityRequest.create()), - {:ok, identity_response} <- IdentityResponse.create(body) do - {:ok, Map.fetch!(identity_response, :identity), Map.fetch!(identity_response, :identity_id)} - end - end - - def get(identity_name) do - with {:ok, body} <- api_request(:get, api_path({:get, identity_name}), ""), - {:ok, identity_response} <- IdentityResponse.create(body) do - {:ok, Map.fetch!(identity_response, :identity), Map.fetch!(identity_response, :identity_id)} - end - end - - @spec validate_identity_change_history(contact :: t()) :: - {:ok, contact_id :: binary()} | {:error, reason :: any()} - def validate_identity_change_history(contact) do - with {:ok, body} <- - api_request( - :post, - api_path(:validate_identity_change_history), - IdentityRequest.validate_identity_change_history(contact) - ), - {:ok, validate_response} <- IdentityResponse.validate_identity_change_history(body) do - contact_id = Map.fetch!(validate_response, :identity_id) - {:ok, contact_id} - end - end - - @spec create_signature(vault_name :: String.t() | nil, identity :: t(), auth_hash :: binary()) :: - {:ok, proof :: proof()} | {:error, reason :: any()} - def create_signature(vault_name, identity, auth_hash) do - with {:ok, body} <- - api_request( - :post, - api_path(:create_signature), - IdentityRequest.create_signature(vault_name, identity, auth_hash) - ), - {:ok, create_signature_response} <- IdentityResponse.create_signature(body) do - {:ok, Map.fetch!(create_signature_response, :proof)} - end - end - - @spec verify_signature( - identity :: t(), - proof :: proof(), - auth_hash :: binary() - ) :: :ok | {:error, reason :: any()} - def verify_signature(identity, proof, auth_hash) do - with {:ok, body} <- - api_request( - :post, - api_path(:verify_signature), - IdentityRequest.verify_signature(identity, proof, auth_hash) - ), - {:ok, verify_response} <- IdentityResponse.verify_signature(body) do - case Map.fetch!(verify_response, :verified) do - true -> :ok - false -> {:error, :invalid_proof} - end - end - end - - @spec compare_identity_change_history(current_identity :: t(), known_identity :: t) :: - {:ok, atom()} | {:error, reason :: any()} - def compare_identity_change_history(current_identity, known_identity) do - with {:ok, body} <- - api_request( - :post, - api_path(:compare_identity_change_history), - IdentityRequest.compare_identity_change_history(current_identity, known_identity) - ) do - IdentityResponse.compare_identity_change_history(body) - end - end - - @spec check_local_private_key(vault_name :: String.t(), identity :: t()) :: - :ok | {:error, reason :: any()} - def check_local_private_key(vault_name, identity) do - case create_signature(vault_name, identity, "") do - {:ok, _proof} -> - :ok - - {:error, reason} -> - {:error, reason} - end - end - - def api_route() do - @api_route - end - - defp api_path({:get, identity_id}) do - identity_id - end - - defp api_path(:create) do - "" - end - - defp api_path(:create_signature) do - "actions/create_signature" - end - - defp api_path(:validate_identity_change_history) do - "actions/validate_identity_change_history" - end - - defp api_path(:verify_signature) do - "actions/verify_signature" - end - - defp api_path(:compare_identity_change_history) do - "actions/compare_identity_change_history" - end - - defp api_request(method, path, request) do - case ApiClient.sync_request(method, path, request, api_route()) do - {:ok, %ApiResponse{status: 200, body: body}} -> - {:ok, body} - - {:ok, %ApiResponse{status: status}} -> - {:error, {:api_error, status}} - - {:error, _} = error -> - error - end - end -end diff --git a/implementations/elixir/ockam/ockam/lib/ockam/identity/stub.ex b/implementations/elixir/ockam/ockam/lib/ockam/identity/stub.ex deleted file mode 100644 index 479e4d1e868..00000000000 --- a/implementations/elixir/ockam/ockam/lib/ockam/identity/stub.ex +++ /dev/null @@ -1,66 +0,0 @@ -defmodule Ockam.Identity.Stub do - @moduledoc """ - Stub for `Ockam.Identity` - """ - - @behaviour Ockam.Identity - - @type t() :: binary() - @type proof() :: binary() - - @spec create() :: {:ok, identity :: t(), identity_id :: binary()} | {:error, reason :: any()} - def create() do - bytes = random() - {:ok, "DATA_" <> bytes, "ID_" <> bytes} - end - - def get(_identity_name) do - {:error, :not_implemented} - end - - @spec validate_identity_change_history(contact :: t()) :: - {:ok, contact_id :: binary()} | {:error, reason :: any()} - def validate_identity_change_history("DATA_" <> bytes) do - {:ok, "ID_" <> bytes} - end - - def validate_identity_change_history(other) do - {:error, {:invalid_identity, other}} - end - - @spec create_signature(vault_name :: String.t() | nil, identity :: t(), auth_hash :: binary()) :: - {:ok, proof :: proof()} | {:error, reason :: any()} - def create_signature(_vault_name, identity, auth_hash) do - {:ok, identity <> auth_hash} - end - - @spec verify_signature( - identity :: t(), - proof :: proof(), - auth_hash :: binary() - ) :: :ok | {:error, reason :: any()} - def verify_signature(identity, proof, auth_hash) do - {:ok, signature} = create_signature(nil, identity, auth_hash) - - case proof do - ^signature -> :ok - other -> {:error, {:invalid_signature, other, proof}} - end - end - - def compare_identity_change_history(identity, identity) do - {:ok, :equal} - end - - def compare_identity_change_history(identity, known_identity) do - {:error, {:history_update_not_supported, identity, known_identity}} - end - - def check_local_private_key(_vault_name, _identity) do - :ok - end - - def random() do - :rand.uniform(100_000) |> to_string() |> Base.encode32() - end -end diff --git a/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/channel.ex b/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/channel.ex index c5190b4219a..c9b6473e847 100644 --- a/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/channel.ex +++ b/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/channel.ex @@ -37,36 +37,34 @@ defmodule Ockam.SecureChannel.Channel do alias Ockam.SecureChannel.KeyEstablishmentProtocol.XX.Protocol, as: XX alias Ockam.SecureChannel.ServiceMessage alias Ockam.Session.Spawner - alias Ockam.Vault alias Ockam.Wire alias __MODULE__ require Logger - @type encryption_options :: [{:vault, Vault}, {:static_keypair, reference()}] + @type encryption_options :: [ + {:static_keypair, %{public: binary(), private: binary()}}, + {:static_key_attestation, binary()} + ] @type authorization :: list() | map() @type trust_policies :: list() @type secure_channel_opt :: {:identity, binary() | :dynamic} | {:key_exchange_timeout, non_neg_integer()} - # vault name where identity' private key is located - | {:vault_name, String.t() | nil} - | {:identity_module, module()} | {:encryption_options, encryption_options()} | {:address, Ockam.Address.t()} | {:trust_policies, list(TrustPolicy.trust_rule())} | {:authorization, Ockam.Worker.Authorization.config()} | {:additional_metadata, map()} | {:idle_timeout, non_neg_integer() | :infinity} - | {:credential_verifier, {module :: atom(), authorities :: [Identity.t()]}} + | {:authorities, [Identity.t()]} | {:credentials, [binary()]} # Note: we could split each of these into their own file as proper modules and delegate # the handling of messages to them. We can do that after the 3-packet handshake that # will simplify the handshaking anyway. typedstruct module: Handshaking do - field(:vault, Vault) field(:waiting, {pid(), reference()}) field(:xx, XX.t()) field(:timer, reference()) @@ -87,28 +85,16 @@ defmodule Ockam.SecureChannel.Channel do field(:identity, Identity.t()) field(:address, Ockam.Address.t()) field(:inner_address, Ockam.Address.t()) - field(:vault_name, binary()) field(:peer_route, Ockam.Address.route()) field(:trust_policies, trust_policies()) field(:additional_metadata, map()) field(:channel_state, Handshaking.t() | Established.t()) - field( - :credential_verifier, - {module :: atom(), authorities :: %{(identity_id :: binary()) => identity_data :: binary()}} - ) + field(:authorities, [Identity.t()]) field(:credentials, [binary()]) end - defmodule CredentialRejecter do - @moduledoc """ - A verifier that just reject every credential. It's the one used by default if none is specified. - """ - def verify(_credential, _peer_identity_id, _authorities), - do: {:error, :no_credential_verified_configured} - end - @handshake_timeout 30_000 @type listener_opt :: @@ -326,72 +312,37 @@ defmodule Ockam.SecureChannel.Channel do defp noise_payloads(:initiator, id_proof), do: %{message3: id_proof} defp noise_payloads(:responder, id_proof), do: %{message2: id_proof} - defp get_static_keypair(vault, options) do - case Keyword.fetch(options, :static_keypair) do - :error -> - XX.generate_keypair(vault) - - {:ok, %{private: _priv, public: _pub} = keypair} -> - {:ok, keypair} + # TODO: shouldn't be options if they are mandatory + defp get_static_keypair(options) do + Keyword.fetch(options, :static_keypair) + end - {:ok, vault_handle} -> - XX.turn_vault_private_key_handle_to_keypair(vault, vault_handle) - end + defp get_static_key_attestation(options) do + Keyword.fetch(options, :static_key_attestation) end - defp setup_noise_key_exchange(vault, opts, role, identity, vault_name, credentials) do - with {:ok, static_keypair} <- get_static_keypair(vault, opts), - contact_data = Identity.get_data(identity), - {:ok, signature} <- - Identity.create_signature(identity, static_keypair.public, vault_name) do + defp setup_noise_key_exchange(opts, role, identity, credentials) do + with {:ok, static_keypair} <- get_static_keypair(opts), + {:ok, attestation} <- get_static_key_attestation(opts) do proof = %IdentityProof{ - contact: contact_data, - signature: signature, + contact: identity, + attestation: attestation, credentials: credentials } encoded_proof = IdentityProof.encode(proof) payloads = noise_payloads(role, encoded_proof) - options = [vault: vault, payloads: payloads, static_keypair: static_keypair] + options = [payloads: payloads, static_keypair: static_keypair] XX.setup(static_keypair, options) end end - defp vault_from_opts(encryption_options) do - case Keyword.fetch(encryption_options, :vault) do - {:ok, vault} -> {:ok, vault} - :error -> Ockam.Vault.Software.init() - end - end - - defp credential_verifier_from_opts(options) do - {mod, authorities} = Keyword.get(options, :credential_verifier, {CredentialRejecter, []}) - - authorities = - Map.new(authorities, fn authority_identity -> - {:ok, identity_id} = Identity.validate_identity_change_history(authority_identity) - {identity_id, Identity.get_data(authority_identity)} - end) - - {:ok, {mod, authorities}} + defp authorities_form_options(options) do + {:ok, Keyword.get(options, :authorities, [])} end defp identity_from_opts(options) do - identity_module = - Keyword.get_lazy(options, :identity_module, &Identity.default_implementation/0) - - case Keyword.fetch(options, :identity) do - {:ok, :dynamic} -> - with {:ok, identity, _id} <- Identity.create(identity_module) do - {:ok, identity} - end - - {:ok, other} -> - Identity.make_identity(identity_module, other) - - :error -> - {:error, :missing_identity} - end + Keyword.fetch(options, :identity) end def inner_setup_impl(address, inner_address, options) do @@ -399,21 +350,20 @@ defmodule Ockam.SecureChannel.Channel do additional_metadata = Keyword.get(options, :additional_metadata, %{}) encryption_options = Keyword.get(options, :encryption_options, []) key_exchange_timeout = Keyword.get(options, :key_exchange_timeout, @handshake_timeout) - vault_name = Keyword.get(options, :vault_name) - noise_key_exchange_options = Keyword.take(encryption_options, [:static_keypair]) + + noise_key_exchange_options = + Keyword.take(encryption_options, [:static_keypair, :static_key_attestation]) + credentials = Keyword.get(options, :credentials, []) with {:ok, role} <- Keyword.fetch(options, :role), - {:ok, vault} <- vault_from_opts(encryption_options), {:ok, identity} <- identity_from_opts(options), - {:ok, credential_verifier} <- credential_verifier_from_opts(options), + {:ok, authorities} <- authorities_form_options(options), {:ok, key_exchange_state} <- setup_noise_key_exchange( - vault, noise_key_exchange_options, role, identity, - vault_name, credentials ) do {:ok, tref} = :timer.apply_after(key_exchange_timeout, Ockam.Node, :stop, [address]) @@ -423,33 +373,32 @@ defmodule Ockam.SecureChannel.Channel do address: address, inner_address: inner_address, identity: identity, - vault_name: vault_name, trust_policies: trust_policies, additional_metadata: additional_metadata, - credential_verifier: credential_verifier + authorities: authorities } - complete_inner_setup(state, options, key_exchange_state, vault, tref) + complete_inner_setup(state, options, key_exchange_state, tref) end end - defp complete_inner_setup(%Channel{role: :initiator} = state, options, xx, vault, tref) do + defp complete_inner_setup(%Channel{role: :initiator} = state, options, xx, tref) do with {:ok, waiter} <- Keyword.fetch(options, :waiter), {:ok, init_route} <- Keyword.fetch(options, :route) do continue_handshake({:continue, xx}, %Channel{ state | peer_route: init_route, - channel_state: %Handshaking{vault: vault, waiting: waiter, timer: tref} + channel_state: %Handshaking{waiting: waiter, timer: tref} }) end end - defp complete_inner_setup(%Channel{role: :responder} = state, options, xx, vault, tref) do + defp complete_inner_setup(%Channel{role: :responder} = state, options, xx, tref) do with {:ok, init_message} <- Keyword.fetch(options, :init_message) do handle_inner_message_impl(init_message, %Channel{ state | peer_route: init_message.return_route, - channel_state: %Handshaking{xx: xx, timer: tref, vault: vault} + channel_state: %Handshaking{xx: xx, timer: tref} }) end end @@ -467,18 +416,27 @@ defmodule Ockam.SecureChannel.Channel do with {:ok, peer_proof_data} <- Map.fetch(payloads, peer_proof_msg), {:ok, identity_proof} <- IdentityProof.decode(peer_proof_data), - {:ok, peer_identity, peer_identity_id} <- - Ockam.Identity.validate_contact_data(state.identity, identity_proof.contact), - :ok <- Ockam.Identity.verify_signature(peer_identity, identity_proof.signature, rs), + {:ok, peer, peer_identity_id} <- Identity.validate_contact_data(identity_proof.contact), + {:ok, true} <- + Identity.verify_purpose_key_attestation( + peer, + rs, + %Ockam.Identity.PurposeKeyAttestation{attestation: identity_proof.attestation} + ), :ok <- - check_trust(state.trust_policies, state.identity, peer_identity, peer_identity_id), + check_trust( + state.trust_policies, + state.identity, + identity_proof.contact, + peer_identity_id + ), :ok <- process_credentials( identity_proof.credentials, peer_identity_id, - state.credential_verifier + state.authorities ) do - {encrypt_st, decrypt_st} = split(state.channel_state.vault, k1, k2, state.role) + {encrypt_st, decrypt_st} = split(k1, k2, state.role) {:ok, :cancel} = :timer.cancel(state.channel_state.timer) @@ -491,7 +449,7 @@ defmodule Ockam.SecureChannel.Channel do encrypt_st: encrypt_st, decrypt_st: decrypt_st, h: h, - peer_identity: peer_identity, + peer_identity: peer, peer_identity_id: peer_identity_id } @@ -502,26 +460,26 @@ defmodule Ockam.SecureChannel.Channel do end end - defp process_credentials([], _peer_identity_id, _cred_verifier), do: :ok + defp process_credentials([], _peer_identity_id, _authorities), do: :ok - defp process_credentials([cred], peer_identity_id, {cred_verifier_module, authorities}) do - case cred_verifier_module.verify(cred, peer_identity_id, authorities) do + defp process_credentials([cred], peer_identity_id, authorities) do + case Identity.verify_credential(peer_identity_id, authorities, cred) do {:ok, attribute_set} -> AttributeStorage.put_attribute_set(peer_identity_id, attribute_set) - other -> - {:error, {:rejected_credential, other}} + {:error, reason} -> + {:error, {:rejected_credential, reason}} end end - defp process_credentials(_creds, _peer_identity_id, _cred_verifier), + defp process_credentials(_creds, _peer_identity_id, _authorities), do: {:error, :multiple_credentials} - defp split(vault, k1, k2, :initiator), - do: {Encryptor.new(vault, k2, 0), Decryptor.new(vault, k1, 0)} + defp split(k1, k2, :initiator), + do: {Encryptor.new(k2, 0), Decryptor.new(k1, 0)} - defp split(vault, k1, k2, :responder), - do: {Encryptor.new(vault, k1, 0), Decryptor.new(vault, k2, 0)} + defp split(k1, k2, :responder), + do: {Encryptor.new(k1, 0), Decryptor.new(k2, 0)} # Check result of the handshake step, send handshake data to the peer if there is a message to exchange, # and possible move to another state @@ -627,7 +585,7 @@ defmodule Ockam.SecureChannel.Channel do end defp check_trust(policies, identity, contact, contact_id) do - with {:ok, identity_id} <- Identity.validate_identity_change_history(identity) do + with identity_id <- Identity.get_identifier(identity) do TrustPolicy.from_config(policies, %{id: identity_id, identity: identity}, %{ id: contact_id, identity: contact diff --git a/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/crypto.ex b/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/crypto.ex new file mode 100644 index 00000000000..7d78064c5eb --- /dev/null +++ b/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/crypto.ex @@ -0,0 +1,41 @@ +defmodule Ockam.SecureChannel.Crypto do + @moduledoc """ + Crypto functions used in secure channel + """ + + def sha256(value) do + :crypto.hash(:sha256, value) + end + + def generate_dh_keypair() do + {pub_key, secret_key} = :crypto.generate_key(:eddh, :x25519) + {:ok, %{private: secret_key, public: pub_key}} + end + + def dh(peer_public, private) do + {:ok, :crypto.compute_key(:ecdh, peer_public, private, :x25519)} + end + + def aead_aes_gcm_encrypt(k, n, h, plaintext) do + with {a, b} <- :crypto.crypto_one_time_aead(:aes_256_gcm, k, <>, plaintext, h, true) do + {:ok, <>} + end + end + + def aead_aes_gcm_decrypt(k, n, h, ciphertext_and_tag) do + size = byte_size(ciphertext_and_tag) - 16 + <> = ciphertext_and_tag + + case :crypto.crypto_one_time_aead(:aes_256_gcm, k, <>, ciphertext, h, tag, false) do + :error -> {:error, :aead_aes_gcm_decrypt_error} + plaintext -> {:ok, plaintext} + end + end + + def hkdf(salt), do: hkdf(salt, <<>>) + + def hkdf(salt, ikm) do + <> = :hkdf.derive(:sha256, ikm, "", salt, 64) + {k1, k2} + end +end diff --git a/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/encrypted_transport_protocol/aead_aes_gcm.ex b/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/encrypted_transport_protocol/aead_aes_gcm.ex index de44e0276ce..234991587a0 100644 --- a/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/encrypted_transport_protocol/aead_aes_gcm.ex +++ b/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/encrypted_transport_protocol/aead_aes_gcm.ex @@ -1,39 +1,39 @@ defmodule Ockam.SecureChannel.EncryptedTransportProtocol.AeadAesGcm do @moduledoc false - alias Ockam.Vault + alias Ockam.SecureChannel.Crypto alias __MODULE__ @max_nonce trunc(:math.pow(2, 64)) - 1 defmodule Encryptor do @moduledoc false alias __MODULE__ - defstruct [:vault, :k, :nonce, :rekey_each] + alias Ockam.SecureChannel.Crypto + defstruct [:k, :nonce, :rekey_each] @opaque t :: %Encryptor{} - def new(vault, k, nonce), do: new(vault, k, nonce, 32) + def new(k, nonce), do: new(k, nonce, 32) - def new(vault, k, nonce, rekey_each) do - %Encryptor{vault: vault, k: k, nonce: nonce, rekey_each: rekey_each} + def new(k, nonce, rekey_each) do + %Encryptor{k: k, nonce: nonce, rekey_each: rekey_each} end def encrypt( ad, plaintext, - %Encryptor{vault: vault, k: k, nonce: nonce, rekey_each: rekey_each} = state + %Encryptor{k: k, nonce: nonce, rekey_each: rekey_each} = state ) do - with {:ok, ciphertext} <- Vault.aead_aes_gcm_encrypt(vault, k, nonce, ad, plaintext), + with {:ok, ciphertext} <- Crypto.aead_aes_gcm_encrypt(k, nonce, ad, plaintext), {:ok, next_nonce} <- AeadAesGcm.increment_nonce(nonce), - {:ok, next_k, _} <- rotate_if_needed(vault, next_nonce, k, rekey_each) do + {:ok, next_k, _} <- rotate_if_needed(next_nonce, k, rekey_each) do {:ok, <>, %Encryptor{state | nonce: next_nonce, k: next_k}} end end - defp rotate_if_needed(vault, next_nonce, k, rekey_each) do + defp rotate_if_needed(next_nonce, k, rekey_each) do if rem(next_nonce, rekey_each) == 0 do - with {:ok, new_k} <- AeadAesGcm.rekey(vault, k) do - :ok = Vault.secret_destroy(vault, k) + with {:ok, new_k} <- AeadAesGcm.rekey(k) do {:ok, new_k, true} end else @@ -45,14 +45,15 @@ defmodule Ockam.SecureChannel.EncryptedTransportProtocol.AeadAesGcm do defmodule Decryptor do @moduledoc false alias __MODULE__ - defstruct [:vault, :k, :expected_nonce, :rekey_each, :prev_k, :seen, :prev_seen] + alias Ockam.SecureChannel.Crypto + + defstruct [:k, :expected_nonce, :rekey_each, :prev_k, :seen, :prev_seen] @opaque t :: %Decryptor{} - def new(vault, k, nonce), do: new(vault, k, nonce, 32) + def new(k, nonce), do: new(k, nonce, 32) - def new(vault, k, nonce, rekey_each) do + def new(k, nonce, rekey_each) do %Decryptor{ - vault: vault, k: k, expected_nonce: nonce, rekey_each: rekey_each, @@ -71,14 +72,14 @@ defmodule Ockam.SecureChannel.EncryptedTransportProtocol.AeadAesGcm do nonce, ad, ciphertext, - %Decryptor{vault: vault, seen: seen, k: k, expected_nonce: expected_nonce} = state + %Decryptor{seen: seen, k: k, expected_nonce: expected_nonce} = state ) do if MapSet.member?(seen, nonce) do {:error, :repeated_nonce} else {:ok, next_nonce} = AeadAesGcm.increment_nonce(nonce) - case Vault.aead_aes_gcm_decrypt(vault, k, nonce, ad, ciphertext) do + case Crypto.aead_aes_gcm_decrypt(k, nonce, ad, ciphertext) do {:ok, plaintext} -> {:ok, plaintext, %Decryptor{ @@ -98,12 +99,12 @@ defmodule Ockam.SecureChannel.EncryptedTransportProtocol.AeadAesGcm do nonce, ad, ciphertext, - %Decryptor{vault: vault, prev_seen: prev_seen, prev_k: prev_k} = state + %Decryptor{prev_seen: prev_seen, prev_k: prev_k} = state ) do if MapSet.member?(prev_seen, nonce) do {:error, :repeated_nonce} else - case Vault.aead_aes_gcm_decrypt(vault, prev_k, nonce, ad, ciphertext) do + case Crypto.aead_aes_gcm_decrypt(prev_k, nonce, ad, ciphertext) do {:ok, plaintext} -> {:ok, plaintext, %Decryptor{state | prev_seen: MapSet.put(prev_seen, nonce)}} @@ -118,17 +119,13 @@ defmodule Ockam.SecureChannel.EncryptedTransportProtocol.AeadAesGcm do nonce, ad, ciphertext, - %Decryptor{vault: vault, seen: seen, prev_k: prev_k, k: k} = state + %Decryptor{seen: seen, k: k} = state ) do {:ok, next_nonce} = AeadAesGcm.increment_nonce(nonce) - {:ok, new_k} = AeadAesGcm.rekey(vault, k) + {:ok, new_k} = AeadAesGcm.rekey(k) - case Vault.aead_aes_gcm_decrypt(vault, new_k, nonce, ad, ciphertext) do + case Crypto.aead_aes_gcm_decrypt(new_k, nonce, ad, ciphertext) do {:ok, plaintext} -> - if prev_k != nil do - :ok = Vault.secret_destroy(vault, prev_k) - end - {:ok, plaintext, %Decryptor{ state @@ -179,10 +176,10 @@ defmodule Ockam.SecureChannel.EncryptedTransportProtocol.AeadAesGcm do end end - def rekey(vault, k) do - {:ok, <>} = - Vault.aead_aes_gcm_encrypt(vault, k, @max_nonce, <<>>, <<0::32*8>>) - - Vault.secret_import(vault, [type: :aes], new_k) + def rekey(k) do + with {:ok, <>} <- + Crypto.aead_aes_gcm_encrypt(k, @max_nonce, <<>>, <<0::32*8>>) do + {:ok, new_k} + end end end diff --git a/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/identity_proof.ex b/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/identity_proof.ex index c55f955672c..188b964800b 100644 --- a/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/identity_proof.ex +++ b/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/identity_proof.ex @@ -3,22 +3,48 @@ defmodule Ockam.SecureChannel.IdentityProof do Identity Proof and Credentials exchanged during secure channel handshake """ + alias Ockam.SecureChannel.IdentityProof - alias __MODULE__ - @bare_struct {:struct, [contact: :data, signature: :data, credentials: {:array, :data}]} - defstruct [:contact, :signature, :credentials] + defstruct [:contact, :attestation, :credentials] - def encode(%IdentityProof{} = p) do - :bare.encode(p, @bare_struct) - end + def encode(t), do: CBOR.encode(t) + + def decode(data) do + case CBOR.decode(data) do + {:ok, %{1 => change_history, 2 => attestation, 3 => credentials}, ""} -> + {:ok, + %IdentityProof{ + contact: CBOR.encode(change_history), + attestation: CBOR.encode(attestation), + credentials: Enum.map(credentials, fn c -> CBOR.encode(c) end) + }} - def decode(encoded) do - case :bare.decode(encoded, @bare_struct) do - {:ok, map, ""} -> - {:ok, struct(IdentityProof, map)} + {:ok, decoded, rest} -> + {:error, {:decode_error, {:extra_data, rest, decoded}, data}} - error -> - {:error, {:invalid_identity_proof_msg, error}} + {:error, _reason} = error -> + error end end end + +defmodule Ockam.SecureChannel.IdentityProof.Credential do + @moduledoc false + defstruct [:data] +end + +defimpl CBOR.Encoder, for: Ockam.SecureChannel.IdentityProof.Credential do + def encode_into(t, acc), do: acc <> t.data +end + +defimpl CBOR.Encoder, for: Ockam.SecureChannel.IdentityProof do + def encode_into(t, acc) do + %{ + 1 => t.contact, + 2 => t.attestation, + 3 => + Enum.map(t.credentials, fn c -> %Ockam.SecureChannel.IdentityProof.Credential{data: c} end) + } + |> CBOR.Encoder.encode_into(acc) + end +end diff --git a/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/key_establishment_protocol/xx/protocol.ex b/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/key_establishment_protocol/xx/protocol.ex index 7cdd6811396..4425aa1a1db 100644 --- a/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/key_establishment_protocol/xx/protocol.ex +++ b/implementations/elixir/ockam/ockam/lib/ockam/secure_channel/key_establishment_protocol/xx/protocol.ex @@ -1,17 +1,15 @@ defmodule Ockam.SecureChannel.KeyEstablishmentProtocol.XX.Protocol do @moduledoc false - alias Ockam.Vault + alias Ockam.SecureChannel.Crypto @type message :: :message1 | :message2 | :message3 @type t :: %__MODULE__{} defstruct [ - # handle to a vault - :vault, - # static keypair, reference in vault + # static keypair :s, - # ephemeral keypair, reference in vault + # ephemeral keypair :e, # remote peer's identity public key :rs, @@ -47,12 +45,12 @@ defmodule Ockam.SecureChannel.KeyEstablishmentProtocol.XX.Protocol do end def setup(%{public: _, private: _} = static_keypair, options) do - with {:ok, protocol_state} <- - setup_vault(options, %__MODULE__{ - pending_handshake: [:message1, :message2, :message3], - s: static_keypair - }), - {:ok, protocol_state} <- setup_e(options, protocol_state), + protocol_state = %__MODULE__{ + pending_handshake: [:message1, :message2, :message3], + s: static_keypair + } + + with {:ok, protocol_state} <- setup_e(options, protocol_state), {:ok, protocol_state} <- setup_h(protocol_state), {:ok, protocol_state} <- setup_ck(protocol_state), {:ok, protocol_state} <- setup_prologue(options, protocol_state) do @@ -75,10 +73,8 @@ defmodule Ockam.SecureChannel.KeyEstablishmentProtocol.XX.Protocol do end end - defp next(%{pending_handshake: [], vault: vault, ck: ck, h: h, rs: rs, payloads: payloads}) do - k_attributes = %{type: :aes, length: 32, persistence: :ephemeral} - - with {:ok, [k1, k2]} <- Vault.hkdf_sha256(vault, ck, [k_attributes, k_attributes]) do + defp next(%{pending_handshake: [], ck: ck, h: h, rs: rs, payloads: payloads}) do + with {k1, k2} <- Crypto.hkdf(ck) do {:ok, {:complete, {k1, k2, h, rs, payloads}}} end end @@ -87,58 +83,28 @@ defmodule Ockam.SecureChannel.KeyEstablishmentProtocol.XX.Protocol do {:ok, {:continue, state}} end - defp setup_vault(options, state) do - case Keyword.get(options, :vault) do - nil -> {:error, :vault_option_is_nil} - vault -> {:ok, %{state | vault: vault}} - end - end - - defp get_e(options, state) do + defp get_e(options) do case Keyword.fetch(options, :ephemeral_keypair) do :error -> - generate_keypair(state.vault) + Crypto.generate_dh_keypair() {:ok, %{private: _priv, public: _pub} = keypair} -> {:ok, keypair} - - {:ok, vault_handle} -> - turn_vault_private_key_handle_to_keypair(state.vault, vault_handle) end end defp setup_e(options, state) do - with {:ok, keypair} <- get_e(options, state) do + with {:ok, keypair} <- get_e(options) do {:ok, %{state | e: keypair}} end end - def turn_vault_private_key_handle_to_keypair(vault, handle) do - with {:ok, public_key} <- Vault.secret_publickey_get(vault, handle) do - {:ok, %{private: handle, public: public_key}} - end - end - - def generate_keypair(vault) do - case Ockam.Vault.secret_generate(vault, type: :curve25519) do - {:ok, key_handle} -> - turn_vault_private_key_handle_to_keypair(vault, key_handle) - - {:error, reason} -> - {:error, {:could_not_generate_key, reason}} - end - end - defp setup_h(state) do - h = zero_padded_protocol_name() - {:ok, %{state | h: h}} + {:ok, %{state | h: zero_padded_protocol_name()}} end - defp setup_ck(%{vault: vault} = state) do - case Vault.secret_import(vault, [type: :buffer], zero_padded_protocol_name()) do - {:ok, ck} -> {:ok, %{state | ck: ck}} - {:error, reason} -> {:error, {:could_not_setup_ck, reason}} - end + defp setup_ck(state) do + {:ok, %{state | ck: zero_padded_protocol_name()}} end defp setup_prologue(options, state) do @@ -167,10 +133,10 @@ defmodule Ockam.SecureChannel.KeyEstablishmentProtocol.XX.Protocol do payload = Map.get(payloads, :message2, "") with {:ok, state} <- mix_hash(state, e.public), - {:ok, shared_secret} <- dh(state, e, re), + {:ok, shared_secret} <- Crypto.dh(re, e.private), {:ok, state} <- mix_key(state, shared_secret), {:ok, state, encrypted_s_and_tag} <- encrypt_and_hash(state, s.public), - {:ok, shared_secret} <- dh(state, s, re), + {:ok, shared_secret} <- Crypto.dh(re, s.private), {:ok, state} <- mix_key(state, shared_secret), {:ok, state, encrypted_payload_and_tag} <- encrypt_and_hash(state, payload) do {:ok, e.public <> encrypted_s_and_tag <> encrypted_payload_and_tag, state} @@ -181,7 +147,7 @@ defmodule Ockam.SecureChannel.KeyEstablishmentProtocol.XX.Protocol do payload = Map.get(payloads, :message3, "") with {:ok, state, encrypted_s_and_tag} <- encrypt_and_hash(state, s.public), - {:ok, shared_secret} <- dh(state, s, re), + {:ok, shared_secret} <- Crypto.dh(re, s.private), {:ok, state} <- mix_key(state, shared_secret), {:ok, state, encrypted_payload_and_tag} <- encrypt_and_hash(state, payload) do {:ok, encrypted_s_and_tag <> encrypted_payload_and_tag, state} @@ -199,10 +165,10 @@ defmodule Ockam.SecureChannel.KeyEstablishmentProtocol.XX.Protocol do def decode(:message2, %{e: e} = state, message) do with {:ok, re, encrypted_rs_and_tag, encrypted_payload_and_tag} <- parse_message2(message), {:ok, state} <- mix_hash(state, re), - {:ok, shared_secret} <- dh(state, e, re), + {:ok, shared_secret} <- Crypto.dh(re, e.private), {:ok, state} <- mix_key(state, shared_secret), {:ok, state, rs} <- decrypt_and_hash(state, encrypted_rs_and_tag), - {:ok, shared_secret} <- dh(state, e, rs), + {:ok, shared_secret} <- Crypto.dh(rs, e.private), {:ok, state} <- mix_key(state, shared_secret), {:ok, state, payload} <- decrypt_and_hash(state, encrypted_payload_and_tag) do {:ok, payload, %{state | re: re, rs: rs}} @@ -212,7 +178,7 @@ defmodule Ockam.SecureChannel.KeyEstablishmentProtocol.XX.Protocol do def decode(:message3, %{e: e} = state, message) do with {:ok, encrypted_rs_and_tag, encrypted_payload_and_tag} <- parse_message3(message), {:ok, state, rs} <- decrypt_and_hash(state, encrypted_rs_and_tag), - {:ok, shared_secret} <- dh(state, e, rs), + {:ok, shared_secret} <- Crypto.dh(rs, e.private), {:ok, state} <- mix_key(state, shared_secret), {:ok, state, payload} <- decrypt_and_hash(state, encrypted_payload_and_tag) do {:ok, payload, %{state | rs: rs}} @@ -234,52 +200,32 @@ defmodule Ockam.SecureChannel.KeyEstablishmentProtocol.XX.Protocol do def parse_message3(message), do: {:error, {:unexpected_structure, :message3, message}} - def mix_hash(%{vault: vault, h: h} = state, value) do - case Vault.sha256(vault, h <> value) do - {:ok, h} -> {:ok, %{state | h: h}} - error -> {:error, {:could_not_mix_hash, {state, value, error}}} - end + def mix_hash(%{h: h} = state, value) do + {:ok, %{state | h: Crypto.sha256(h <> value)}} end - def mix_key(%{vault: vault, ck: ck} = state, input_key_material) do - ck_attributes = %{type: :buffer, length: 32, persistence: :ephemeral} - k_attributes = %{type: :aes, length: 32, persistence: :ephemeral} - kdf_result = Vault.hkdf_sha256(vault, ck, input_key_material, [ck_attributes, k_attributes]) - - with {:ok, [ck, k]} <- kdf_result do + def mix_key(%{ck: ck} = state, input_key_material) do + with {ck, k} <- Crypto.hkdf(ck, input_key_material) do {:ok, %{state | n: 0, ck: ck, k: k}} end end - def dh(%{vault: vault}, keypair, peer_public) do - Vault.ecdh(vault, keypair.private, peer_public) - end - - def encrypt_and_hash(%{vault: vault, k: k, n: n, h: h} = state, plaintext) do - with {:ok, k} <- Vault.secret_export(vault, k), - {:ok, k} <- Vault.secret_import(vault, [type: :aes], k), - {:ok, ciphertext_and_tag} <- Vault.aead_aes_gcm_encrypt(vault, k, n, h, plaintext), - :ok <- Vault.secret_destroy(vault, k), + def encrypt_and_hash(%{k: k, n: n, h: h} = state, plaintext) do + with {:ok, ciphertext_and_tag} <- Crypto.aead_aes_gcm_encrypt(k, n, h, plaintext), {:ok, state} <- mix_hash(state, ciphertext_and_tag) do {:ok, %{state | n: n + 1}, ciphertext_and_tag} end end - def decrypt_and_hash(%{vault: vault, k: k, n: n, h: h} = state, ciphertext_and_tag) do - with {:ok, k} <- Vault.secret_export(vault, k), - {:ok, k} <- Vault.secret_import(vault, [type: :aes], k), - {:ok, plaintext} <- Vault.aead_aes_gcm_decrypt(vault, k, n, h, ciphertext_and_tag), - :ok <- Vault.secret_destroy(vault, k), + def decrypt_and_hash(%{k: k, n: n, h: h} = state, ciphertext_and_tag) do + with {:ok, plaintext} <- Crypto.aead_aes_gcm_decrypt(k, n, h, ciphertext_and_tag), {:ok, state} <- mix_hash(state, ciphertext_and_tag) do {:ok, %{state | n: n + 1}, plaintext} end end - def split(%{xx_key_establishment_state: %{vault: vault, ck: ck, h: h}} = data) do - k1_attributes = %{type: :aes, length: 32, persistence: :ephemeral} - k2_attributes = %{type: :aes, length: 32, persistence: :ephemeral} - - with {:ok, [k1, k2]} <- Vault.hkdf_sha256(vault, ck, [k1_attributes, k2_attributes]) do + def split(%{xx_key_establishment_state: %{ck: ck, h: h}} = data) do + with {k1, k2} <- Crypto.hkdf(ck) do {:ok, {k1, k2, h}, data} end end diff --git a/implementations/elixir/ockam/ockam/lib/ockam/vault.ex b/implementations/elixir/ockam/ockam/lib/ockam/vault.ex deleted file mode 100644 index cce3cfe79e1..00000000000 --- a/implementations/elixir/ockam/ockam/lib/ockam/vault.ex +++ /dev/null @@ -1,146 +0,0 @@ -defmodule Ockam.Vault do - @moduledoc false - - ## NIF functions always infer as any() - ## The types are useful for readability - @dialyzer [:no_contracts] - - @default_secret_attributes [type: :curve25519, persistence: :ephemeral, length: 32] - - @doc """ - Computes a SHA-256 hash based on input data. - """ - @spec sha256(Ockam.Vault, binary | String.t()) :: {:ok, binary} | :error - def sha256(%vault_module{id: vault_id}, input) do - vault_module.sha256(vault_id, input) - end - - @doc """ - Fills output_buffer with randomly generate bytes. - """ - @spec random_bytes(Ockam.Vault, binary) :: :error - def random_bytes(%vault_module{id: vault_id}, output_buffer) do - vault_module.random_bytes(vault_id, output_buffer) - end - - @doc """ - Generates an ockam secret. Attributes struct must specify the - configuration for the type of secret to generate. - """ - @spec secret_generate(Ockam.Vault, keyword()) :: {:ok, reference()} | :error - def secret_generate(%vault_module{id: vault_id}, attributes) when is_list(attributes) do - attributes = @default_secret_attributes |> Keyword.merge(attributes) |> Map.new() - vault_module.secret_generate(vault_id, attributes) - end - - @doc """ - Imports the specified data into the supplied ockam vault secret. - """ - @spec secret_import(Ockam.Vault, keyword(), binary) :: {:ok, reference()} | :error - def secret_import(%vault_module{id: vault_id}, attributes, input) when is_list(attributes) do - attributes = @default_secret_attributes |> Keyword.merge(attributes) |> Map.new() - vault_module.secret_import(vault_id, attributes, input) - end - - @doc """ - Exports data from an ockam vault secret into the supplied output buffer. - """ - @spec secret_export(Ockam.Vault, reference()) :: {:ok, binary} | :error - def secret_export(%vault_module{id: vault_id}, secret_handle) do - vault_module.secret_export(vault_id, secret_handle) - end - - @doc """ - Retrieves the public key from an ockam vault secret. - """ - @spec secret_publickey_get(Ockam.Vault, reference()) :: {:ok, reference()} | :error - def secret_publickey_get(%vault_module{id: vault_id}, secret_handle) do - vault_module.secret_publickey_get(vault_id, secret_handle) - end - - @doc """ - Retrieves the attributes for a specified secret - """ - @spec secret_attributes_get(Ockam.Vault, reference()) :: {:ok, keyword()} | :error - def secret_attributes_get(%vault_module{id: vault_id}, secret_handle) do - with {:ok, attributes} <- vault_module.secret_attributes_get(vault_id, secret_handle) do - {:ok, Map.to_list(attributes)} - end - end - - @doc """ - Deletes an ockam vault secret. - """ - @spec secret_destroy(Ockam.Vault, reference()) :: :ok | :error - def secret_destroy(%vault_module{id: vault_id}, secret_handle) do - vault_module.secret_destroy(vault_id, secret_handle) - end - - @doc """ - Performs an ECDH operation on the supplied ockam vault secret and peer_publickey. - The result is another ockam vault secret of type unknown. - """ - @spec ecdh(Ockam.Vault, reference(), binary) :: {:ok, reference()} | :error - def ecdh(%vault_module{id: vault_id}, secret_handle, peer_public_key) do - vault_module.ecdh(vault_id, secret_handle, peer_public_key) - end - - @doc """ - Performs an HMAC-SHA256 based key derivation function on the supplied salt and input - key material. - Returns handle to derived_output. - """ - @spec hkdf_sha256(Ockam.Vault, reference(), reference(), non_neg_integer()) :: - {:ok, reference()} | :error - def hkdf_sha256(%vault_module{id: vault_id}, salt_handle, ikm_handle, derived_outputs_count) do - vault_module.hkdf_sha256(vault_id, salt_handle, ikm_handle, derived_outputs_count) - end - - @doc """ - Performs an HMAC-SHA256 based key derivation function on the supplied salt and input key - material. - Returns handle to derived_output. - """ - @spec hkdf_sha256(Ockam.Vault, reference(), reference()) :: {:ok, reference()} | :error - def hkdf_sha256(%vault_module{id: vault_id}, salt_handle, ikm_handle) do - vault_module.hkdf_sha256(vault_id, salt_handle, ikm_handle) - end - - @doc """ - Encrypts a payload using AES-GCM. - Returns cipher_text after an encryption. - """ - @spec aead_aes_gcm_encrypt( - Ockam.Vault, - reference(), - non_neg_integer(), - String.t() | binary, - binary | String.t() - ) :: {:ok, binary} | :error - def aead_aes_gcm_encrypt(%vault_module{id: vault_id}, key_handle, nonce, ad, plain_text) do - vault_module.aead_aes_gcm_encrypt(vault_id, key_handle, nonce, ad, plain_text) - end - - @doc """ - Decrypts a payload using AES-GCM. - Returns decrypted payload. - """ - @spec aead_aes_gcm_decrypt( - Ockam.Vault, - reference(), - non_neg_integer(), - binary | String.t(), - binary - ) :: {:ok, binary | String.t()} | :error - def aead_aes_gcm_decrypt(%vault_module{id: vault_id}, key_handle, nonce, ad, cipher_text) do - vault_module.aead_aes_gcm_decrypt(vault_id, key_handle, nonce, ad, cipher_text) - end - - @doc """ - Deinitializes the specified ockam vault object. - """ - @spec deinit(Ockam.Vault) :: :ok | :error - def deinit(%vault_module{id: vault_id}) do - vault_module.deinit(vault_id) - end -end diff --git a/implementations/elixir/ockam/ockam/mix.exs b/implementations/elixir/ockam/ockam/mix.exs index 75e9211b72f..22c14971203 100644 --- a/implementations/elixir/ockam/ockam/mix.exs +++ b/implementations/elixir/ockam/ockam/mix.exs @@ -53,12 +53,12 @@ defmodule Ockam.MixProject do {:cbor, "~> 1.0.0"}, {:ockam_typed_cbor, path: "../ockam_typed_cbor"}, {:gen_state_machine, "~> 3.0"}, - {:ockam_vault_software, path: "../ockam_vault_software", optional: true}, {:telemetry, "~> 1.0", optional: true}, {:ranch, "~> 2.1", optional: true}, {:ex_doc, "~> 0.25", only: :dev, runtime: false}, {:credo, "~> 1.6", only: [:dev, :test], runtime: false}, - {:dialyxir, "~> 1.1", only: [:dev], runtime: false} + {:dialyxir, "~> 1.1", only: [:dev], runtime: false}, + {:ockly, path: "../ockly"} ] end diff --git a/implementations/elixir/ockam/ockam/mix.lock b/implementations/elixir/ockam/ockam/mix.lock index a95fa0b443d..3bc681d6bee 100644 --- a/implementations/elixir/ockam/ockam/mix.lock +++ b/implementations/elixir/ockam/ockam/mix.lock @@ -9,12 +9,15 @@ "ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "gen_state_machine": {:hex, :gen_state_machine, "3.0.0", "1e57f86a494e5c6b14137ebef26a7eb342b3b0070c7135f2d6768ed3f6b6cdff", [:mix], [], "hexpm", "0a59652574bebceb7309f6b749d2a41b45fdeda8dbb4da0791e355dd19f0ed15"}, + "hkdf_erlang": {:hex, :hkdf_erlang, "1.0.0", "0b681b428805eacf1286e02ece8617e843cdceae7adb5fad43c283c98088fbc2", [:rebar3], [], "hexpm", "d2091d9e0df97613fd149074929a94e3675047f28cca6b1626fe7be60f5d76ac"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, "ranch": {:hex, :ranch, "2.1.0", "2261f9ed9574dcfcc444106b9f6da155e6e540b2f82ba3d42b339b93673b72a3", [:make, :rebar3], [], "hexpm", "244ee3fa2a6175270d8e1fc59024fd9dbc76294a321057de8f803b1479e76916"}, + "rustler": {:git, "https://github.com/polvorin/rustler.git", "f6ead5802b3b8bf38b3c5fcaf0a0dbdf0e66a221", [branch: "fix_local_crate", sparse: "rustler_mix"]}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, "typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"}, } diff --git a/implementations/elixir/ockam/ockam/test/ockam/identity/identity_api_test.exs b/implementations/elixir/ockam/ockam/test/ockam/identity/identity_api_test.exs deleted file mode 100644 index 9d62bcee5f3..00000000000 --- a/implementations/elixir/ockam/ockam/test/ockam/identity/identity_api_test.exs +++ /dev/null @@ -1,77 +0,0 @@ -defmodule Ockam.Identity.API.Tests do - use ExUnit.Case - - alias Ockam.Identity.API.Request - alias Ockam.Identity.API.Response - - require Logger - - describe "Request" do - test "validate_identity_change_history" do - identity = "my identity" - data = Request.validate_identity_change_history(identity) - Logger.info("#{inspect(data)}") - {:ok, %{identity: ^identity}} = Request.decode_validate_identity_change_history(data) - end - - test "create_signature" do - identity = "my identity" - hash = "my hash" - data = Request.create_signature(nil, identity, hash) - Logger.info("#{inspect(data)}") - {:ok, %{identity: ^identity, state: ^hash}} = Request.decode_create_signature(data) - end - - test "verify_signature" do - identity = "my identity" - hash = "my hash" - proof = "my proof" - data = Request.verify_signature(identity, proof, hash) - Logger.info("#{inspect(data)}") - - {:ok, %{identity: ^identity, state: ^hash, proof: ^proof}} = - Request.decode_verify_signature(data) - end - - test "compare_identity_change_history" do - identity = "my identity" - known_identity = "other identity" - data = Request.compare_identity_change_history(identity, known_identity) - Logger.info("#{inspect(data)}") - - {:ok, %{identity: ^identity, known_identity: ^known_identity}} = - Request.decode_compare_identity_change_history(data) - end - end - - describe "Response" do - test "create" do - identity = "my identity" - identity_id = "my_id" - data = Response.encode_create(%{identity: identity, identity_id: identity_id}) - {:ok, %{identity: ^identity, identity_id: ^identity_id}} = Response.create(data) - end - - test "validate_identity_change_history" do - identity_id = "my_id" - data = Response.encode_validate_identity_change_history(%{identity_id: identity_id}) - {:ok, %{identity_id: ^identity_id}} = Response.validate_identity_change_history(data) - end - - test "create_signature" do - proof = "my proof" - data = Response.encode_create_signature(%{proof: proof}) - {:ok, %{proof: ^proof}} = Response.create_signature(data) - end - - test "verify_signature" do - data = Response.encode_verify_signature(%{verified: true}) - {:ok, %{verified: true}} = Response.verify_signature(data) - end - - test "compare_identity_change_history" do - data = Response.encode_compare_identity_change_history(:conflict) - {:ok, :conflict} = Response.compare_identity_change_history(data) - end - end -end diff --git a/implementations/elixir/ockam/ockam/test/ockam/identity_tests.exs b/implementations/elixir/ockam/ockam/test/ockam/identity_tests.exs new file mode 100644 index 00000000000..0cf38c99847 --- /dev/null +++ b/implementations/elixir/ockam/ockam/test/ockam/identity_tests.exs @@ -0,0 +1,34 @@ +defmodule Ockam.Identity.Tests do + use ExUnit.Case, async: true + doctest Ockam.Identity + alias Ockam.Identity + + @existing_secret <<83, 231, 139, 244, 109, 254, 138, 112, 211, 93, 197, 106, 173, 226, 235, 88, + 141, 218, 113, 168, 209, 229, 28, 241, 69, 249, 106, 70, 50, 54, 218, 217>> + @existing_identity <<129, 162, 1, 88, 59, 162, 1, 1, 2, 88, 53, 164, 2, 130, 1, 129, 88, 32, 83, + 241, 75, 224, 25, 93, 231, 146, 168, 52, 2, 192, 228, 60, 198, 200, 216, + 60, 101, 169, 165, 128, 75, 221, 124, 29, 3, 224, 11, 89, 124, 70, 3, 244, + 4, 26, 100, 248, 141, 178, 5, 26, 119, 196, 144, 178, 2, 130, 1, 129, 88, + 64, 236, 140, 158, 157, 188, 146, 79, 243, 149, 182, 13, 3, 100, 174, 45, + 5, 37, 208, 240, 3, 205, 7, 29, 61, 74, 44, 28, 166, 51, 161, 201, 36, 211, + 72, 21, 1, 200, 238, 124, 183, 24, 26, 236, 66, 106, 172, 219, 61, 169, + 171, 103, 167, 2, 40, 11, 183, 202, 162, 217, 237, 91, 244, 59, 1>> + @expected_identifier "I31f064878eb4fc0852d55a0fbb7305270b8fa1d7" + + describe "Ockam.Identity.import/2" do + test "existing identity can be imported and used" do + {:ok, identity, identifier} = Identity.import(@existing_identity, @existing_secret) + assert identifier == @expected_identifier + {:ok, keypair} = Ockam.SecureChannel.Crypto.generate_dh_keypair() + {:ok, attestation} = Identity.attest_purpose_key(identity, keypair) + {:ok, true} = Identity.verify_purpose_key_attestation(identity, keypair.public, attestation) + end + end + + describe "Ockam.Identity.create/1" do + test "identity can be created with explicit key" do + {_pub, secret} = :crypto.generate_key(:eddsa, :ed25519) + {:ok, identity} = Identity.create(secret) + end + end +end diff --git a/implementations/elixir/ockam/ockam/test/ockam/secure_channel/encrypted_transport_protocol/aead_aes_gcm_test.exs b/implementations/elixir/ockam/ockam/test/ockam/secure_channel/encrypted_transport_protocol/aead_aes_gcm_test.exs index 15cb2d7a3d7..30c168474a6 100644 --- a/implementations/elixir/ockam/ockam/test/ockam/secure_channel/encrypted_transport_protocol/aead_aes_gcm_test.exs +++ b/implementations/elixir/ockam/ockam/test/ockam/secure_channel/encrypted_transport_protocol/aead_aes_gcm_test.exs @@ -3,20 +3,11 @@ defmodule Ockam.SecureChannel.EncryptedTransportProtocol.AeadAesGcmTests do alias Ockam.SecureChannel.EncryptedTransportProtocol.AeadAesGcm.Decryptor alias Ockam.SecureChannel.EncryptedTransportProtocol.AeadAesGcm.Encryptor - alias Ockam.Vault - alias Ockam.Vault.Software, as: SoftwareVault test "normal flow" do - # We can't share the _same_ k between encryptor and decryptor on the same vault, as when the encryptor - # rotate the key, it destroy the old k. But that might still be used by the decryptor to decrypt yet-to-be - # delivered packets. - {:ok, encryptor_vault} = SoftwareVault.init() - {:ok, decryptor_vault} = SoftwareVault.init() shared_k = :crypto.strong_rand_bytes(32) - {:ok, ke} = Vault.secret_import(encryptor_vault, [type: :aes], shared_k) - {:ok, kd} = Vault.secret_import(decryptor_vault, [type: :aes], shared_k) - encryptor = Encryptor.new(encryptor_vault, ke, 0) - decryptor = Decryptor.new(decryptor_vault, kd, 0) + encryptor = Encryptor.new(shared_k, 0) + decryptor = Decryptor.new(shared_k, 0) Enum.reduce(0..200, {encryptor, decryptor}, fn _i, {encryptor, decryptor} -> plain = :crypto.strong_rand_bytes(64) @@ -27,13 +18,9 @@ defmodule Ockam.SecureChannel.EncryptedTransportProtocol.AeadAesGcmTests do end test "message lost" do - {:ok, encryptor_vault} = SoftwareVault.init() - {:ok, decryptor_vault} = SoftwareVault.init() shared_k = :crypto.strong_rand_bytes(32) - {:ok, ke} = Vault.secret_import(encryptor_vault, [type: :aes], shared_k) - {:ok, kd} = Vault.secret_import(decryptor_vault, [type: :aes], shared_k) - encryptor = Encryptor.new(encryptor_vault, ke, 0, 32) - decryptor = Decryptor.new(decryptor_vault, kd, 0, 32) + encryptor = Encryptor.new(shared_k, 0, 32) + decryptor = Decryptor.new(shared_k, 0, 32) Enum.reduce(0..200, {encryptor, decryptor}, fn i, {encryptor, decryptor} -> plain = :crypto.strong_rand_bytes(64) @@ -52,13 +39,9 @@ defmodule Ockam.SecureChannel.EncryptedTransportProtocol.AeadAesGcmTests do # We can't share the _same_ k between encryptor and decryptor on the same vault, as when the encryptor # rotate the key, it destroy the old k. But that might still be used by the decryptor to decrypt yet-to-be # delivered packets. - {:ok, encryptor_vault} = SoftwareVault.init() - {:ok, decryptor_vault} = SoftwareVault.init() shared_k = :crypto.strong_rand_bytes(32) - {:ok, ke} = Vault.secret_import(encryptor_vault, [type: :aes], shared_k) - {:ok, kd} = Vault.secret_import(decryptor_vault, [type: :aes], shared_k) - encryptor = Encryptor.new(encryptor_vault, ke, 0, 32) - decryptor = Decryptor.new(decryptor_vault, kd, 0, 32) + encryptor = Encryptor.new(shared_k, 0, 32) + decryptor = Decryptor.new(shared_k, 0, 32) {msgs, encryptor} = Enum.reduce(0..1000, {[], encryptor}, fn _i, {acc, encryptor} -> @@ -91,14 +74,10 @@ defmodule Ockam.SecureChannel.EncryptedTransportProtocol.AeadAesGcmTests do test "out of order, exact sliding window" do # Test values taken from nonce_tracker.rs test case - {:ok, encryptor_vault} = SoftwareVault.init() - {:ok, decryptor_vault} = SoftwareVault.init() shared_k = :crypto.strong_rand_bytes(32) - {:ok, ke} = Vault.secret_import(encryptor_vault, [type: :aes], shared_k) - {:ok, kd} = Vault.secret_import(decryptor_vault, [type: :aes], shared_k) key_renewal_interval = 32 - encryptor = Encryptor.new(encryptor_vault, ke, 0, key_renewal_interval) - decryptor = Decryptor.new(decryptor_vault, kd, 0, key_renewal_interval) + encryptor = Encryptor.new(shared_k, 0, key_renewal_interval) + decryptor = Decryptor.new(shared_k, 0, key_renewal_interval) {msgs, _encryptor} = Enum.reduce(0..(key_renewal_interval * 5), {[], encryptor}, fn _i, {acc, encryptor} -> @@ -118,13 +97,13 @@ defmodule Ockam.SecureChannel.EncryptedTransportProtocol.AeadAesGcmTests do {_, ciphertext} = Enum.at(msgs, 0) {:error, _} = Decryptor.decrypt(<<>>, ciphertext, decryptor) - {plaintext, ciphertext} = Enum.at(msgs, key_renewal_interval + 2) + {_plaintext, ciphertext} = Enum.at(msgs, key_renewal_interval + 2) {:error, _} = Decryptor.decrypt(<<>>, ciphertext, decryptor) {plaintext, ciphertext} = Enum.at(msgs, key_renewal_interval + 1) {:ok, ^plaintext, decryptor} = Decryptor.decrypt(<<>>, ciphertext, decryptor) - {plaintext, ciphertext} = Enum.at(msgs, 1) + {_plaintext, ciphertext} = Enum.at(msgs, 1) {:error, _} = Decryptor.decrypt(<<>>, ciphertext, decryptor) {plaintext, ciphertext} = Enum.at(msgs, key_renewal_interval + 2) @@ -132,15 +111,15 @@ defmodule Ockam.SecureChannel.EncryptedTransportProtocol.AeadAesGcmTests do {plaintext, ciphertext} = Enum.at(msgs, key_renewal_interval + 3) {:ok, ^plaintext, decryptor} = Decryptor.decrypt(<<>>, ciphertext, decryptor) - {plaintext, ciphertext} = Enum.at(msgs, key_renewal_interval + 1) + {_plaintext, ciphertext} = Enum.at(msgs, key_renewal_interval + 1) {:error, _} = Decryptor.decrypt(<<>>, ciphertext, decryptor) - {plaintext, ciphertext} = Enum.at(msgs, key_renewal_interval + 2) + {_plaintext, ciphertext} = Enum.at(msgs, key_renewal_interval + 2) {:error, _} = Decryptor.decrypt(<<>>, ciphertext, decryptor) {plaintext, ciphertext} = Enum.at(msgs, 2 * key_renewal_interval) {:ok, ^plaintext, decryptor} = Decryptor.decrypt(<<>>, ciphertext, decryptor) - {plaintext, ciphertext} = Enum.at(msgs, key_renewal_interval - 1) + {_plaintext, ciphertext} = Enum.at(msgs, key_renewal_interval - 1) {:error, _} = Decryptor.decrypt(<<>>, ciphertext, decryptor) {plaintext, ciphertext} = Enum.at(msgs, 3 * key_renewal_interval) @@ -150,14 +129,14 @@ defmodule Ockam.SecureChannel.EncryptedTransportProtocol.AeadAesGcmTests do decryptor = Enum.reduce((3 * key_renewal_interval + 1)..(4 * key_renewal_interval - 1), decryptor, fn i, - encryptor -> + decryptor -> {plaintext, ciphertext} = Enum.at(msgs, i) {:ok, ^plaintext, decryptor} = Decryptor.decrypt(<<>>, ciphertext, decryptor) decryptor end) Enum.reduce((4 * key_renewal_interval + 1)..(5 * key_renewal_interval), decryptor, fn i, - encryptor -> + decryptor -> {plaintext, ciphertext} = Enum.at(msgs, i) {:ok, ^plaintext, decryptor} = Decryptor.decrypt(<<>>, ciphertext, decryptor) decryptor diff --git a/implementations/elixir/ockam/ockam/test/ockam/secure_channel/key_establishment_protocol/xx/protocol_test.exs b/implementations/elixir/ockam/ockam/test/ockam/secure_channel/key_establishment_protocol/xx/protocol_test.exs index 97f80b06624..0fcbdff770b 100644 --- a/implementations/elixir/ockam/ockam/test/ockam/secure_channel/key_establishment_protocol/xx/protocol_test.exs +++ b/implementations/elixir/ockam/ockam/test/ockam/secure_channel/key_establishment_protocol/xx/protocol_test.exs @@ -3,8 +3,6 @@ defmodule Ockam.SecureChannel.KeyEstablishmentProtocol.XX.Protocol.Tests do doctest Ockam.SecureChannel.KeyEstablishmentProtocol.XX.Protocol alias Ockam.SecureChannel.KeyEstablishmentProtocol.XX.Protocol - alias Ockam.Vault - alias Ockam.Vault.Software, as: SoftwareVault @test_case1 %{ initiator_static: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", @@ -43,8 +41,6 @@ defmodule Ockam.SecureChannel.KeyEstablishmentProtocol.XX.Protocol.Tests do |> Enum.map(fn {k, v} -> {k, Base.decode16!(v, case: :lower)} end) |> Enum.into(%{}) - {:ok, vault} = SoftwareVault.init() - keypairs = [ :initiator_static, :initiator_ephemeral, @@ -55,21 +51,18 @@ defmodule Ockam.SecureChannel.KeyEstablishmentProtocol.XX.Protocol.Tests do test_case = Enum.reduce(keypairs, test_case, fn k, test_case -> private_key = Map.get(test_case, k) - {:ok, private} = Vault.secret_import(vault, [type: :curve25519], private_key) - {:ok, public} = Vault.secret_publickey_get(vault, private) - %{test_case | k => %{private: private, public: public}} + {public_key, ^private_key} = :crypto.generate_key(:ecdh, :x25519, private_key) + %{test_case | k => %{private: private_key, public: public_key}} end) {:ok, initiator_state} = Protocol.setup(test_case.initiator_static, - vault: vault, ephemeral_keypair: test_case.initiator_ephemeral, payloads: %{message1: test_case.message_1_payload, message3: test_case.message_3_payload} ) {:ok, responder_state} = Protocol.setup(test_case.responder_static, - vault: vault, ephemeral_keypair: test_case.responder_ephemeral, payloads: %{message2: test_case.message_2_payload} ) @@ -92,8 +85,8 @@ defmodule Ockam.SecureChannel.KeyEstablishmentProtocol.XX.Protocol.Tests do {:ok, {:complete, {k1_r, k2_r, h_r, _rs, p_r}}} = Protocol.in_payload(responder_state, message_3_ciphertext) - assert Vault.secret_export(vault, k1_i) == Vault.secret_export(vault, k1_r) - assert Vault.secret_export(vault, k2_i) == Vault.secret_export(vault, k2_r) + assert k1_i == k1_r + assert k2_i == k2_r assert h_i == h_r assert message_1_ciphertext == test_case.message_1_ciphertext assert message_2_ciphertext == test_case.message_2_ciphertext diff --git a/implementations/elixir/ockam/ockam/test/ockam/secure_channel_test.exs b/implementations/elixir/ockam/ockam/test/ockam/secure_channel_test.exs index 270dc05e9c7..1b95a0688cc 100644 --- a/implementations/elixir/ockam/ockam/test/ockam/secure_channel_test.exs +++ b/implementations/elixir/ockam/ockam/test/ockam/secure_channel_test.exs @@ -8,21 +8,20 @@ defmodule Ockam.SecureChannel.Tests do alias Ockam.Node alias Ockam.Router alias Ockam.SecureChannel + alias Ockam.SecureChannel.Crypto alias Ockam.Tests.Helpers.Echoer - alias Ockam.Vault - alias Ockam.Vault.Software, as: SoftwareVault @identity_impl Ockam.Identity.Stub setup do Node.register_address("test") - {:ok, alice, alice_id} = Identity.create(@identity_impl) - {:ok, bob, bob_id} = Identity.create(@identity_impl) + {:ok, alice} = Identity.create() + {:ok, bob} = Identity.create() on_exit(fn -> Node.unregister_address("test") end) # TODO: rework the relationship on credential exchange API, attribute storage and secure channel :ok = AttributeStorage.init() - {:ok, alice: alice, alice_id: alice_id, bob: bob, bob_id: bob_id} + {:ok, alice: alice, bob: bob} end defp man_in_the_middle(callback) do @@ -252,32 +251,20 @@ defmodule Ockam.SecureChannel.Tests do 10_000 end - test "local secure channel", %{alice: alice, alice_id: alice_id, bob: bob, bob_id: bob_id} do - {:ok, vault} = SoftwareVault.init() + test "local secure channel", %{alice: alice, bob: bob} do + {:ok, listener} = create_secure_channel_listener(alice) - {:ok, listener} = - SecureChannel.create_listener( - identity: alice, - encryption_options: [vault: vault] - ) - - {:ok, channel} = - SecureChannel.create_channel( - [ - identity: bob, - encryption_options: [vault: vault], - route: [listener] - ], - 3000 - ) + {:ok, channel} = create_secure_channel([listener], bob) channel_pid = Ockam.Node.whereis(channel) ref1 = Process.monitor(channel_pid) assert {:ok, alice} == SecureChannel.get_remote_identity(channel) - assert {:ok, alice_id} == SecureChannel.get_remote_identity_id(channel) - assert {:ok, alice, alice_id} == SecureChannel.get_remote_identity_with_id(channel) + assert {:ok, Identity.get_identifier(alice)} == SecureChannel.get_remote_identity_id(channel) + + assert {:ok, alice, Identity.get_identifier(alice)} == + SecureChannel.get_remote_identity_with_id(channel) {:ok, me} = Ockam.Node.register_random_address() Ockam.Router.route("PING!", [channel, me], [me]) @@ -289,7 +276,7 @@ defmodule Ockam.SecureChannel.Tests do local_metadata: %{identity_id: id, identity: _identity, channel: :secure_channel} } - assert id == bob_id + assert id == Identity.get_identifier(bob) # Hacky way to get the receiver' pid, so we can monitor it and ensure it get terminated # after disconnection @@ -306,7 +293,7 @@ defmodule Ockam.SecureChannel.Tests do local_metadata: %{identity_id: id, identity: _identity, channel: :secure_channel} } - assert id == alice_id + assert id == Identity.get_identifier(alice) SecureChannel.disconnect(channel) assert_receive {:DOWN, ^ref1, _, _, _} @@ -317,20 +304,10 @@ defmodule Ockam.SecureChannel.Tests do ## Inner address is the one pointing to the other peer. ## This just test that it don't pass messages around, as ## the message will fail to be decrypted - {:ok, vault} = SoftwareVault.init() - {:ok, listener} = - SecureChannel.create_listener( - identity: alice, - encryption_options: [vault: vault] - ) + {:ok, listener} = create_secure_channel_listener(alice) - {:ok, channel} = - SecureChannel.create_channel( - identity: bob, - encryption_options: [vault: vault], - route: [listener] - ) + {:ok, channel} = create_secure_channel([listener], bob) {:ok, bob_inner_address} = Ockam.AsymmetricWorker.get_inner_address(channel) @@ -345,22 +322,8 @@ defmodule Ockam.SecureChannel.Tests do end test "additional metadata", %{alice: alice, bob: bob} do - {:ok, vault} = SoftwareVault.init() - - {:ok, listener} = - SecureChannel.create_listener( - identity: alice, - encryption_options: [vault: vault], - additional_metadata: %{foo: :bar} - ) - - {:ok, channel} = - SecureChannel.create_channel( - identity: bob, - encryption_options: [vault: vault], - route: [listener], - additional_metadata: %{bar: :foo} - ) + {:ok, listener} = create_secure_channel_listener(alice, %{foo: :bar}) + {:ok, channel} = create_secure_channel([listener], bob, %{bar: :foo}) {:ok, me} = Ockam.Node.register_random_address() Ockam.Router.route("PING!", [channel, me], [me]) @@ -393,21 +356,17 @@ defmodule Ockam.SecureChannel.Tests do end test "initiator trust policy", %{alice: alice, bob: bob} do - {:ok, vault} = SoftwareVault.init() + {:ok, listener} = create_secure_channel_listener(alice, %{foo: :bar}) - {:ok, listener} = - SecureChannel.create_listener( - identity: alice, - encryption_options: [vault: vault], - additional_metadata: %{foo: :bar} - ) + {:ok, keypair} = Crypto.generate_dh_keypair() + attestation = Identity.attest_purpose_key(bob, keypair) {:error, _reason} = SecureChannel.create_channel( [ identity: bob, - encryption_options: [vault: vault], route: [listener], + encryption_options: [static_keypair: keypair, static_key_attestation: attestation], additional_metadata: %{bar: :foo}, trust_policies: [fn _me, _contact -> {:error, :test} end] ], @@ -416,23 +375,18 @@ defmodule Ockam.SecureChannel.Tests do end test "responder trust policy", %{alice: alice, bob: bob} do - {:ok, vault} = SoftwareVault.init() + {:ok, keypair} = Crypto.generate_dh_keypair() + {:ok, attestation} = Identity.attest_purpose_key(alice, keypair) {:ok, listener} = SecureChannel.create_listener( identity: alice, - encryption_options: [vault: vault], + encryption_options: [static_keypair: keypair, static_key_attestation: attestation], additional_metadata: %{foo: :bar}, trust_policies: [fn _me, _contact -> {:error, :test} end] ) - {:ok, channel} = - SecureChannel.create_channel( - identity: bob, - encryption_options: [vault: vault], - route: [listener], - additional_metadata: %{bar: :foo} - ) + {:ok, channel} = create_secure_channel([listener], bob, %{bar: :foo}) {:ok, me} = Ockam.Node.register_random_address() Ockam.Router.route("PING!", [channel, me], [me]) @@ -443,89 +397,41 @@ defmodule Ockam.SecureChannel.Tests do } end - test "dynamic identity" do - {:ok, listener} = SecureChannel.create_listener(identity: :dynamic) - - {:ok, channel} = - SecureChannel.create_channel( - identity: :dynamic, - route: [listener] - ) - - {:ok, me} = Ockam.Node.register_random_address() - Ockam.Router.route("PING!", [channel, me], [me]) - - assert_receive %Ockam.Message{ - onward_route: [^me], - payload: "PING!", - return_route: [_channel, ^me], - local_metadata: %{identity_id: _id, identity: _identity, channel: :secure_channel} - } - end - - defmodule FakeVerifier do - @moduledoc """ - Just for testing purposes. - """ - - alias Ockam.Credential.AttributeSet - alias Ockam.Credential.AttributeSet.Attributes - - def verify(credential, identity_id, authorities) do - with {:credential, authority_id, ^identity_id, attributes, expiration} <- - :erlang.binary_to_term(credential), - {:ok, _} <- Map.fetch(authorities, authority_id) do - {:ok, - %AttributeSet{attributes: %Attributes{attributes: attributes}, expiration: expiration}} - else - _other -> - {:error, :rejected} - end - end - - def credential(subject_id, authority, attributes, expiration) do - with {:ok, authority_id} <- Identity.validate_identity_change_history(authority) do - {:ok, - :erlang.term_to_binary({:credential, authority_id, subject_id, attributes, expiration})} - end - end - end - test "credential in handshake accepted", %{ alice: alice, - bob: bob, - bob_id: bob_id, - alice_id: alice_id + bob: bob } do - {:ok, vault} = SoftwareVault.init() - - {:ok, authority, _authority_id} = Identity.create(@identity_impl) + {:ok, authority} = Identity.create() alice_attributes = %{"role" => "server"} - expiration = System.os_time(:second) + 100 + alice_id = Identity.get_identifier(alice) + bob_id = Identity.get_identifier(bob) + {:ok, keypair} = Crypto.generate_dh_keypair() + {:ok, attestation} = Identity.attest_purpose_key(alice, keypair) {:ok, alice_credential} = - FakeVerifier.credential(alice_id, authority, alice_attributes, expiration) + Identity.issue_credential(authority, alice_id, alice_attributes, 100) {:ok, listener} = SecureChannel.create_listener( identity: alice, - encryption_options: [vault: vault], - credential_verifier: {FakeVerifier, [authority]}, + encryption_options: [static_keypair: keypair, static_key_attestation: attestation], + authorities: [authority], credentials: [alice_credential] ) bob_attributes = %{"role" => "member"} - expiration = System.os_time(:second) + 100 - {:ok, bob_credential} = FakeVerifier.credential(bob_id, authority, bob_attributes, expiration) + {:ok, keypair} = Crypto.generate_dh_keypair() + {:ok, attestation} = Identity.attest_purpose_key(bob, keypair) + {:ok, bob_credential} = Identity.issue_credential(authority, bob_id, bob_attributes, 100) {:ok, channel} = SecureChannel.create_channel( [ identity: bob, - encryption_options: [vault: vault], + encryption_options: [static_keypair: keypair, static_key_attestation: attestation], route: [listener], - credential_verifier: {FakeVerifier, [authority]}, + authorities: [authority], credentials: [bob_credential] ], 3000 @@ -552,47 +458,45 @@ defmodule Ockam.SecureChannel.Tests do # Secure channel is terminated if we present invalid credential # Credential by unknown authority - {:ok, wrong_authority, _authority_id} = Identity.create(@identity_impl) + {:ok, wrong_authority} = Identity.create() attributes = %{"role" => "attacker"} - expiration = System.os_time(:second) + 100 - {:ok, wrong_credential} = - FakeVerifier.credential(bob_id, wrong_authority, attributes, expiration) + {:ok, wrong_credential} = Identity.issue_credential(wrong_authority, bob_id, attributes, 100) - {:ok, channel} = + # {:ok, channel} = + {:error, _} = SecureChannel.create_channel( [ identity: bob, - encryption_options: [vault: vault], + encryption_options: [static_keypair: keypair, static_key_attestation: attestation], route: [listener], credentials: [wrong_credential], - credential_verifier: {FakeVerifier, [authority]} + authority: [authority] ], 1000 ) - Ockam.Router.route("PING!", [channel, me], [me]) + # Ockam.Router.route("PING!", [channel, me], [me]) - refute_receive %Ockam.Message{ - onward_route: [^me], - payload: "PING!", - return_route: [_channel, ^me], - local_metadata: %{identity_id: ^bob_id, channel: :secure_channel} - } + # refute_receive %Ockam.Message{ + # onward_route: [^me], + # payload: "PING!", + # return_route: [_channel, ^me], + # local_metadata: %{identity_id: ^bob_id, channel: :secure_channel} + # } # Credential for another identifier attributes = %{"role" => "attacker"} - expiration = System.os_time(:second) + 100 - {:ok, wrong_credential} = FakeVerifier.credential(alice_id, authority, attributes, expiration) + {:ok, wrong_credential} = Identity.issue_credential(authority, alice_id, attributes, 100) {:ok, channel} = SecureChannel.create_channel( [ identity: bob, - encryption_options: [vault: vault], + encryption_options: [static_keypair: keypair, static_key_attestation: attestation], route: [listener], credentials: [wrong_credential], - credential_verifier: {FakeVerifier, [authority]} + authorities: [authority] ], 1000 ) @@ -611,35 +515,53 @@ defmodule Ockam.SecureChannel.Tests do SecureChannel.create_channel( [ identity: bob, - encryption_options: [vault: vault], + encryption_options: [static_keypair: keypair, static_key_attestation: attestation], route: [listener], - credential_verifier: {FakeVerifier, [wrong_authority]} + authorities: [wrong_authority] ], 1000 ) end defp create_secure_channel_listener() do - {:ok, vault} = SoftwareVault.init() - {:ok, keypair} = Vault.secret_generate(vault, type: :curve25519) - {:ok, identity, _identity_id} = Identity.create(@identity_impl) + {:ok, identity} = Identity.create() + create_secure_channel_listener(identity) + end + + defp create_secure_channel_listener(identity) do + create_secure_channel_listener(identity, %{}) + end + + defp create_secure_channel_listener(identity, additional_metadata) do + {:ok, keypair} = Crypto.generate_dh_keypair() + {:ok, attestation} = Identity.attest_purpose_key(identity, keypair) SecureChannel.create_listener( identity: identity, - encryption_options: [vault: vault, static_keypair: keypair] + encryption_options: [static_keypair: keypair, static_key_attestation: attestation], + additional_metadata: additional_metadata ) end defp create_secure_channel(route_to_listener) do - {:ok, vault} = SoftwareVault.init() - {:ok, keypair} = Vault.secret_generate(vault, type: :curve25519) - {:ok, identity, _identity_id} = Identity.create(@identity_impl) + {:ok, identity} = Identity.create() + create_secure_channel(route_to_listener, identity) + end + + defp create_secure_channel(route_to_listener, identity) do + create_secure_channel(route_to_listener, identity, %{}) + end + + defp create_secure_channel(route_to_listener, identity, additional_metadata) do + {:ok, keypair} = Crypto.generate_dh_keypair() + {:ok, attestation} = Identity.attest_purpose_key(identity, keypair) {:ok, c} = SecureChannel.create_channel( identity: identity, route: route_to_listener, - encryption_options: [vault: vault, static_keypair: keypair] + encryption_options: [static_keypair: keypair, static_key_attestation: attestation], + additional_metadata: additional_metadata ) {:ok, c} diff --git a/implementations/elixir/ockam/ockam_abac/mix.lock b/implementations/elixir/ockam/ockam_abac/mix.lock index b57fba2e395..43ccd52c6b3 100644 --- a/implementations/elixir/ockam/ockam_abac/mix.lock +++ b/implementations/elixir/ockam/ockam_abac/mix.lock @@ -9,11 +9,14 @@ "ex_doc": {:hex, :ex_doc, "0.30.2", "7a3e63ddb387746925bbbbcf6e9cb00e43c757cc60359a2b40059aea573e3e57", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5ba8cb61d069012f16b50e575b0e3e6cf4083935f7444fab0d92c9314ce86bb6"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "gen_state_machine": {:hex, :gen_state_machine, "3.0.0", "1e57f86a494e5c6b14137ebef26a7eb342b3b0070c7135f2d6768ed3f6b6cdff", [:mix], [], "hexpm", "0a59652574bebceb7309f6b749d2a41b45fdeda8dbb4da0791e355dd19f0ed15"}, + "hkdf_erlang": {:hex, :hkdf_erlang, "1.0.0", "0b681b428805eacf1286e02ece8617e843cdceae7adb5fad43c283c98088fbc2", [:rebar3], [], "hexpm", "d2091d9e0df97613fd149074929a94e3675047f28cca6b1626fe7be60f5d76ac"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, "neotoma": {:git, "https://github.com/seancribbs/neotoma.git", "9e57d8ebd4ebb02c3e2428b08f3a01e2ff834ce2", []}, "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, + "rustler": {:git, "https://github.com/polvorin/rustler.git", "f6ead5802b3b8bf38b3c5fcaf0a0dbdf0e66a221", [branch: "fix_local_crate", sparse: "rustler_mix"]}, + "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, "typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"}, } diff --git a/implementations/elixir/ockam/ockam_cloud_node/mix.lock b/implementations/elixir/ockam/ockam_cloud_node/mix.lock index e969170490b..8e605d9ec0f 100644 --- a/implementations/elixir/ockam/ockam_cloud_node/mix.lock +++ b/implementations/elixir/ockam/ockam_cloud_node/mix.lock @@ -20,6 +20,7 @@ "gettext": {:hex, :gettext, "0.19.1", "564953fd21f29358e68b91634799d9d26989f8d039d7512622efb3c3b1c97892", [:mix], [], "hexpm", "10c656c0912b8299adba9b061c06947511e3f109ab0d18b44a866a4498e77222"}, "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, "hex2bin": {:hex, :hex2bin, "1.0.0", "aac26eab998ae80eacee1c7607c629ab503ebf77a62b9242bae2b94d47dcb71e", [:rebar3], [], "hexpm", "e7012d1d9aadd26e680f0983d26fb8923707f05fac9688f19f530fa3795e716f"}, + "hkdf_erlang": {:hex, :hkdf_erlang, "1.0.0", "0b681b428805eacf1286e02ece8617e843cdceae7adb5fad43c283c98088fbc2", [:rebar3], [], "hexpm", "d2091d9e0df97613fd149074929a94e3675047f28cca6b1626fe7be60f5d76ac"}, "httpoison": {:hex, :httpoison, "2.1.0", "655fd9a7b0b95ee3e9a3b535cf7ac8e08ef5229bab187fa86ac4208b122d934b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "fc455cb4306b43827def4f57299b2d5ac8ac331cb23f517e734a4b78210a160c"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, @@ -38,6 +39,7 @@ "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"}, "poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"}, "ranch": {:hex, :ranch, "2.1.0", "2261f9ed9574dcfcc444106b9f6da155e6e540b2f82ba3d42b339b93673b72a3", [:make, :rebar3], [], "hexpm", "244ee3fa2a6175270d8e1fc59024fd9dbc76294a321057de8f803b1479e76916"}, + "rustler": {:git, "https://github.com/polvorin/rustler.git", "f6ead5802b3b8bf38b3c5fcaf0a0dbdf0e66a221", [branch: "fix_local_crate", sparse: "rustler_mix"]}, "sched_ex": {:hex, :sched_ex, "1.1.4", "893de8ffcb1590ae6089d9862d49447fbb948535ba5777c231e55c8404bc3e6e", [:mix], [{:crontab, "~> 1.1.2", [hex: :crontab, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "f32336c40a62aba581c57d6d6009e40e5c52011bb8305fc3a33ef9e5815b861c"}, "snappyer": {:hex, :snappyer, "1.2.9", "9cc58470798648ce34c662ca0aa6daae31367667714c9a543384430a3586e5d3", [:rebar3], [], "hexpm", "18d00ca218ae613416e6eecafe1078db86342a66f86277bd45c95f05bf1c8b29"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, @@ -48,6 +50,7 @@ "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.1.0", "4e15f6d7dbedb3a4e3aed2262b7e1407f166fcb9c30ca3f96635dfbbef99965c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0dd10e7fe8070095df063798f82709b0a1224c31b8baf6278b423898d591a069"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, "timex": {:hex, :timex, "3.7.7", "3ed093cae596a410759104d878ad7b38e78b7c2151c6190340835515d4a46b8a", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "0ec4b09f25fe311321f9fc04144a7e3affe48eb29481d7a5583849b6c4dfa0a7"}, + "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, "typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"}, "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, diff --git a/implementations/elixir/ockam/ockam_healthcheck/config/runtime.exs b/implementations/elixir/ockam/ockam_healthcheck/config/runtime.exs index 625f50f62f9..0254e534b65 100644 --- a/implementations/elixir/ockam/ockam_healthcheck/config/runtime.exs +++ b/implementations/elixir/ockam/ockam_healthcheck/config/runtime.exs @@ -1,31 +1,5 @@ import Config -## Ockam identity config - -identity_module = - case System.get_env("IDENTITY_IMPLEMENTATION", "") do - "sidecar" -> - Ockam.Identity.Sidecar - - "stub" -> - Ockam.Identity.Stub - - "" -> - case Mix.env() do - :test -> - Ockam.Identity.Stub - - _other -> - Ockam.Identity.Sidecar - end - - other -> - IO.puts(:stderr, "Unknown identity implementation: #{inspect(other)}") - exit(:invalid_config) - end - -config :ockam, identity_module: identity_module - ## Metrics config # PROMETHEUS_PORT must be set for prometheus metrics to be enabled @@ -45,34 +19,6 @@ config :logger, :console, metadata: [:module, :line, :pid], format_string: "$dateT$time $metadata[$level] $message\n" -## Services config - -sidecar_host = System.get_env("OCKAM_SIDECAR_HOST", "localhost") -sidecar_port = String.to_integer(System.get_env("OCKAM_SIDECAR_PORT", "4100")) - -identity_sidecar_services = - case identity_module do - Ockam.Identity.Sidecar -> - [ - identity_sidecar: [ - authorization: [:is_local], - sidecar_host: sidecar_host, - sidecar_port: sidecar_port - ] - ] - - _ -> - [] - end - -config :ockam_services, - service_providers: [ - # sidecar services - Ockam.Services.Provider.Sidecar - ], - ## Start services by default - services: identity_sidecar_services - ## Healthcheck targets config targets_config = System.get_env("HEALTHCHECK_TARGETS", "[]") @@ -100,9 +46,11 @@ identity_source = end identity_file = System.get_env("HEALTHCHECK_IDENTITY_FILE") +identity_signing_key_file = System.get_env("HEALTHCHECK_IDENTITY_SIGNING_KEY_FILE") config :ockam_healthcheck, targets: targets, identity_source: identity_source, identity_file: identity_file, + identity_signing_key_file: identity_signing_key_file, identity_function: &Ockam.Healthcheck.get_identity/0 diff --git a/implementations/elixir/ockam/ockam_healthcheck/config/test.exs b/implementations/elixir/ockam/ockam_healthcheck/config/test.exs index f112eee2b6b..becde76932f 100644 --- a/implementations/elixir/ockam/ockam_healthcheck/config/test.exs +++ b/implementations/elixir/ockam/ockam_healthcheck/config/test.exs @@ -1,3 +1 @@ import Config - -config :ockam, identity_module: Ockam.Identity.Stub diff --git a/implementations/elixir/ockam/ockam_healthcheck/lib/healthcheck/healthcheck.ex b/implementations/elixir/ockam/ockam_healthcheck/lib/healthcheck/healthcheck.ex index 03bf287143a..3313f1f8f7f 100644 --- a/implementations/elixir/ockam/ockam_healthcheck/lib/healthcheck/healthcheck.ex +++ b/implementations/elixir/ockam/ockam_healthcheck/lib/healthcheck/healthcheck.ex @@ -160,14 +160,14 @@ defmodule Ockam.Healthcheck do defp connect_secure_channel(tcp_conn, api_worker) do api_route = [tcp_conn, api_worker] - with {:ok, identity, vault_name} <- get_healthcheck_identity() do - Logger.debug("Identity: #{inspect(identity)}") - + with {:ok, identity} <- get_healthcheck_identity(), + {:ok, keypair} <- SecureChannel.Crypto.generate_dh_keypair(), + {:ok, attestation} <- Ockam.Identity.attest_purpose_key(identity, keypair) do case SecureChannel.create_channel( [ route: api_route, identity: identity, - vault_name: vault_name + encryption_options: [static_keypair: keypair, static_key_attestation: attestation] ], ## TODO: make this configurable @key_exchange_timeout @@ -249,7 +249,7 @@ defmodule Ockam.Healthcheck do end @spec get_healthcheck_identity() :: - {:ok, identity :: Ockam.Identity.t(), vault_name :: binary() | nil} + {:ok, identity :: Ockam.Identity.t()} | {:error, reason :: any()} defp get_healthcheck_identity() do case Application.get_env(:ockam_healthcheck, :identity_source) do @@ -260,7 +260,8 @@ defmodule Ockam.Healthcheck do :file -> file = Application.get_env(:ockam_healthcheck, :identity_file) - identity_from_file(file) + secret = Application.get_env(:ockam_healthcheck, :identity_signing_key_file) + identity_from_file(file, secret) end end @@ -272,17 +273,11 @@ defmodule Ockam.Healthcheck do {:error, {:invalid_identity_function, not_function}} end - defp identity_from_file(file) do - case File.read(file) do - {:ok, data} -> - data = :erlang.binary_to_term(data) - - with {:ok, identity} <- Ockam.Identity.make_identity(data) do - {:ok, identity, nil} - end - - {:error, reason} -> - {:error, reason} + defp identity_from_file(file, secret) do + with {:ok, identity_data} <- File.read(file), + {:ok, signing_key} <- File.read(secret), + {:ok, identity, _identity_id} <- Ockam.Identity.import(identity_data, signing_key) do + {:ok, identity} end end @@ -292,14 +287,7 @@ defmodule Ockam.Healthcheck do generate_and_cache_identity() identity -> - ## Validate that identity is still valid - case Ockam.Identity.check_local_private_key(identity) do - :ok -> - {:ok, identity, nil} - - {:error, _reason} -> - generate_and_cache_identity() - end + {:ok, identity} end end @@ -307,7 +295,7 @@ defmodule Ockam.Healthcheck do case generate_identity() do {:ok, identity} -> :persistent_term.put(:healthcheck_identity, identity) - {:ok, identity, nil} + {:ok, identity} {:error, reason} -> {:error, reason} diff --git a/implementations/elixir/ockam/ockam_healthcheck/mix.lock b/implementations/elixir/ockam/ockam_healthcheck/mix.lock index 8856f23c9b4..a7b02cd5be7 100644 --- a/implementations/elixir/ockam/ockam_healthcheck/mix.lock +++ b/implementations/elixir/ockam/ockam_healthcheck/mix.lock @@ -17,6 +17,7 @@ "gen_state_machine": {:hex, :gen_state_machine, "3.0.0", "1e57f86a494e5c6b14137ebef26a7eb342b3b0070c7135f2d6768ed3f6b6cdff", [:mix], [], "hexpm", "0a59652574bebceb7309f6b749d2a41b45fdeda8dbb4da0791e355dd19f0ed15"}, "gettext": {:hex, :gettext, "0.19.1", "564953fd21f29358e68b91634799d9d26989f8d039d7512622efb3c3b1c97892", [:mix], [], "hexpm", "10c656c0912b8299adba9b061c06947511e3f109ab0d18b44a866a4498e77222"}, "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, + "hkdf_erlang": {:hex, :hkdf_erlang, "1.0.0", "0b681b428805eacf1286e02ece8617e843cdceae7adb5fad43c283c98088fbc2", [:rebar3], [], "hexpm", "d2091d9e0df97613fd149074929a94e3675047f28cca6b1626fe7be60f5d76ac"}, "httpoison": {:hex, :httpoison, "2.1.0", "655fd9a7b0b95ee3e9a3b535cf7ac8e08ef5229bab187fa86ac4208b122d934b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "fc455cb4306b43827def4f57299b2d5ac8ac331cb23f517e734a4b78210a160c"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, @@ -34,6 +35,7 @@ "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"}, "poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"}, "ranch": {:hex, :ranch, "2.1.0", "2261f9ed9574dcfcc444106b9f6da155e6e540b2f82ba3d42b339b93673b72a3", [:make, :rebar3], [], "hexpm", "244ee3fa2a6175270d8e1fc59024fd9dbc76294a321057de8f803b1479e76916"}, + "rustler": {:git, "https://github.com/polvorin/rustler.git", "f6ead5802b3b8bf38b3c5fcaf0a0dbdf0e66a221", [branch: "fix_local_crate", sparse: "rustler_mix"]}, "sched_ex": {:hex, :sched_ex, "1.1.4", "893de8ffcb1590ae6089d9862d49447fbb948535ba5777c231e55c8404bc3e6e", [:mix], [{:crontab, "~> 1.1.2", [hex: :crontab, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "f32336c40a62aba581c57d6d6009e40e5c52011bb8305fc3a33ef9e5815b861c"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, @@ -42,6 +44,7 @@ "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.1.0", "4e15f6d7dbedb3a4e3aed2262b7e1407f166fcb9c30ca3f96635dfbbef99965c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0dd10e7fe8070095df063798f82709b0a1224c31b8baf6278b423898d591a069"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, "timex": {:hex, :timex, "3.7.7", "3ed093cae596a410759104d878ad7b38e78b7c2151c6190340835515d4a46b8a", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "0ec4b09f25fe311321f9fc04144a7e3affe48eb29481d7a5583849b6c4dfa0a7"}, + "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, "typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"}, "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, diff --git a/implementations/elixir/ockam/ockam_healthcheck/test/healthcheck/healthcheck_test.exs b/implementations/elixir/ockam/ockam_healthcheck/test/healthcheck/healthcheck_test.exs index 6f478af0e11..9ce56b79fee 100644 --- a/implementations/elixir/ockam/ockam_healthcheck/test/healthcheck/healthcheck_test.exs +++ b/implementations/elixir/ockam/ockam_healthcheck/test/healthcheck/healthcheck_test.exs @@ -8,11 +8,15 @@ defmodule Ockam.Healthcheck.Test do setup_all do start_supervised({Ockam.Transport.TCP, [listen: [port: 4000]]}) + {:ok, identity} = Ockam.Identity.create() + {:ok, keypair} = Ockam.SecureChannel.Crypto.generate_dh_keypair() + {:ok, attestation} = Ockam.Identity.attest_purpose_key(identity, keypair) {:ok, _api} = Ockam.SecureChannel.create_listener( - identity: :dynamic, address: "api", + identity: identity, + encryption_options: [static_keypair: keypair, static_key_attestation: attestation], trust_policies: [] ) diff --git a/implementations/elixir/ockam/ockam_kafka/mix.lock b/implementations/elixir/ockam/ockam_kafka/mix.lock index 837e26831f6..58a1be843b0 100644 --- a/implementations/elixir/ockam/ockam_kafka/mix.lock +++ b/implementations/elixir/ockam/ockam_kafka/mix.lock @@ -17,6 +17,7 @@ "gen_state_machine": {:hex, :gen_state_machine, "3.0.0", "1e57f86a494e5c6b14137ebef26a7eb342b3b0070c7135f2d6768ed3f6b6cdff", [:mix], [], "hexpm", "0a59652574bebceb7309f6b749d2a41b45fdeda8dbb4da0791e355dd19f0ed15"}, "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, "hex2bin": {:hex, :hex2bin, "1.0.0", "aac26eab998ae80eacee1c7607c629ab503ebf77a62b9242bae2b94d47dcb71e", [:rebar3], [], "hexpm", "e7012d1d9aadd26e680f0983d26fb8923707f05fac9688f19f530fa3795e716f"}, + "hkdf_erlang": {:hex, :hkdf_erlang, "1.0.0", "0b681b428805eacf1286e02ece8617e843cdceae7adb5fad43c283c98088fbc2", [:rebar3], [], "hexpm", "d2091d9e0df97613fd149074929a94e3675047f28cca6b1626fe7be60f5d76ac"}, "httpoison": {:hex, :httpoison, "2.1.0", "655fd9a7b0b95ee3e9a3b535cf7ac8e08ef5229bab187fa86ac4208b122d934b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "fc455cb4306b43827def4f57299b2d5ac8ac331cb23f517e734a4b78210a160c"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, @@ -35,6 +36,7 @@ "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"}, "poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"}, "ranch": {:hex, :ranch, "2.1.0", "2261f9ed9574dcfcc444106b9f6da155e6e540b2f82ba3d42b339b93673b72a3", [:make, :rebar3], [], "hexpm", "244ee3fa2a6175270d8e1fc59024fd9dbc76294a321057de8f803b1479e76916"}, + "rustler": {:git, "https://github.com/polvorin/rustler.git", "f6ead5802b3b8bf38b3c5fcaf0a0dbdf0e66a221", [branch: "fix_local_crate", sparse: "rustler_mix"]}, "snappyer": {:hex, :snappyer, "1.2.9", "9cc58470798648ce34c662ca0aa6daae31367667714c9a543384430a3586e5d3", [:rebar3], [], "hexpm", "18d00ca218ae613416e6eecafe1078db86342a66f86277bd45c95f05bf1c8b29"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "supervisor3": {:git, "https://github.com/kafka4beam/supervisor3.git", "c11256561ad70052b25cedd6307261e5643f0550", [tag: "1.1.11"]}, @@ -43,6 +45,7 @@ "telemetry_metrics_prometheus": {:hex, :telemetry_metrics_prometheus, "1.1.0", "1cc23e932c1ef9aa3b91db257ead31ea58d53229d407e059b29bb962c1505a13", [:mix], [{:plug_cowboy, "~> 2.1", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}], "hexpm", "d43b3659b3244da44fe0275b717701542365d4519b79d9ce895b9719c1ce4d26"}, "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.1.0", "4e15f6d7dbedb3a4e3aed2262b7e1407f166fcb9c30ca3f96635dfbbef99965c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0dd10e7fe8070095df063798f82709b0a1224c31b8baf6278b423898d591a069"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, + "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, "typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, } diff --git a/implementations/elixir/ockam/ockam_metrics/mix.exs b/implementations/elixir/ockam/ockam_metrics/mix.exs index 4b5e9883bad..c06e8ee2ee4 100644 --- a/implementations/elixir/ockam/ockam_metrics/mix.exs +++ b/implementations/elixir/ockam/ockam_metrics/mix.exs @@ -58,9 +58,7 @@ defmodule Ockam.Metrics.MixProject do {:telemetry_metrics, "~> 0.6.1"}, {:telemetry_metrics_prometheus, "~> 1.1.0"}, # Needed to avoid conflic on ranch version used by cowboy (telemetry_metrics_prometheus dep) - {:ranch, "~> 2.1.0", override: true}, - # Needed to create secure channels on test cases - {:ockam_vault_software, path: "../ockam_vault_software", only: :test} + {:ranch, "~> 2.1.0", override: true} ] end diff --git a/implementations/elixir/ockam/ockam_metrics/mix.lock b/implementations/elixir/ockam/ockam_metrics/mix.lock index 1c0bf5fb72f..1fb8a8276aa 100644 --- a/implementations/elixir/ockam/ockam_metrics/mix.lock +++ b/implementations/elixir/ockam/ockam_metrics/mix.lock @@ -12,6 +12,7 @@ "ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "gen_state_machine": {:hex, :gen_state_machine, "3.0.0", "1e57f86a494e5c6b14137ebef26a7eb342b3b0070c7135f2d6768ed3f6b6cdff", [:mix], [], "hexpm", "0a59652574bebceb7309f6b749d2a41b45fdeda8dbb4da0791e355dd19f0ed15"}, + "hkdf_erlang": {:hex, :hkdf_erlang, "1.0.0", "0b681b428805eacf1286e02ece8617e843cdceae7adb5fad43c283c98088fbc2", [:rebar3], [], "hexpm", "d2091d9e0df97613fd149074929a94e3675047f28cca6b1626fe7be60f5d76ac"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, @@ -22,10 +23,12 @@ "plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"}, "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"}, "ranch": {:hex, :ranch, "2.1.0", "2261f9ed9574dcfcc444106b9f6da155e6e540b2f82ba3d42b339b93673b72a3", [:make, :rebar3], [], "hexpm", "244ee3fa2a6175270d8e1fc59024fd9dbc76294a321057de8f803b1479e76916"}, + "rustler": {:git, "https://github.com/polvorin/rustler.git", "f6ead5802b3b8bf38b3c5fcaf0a0dbdf0e66a221", [branch: "fix_local_crate", sparse: "rustler_mix"]}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, "telemetry_metrics_prometheus": {:hex, :telemetry_metrics_prometheus, "1.1.0", "1cc23e932c1ef9aa3b91db257ead31ea58d53229d407e059b29bb962c1505a13", [:mix], [{:plug_cowboy, "~> 2.1", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}], "hexpm", "d43b3659b3244da44fe0275b717701542365d4519b79d9ce895b9719c1ce4d26"}, "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.1.0", "4e15f6d7dbedb3a4e3aed2262b7e1407f166fcb9c30ca3f96635dfbbef99965c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0dd10e7fe8070095df063798f82709b0a1224c31b8baf6278b423898d591a069"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, + "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, "typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"}, } diff --git a/implementations/elixir/ockam/ockam_metrics/test/ockam_metrics/telemetry_poller_test.exs b/implementations/elixir/ockam/ockam_metrics/test/ockam_metrics/telemetry_poller_test.exs index 4ea3e40ec09..15127d03f49 100644 --- a/implementations/elixir/ockam/ockam_metrics/test/ockam_metrics/telemetry_poller_test.exs +++ b/implementations/elixir/ockam/ockam_metrics/test/ockam_metrics/telemetry_poller_test.exs @@ -5,7 +5,6 @@ defmodule Ockam.Metrics.TelemetryPoller.Tests do alias Ockam.Identity alias Ockam.Metrics.TelemetryPoller alias Ockam.SecureChannel - alias Ockam.Vault.Software, as: SoftwareVault setup do {:ok, me} = Ockam.Node.register_random_address() @@ -14,21 +13,28 @@ defmodule Ockam.Metrics.TelemetryPoller.Tests do end test "secure channels metrics", %{me: self_addr} do - {:ok, vault} = SoftwareVault.init() - {:ok, alice, alice_id} = Identity.create(Ockam.Identity.Stub) + {:ok, alice} = Identity.create() + alice_id = Identity.get_identifier(alice) + {:ok, alice_keypair} = SecureChannel.Crypto.generate_dh_keypair() + {:ok, alice_attestation} = Identity.attest_purpose_key(alice, alice_keypair) {:ok, listener} = SecureChannel.create_listener( identity: alice, - encryption_options: [vault: vault] + encryption_options: [ + static_keypair: alice_keypair, + static_key_attestation: alice_attestation + ] ) - {:ok, bob, _bob_id} = Identity.create(Ockam.Identity.Stub) + {:ok, bob} = Identity.create() + {:ok, bob_keypair} = SecureChannel.Crypto.generate_dh_keypair() + {:ok, bob_attestation} = Identity.attest_purpose_key(bob, bob_keypair) {:ok, channel} = SecureChannel.create_channel( identity: bob, - encryption_options: [vault: vault], + encryption_options: [static_keypair: bob_keypair, static_key_attestation: bob_attestation], route: [listener] ) @@ -41,7 +47,7 @@ defmodule Ockam.Metrics.TelemetryPoller.Tests do {:ok, channel2} = SecureChannel.create_channel( identity: bob, - encryption_options: [vault: vault], + encryption_options: [static_keypair: bob_keypair, static_key_attestation: bob_attestation], route: [listener] ) diff --git a/implementations/elixir/ockam/ockam_services/lib/services/api/credential_exchange_api.ex b/implementations/elixir/ockam/ockam_services/lib/services/api/credential_exchange_api.ex deleted file mode 100644 index 77dccc2a3ca..00000000000 --- a/implementations/elixir/ockam/ockam_services/lib/services/api/credential_exchange_api.ex +++ /dev/null @@ -1,116 +0,0 @@ -defmodule Ockam.Services.API.CredentialExchange do - @moduledoc """ - API to accept credentials. - - Verifies credentials using provided verifier_module. - Saves attributes in AttributeStorageETS table per identity. - - Options: - - - authorities :: [identity :: binary] - list of supported CA public keys - - verifier_module - module to call verify/3 with, see Ockam.Credential.Verifier.Sidecar - """ - use Ockam.Services.API - - alias Ockam.Credential.AttributeStorageETS, as: AttributeStorage - - alias Ockam.Identity - alias Ockam.Telemetry - - @default_verifier Ockam.Credential.Verifier.Sidecar - - def set_authorities(worker, identities_data) when is_list(identities_data) do - Ockam.Worker.call(worker, {:set_authorities, identities_data}) - end - - @impl true - def setup(options, state) do - ## TODO: API to update authorities - with {:ok, authorities} <- Keyword.fetch!(options, :authorities) |> prepare_authorities() do - verifier_module = Keyword.get(options, :verifier_module, @default_verifier) - - :ok = AttributeStorage.init() - - {:ok, Map.merge(state, %{authorities: authorities, verifier_module: verifier_module})} - end - end - - @impl true - def handle_request(%Request{method: :post, path: "actions/present"} = request, state) do - case request do - %{body: credential, local_metadata: %{identity_id: subject_id}} -> - authorities = Map.fetch!(state, :authorities) - verifier_module = Map.fetch!(state, :verifier_module) - - emit_credential_presented(subject_id, state) - - with {:ok, attribute_set} <- verifier_module.verify(credential, subject_id, authorities), - :ok <- AttributeStorage.put_attribute_set(subject_id, attribute_set) do - emit_attributes_verified(subject_id, attribute_set, state) - {:reply, :ok, nil, state} - end - - _other -> - {:error, {:bad_request, "secure channel required"}} - end - end - - def handle_request(%Request{method: :post}, _state) do - {:error, :not_found} - end - - def handle_request(%Request{}, _state) do - {:error, :method_not_allowed} - end - - @impl true - def handle_call({:set_authorities, identities_data}, _from, state) do - new_authorities = prepare_authorities(identities_data) - {:reply, :ok, Map.put(state, :authorities, new_authorities)} - end - - defp emit_credential_presented(subject_id, _state) do - ## TODO: record which authority is used? - Telemetry.emit_event([:credentials, :presented], - measurements: %{count: 1}, - metadata: %{identity_id: subject_id} - ) - end - - defp emit_attributes_verified(subject_id, attribute_set, _state) do - Telemetry.emit_event([:credentials, :verified], - measurements: %{count: 1}, - ## TODO: remove 2 layers of attributes in Elixir data structures - metadata: %{ - identity_id: subject_id, - attributes: Enum.count(attribute_set.attributes.attributes) - } - ) - end - - defp prepare_authorities(authorities_config) when is_map(authorities_config) do - {:ok, authorities_config} - end - - defp prepare_authorities(authorities_config) when is_list(authorities_config) do - prepare_result = - Enum.reduce(authorities_config, {:ok, []}, fn - identity_data, {:ok, tuple_list} -> - with {:ok, identity} <- Identity.make_identity(identity_data), - {:ok, identity_id} <- Identity.validate_identity_change_history(identity) do - {:ok, [{identity_id, Identity.get_data(identity)} | tuple_list]} - end - - _identity_data, {:error, _reason} = error -> - error - end) - - case prepare_result do - {:ok, tuple_list} -> - {:ok, Map.new(tuple_list)} - - {:error, reason} -> - {:error, reason} - end - end -end diff --git a/implementations/elixir/ockam/ockam_services/lib/services/provider/secure_channel.ex b/implementations/elixir/ockam/ockam_services/lib/services/provider/secure_channel.ex index 423478128d8..42e25095a19 100644 --- a/implementations/elixir/ockam/ockam_services/lib/services/provider/secure_channel.ex +++ b/implementations/elixir/ockam/ockam_services/lib/services/provider/secure_channel.ex @@ -5,7 +5,8 @@ defmodule Ockam.Services.Provider.SecureChannel do """ @behaviour Ockam.Services.Provider - alias Ockam.Vault.Software, as: SoftwareVault + alias Ockam.Identity + alias Ockam.SecureChannel.Crypto @services [:secure_channel] @@ -32,23 +33,20 @@ defmodule Ockam.Services.Provider.SecureChannel do end def service_options(:secure_channel, args) do - ## TODO: make it possible to read service identity from some storage - identity_module = Keyword.get(args, :identity_module, Ockam.Identity.default_implementation()) - trust_policies = Keyword.get(args, :trust_policies, [ {:cached_identity, [Ockam.Identity.TrustPolicy.KnownIdentitiesEts]} ]) - other_args = Keyword.drop(args, [:identity_module, :trust_policies]) + other_args = Keyword.drop(args, [:trust_policies]) - with {:ok, vault} <- SoftwareVault.init(), - {:ok, keypair} <- Ockam.Vault.secret_generate(vault, type: :curve25519) do + with {:ok, identity} <- Identity.create(), + {:ok, keypair} <- Crypto.generate_dh_keypair(), + {:ok, attestation} <- Identity.attest_purpose_key(identity, keypair) do Keyword.merge( [ - identity: :dynamic, - identity_module: identity_module, - encryption_options: [vault: vault, static_keypair: keypair], + identity: identity, + encryption_options: [static_keypair: keypair, static_key_attestation: attestation], address: "secure_channel", trust_policies: trust_policies ], diff --git a/implementations/elixir/ockam/ockam_services/lib/services/provider/sidecar.ex b/implementations/elixir/ockam/ockam_services/lib/services/provider/sidecar.ex deleted file mode 100644 index 9977429ce16..00000000000 --- a/implementations/elixir/ockam/ockam_services/lib/services/provider/sidecar.ex +++ /dev/null @@ -1,111 +0,0 @@ -defmodule Ockam.Services.Provider.Sidecar do - @moduledoc """ - Implementation for Ockam.Services.Provider - providing services on Rust sidecar - - Services: - - :identity_sidecar - sidecar service providing identity API for Ockam.Identity.Sidecar - - Options: - - - sidecar_host - hostname for sidecar node, default is "localhost" - - sidecar_port - port for sidecar node, default is 4100 - - sidecar_address - worker address on the sidecar node, default is "identity_service" - - **NOTE** service address is used by Ockam.Identity.Sidecar - and will always be as defined in Ockam.Identity.Sidecar.api_route() - - :ca_verifier_sidecar - sidecar service providing credential verifier for Ockam.Credential.Verifier.Sidecar - - Options: - - - sidecar_host - hostname for sidecar node, default is "localhost" - - sidecar_port - port for sidecar node, default is 4100 - - sidecar_address - worker address on the sidecar node, default is "ca_verifier_service" - - :sidecar_node - service forwarding to a sidecar node (essentially a persistent TCP client) - - Options: - - service_id - atom id of the service for the supervisor - - address - optional, address of the proxy worker, defaults to string service_id - - sidecar_host - hostname for sidecar node, default is "localhost" - - sidecar_port - port for sidecar node, default is 4100 - - refresh_timeout - heartbeat timeout to check the connection - - :sidecar_proxy - generic sidecar proxy service - - Options: - - service_id - atom id of the service for the supervisor - - address - optional, address of the proxy worker, defaults to string service_id - - sidecar_host - hostname for sidecar node, default is "localhost" - - sidecar_port - port for sidecar node, default is 4100 - - sidecar_address - worker address on the sidecar node - """ - @behaviour Ockam.Services.Provider - - alias Ockam.Transport.TCP.RecoverableClient - alias Ockam.Transport.TCPAddress - - @impl true - def services() do - [:identity_sidecar, :sidecar_proxy, :sidecar_node] - end - - @impl true - def child_spec(:identity_sidecar = service_id, args) do - sidecar_address = Keyword.get(args, :sidecar_address, "identity_service") - [address] = Ockam.Identity.Sidecar.api_route() - - child_spec( - :sidecar_proxy, - args ++ [service_id: service_id, address: address, sidecar_address: sidecar_address] - ) - end - - def child_spec(:sidecar_proxy, args) do - service_id = Keyword.fetch!(args, :service_id) - address = Keyword.get(args, :address, to_string(service_id)) - - sidecar_address = Keyword.fetch!(args, :sidecar_address) - sidecar_host = Keyword.get(args, :sidecar_host, "localhost") - sidecar_port = Keyword.get(args, :sidecar_port, 4100) - forward_route = [TCPAddress.new(sidecar_host, sidecar_port), sidecar_address] - - extra_options = - Keyword.drop( - args, - [:sidecar_port, :sidecar_host, :sidecar_address, :service_id] - ) - - options = Keyword.merge(extra_options, forward_route: forward_route, address: address) - - %{ - id: service_id, - start: {Ockam.Services.Proxy, :start_link, [options]} - } - end - - def child_spec(:sidecar_node, args) do - service_id = Keyword.fetch!(args, :service_id) - address = Keyword.get(args, :address, to_string(service_id)) - - sidecar_host = Keyword.get(args, :sidecar_host, "localhost") - sidecar_port = Keyword.get(args, :sidecar_port, 4100) - - destination = TCPAddress.new(sidecar_host, sidecar_port) - - extra_options = - Keyword.drop( - args, - [:sidecar_port, :sidecar_host, :service_id] - ) - - options = Keyword.merge(extra_options, destination: destination, address: address) - - %{ - id: service_id, - start: {RecoverableClient, :start_link, [options]} - } - end -end diff --git a/implementations/elixir/ockam/ockam_services/mix.exs b/implementations/elixir/ockam/ockam_services/mix.exs index 26f38af793d..86a16474be6 100644 --- a/implementations/elixir/ockam/ockam_services/mix.exs +++ b/implementations/elixir/ockam/ockam_services/mix.exs @@ -49,7 +49,6 @@ defmodule Ockam.Services.MixProject do {:credo, "~> 1.6", only: [:dev, :test], runtime: false}, {:dialyxir, "~> 1.1", only: [:dev], runtime: false}, {:ex_doc, "~> 0.25", only: :dev, runtime: false}, - {:ockam_vault_software, path: "../ockam_vault_software"}, {:ockam, path: "../ockam"}, {:ockam_metrics, path: "../ockam_metrics"}, {:ockam_abac, path: "../ockam_abac"}, diff --git a/implementations/elixir/ockam/ockam_services/mix.lock b/implementations/elixir/ockam/ockam_services/mix.lock index 27a2279cc2d..ee7fc45a8ad 100644 --- a/implementations/elixir/ockam/ockam_services/mix.lock +++ b/implementations/elixir/ockam/ockam_services/mix.lock @@ -14,6 +14,7 @@ "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "gen_state_machine": {:hex, :gen_state_machine, "3.0.0", "1e57f86a494e5c6b14137ebef26a7eb342b3b0070c7135f2d6768ed3f6b6cdff", [:mix], [], "hexpm", "0a59652574bebceb7309f6b749d2a41b45fdeda8dbb4da0791e355dd19f0ed15"}, "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, + "hkdf_erlang": {:hex, :hkdf_erlang, "1.0.0", "0b681b428805eacf1286e02ece8617e843cdceae7adb5fad43c283c98088fbc2", [:rebar3], [], "hexpm", "d2091d9e0df97613fd149074929a94e3675047f28cca6b1626fe7be60f5d76ac"}, "httpoison": {:hex, :httpoison, "2.1.0", "655fd9a7b0b95ee3e9a3b535cf7ac8e08ef5229bab187fa86ac4208b122d934b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "fc455cb4306b43827def4f57299b2d5ac8ac331cb23f517e734a4b78210a160c"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, @@ -31,12 +32,14 @@ "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"}, "poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"}, "ranch": {:hex, :ranch, "2.1.0", "2261f9ed9574dcfcc444106b9f6da155e6e540b2f82ba3d42b339b93673b72a3", [:make, :rebar3], [], "hexpm", "244ee3fa2a6175270d8e1fc59024fd9dbc76294a321057de8f803b1479e76916"}, + "rustler": {:git, "https://github.com/polvorin/rustler.git", "f6ead5802b3b8bf38b3c5fcaf0a0dbdf0e66a221", [branch: "fix_local_crate", sparse: "rustler_mix"]}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, "telemetry_metrics_prometheus": {:hex, :telemetry_metrics_prometheus, "1.1.0", "1cc23e932c1ef9aa3b91db257ead31ea58d53229d407e059b29bb962c1505a13", [:mix], [{:plug_cowboy, "~> 2.1", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}], "hexpm", "d43b3659b3244da44fe0275b717701542365d4519b79d9ce895b9719c1ce4d26"}, "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.1.0", "4e15f6d7dbedb3a4e3aed2262b7e1407f166fcb9c30ca3f96635dfbbef99965c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0dd10e7fe8070095df063798f82709b0a1224c31b8baf6278b423898d591a069"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, + "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, "typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, } diff --git a/implementations/elixir/ockam/ockam_services/test/authorization_test.exs b/implementations/elixir/ockam/ockam_services/test/authorization_test.exs index 50685637527..de75075fe23 100644 --- a/implementations/elixir/ockam/ockam_services/test/authorization_test.exs +++ b/implementations/elixir/ockam/ockam_services/test/authorization_test.exs @@ -7,26 +7,35 @@ defmodule Ockam.Services.Authorization.Tests do alias Ockam.Message alias Ockam.Router - alias Ockam.Services.Echo + alias Ockam.SecureChannel + alias Ockam.SecureChannel.Crypto - alias Ockam.Vault.Software, as: SoftwareVault + alias Ockam.Services.Echo require Logger setup_all do - {:ok, vault} = SoftwareVault.init() + {:ok, listener_identity} = Identity.create() + {:ok, listener_keypair} = Crypto.generate_dh_keypair() + {:ok, attestation} = Identity.attest_purpose_key(listener_identity, listener_keypair) {:ok, channel_listener} = - Ockam.SecureChannel.create_listener(identity: :dynamic, encryption_options: [vault: vault]) + SecureChannel.create_listener( + identity: listener_identity, + encryption_options: [ + static_keypair: listener_keypair, + static_key_attestation: attestation + ] + ) on_exit(fn -> Ockam.Node.stop(channel_listener) end) - [vault: vault, channel_listener: channel_listener] + [channel_listener: channel_listener] end - test "Worker requiring local message", %{vault: vault, channel_listener: channel_listener} do + test "Worker requiring local message", %{channel_listener: channel_listener} do {:ok, echoer} = Echo.create(authorization: [:is_local]) {:ok, me} = Ockam.Node.register_random_address() @@ -48,12 +57,7 @@ defmodule Ockam.Services.Authorization.Tests do refute_receive(%Message{onward_route: [^me], payload: "Hello transport"}, 500) - {:ok, channel} = - Ockam.SecureChannel.create_channel( - identity: :dynamic, - route: [channel_listener], - encryption_options: [vault: vault] - ) + {:ok, channel} = create_channel([channel_listener]) Router.route(%Message{ payload: "Hello secure channel", @@ -64,29 +68,13 @@ defmodule Ockam.Services.Authorization.Tests do refute_receive(%Message{onward_route: [^me], payload: "Hello secure channel"}, 500) end - test "Identity secure channel authorization" do - {:ok, vault} = SoftwareVault.init() - {:ok, listener_identity, _id} = Identity.create(Ockam.Identity.Stub) - - {:ok, listener} = - Ockam.SecureChannel.create_listener( - identity: listener_identity, - encryption_options: [vault: vault] - ) - - {:ok, bob, _bob_id} = Identity.create(Ockam.Identity.Stub) - - {:ok, bob_channel} = - Ockam.SecureChannel.create_channel( - identity: bob, - encryption_options: [vault: vault], - route: [listener] - ) + test "Identity secure channel authorization", %{channel_listener: channel_listener} do + {:ok, channel} = create_channel([channel_listener]) {:ok, echoer} = Echo.create(authorization: [:from_secure_channel]) {:ok, me} = Ockam.Node.register_random_address() - Ockam.Router.route("VIA CHANNEL", [bob_channel, echoer], [me]) + Ockam.Router.route("VIA CHANNEL", [channel, echoer], [me]) assert_receive(%Ockam.Message{onward_route: [^me], payload: "VIA CHANNEL"}, 500) @@ -95,34 +83,17 @@ defmodule Ockam.Services.Authorization.Tests do refute_receive(%Ockam.Message{onward_route: [^me], payload: "WITHOUT CHANNEL"}, 500) end - test "Identity secure channel initiator authorization" do - {:ok, vault} = SoftwareVault.init() - {:ok, listener_identity, _id} = Identity.create(Ockam.Identity.Stub) - - {:ok, listener} = - Ockam.SecureChannel.create_listener( - identity: listener_identity, - encryption_options: [vault: vault] - ) - - {:ok, bob, _bob_id} = Identity.create(Ockam.Identity.Stub) - - {:ok, bob_channel} = - Ockam.SecureChannel.create_channel( - identity: bob, - encryption_options: [vault: vault], - route: [listener], - authorization: [:is_local] - ) + test "Identity secure channel initiator authorization", %{channel_listener: channel_listener} do + {:ok, channel} = create_channel([channel_listener], [:is_local]) {:ok, me} = Ockam.Node.register_random_address() - Ockam.Router.route("initiator from local", [bob_channel, me], [me]) + Ockam.Router.route("initiator from local", [me], [me]) assert_receive(%Ockam.Message{onward_route: [^me], payload: "initiator from local"}, 500) Ockam.Router.route(%Ockam.Message{ payload: "initiator from channel", - onward_route: [bob_channel, me], + onward_route: [channel, me], return_route: [me], local_metadata: %{source: :channel, channel: :some_transport} }) @@ -131,24 +102,21 @@ defmodule Ockam.Services.Authorization.Tests do end test "Identity secure channel responer authorization" do - {:ok, vault} = SoftwareVault.init() - {:ok, listener_identity, _id} = Identity.create(Ockam.Identity.Stub) + {:ok, listener_identity} = Identity.create() + {:ok, listener_keypair} = Crypto.generate_dh_keypair() + {:ok, attestation} = Identity.attest_purpose_key(listener_identity, listener_keypair) {:ok, listener} = - Ockam.SecureChannel.create_listener( + SecureChannel.create_listener( identity: listener_identity, - encryption_options: [vault: vault], + encryption_options: [ + static_keypair: listener_keypair, + static_key_attestation: attestation + ], responder_authorization: [:is_local] ) - {:ok, bob, _bob_id} = Identity.create(Ockam.Identity.Stub) - - {:ok, bob_channel} = - Ockam.SecureChannel.create_channel( - identity: bob, - encryption_options: [vault: vault], - route: [listener] - ) + {:ok, bob_channel} = create_channel([listener]) {:ok, me} = Ockam.Node.register_random_address() Ockam.Router.route("VIA CHANNEL", [bob_channel, me], [me]) @@ -277,4 +245,18 @@ defmodule Ockam.Services.Authorization.Tests do 500 ) end + + defp create_channel(route, authorization \\ []) do + {:ok, identity} = Identity.create() + + {:ok, keypair} = Crypto.generate_dh_keypair() + {:ok, attestation} = Identity.attest_purpose_key(identity, keypair) + + SecureChannel.create_channel( + identity: identity, + encryption_options: [static_keypair: keypair, static_key_attestation: attestation], + route: route, + authorization: authorization + ) + end end diff --git a/implementations/elixir/ockam/ockam_services/test/services/credential_api_test.exs b/implementations/elixir/ockam/ockam_services/test/services/credential_api_test.exs deleted file mode 100644 index e2f88ee7bc2..00000000000 --- a/implementations/elixir/ockam/ockam_services/test/services/credential_api_test.exs +++ /dev/null @@ -1,82 +0,0 @@ -defmodule Test.Services.CredentialExchangeTest do - use ExUnit.Case - - alias Ockam.API.Client, as: ApiClient - alias Ockam.Credential.AttributeStorageETS, as: AttributeStorage - alias Ockam.Identity - alias Ockam.SecureChannel - alias Ockam.Services.API.CredentialExchange - - alias Ockam.Services.Tests.TelemetryListener - - @telemetry_table :credential_test_telemetry_listener - - @telemetry_events [ - [:ockam, :credentials, :presented], - [:ockam, :credentials, :verified] - ] - - setup_all do - {:ok, listener} = SecureChannel.create_listener(identity: :dynamic) - {:ok, member_identity, member_id} = Identity.create() - {:ok, channel} = SecureChannel.create_channel(identity: member_identity, route: [listener]) - - metrics_listener = TelemetryListener.start(@telemetry_table, @telemetry_events) - - on_exit(fn -> - send(metrics_listener, :stop) - end) - - [channel: channel, member_identity: member_identity, member_id: member_id] - end - - test "credential api requires identity_id" do - {:ok, api} = CredentialExchange.create(authorities: []) - - TelemetryListener.reset(@telemetry_table) - - on_exit(fn -> - Ockam.Node.stop(api) - end) - - {:ok, resp} = ApiClient.sync_request(:post, "actions/present", "", [api]) - - assert %{status: 400, body: body} = resp - - expected_body = CBOR.encode("secure channel required") - assert body == expected_body - - metrics = TelemetryListener.get_metrics(@telemetry_table) - - assert [] = metrics - end - - test "credential api adds attributes", %{member_id: member_id, channel: channel} do - {:ok, api} = - CredentialExchange.create(authorities: [], verifier_module: Ockam.Credential.Verifier.Stub) - - TelemetryListener.reset(@telemetry_table) - - on_exit(fn -> - Ockam.Node.stop(api) - end) - - attributes = %{"project" => "123", "role" => "member"} - expiration = System.os_time(:second) + 100 - credential = Ockam.Credential.Verifier.Stub.make_credential(attributes, expiration) - - {:ok, resp} = ApiClient.sync_request(:post, "actions/present", credential, [channel, api]) - - assert %{status: 200} = resp - - member_attributes = AttributeStorage.get_attributes(member_id) - assert attributes == member_attributes - - metrics = TelemetryListener.get_metrics(@telemetry_table) - - assert [ - {[:ockam, :credentials, :presented], _}, - {[:ockam, :credentials, :verified], _} - ] = Enum.sort(metrics) - end -end diff --git a/implementations/elixir/ockam/ockam_services/test/services/token_lease_manager_test.exs b/implementations/elixir/ockam/ockam_services/test/services/token_lease_manager_test.exs index b8ec932c66f..be732ca9afc 100644 --- a/implementations/elixir/ockam/ockam_services/test/services/token_lease_manager_test.exs +++ b/implementations/elixir/ockam/ockam_services/test/services/token_lease_manager_test.exs @@ -38,7 +38,6 @@ defmodule Ockam.Services.TokenLeaseManager.Test do alias Ockam.Services.TokenLeaseManager alias Ockam.Services.TokenLeaseManager.Lease alias Ockam.Services.TokenLeaseManager.StorageService.Memory, as: MemoryStorage - alias Ockam.Vault.Software, as: SoftwareVault setup do {:ok, lm} = @@ -57,41 +56,71 @@ defmodule Ockam.Services.TokenLeaseManager.Test do ttl: 1 ) - on_exit(fn -> - Ockam.Node.stop(lm) - Ockam.Node.stop(short_live_lm) - end) - - [lm: lm, short_live_lm: short_live_lm] - end - - test "create and list leases", %{lm: lm} do - {:ok, vault} = SoftwareVault.init() - {:ok, listener_identity, _id} = Identity.create(Ockam.Identity.Stub) + {:ok, listener_identity} = Identity.create() + {:ok, listener_keypair} = SecureChannel.Crypto.generate_dh_keypair() + {:ok, attestation} = Identity.attest_purpose_key(listener_identity, listener_keypair) {:ok, listener} = SecureChannel.create_listener( identity: listener_identity, - encryption_options: [vault: vault] + encryption_options: [ + static_keypair: listener_keypair, + static_key_attestation: attestation + ] ) - {:ok, bob, bob_id} = Identity.create(Ockam.Identity.Stub) - {:ok, alice, alice_id} = Identity.create(Ockam.Identity.Stub) + {:ok, bob} = Identity.create() + {:ok, alice} = Identity.create() + bob_id = Identity.get_identifier(bob) + alice_id = Identity.get_identifier(alice) + + {:ok, bob_keypair} = SecureChannel.Crypto.generate_dh_keypair() + {:ok, bob_attestation} = Identity.attest_purpose_key(bob, bob_keypair) + + {:ok, alice_keypair} = SecureChannel.Crypto.generate_dh_keypair() + {:ok, alice_attestation} = Identity.attest_purpose_key(alice, alice_keypair) {:ok, bob_channel} = SecureChannel.create_channel( identity: bob, - encryption_options: [vault: vault], + encryption_options: [static_keypair: bob_keypair, static_key_attestation: bob_attestation], route: [listener] ) {:ok, alice_channel} = SecureChannel.create_channel( identity: alice, - encryption_options: [vault: vault], + encryption_options: [ + static_keypair: alice_keypair, + static_key_attestation: alice_attestation + ], route: [listener] ) + on_exit(fn -> + Ockam.Node.stop(lm) + Ockam.Node.stop(short_live_lm) + end) + + [ + lm: lm, + short_live_lm: short_live_lm, + bob_channel: bob_channel, + alice_channel: alice_channel, + bob_id: bob_id, + alice_id: alice_id, + listener: listener + ] + end + + test "create and list leases", + %{ + lm: lm, + bob_channel: bob_channel, + alice_channel: alice_channel, + bob_id: bob_id, + alice_id: alice_id + } do # Initially no leases {:ok, resp} = Client.sync_request(:get, "/", nil, [bob_channel, lm]) assert %{status: 200, body: body} = resp @@ -126,33 +155,8 @@ defmodule Ockam.Services.TokenLeaseManager.Test do assert {:ok, [^alice_lease], ""} = Lease.decode_list(body) end - test "lease get", %{lm: lm} do - {:ok, vault} = SoftwareVault.init() - {:ok, listener_identity, _id} = Identity.create(Ockam.Identity.Stub) - - {:ok, listener} = - SecureChannel.create_listener( - identity: listener_identity, - encryption_options: [vault: vault] - ) - - {:ok, bob, bob_id} = Identity.create(Ockam.Identity.Stub) - {:ok, alice, _alice_id} = Identity.create(Ockam.Identity.Stub) - - {:ok, bob_channel} = - SecureChannel.create_channel( - identity: bob, - encryption_options: [vault: vault], - route: [listener] - ) - - {:ok, alice_channel} = - SecureChannel.create_channel( - identity: alice, - encryption_options: [vault: vault], - route: [listener] - ) - + test "lease get", + %{lm: lm, bob_channel: bob_channel, bob_id: bob_id, alice_channel: alice_channel} do {:ok, resp} = Client.sync_request(:post, "/", nil, [bob_channel, lm]) assert %{status: 200, body: body} = resp @@ -168,26 +172,8 @@ defmodule Ockam.Services.TokenLeaseManager.Test do Client.sync_request(:get, "/#{bob_lease_1_id}", nil, [alice_channel, lm]) end - test "lease revoke", %{lm: lm} do - {:ok, vault} = SoftwareVault.init() - {:ok, listener_identity, _id} = Identity.create(Ockam.Identity.Stub) - - {:ok, listener} = - SecureChannel.create_listener( - identity: listener_identity, - encryption_options: [vault: vault] - ) - - {:ok, bob, bob_id} = Identity.create(Ockam.Identity.Stub) - {:ok, alice, _alice_id} = Identity.create(Ockam.Identity.Stub) - - {:ok, bob_channel} = - SecureChannel.create_channel( - identity: bob, - encryption_options: [vault: vault], - route: [listener] - ) - + test "lease revoke", + %{lm: lm, bob_channel: bob_channel, alice_channel: alice_channel, bob_id: bob_id} do {:ok, resp} = Client.sync_request(:post, "/", nil, [bob_channel, lm]) assert %{status: 200, body: body} = resp assert {:ok, %Lease{id: bob_lease_1_id, issued_for: ^bob_id}} = Lease.decode_strict(body) @@ -203,12 +189,6 @@ defmodule Ockam.Services.TokenLeaseManager.Test do assert {:ok, [%Lease{id: bob_lease_2_id}], ""} = Lease.decode_list(body) # Alice can't delete bob' lease - {:ok, alice_channel} = - SecureChannel.create_channel( - identity: alice, - encryption_options: [vault: vault], - route: [listener] - ) {:ok, resp} = Client.sync_request(:delete, "/#{bob_lease_2_id}", nil, [alice_channel, lm]) assert %{status: 404} = resp @@ -218,25 +198,8 @@ defmodule Ockam.Services.TokenLeaseManager.Test do assert {:ok, [%Lease{id: ^bob_lease_2_id}], ""} = Lease.decode_list(body) end - test "lease expiration", %{short_live_lm: short_live_lm} do - {:ok, vault} = SoftwareVault.init() - {:ok, listener_identity, _id} = Identity.create(Ockam.Identity.Stub) - - {:ok, listener} = - SecureChannel.create_listener( - identity: listener_identity, - encryption_options: [vault: vault] - ) - - {:ok, bob, bob_id} = Identity.create(Ockam.Identity.Stub) - - {:ok, bob_channel} = - SecureChannel.create_channel( - identity: bob, - encryption_options: [vault: vault], - route: [listener] - ) - + test "lease expiration", + %{short_live_lm: short_live_lm, bob_channel: bob_channel, bob_id: bob_id} do {:ok, resp} = Client.sync_request(:post, "/", nil, [bob_channel, short_live_lm]) assert %{status: 200, body: body} = resp assert {:ok, %Lease{issued_for: ^bob_id} = bob_lease1} = Lease.decode_strict(body) diff --git a/implementations/elixir/ockam/ockam_vault_software/.credo.exs b/implementations/elixir/ockam/ockam_vault_software/.credo.exs deleted file mode 100644 index 45aa5e6556a..00000000000 --- a/implementations/elixir/ockam/ockam_vault_software/.credo.exs +++ /dev/null @@ -1,137 +0,0 @@ -# This file contains the configuration for credo. -# -# It was first generated with `mix credo.gen.config` and then tweaked. -%{ - configs: [ - %{ - name: "default", - - # These are the files included in the analysis: - files: %{ - included: [ - "lib/", - "test/" - ], - excluded: [ - ~r"/_build/", - ~r"/deps/" - ] - }, - strict: false, - parse_timeout: 5000, - color: true, - - # - # To disable a check put `false` as second element: - # - # {Credo.Check.Design.DuplicatedCode, false} - # - checks: [ - # - ## Consistency Checks - # - {Credo.Check.Consistency.ExceptionNames, []}, - {Credo.Check.Consistency.LineEndings, []}, - {Credo.Check.Consistency.ParameterPatternMatching, []}, - {Credo.Check.Consistency.SpaceAroundOperators, []}, - {Credo.Check.Consistency.SpaceInParentheses, []}, - {Credo.Check.Consistency.TabsOrSpaces, []}, - - # - ## Design Checks - # - {Credo.Check.Design.AliasUsage, - [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, - {Credo.Check.Design.TagTODO, false}, - {Credo.Check.Design.TagFIXME, []}, - - # - ## Readability Checks - # - {Credo.Check.Readability.AliasOrder, []}, - {Credo.Check.Readability.FunctionNames, []}, - {Credo.Check.Readability.LargeNumbers, []}, - {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, - {Credo.Check.Readability.ModuleAttributeNames, []}, - {Credo.Check.Readability.ModuleDoc, []}, - {Credo.Check.Readability.ModuleNames, []}, - {Credo.Check.Readability.ParenthesesInCondition, []}, - {Credo.Check.Readability.ParenthesesOnZeroArityDefs, false}, - {Credo.Check.Readability.PredicateFunctionNames, []}, - {Credo.Check.Readability.PreferImplicitTry, []}, - {Credo.Check.Readability.RedundantBlankLines, []}, - {Credo.Check.Readability.Semicolons, []}, - {Credo.Check.Readability.SpaceAfterCommas, []}, - {Credo.Check.Readability.StringSigils, []}, - {Credo.Check.Readability.TrailingBlankLine, []}, - {Credo.Check.Readability.TrailingWhiteSpace, []}, - {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, - {Credo.Check.Readability.VariableNames, []}, - - # - ## Refactoring Opportunities - # - {Credo.Check.Refactor.CondStatements, []}, - {Credo.Check.Refactor.CyclomaticComplexity, []}, - {Credo.Check.Refactor.FunctionArity, []}, - {Credo.Check.Refactor.LongQuoteBlocks, []}, - {Credo.Check.Refactor.MapInto, false}, - {Credo.Check.Refactor.MatchInCondition, []}, - {Credo.Check.Refactor.NegatedConditionsInUnless, []}, - {Credo.Check.Refactor.NegatedConditionsWithElse, []}, - {Credo.Check.Refactor.Nesting, [max_nesting: 3]}, - {Credo.Check.Refactor.UnlessWithElse, []}, - {Credo.Check.Refactor.WithClauses, []}, - - # - ## Warnings - # - {Credo.Check.Warning.BoolOperationOnSameValues, []}, - {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, - {Credo.Check.Warning.IExPry, []}, - {Credo.Check.Warning.IoInspect, []}, - {Credo.Check.Warning.LazyLogging, false}, - {Credo.Check.Warning.MixEnv, false}, - {Credo.Check.Warning.OperationOnSameValues, []}, - {Credo.Check.Warning.OperationWithConstantResult, []}, - {Credo.Check.Warning.RaiseInsideRescue, []}, - {Credo.Check.Warning.UnusedEnumOperation, []}, - {Credo.Check.Warning.UnusedFileOperation, []}, - {Credo.Check.Warning.UnusedKeywordOperation, []}, - {Credo.Check.Warning.UnusedListOperation, []}, - {Credo.Check.Warning.UnusedPathOperation, []}, - {Credo.Check.Warning.UnusedRegexOperation, []}, - {Credo.Check.Warning.UnusedStringOperation, []}, - {Credo.Check.Warning.UnusedTupleOperation, []}, - {Credo.Check.Warning.UnsafeExec, []}, - - # - ## Controversial and experimental checks (opt-in, replace `false` with `[]`) - # - {Credo.Check.Readability.StrictModuleLayout, []}, - {Credo.Check.Consistency.MultiAliasImportRequireUse, false}, - {Credo.Check.Consistency.UnusedVariableNames, []}, - {Credo.Check.Design.DuplicatedCode, false}, - {Credo.Check.Readability.AliasAs, false}, - {Credo.Check.Readability.MultiAlias, []}, - {Credo.Check.Readability.Specs, false}, - {Credo.Check.Readability.SinglePipe, false}, - {Credo.Check.Readability.WithCustomTaggedTuple, []}, - {Credo.Check.Refactor.ABCSize, false}, - {Credo.Check.Refactor.AppendSingleItem, false}, - {Credo.Check.Refactor.DoubleBooleanNegation, []}, - {Credo.Check.Refactor.ModuleDependencies, [max_deps: 15]}, - {Credo.Check.Refactor.NegatedIsNil, []}, - {Credo.Check.Refactor.PipeChainStart, false}, - {Credo.Check.Refactor.VariableRebinding, false}, - {Credo.Check.Warning.LeakyEnvironment, false}, - {Credo.Check.Warning.MapGetUnsafePass, []}, - {Credo.Check.Warning.UnsafeToAtom, []} - - # - # Custom checks can be created using `mix credo.gen.check`. - # - ] - } - ] -} diff --git a/implementations/elixir/ockam/ockam_vault_software/.gitignore b/implementations/elixir/ockam/ockam_vault_software/.gitignore deleted file mode 100644 index e3941904f60..00000000000 --- a/implementations/elixir/ockam/ockam_vault_software/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -_build -deps -cmake-build-* -priv/* diff --git a/implementations/elixir/ockam/ockam_vault_software/README.md b/implementations/elixir/ockam/ockam_vault_software/README.md deleted file mode 100644 index c63401db6cc..00000000000 --- a/implementations/elixir/ockam/ockam_vault_software/README.md +++ /dev/null @@ -1,40 +0,0 @@ -## Ockam vault - -This application provides NIFs to access Vault functions implemented in Rust. - -## NIF libs - -This application requires a `libockam_elixir_ffi.so` NIF to function. - -Ockam release provides pre-built NIF libraries for MacOS(universal) and Linux(x86_64_gnu) in https://github.com/build-trust/ockam/releases/latest - -If you run this application on supported architectures, it will download the libraries from release. -For other architectures the build process will try to re-build the NIFs and put them in `priv/native`. - -**HEX packages are shipped with release NIFs of same version number as the HEX package.** - -## Rebuilding NIFs - -NIFs are built using CMake - -Build requires existing and built of `ockam_vault` and `ockam_ffi` Rust libraries, you can build them in `implementations/rust/ockam/ockam_ffi` by running `cargo build --release`. - -You can force build the NIFs even for MacOS and Linux by running `mix recompile.native`. -If there are some issues with the libs loading, for example. - -**NOTE Custom built libs take precedence when loading. If there are lib files in `priv/native`, they will be used instead those downloaded to `priv/.../native`** - - -## Publishing the package - -To publish the current version: - -`mix hex.publish` - -Publish will download release libs from the same version as the package. - -To build a new version (without changing mix.exs): - -`VERSION= mix hex.publish` - - diff --git a/implementations/elixir/ockam/ockam_vault_software/build-elixir-universal-lib.sh b/implementations/elixir/ockam/ockam_vault_software/build-elixir-universal-lib.sh deleted file mode 100755 index 6b897076b0d..00000000000 --- a/implementations/elixir/ockam/ockam_vault_software/build-elixir-universal-lib.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/bin/bash -set -e -# This requires `ockam_ffi/include/vault.h` to be -# renamed to `ockam_ffi/include/ockam/vault.h`, -# to match what erlang is including. IMO that -# seems reasonable anyway. -# -# Also, note that clang (and apple) call 64-bit -# arm "arm64" and rust (and ARM) call it "aarch64". -# (It's a bit like x86_64 vs amd64) -# -# Finally, note that I don't remember how bash works, -# and you could definitely clean this up. -# -# Possible issues: -# - always building as release. -# - use of `cc` over e.g. `xcrun cc` -# - not passing `xcrun --show-sdk-path --sdk macosx` -# - no explicit MACOSX_DEPLOYMENT_TARGET / min macos version -# - not allowing custom "$CFLAGS", etc. -# - produces a .dylib when the elixir wants a .so. it's better for us -# I think if we passed `-o libblah.so` then -# - no support for "$CARGO_TARGET_DIR" -# - doesn't detect that the user needs to -# `rustup target add x86_64-apple-darwin aarch64-apple-darwin` -# - always builds everything every time... - -ERLANG_INCLUDE_DIR=$(erl -eval 'io:format("~s", [lists:concat([code:root_dir(), "/erts-", erlang:system_info(version), "/include"])])' -s init stop -noshell) - -OCKAM_ROOT=$(git rev-parse --show-toplevel) - -OCKAM_VAULT_SOFTWARE_DIR="$OCKAM_ROOT/implementations/elixir/ockam/ockam_vault_software" -# NOT SURE THAT THIS -BUILD_DIR="$OCKAM_VAULT_SOFTWARE_DIR/priv" - -OCKAM_FFI_DIR="$OCKAM_ROOT/implementations/rust/ockam/ockam_ffi" -NIF_SOURCE_DIR="$OCKAM_VAULT_SOFTWARE_DIR/native/vault/software" - -mkdir -p "$BUILD_DIR/darwin_x86_64/native" "$BUILD_DIR/darwin_arm64/native" "$BUILD_DIR/darwin_universal/native" - -# cargo build for x86_64 -pushd "$OCKAM_FFI_DIR" -cargo build --release --target=x86_64-apple-darwin -popd - -# build for x86_64, needs: `-arch x86_64 -m64` flags -# the x86_64-apple-darwin rust lib as an input -# and to be placed in the right output -cc \ - -I "$NIF_SOURCE_DIR" -I "$OCKAM_FFI_DIR/include" -I "$ERLANG_INCLUDE_DIR" \ - -arch x86_64 -m64 "$OCKAM_ROOT/target/x86_64-apple-darwin/release/libockam_ffi.a" \ - "$NIF_SOURCE_DIR/common.c" "$NIF_SOURCE_DIR/nifs.c" "$NIF_SOURCE_DIR/vault.c" \ - -O3 -fPIC -shared -Wl,-undefined,dynamic_lookup \ - -o "$BUILD_DIR/darwin_x86_64/native/libockam_elixir_ffi.dylib" - -echo "### Building rust code for for aarch64" - -# build for aarch64, needs: `-arch arm64` (note: not aarch64!) -# the aarch64-apple-darwin rust lib as an input -# and to be placed in the right output -pushd "$OCKAM_FFI_DIR" -cargo build --release --target=aarch64-apple-darwin -popd - -cc \ - -I "$NIF_SOURCE_DIR" \ - -I "$OCKAM_FFI_DIR/include" \ - -I "$ERLANG_INCLUDE_DIR" \ - -arch arm64 "$OCKAM_ROOT/target/aarch64-apple-darwin/release/libockam_ffi.a" \ - "$NIF_SOURCE_DIR/common.c" "$NIF_SOURCE_DIR/nifs.c" "$NIF_SOURCE_DIR/vault.c" \ - -O3 -fPIC -shared -Wl,-undefined,dynamic_lookup \ - -o "$BUILD_DIR/darwin_arm64/native/libockam_elixir_ffi.dylib" - -echo "### Producing universal binary" -# Create a universal binary -lipo -create \ - -output "$BUILD_DIR/darwin_universal/native/libockam_elixir_ffi.dylib" \ - "$BUILD_DIR/darwin_arm64/native/libockam_elixir_ffi.dylib" \ - "$BUILD_DIR/darwin_x86_64/native/libockam_elixir_ffi.dylib" - -# Rename to .so to make erlang load it properly -mv "$BUILD_DIR/darwin_arm64/native/libockam_elixir_ffi.dylib" "$BUILD_DIR/darwin_arm64/native/libockam_elixir_ffi.so" -mv "$BUILD_DIR/darwin_x86_64/native/libockam_elixir_ffi.dylib" "$BUILD_DIR/darwin_x86_64/native/libockam_elixir_ffi.so" -mv "$BUILD_DIR/darwin_universal/native/libockam_elixir_ffi.dylib" "$BUILD_DIR/darwin_universal/native/libockam_elixir_ffi.so" diff --git a/implementations/elixir/ockam/ockam_vault_software/lib/vault_software.ex b/implementations/elixir/ockam/ockam_vault_software/lib/vault_software.ex deleted file mode 100644 index da5a85cebc6..00000000000 --- a/implementations/elixir/ockam/ockam_vault_software/lib/vault_software.ex +++ /dev/null @@ -1,186 +0,0 @@ -defmodule Ockam.Vault.Software do - @moduledoc """ - Ockam.Vault.Software - """ - - use Application - - defstruct [:id] - - @dialyzer :no_return - - @on_load {:load_natively_implemented_functions, 0} - - app = Mix.Project.config()[:app] - - def load_natively_implemented_functions do - native_path = native_lib_path() - - case :erlang.load_nif(to_charlist(native_path), 0) do - :ok -> - :ok - - {:error, {reason, text}} when reason == :load_failed or reason == :bad_lib -> - error = - case prebuilt_path?(native_path) do - true -> - "Failed to load pre-built ockam vault NIF: #{text}\nYou can run `mix recompile.native` for ockam_vault_software to re-build the NIF library" - - false -> - "Failed to load ockam vault NIF: #{text}\n" - end - - raise error - end - end - - defp native_lib_path() do - custom_build_path = get_native_lib_path("") - - case lib_path_exists?(custom_build_path) do - ## There is a custom-build lib - true -> - custom_build_path - - false -> - with {:ok, subdir} <- os_subdir(), - prebuilt <- get_native_lib_path(subdir), - true <- lib_path_exists?(prebuilt) do - prebuilt - else - _err -> - error = - "Ockam vault NIF lib not found. Please run `mix recompile.native` for ockam_vault_software to re-build the NIF library" - - raise error - end - end - end - - defp prebuilt_path?(lib_path) do - lib_path != get_native_lib_path("") - end - - defp lib_path_exists?(lib_path) do - Enum.count(Path.wildcard(lib_path <> "*")) > 0 - end - - defp get_native_lib_path(subdir) do - Path.join([:code.priv_dir(unquote(app)), subdir, "native", "libockam_elixir_ffi"]) - end - - defp os_subdir() do - case {:os.type(), to_string(:erlang.system_info(:system_architecture))} do - ## Linux libs only built for GNU - {{:unix, :linux}, "x86_64" <> type} -> - if String.ends_with?(type, "gnu") do - {:ok, "linux_x86_64_gnu"} - else - :error - end - - {{:unix, :linux}, "aarch64" <> type} -> - if String.ends_with?(type, "gnu") do - {:ok, "linux_aarch64_gnu"} - else - :error - end - - ## MacOS libs are multi-arch - {{:unix, :darwin}, "x86_64" <> _} -> - {:ok, "darwin_universal"} - - {{:unix, :darwin}, "aarch64" <> _} -> - {:ok, "darwin_universal"} - - _err -> - :error - end - end - - # Called when the Ockam application is started. - # - # This function is called when an application is started using - # `Application.start/2`, `Application.ensure_started/2` etc. - # - @doc false - def start(_type, _args) do - # Specifications of child processes that will be started and supervised. - # - # See the "Child specification" section in the `Supervisor` module for more - # detailed information. - children = [] - - # Start a supervisor with the given children. The supervisor will inturn - # start the given children. - # - # The :one_for_one supervision strategy is used, if a child process - # terminates, only that process is restarted. - # - # See the "Strategies" section in the `Supervisor` module for more - # detailed information. - Supervisor.start_link(children, strategy: :one_for_one, name: __MODULE__) - end - - def init do - with {:ok, id} <- default_init() do - {:ok, %__MODULE__{id: id}} - end - end - - def default_init do - raise "natively implemented default_init/0 not loaded" - end - - def sha256(_vault, _input) do - raise "natively implemented sha256/2 not loaded" - end - - def secret_generate(_vault, _attributes) do - raise "natively implemented secret_generate/2 not loaded" - end - - def secret_import(_vault, _attributes, _input) do - raise "natively implemented secret_import/3 not loaded" - end - - def secret_export(_vault, _secret_handle) do - raise "natively implemented secret_export/2 not loaded" - end - - def secret_publickey_get(_vault, _secret_handle) do - raise "natively implemented secret_publickey_get/2 not loaded" - end - - def secret_attributes_get(_vault, _secret_handle) do - raise "natively implemented secret_attributes_get/2 not loaded" - end - - def secret_destroy(_vault, _secret_handle) do - raise "natively implemented secret_destroy/2 not loaded" - end - - def ecdh(_vault, _secret_handle, _input) do - raise "natively implemented ecdh/3 not loaded" - end - - def hkdf_sha256(_vault, _salt_handle, _ikm_handle, _derived_outputs_count) do - raise "natively implemented hkdf_sha256/4 not loaded" - end - - def hkdf_sha256(_vault, _salt_handle, _ikm_handle) do - raise "natively implemented hkdf_sha256/3 not loaded" - end - - def aead_aes_gcm_encrypt(_vault, _key_handle, _nonce, _ad, _plain_text) do - raise "natively implemented aead_aes_gcm_encrypt/5 not loaded" - end - - def aead_aes_gcm_decrypt(_vault, _key_handle, _nonce, _ad, _cipher_text) do - raise "natively implemented aead_aes_gcm_decrypt/5 not loaded" - end - - def deinit(_vault) do - raise "natively implemented deinit/1 not loaded" - end -end diff --git a/implementations/elixir/ockam/ockam_vault_software/mix.exs b/implementations/elixir/ockam/ockam_vault_software/mix.exs deleted file mode 100644 index 22965fa301b..00000000000 --- a/implementations/elixir/ockam/ockam_vault_software/mix.exs +++ /dev/null @@ -1,311 +0,0 @@ -defmodule Ockam.Vault.Software.MixProject do - use Mix.Project - - @version "0.87.0" - - @elixir_requirement "~> 1.12" - - @ockam_release_url "https://github.com/build-trust/ockam/releases" - - @download_libs [ - {"ockam.linux_x86_64_gnu_elixir_ffi.so", - ["linux_x86_64_gnu", "native", "libockam_elixir_ffi.so"]}, - {"ockam.linux_aarch64_gnu_elixir_ffi.so", - ["linux_aarch64_gnu", "native", "libockam_elixir_ffi.so"]}, - {"ockam.darwin_universal_elixir_ffi.so", - ["darwin_universal", "native", "libockam_elixir_ffi.so"]} - ] - - @ockam_github_repo "https://github.com/build-trust/ockam" - @ockam_github_repo_path "implementations/elixir/ockam/ockam_vault_software" - - def project do - [ - app: :ockam_vault_software, - version: package_version(), - elixir: @elixir_requirement, - consolidate_protocols: Mix.env() != :test, - elixirc_options: [warnings_as_errors: true], - deps: deps(), - aliases: aliases(), - - # lint - dialyzer: [flags: ["-Wunmatched_returns", :underspecs]], - - # test - test_coverage: [output: "_build/cover"], - preferred_cli_env: ["test.cover": :test], - - # hex - description: "A software implementation of the ockam vault behaviour.", - package: package(), - - # docs - name: "Ockam Vault Software", - docs: docs() - ] - end - - # mix help compile.app for more - def application do - [ - mod: {Ockam.Vault.Software, []}, - extra_applications: [] - ] - end - - defp deps do - [ - {:ex_doc, "~> 0.25", only: :dev, runtime: false}, - {:credo, "~> 1.6", only: [:dev, :test], runtime: false}, - {:dialyxir, "~> 1.1", only: [:dev], runtime: false} - ] - end - - # used by hex - defp package do - [ - links: %{"GitHub" => @ockam_github_repo}, - licenses: ["Apache-2.0"], - source_url: @ockam_github_repo - ] - end - - # used by ex_doc - defp docs do - [ - main: "Ockam.Vault.Software", - source_url_pattern: - "#{@ockam_github_repo}/blob/v#{package_version()}/#{@ockam_github_repo_path}/%{path}#L%{line}" - ] - end - - defp aliases do - [ - "check.native": &check_native/1, - "download.native": &download_native/1, - "recompile.native": &recompile_native/1, - "clean.native": &clean_native/1, - "hex.build": ["download.native --version=#{package_version()}", "hex.build"], - "hex.publish": ["download.native --version=#{package_version()}", "hex.publish"], - compile: ["check.native", "compile"], - clean: ["clean", "clean.native"], - docs: "docs --output _build/docs --formatter html", - "test.cover": "test --cover", - "lint.format": "format --check-formatted", - "lint.credo": "credo --strict", - "lint.dialyzer": "dialyzer --format dialyxir", - lint: ["lint.format", "lint.credo"] - ] - end - - defp native_build_path(), do: Path.join([Mix.Project.build_path(), "native"]) - - defp native_priv_path() do - Path.join([Mix.Project.app_path(), "priv", "native"]) - end - - defp check_native(args) do - case test_recompile?() do - true -> - recompile_native(args) - - _ -> - case prebuilt_lib_exists?() do - true -> :ok - false -> download_native(args) - end - - ## Check again if download failed or file is missing - case prebuilt_lib_exists?() do - true -> - :ok - - false -> - IO.puts("Could not download prebuilt lib. Recompiling.") - recompile_native(args) - end - end - end - - defp download_native(args) do - version_path = - case ockam_version(args) do - "latest" -> "/latest/download" - other -> "/download/ockam_" <> other - end - - base_url = @ockam_release_url <> version_path - - ## To download files we need inets and ssl - :inets.start() - :ssl.start() - - Enum.each(@download_libs, fn {from, to} -> - download_url = base_url <> "/" <> from - dest_path = ["priv" | to] - - dest_file = Path.join(dest_path) - dest_dir = Path.join(Enum.take(dest_path, length(dest_path) - 1)) - - File.mkdir_p!(dest_dir) - - IO.puts("Downloading lib from #{download_url} to #{dest_file}") - File.rm_rf(dest_file) - - download_result = - :httpc.request( - :get, - {to_charlist(download_url), []}, - # We should ensure we test this TLS version when - # we update OTP. - [{:ssl, [{:versions, [:"tlsv1.2"]}]}], - stream: to_charlist(dest_file) - ) - - case download_result do - {:ok, :saved_to_file} -> IO.puts("Download OK") - {:ok, other} -> IO.puts("Download error: #{inspect(other)}") - other -> IO.puts("Download error: #{inspect(other)}") - end - end) - end - - defp package_version() do - case System.get_env("VERSION") do - nil -> @version - version -> version - end - end - - defp ockam_version(args) do - case OptionParser.parse(args, switches: [version: :string]) do - {[version: version], _, _} -> - "v" <> version - - _ -> - case Mix.env() do - :dev -> "latest" - :prod -> "v" <> package_version() - end - end - end - - defp test_recompile?() do - Mix.env() == :test and System.get_env("NO_RECOMPILE_NATIVE") != "true" - end - - defp prebuilt_lib_exists?() do - case prebuilt_lib_path() do - {:ok, _path} -> true - _ -> false - end - end - - defp prebuilt_lib_path() do - with {:ok, subdir} <- os_subdir() do - case Path.wildcard(Path.join(["priv", subdir, "native", "libockam_elixir_ffi.*"])) do - [] -> :error - [_file] -> {:ok, Path.join("priv", subdir)} - end - end - end - - ## NOTE: duplicate in vault_software.ex - ## we need to run this both in compile-time and in runtime - defp os_subdir() do - case {:os.type(), to_string(:erlang.system_info(:system_architecture))} do - ## Linux libs only built for GNU - {{:unix, :linux}, "x86_64" <> type} -> - if String.ends_with?(type, "gnu") do - {:ok, "linux_x86_64_gnu"} - else - :error - end - - {{:unix, :linux}, "aarch64" <> type} -> - if String.ends_with?(type, "gnu") do - {:ok, "linux_aarch64_gnu"} - else - :error - end - - ## MacOS libs are multi-arch - {{:unix, :darwin}, "x86_64" <> _} -> - {:ok, "darwin_universal"} - - {{:unix, :darwin}, "aarch64" <> _} -> - {:ok, "darwin_universal"} - - _ -> - :error - end - end - - defp recompile_native(args) do - clean_native(args) - :ok = cmake_generate() - :ok = cmake_build() - :ok = copy_to_priv() - :ok - end - - defp clean_native(_) do - File.rm_rf!(native_build_path()) - File.rm_rf!(native_priv_path()) - end - - defp cmake_generate() do - {_, 0} = - System.cmd( - "cmake", - [ - "-S", - "native", - "-B", - native_build_path(), - "-DBUILD_SHARED_LIBS=ON", - "-DCMAKE_BUILD_TYPE=Release" - ], - into: IO.stream(:stdio, :line), - env: [{"ERL_INCLUDE_DIR", erl_include_dir()}] - ) - - :ok - end - - defp cmake_build() do - {_, 0} = - System.cmd( - "cmake", - ["--build", native_build_path()], - into: IO.stream(:stdio, :line), - env: [{"ERL_INCLUDE_DIR", erl_include_dir()}] - ) - - :ok - end - - defp erl_include_dir() do - [:code.root_dir(), Enum.concat('erts-', :erlang.system_info(:version)), 'include'] - |> Path.join() - |> to_string - end - - defp copy_to_priv() do - priv_path = native_priv_path() - File.mkdir_p!(priv_path) - - Enum.each(["dylib", "so"], fn extension -> - Path.join([native_build_path(), "**", "*.#{extension}"]) - |> Path.wildcard() - |> Enum.each(fn lib -> - filename = Path.basename(lib, ".#{extension}") - destination = Path.join(priv_path, "#{filename}.so") - File.cp!(lib, destination) - end) - end) - - :ok - end -end diff --git a/implementations/elixir/ockam/ockam_vault_software/mix.lock b/implementations/elixir/ockam/ockam_vault_software/mix.lock deleted file mode 100644 index cce3b8d9841..00000000000 --- a/implementations/elixir/ockam/ockam_vault_software/mix.lock +++ /dev/null @@ -1,14 +0,0 @@ -%{ - "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, - "credo": {:hex, :credo, "1.6.1", "7dc76dcdb764a4316c1596804c48eada9fff44bd4b733a91ccbf0c0f368be61e", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "698607fb5993720c7e93d2d8e76f2175bba024de964e160e2f7151ef3ab82ac5"}, - "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.16", "607709303e1d4e3e02f1444df0c821529af1c03b8578dfc81bb9cf64553d02b9", [:mix], [], "hexpm", "69fcf696168f5a274dd012e3e305027010658b2d1630cef68421d6baaeaccead"}, - "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_doc": {:hex, :ex_doc, "0.26.0", "1922164bac0b18b02f84d6f69cab1b93bc3e870e2ad18d5dacb50a9e06b542a3", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2775d66e494a9a48355db7867478ffd997864c61c65a47d31c4949459281c78d"}, - "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, - "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.15.2", "dc72dfe17eb240552857465cc00cce390960d9a0c055c4ccd38b70629227e97c", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "fd23ae48d09b32eff49d4ced2b43c9f086d402ee4fd4fcb2d7fad97fa8823e75"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, -} diff --git a/implementations/elixir/ockam/ockam_vault_software/native/CMakeLists.txt b/implementations/elixir/ockam/ockam_vault_software/native/CMakeLists.txt deleted file mode 100644 index 907d8935819..00000000000 --- a/implementations/elixir/ockam/ockam_vault_software/native/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -cmake_minimum_required(VERSION 3.1...3.18 FATAL_ERROR) - -project(ockam_elixir VERSION 0.10.1 LANGUAGES C) - -add_subdirectory(vault) diff --git a/implementations/elixir/ockam/ockam_vault_software/native/vault/CMakeLists.txt b/implementations/elixir/ockam/ockam_vault_software/native/vault/CMakeLists.txt deleted file mode 100644 index 8b076ad6517..00000000000 --- a/implementations/elixir/ockam/ockam_vault_software/native/vault/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ - -# add all subdirectories - -file(GLOB children RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*) - -foreach(child ${children}) - if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${child}) - add_subdirectory(${child}) - endif() -endforeach() diff --git a/implementations/elixir/ockam/ockam_vault_software/native/vault/software/CMakeLists.txt b/implementations/elixir/ockam/ockam_vault_software/native/vault/software/CMakeLists.txt deleted file mode 100644 index e010c9a17d0..00000000000 --- a/implementations/elixir/ockam/ockam_vault_software/native/vault/software/CMakeLists.txt +++ /dev/null @@ -1,56 +0,0 @@ - -# --- -# ockam::ffi_interface -# --- -add_library(ockam_ffi_interface INTERFACE) -add_library(ockam::ffi_interface ALIAS ockam_ffi_interface) - -set(INCLUDE_DIR ${CMAKE_CURRENT_BINARY_DIR}/include) - -target_include_directories(ockam_ffi_interface INTERFACE ${INCLUDE_DIR}) - -file(COPY ../../../../../../rust/ockam/ockam_ffi/include/ockam/vault.h DESTINATION ${INCLUDE_DIR}/ockam) - -target_sources( - ockam_ffi_interface - INTERFACE - ${INCLUDE_DIR}/ockam/vault.h -) - -add_library(ockam_ffi STATIC IMPORTED GLOBAL) -add_library(ockam::ffi ALIAS ockam_ffi) - -file(GLOB - FFI_LIB_PATH - ${CMAKE_CURRENT_LIST_DIR}/../../../../../../../target/release/libockam_ffi${CMAKE_STATIC_LIBRARY_SUFFIX} - ) - -file(COPY ${FFI_LIB_PATH} DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") - -set(FFI_LIB_PATH "${CMAKE_CURRENT_BINARY_DIR}/libockam_ffi${CMAKE_STATIC_LIBRARY_SUFFIX}") - -get_filename_component(FFI_LIB_PATH "${FFI_LIB_PATH}" REALPATH BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}") -set_target_properties( - ockam_ffi - PROPERTIES - IMPORTED_LOCATION "${FFI_LIB_PATH}" -) - -add_library(ockam_elixir_ffi SHARED) -add_library(ockam::elixir_ffi ALIAS ockam_elixir_ffi) - -target_sources(ockam_elixir_ffi PRIVATE nifs.c vault.c vault.h common.c common.h) - -target_include_directories(ockam_elixir_ffi PUBLIC $ENV{ERL_INCLUDE_DIR}) - -if(APPLE) -set_target_properties(ockam_elixir_ffi PROPERTIES LINK_FLAGS "-dynamiclib -undefined dynamic_lookup") -endif() - -if(UNIX AND NOT APPLE) -set_target_properties(ockam_elixir_ffi PROPERTIES LINK_FLAGS "-fPIC -shared") -endif() - -target_link_libraries(ockam_elixir_ffi ockam::ffi) - -target_link_libraries(ockam_elixir_ffi ockam::ffi_interface) diff --git a/implementations/elixir/ockam/ockam_vault_software/native/vault/software/common.c b/implementations/elixir/ockam/ockam_vault_software/native/vault/software/common.c deleted file mode 100644 index 9784b206bd9..00000000000 --- a/implementations/elixir/ockam/ockam_vault_software/native/vault/software/common.c +++ /dev/null @@ -1,61 +0,0 @@ -#include "common.h" -#include - -bool extern_error_has_error(const ockam_vault_extern_error_t* error) { - return error->code != 0; -} - -bool extern_error_check_and_free_error(ockam_vault_extern_error_t* error) { - bool result = extern_error_has_error(error); - ockam_vault_free_error(error); - return result; -} - -ERL_NIF_TERM ok_void(ErlNifEnv *env) { - return enif_make_atom(env, "ok"); -} - -ERL_NIF_TERM ok(ErlNifEnv *env, ERL_NIF_TERM result) { - ERL_NIF_TERM id = enif_make_atom(env, "ok"); - return enif_make_tuple2(env, id, result); -} - -ERL_NIF_TERM error_tuple(ErlNifEnv *env, const char* msg) { - ERL_NIF_TERM e = enif_make_atom(env, "error"); - ERL_NIF_TERM m = enif_make_string(env, msg, ERL_NIF_LATIN1); - return enif_make_tuple2(env, e, m); -} - -int parse_vault_handle(ErlNifEnv *env, ERL_NIF_TERM argv, ockam_vault_t* vault) { - unsigned int count; - if (0 == enif_get_list_length(env, argv, &count)) { - return -1; - } - - if (count != 2) { - return -1; - } - - ERL_NIF_TERM current_list = argv; - ERL_NIF_TERM head; - ERL_NIF_TERM tail; - - if (0 == enif_get_list_cell(env, current_list, &head, &tail)) { - return -1; - } - current_list = tail; - - ErlNifUInt64 handle = 0; - enif_get_uint64(env, head, &handle); - - if (0 == enif_get_list_cell(env, current_list, &head, &tail)) { - return -1; - } - - ErlNifUInt64 vault_type = 0; - enif_get_uint64(env, head, &vault_type); - vault->handle = handle; - vault->vault_type = vault_type; - - return 0; -} diff --git a/implementations/elixir/ockam/ockam_vault_software/native/vault/software/common.h b/implementations/elixir/ockam/ockam_vault_software/native/vault/software/common.h deleted file mode 100644 index 07f925959f0..00000000000 --- a/implementations/elixir/ockam/ockam_vault_software/native/vault/software/common.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef OCKAM_ELIXIR_COMMON_H -#define OCKAM_ELIXIR_COMMON_H - -#include -#include -#include -#include "erl_nif.h" - -bool extern_error_has_error(const ockam_vault_extern_error_t *error); -bool extern_error_check_and_free_error(ockam_vault_extern_error_t *error); - -ERL_NIF_TERM ok_void(ErlNifEnv *env); - -ERL_NIF_TERM ok(ErlNifEnv *env, ERL_NIF_TERM result); - -ERL_NIF_TERM error_tuple(ErlNifEnv *env, const char* msg); - -int parse_vault_handle(ErlNifEnv *env, ERL_NIF_TERM argv, ockam_vault_t* vault); - -#endif //OCKAM_ELIXIR_COMMON_H diff --git a/implementations/elixir/ockam/ockam_vault_software/native/vault/software/nifs.c b/implementations/elixir/ockam/ockam_vault_software/native/vault/software/nifs.c deleted file mode 100644 index c9682b774e1..00000000000 --- a/implementations/elixir/ockam/ockam_vault_software/native/vault/software/nifs.c +++ /dev/null @@ -1,22 +0,0 @@ -#include "erl_nif.h" -#include "vault.h" - -static ErlNifFunc nifs[] = { - // {erl_function_name, erl_function_arity, c_function} - {"default_init", 0, default_init}, - {"sha256", 2, sha256}, - {"secret_generate", 2, secret_generate}, - {"secret_import", 3, secret_import}, - {"secret_export", 2, secret_export}, - {"secret_publickey_get", 2, secret_publickey_get}, - {"secret_attributes_get", 2, secret_attributes_get}, - {"secret_destroy", 2, secret_destroy}, - {"ecdh", 3, ecdh}, - {"hkdf_sha256", 3, hkdf_sha256}, - {"hkdf_sha256", 4, hkdf_sha256}, - {"aead_aes_gcm_encrypt", 5, aead_aes_gcm_encrypt}, - {"aead_aes_gcm_decrypt", 5, aead_aes_gcm_decrypt}, - {"deinit", 1, deinit}, -}; - -ERL_NIF_INIT(Elixir.Ockam.Vault.Software, nifs, NULL, NULL, NULL, NULL) diff --git a/implementations/elixir/ockam/ockam_vault_software/native/vault/software/vault.c b/implementations/elixir/ockam/ockam_vault_software/native/vault/software/vault.c deleted file mode 100644 index 9ff5a05e0d8..00000000000 --- a/implementations/elixir/ockam/ockam_vault_software/native/vault/software/vault.c +++ /dev/null @@ -1,614 +0,0 @@ -#include -#include "common.h" -#include "vault.h" -#include "ockam/vault.h" - -static const size_t MAX_ARG_STR_SIZE = 32; -static const size_t MAX_SECRET_EXPORT_SIZE = 65; -static const size_t MAX_PUBLICKEY_SIZE = 65; -static const size_t MAX_DERIVED_OUTPUT_COUNT = 2; -static const size_t MAX_PERSISTENCE_ID_SIZE = 64; - -static const char* SECRET_TYPE_KEY = "type"; -static const char* SECRET_TYPE_BUFFER = "buffer"; -static const char* SECRET_TYPE_AES = "aes"; -static const char* SECRET_TYPE_CURVE25519 = "curve25519"; -static const char* SECRET_TYPE_P256 = "p256"; - -static const char* SECRET_PERSISTENCE_KEY = "persistence"; -static const char* SECRET_PERSISTENCE_EPHEMERAL = "ephemeral"; -static const char* SECRET_PERSISTENCE_PERSISTENT = "persistent"; - -static const char* SECRET_LENGTH_KEY = "length"; - -static int parse_secret_attributes(ErlNifEnv *env, ERL_NIF_TERM arg, ockam_vault_secret_attributes_t* attributes) { - size_t num_keys; - if (0 == enif_get_map_size(env, arg, &num_keys)) { - return -1; - } - - if (num_keys < 3 || 4 < num_keys) { - return -1; - } - - ERL_NIF_TERM term = enif_make_atom(env, SECRET_TYPE_KEY); - ERL_NIF_TERM value; - - if (0 == enif_get_map_value(env, arg, term, &value)) { - return -1; - } - - char buf[MAX_ARG_STR_SIZE]; // TODO: Document max allowed size somewhere? - - if (0 == enif_get_atom(env, value, buf, sizeof(buf), ERL_NIF_LATIN1)) { - return -1; - } - - if (strncmp(SECRET_TYPE_BUFFER, buf, sizeof(buf)) == 0) { - attributes->type = OCKAM_VAULT_SECRET_TYPE_BUFFER; - } else if (strncmp(SECRET_TYPE_AES, buf, sizeof(buf)) == 0) { - attributes->type = OCKAM_VAULT_SECRET_TYPE_AES_KEY; - } else if (strncmp(SECRET_TYPE_CURVE25519, buf, sizeof(buf)) == 0) { - attributes->type = OCKAM_VAULT_SECRET_TYPE_CURVE25519_PRIVATEKEY; - } else if (strncmp(SECRET_TYPE_P256, buf, sizeof(buf)) == 0) { - attributes->type = OCKAM_VAULT_SECRET_TYPE_P256_PRIVATEKEY; - } else { - return -1; - } - - term = enif_make_atom(env, SECRET_PERSISTENCE_KEY); - - // FIXME: Replace with iterator - if (0 == enif_get_map_value(env, arg, term, &value)) { - return -1; - } - - if (0 == enif_get_atom(env, value, buf, sizeof(buf), ERL_NIF_LATIN1)) { - return -1; - } - - if (strncmp(SECRET_PERSISTENCE_EPHEMERAL, buf, sizeof(buf)) == 0) { - attributes->persistence = OCKAM_VAULT_SECRET_EPHEMERAL; - } else if (strncmp(SECRET_PERSISTENCE_PERSISTENT, buf, sizeof(buf)) == 0) { - attributes->persistence = OCKAM_VAULT_SECRET_PERSISTENT; - } else { - return -1; - } - - term = enif_make_atom(env, SECRET_LENGTH_KEY); - - uint32_t length = 0; - if (0 != enif_get_map_value(env, arg, term, &value)) { - if (0 == enif_get_uint(env, value, &length)) { - return -1; - } - } - - attributes->length = length; - - return 0; -} - -static ERL_NIF_TERM create_term_from_secret_attributes(ErlNifEnv *env, const ockam_vault_secret_attributes_t* attributes) { - ERL_NIF_TERM map = enif_make_new_map(env); - - ERL_NIF_TERM type_key = enif_make_atom(env, SECRET_TYPE_KEY); - - const char* type_value_str; - switch (attributes->type) { - case OCKAM_VAULT_SECRET_TYPE_BUFFER: type_value_str = SECRET_TYPE_BUFFER; break; - case OCKAM_VAULT_SECRET_TYPE_AES_KEY: type_value_str = SECRET_TYPE_AES; break; - case OCKAM_VAULT_SECRET_TYPE_CURVE25519_PRIVATEKEY: type_value_str = SECRET_TYPE_CURVE25519; break; - case OCKAM_VAULT_SECRET_TYPE_P256_PRIVATEKEY: type_value_str = SECRET_TYPE_P256; break; - - default: - return enif_make_badarg(env); - } - - ERL_NIF_TERM type_value = enif_make_atom(env, type_value_str); - - if (0 == enif_make_map_put(env, map, type_key, type_value, &map)) { - return enif_make_badarg(env); - } - - ERL_NIF_TERM persistence_key = enif_make_atom(env, SECRET_PERSISTENCE_KEY); - - const char* persistence_value_str; - switch (attributes->persistence) { - case OCKAM_VAULT_SECRET_EPHEMERAL: persistence_value_str = SECRET_PERSISTENCE_EPHEMERAL; break; - case OCKAM_VAULT_SECRET_PERSISTENT: persistence_value_str = SECRET_PERSISTENCE_PERSISTENT; break; - - default: - return enif_make_badarg(env); - } - - ERL_NIF_TERM persistence_value = enif_make_atom(env, persistence_value_str); - - if (0 == enif_make_map_put(env, map, persistence_key, persistence_value, &map)) { - return enif_make_badarg(env); - } - - ERL_NIF_TERM length_key = enif_make_atom(env, SECRET_LENGTH_KEY); - ERL_NIF_TERM length_value = enif_make_uint(env, attributes->length); - - if (0 == enif_make_map_put(env, map, length_key, length_value, &map)) { - return enif_make_badarg(env); - } - - return ok(env, map); -} - -ERL_NIF_TERM default_init(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - if (0 != argc) { - return enif_make_badarg(env); - } - - ockam_vault_t vault; - - ockam_vault_extern_error_t error = ockam_vault_default_init(&vault); - if (extern_error_check_and_free_error(&error)) { - return error_tuple(env, "failed to create vault connection"); - } - - ERL_NIF_TERM handle = enif_make_uint64(env, vault.handle); - ERL_NIF_TERM vault_type = enif_make_uint64(env, vault.vault_type); - - ERL_NIF_TERM vault_handle = enif_make_list2(env, handle, vault_type); - - return ok(env, vault_handle); -} - -ERL_NIF_TERM sha256(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - if (2 != argc) { - return enif_make_badarg(env); - } - - ockam_vault_t vault; - if (0 != parse_vault_handle(env, argv[0], &vault)) { - return enif_make_badarg(env); - } - - ErlNifBinary input; - if (0 == enif_inspect_binary(env, argv[1], &input)) { - return enif_make_badarg(env); - } - - ERL_NIF_TERM term; - uint8_t* digest = enif_make_new_binary(env, 32, &term); - - if (NULL == digest) { - return error_tuple(env, "failed to create buffer for hash"); - } - - memset(digest, 0, 32); - - ockam_vault_extern_error_t error = ockam_vault_sha256(vault, input.data, input.size, digest); - if (extern_error_check_and_free_error(&error)) { - return error_tuple(env, "failed to compute sha256 digest"); - } - - return ok(env, term); -} - -ERL_NIF_TERM secret_generate(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - if (2 != argc) { - return enif_make_badarg(env); - } - - ockam_vault_t vault; - if (0 != parse_vault_handle(env, argv[0], &vault)) { - return enif_make_badarg(env); - } - - ockam_vault_secret_attributes_t attributes; - if (0 != parse_secret_attributes(env, argv[1], &attributes)) { - return enif_make_badarg(env); - } - - ockam_vault_secret_t secret; - ockam_vault_extern_error_t error = ockam_vault_secret_generate(vault, &secret, attributes); - if (extern_error_check_and_free_error(&error)) { - return error_tuple(env, "unable to generate the secret"); - } - - ERL_NIF_TERM secret_handle = enif_make_uint64(env, secret); - - return ok(env, secret_handle); -} - -ERL_NIF_TERM secret_import(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - if (3 != argc) { - return enif_make_badarg(env); - } - - ockam_vault_t vault; - if (0 != parse_vault_handle(env, argv[0], &vault)) { - return enif_make_badarg(env); - } - - ockam_vault_secret_attributes_t attributes; - if (0 != parse_secret_attributes(env, argv[1], &attributes)) { - return enif_make_badarg(env); - } - - ErlNifBinary input; - if (0 == enif_inspect_binary(env, argv[2], &input)) { - return enif_make_badarg(env); - } - - ockam_vault_secret_t secret; - ockam_vault_extern_error_t error = ockam_vault_secret_import(vault, &secret, attributes, input.data, input.size); - if (extern_error_check_and_free_error(&error)) { - return error_tuple(env, "unable to import the secret"); - } - - ERL_NIF_TERM secret_handle = enif_make_uint64(env, secret); - - return ok(env, secret_handle); -} - -ERL_NIF_TERM secret_export(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - if (2 != argc) { - return enif_make_badarg(env); - } - - ockam_vault_t vault; - if (0 != parse_vault_handle(env, argv[0], &vault)) { - return enif_make_badarg(env); - } - - ErlNifUInt64 secret_handle; - if (0 == enif_get_uint64(env, argv[1], &secret_handle)) { - return enif_make_badarg(env); - } - - uint8_t buffer[MAX_SECRET_EXPORT_SIZE]; - uint32_t length = 0; - - ockam_vault_extern_error_t error = ockam_vault_secret_export(vault, secret_handle, buffer, MAX_SECRET_EXPORT_SIZE, &length); - if (extern_error_check_and_free_error(&error)) { - return error_tuple(env, "failed to ockam_vault_secret_export"); - } - - ERL_NIF_TERM output; - uint8_t* bytes = enif_make_new_binary(env, length, &output); - - if (0 == bytes) { - return error_tuple(env, "failed to create buffer for secret export"); - } - memcpy(bytes, buffer, length); - - return ok(env, output); -} - -ERL_NIF_TERM secret_publickey_get(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - if (2 != argc) { - return enif_make_badarg(env); - } - - ockam_vault_t vault; - if (0 != parse_vault_handle(env, argv[0], &vault)) { - return enif_make_badarg(env); - } - - ErlNifUInt64 secret_handle; - if (0 == enif_get_uint64(env, argv[1], &secret_handle)) { - return enif_make_badarg(env); - } - - uint8_t buffer[MAX_PUBLICKEY_SIZE]; - uint32_t length = 0; - - ockam_vault_extern_error_t error = ockam_vault_secret_publickey_get(vault, secret_handle, buffer, MAX_SECRET_EXPORT_SIZE, &length); - if (extern_error_check_and_free_error(&error)) { - return error_tuple(env, "failed to ockam_vault_secret_publickey_get"); - } - - ERL_NIF_TERM output; - uint8_t* bytes = enif_make_new_binary(env, length, &output); - - if (0 == bytes) { - return error_tuple(env, "failed to create buffer for secret_publickey_get"); - } - memcpy(bytes, buffer, length); - - return ok(env, output); -} - -ERL_NIF_TERM secret_attributes_get(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - if (2 != argc) { - return enif_make_badarg(env); - } - - ockam_vault_t vault; - if (0 != parse_vault_handle(env, argv[0], &vault)) { - return enif_make_badarg(env); - } - - ErlNifUInt64 secret_handle; - if (0 == enif_get_uint64(env, argv[1], &secret_handle)) { - return enif_make_badarg(env); - } - - ockam_vault_secret_attributes_t attributes; - ockam_vault_extern_error_t error = ockam_vault_secret_attributes_get(vault, secret_handle, &attributes); - if (extern_error_check_and_free_error(&error)) { - return error_tuple(env, "failed to secret_attributes_get"); - } - - return create_term_from_secret_attributes(env, &attributes); -} - -ERL_NIF_TERM secret_destroy(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - if (2 != argc) { - return enif_make_badarg(env); - } - - ockam_vault_t vault; - if (0 != parse_vault_handle(env, argv[0], &vault)) { - return enif_make_badarg(env); - } - - ErlNifUInt64 secret_handle; - if (0 == enif_get_uint64(env, argv[1], &secret_handle)) { - return enif_make_badarg(env); - } - - ockam_vault_extern_error_t error = ockam_vault_secret_destroy(vault, secret_handle); - if (extern_error_check_and_free_error(&error)) { - return error_tuple(env, "failed to secret_destroy"); - } - - return ok_void(env); -} - -ERL_NIF_TERM ecdh(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - if (3 != argc) { - return enif_make_badarg(env); - } - - ockam_vault_t vault; - if (0 != parse_vault_handle(env, argv[0], &vault)) { - return enif_make_badarg(env); - } - - ErlNifUInt64 secret_handle; - if (0 == enif_get_uint64(env, argv[1], &secret_handle)) { - return enif_make_badarg(env); - } - - ErlNifBinary input; - if (0 == enif_inspect_binary(env, argv[2], &input)) { - return enif_make_badarg(env); - } - - ockam_vault_secret_t shared_secret; - ockam_vault_extern_error_t error = ockam_vault_ecdh(vault, secret_handle, input.data, input.size, &shared_secret); - if (extern_error_check_and_free_error(&error)) { - return error_tuple(env, "failed to ecdh"); - } - - ERL_NIF_TERM shared_secret_term = enif_make_uint64(env, shared_secret); - - return ok(env, shared_secret_term); -} - -ERL_NIF_TERM hkdf_sha256(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - if (4 != argc && 3 != argc) { - return enif_make_badarg(env); - } - - ockam_vault_t vault; - if (0 != parse_vault_handle(env, argv[0], &vault)) { - return enif_make_badarg(env); - } - - ErlNifUInt64 salt_handle; - if (0 == enif_get_uint64(env, argv[1], &salt_handle)) { - return enif_make_badarg(env); - } - - size_t i = 2; - ErlNifUInt64 ikm_handle; - const ockam_vault_secret_t *ikm_handle_ptr = (const ockam_vault_secret_t *) &ikm_handle; - if (argc == 4) { - if (0 == enif_get_uint64(env, argv[2], &ikm_handle)) { - return enif_make_badarg(env); - } - i++; - } - else { - ikm_handle_ptr = NULL; - } - - unsigned int derived_outputs_count; - if (0 == enif_get_list_length(env, argv[i], &derived_outputs_count)) { - return enif_make_badarg(env); - } - - if (derived_outputs_count > MAX_DERIVED_OUTPUT_COUNT) { - return enif_make_badarg(env); - } - - ockam_vault_secret_attributes_t attributes[MAX_DERIVED_OUTPUT_COUNT]; - ockam_vault_secret_attributes_t* attr = attributes; - - ERL_NIF_TERM current_list = argv[i]; - ERL_NIF_TERM head; - ERL_NIF_TERM tail; - - for (unsigned int j = 0; j < derived_outputs_count; j++, attr++) { - if (0 == enif_get_list_cell(env, current_list, &head, &tail)) { - return enif_make_badarg(env); - } - current_list = tail; - if (0 != parse_secret_attributes(env, head, attr)) { - return enif_make_badarg(env); - } - } - - ockam_vault_secret_t shared_secrets[MAX_DERIVED_OUTPUT_COUNT]; - ockam_vault_extern_error_t error = ockam_vault_hkdf_sha256(vault, salt_handle, ikm_handle_ptr, attributes, derived_outputs_count, shared_secrets); - if (extern_error_check_and_free_error(&error)) { - return error_tuple(env, "failed to hkdf_sha256"); - } - - ERL_NIF_TERM output_array[MAX_DERIVED_OUTPUT_COUNT]; - for (size_t j = 0; j < derived_outputs_count; j++) { - output_array[j] = enif_make_uint64(env, shared_secrets[j]); - } - - ERL_NIF_TERM output = enif_make_list_from_array(env, output_array, derived_outputs_count); - - return ok(env, output); -} - -ERL_NIF_TERM aead_aes_gcm_encrypt(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - if (5 != argc) { - return enif_make_badarg(env); - } - - ockam_vault_t vault; - if (0 != parse_vault_handle(env, argv[0], &vault)) { - return enif_make_badarg(env); - } - - ErlNifUInt64 key_handle; - if (0 == enif_get_uint64(env, argv[1], &key_handle)) { - return enif_make_badarg(env); - } - - ErlNifUInt64 nonce; - if (0 == enif_get_uint64(env, argv[2], &nonce)) { - return enif_make_badarg(env); - } - - ErlNifBinary ad; - if (0 == enif_inspect_binary(env, argv[3], &ad)) { - return enif_make_badarg(env); - } - - ErlNifBinary plain_text; - if (0 == enif_inspect_binary(env, argv[4], &plain_text)) { - return enif_make_badarg(env); - } - - ERL_NIF_TERM term; - // FIXME: Allocated size should be provided by the rust lib - size_t size = plain_text.size + 16; - uint8_t* cipher_text = enif_make_new_binary(env, size, &term); - - if (NULL == cipher_text) { - return error_tuple(env, "failed to create buffer for aead_aes_gcm_encrypt"); - } - - memset(cipher_text, 0, size); - - uint32_t length = 0; - - ockam_vault_extern_error_t error = ockam_vault_aead_aes_gcm_encrypt(vault, - key_handle, - nonce, - ad.data, - ad.size, - plain_text.data, - plain_text.size, - cipher_text, - size, - &length); - if (extern_error_check_and_free_error(&error)) { - return error_tuple(env, "failed to aead_aes_gcm_encrypt"); - } - - if (length != size) { - return error_tuple(env, "buffer size is invalid during aead_aes_gcm_encrypt"); - } - - return ok(env, term); -} - -ERL_NIF_TERM aead_aes_gcm_decrypt(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - if (5 != argc) { - return enif_make_badarg(env); - } - - ockam_vault_t vault; - if (0 != parse_vault_handle(env, argv[0], &vault)) { - return enif_make_badarg(env); - } - - ErlNifUInt64 key_handle; - if (0 == enif_get_uint64(env, argv[1], &key_handle)) { - return enif_make_badarg(env); - } - - ErlNifUInt64 nonce; - if (0 == enif_get_uint64(env, argv[2], &nonce)) { - return enif_make_badarg(env); - } - - ErlNifBinary ad; - if (0 == enif_inspect_binary(env, argv[3], &ad)) { - return enif_make_badarg(env); - } - - ErlNifBinary cipher_text; - if (0 == enif_inspect_binary(env, argv[4], &cipher_text)) { - return enif_make_badarg(env); - } - - if (cipher_text.size < 16) { - return enif_make_badarg(env); - } - - ERL_NIF_TERM term; - // FIXME: Allocated size should be provided by the rust lib - size_t size = cipher_text.size - 16; - uint8_t* plain_text = enif_make_new_binary(env, size, &term); - - if (NULL == plain_text) { - return error_tuple(env, "failed to create buffer for aead_aes_gcm_decrypt"); - } - - memset(plain_text, 0, size); - - uint32_t length = 0; - - ockam_vault_extern_error_t error = ockam_vault_aead_aes_gcm_decrypt(vault, - key_handle, - nonce, - ad.data, - ad.size, - cipher_text.data, - cipher_text.size, - plain_text, - size, - &length); - if (extern_error_check_and_free_error(&error)) { - return error_tuple(env, "failed to aead_aes_gcm_decrypt"); - } - - if (length != size) { - return error_tuple(env, "buffer size is invalid during aead_aes_gcm_decrypt"); - } - - return ok(env, term); -} - -ERL_NIF_TERM deinit(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { - if (1 != argc) { - return enif_make_badarg(env); - } - - ockam_vault_t vault; - if (0 != parse_vault_handle(env, argv[0], &vault)) { - return enif_make_badarg(env); - } - - ockam_vault_extern_error_t error = ockam_vault_deinit(vault); - if (extern_error_check_and_free_error(&error)) { - return error_tuple(env, "failed to deinit vault"); - } - - return ok_void(env); -} diff --git a/implementations/elixir/ockam/ockam_vault_software/native/vault/software/vault.h b/implementations/elixir/ockam/ockam_vault_software/native/vault/software/vault.h deleted file mode 100644 index ddf1e774678..00000000000 --- a/implementations/elixir/ockam/ockam_vault_software/native/vault/software/vault.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef OCKAM_ELIXIR_VAULT_H -#define OCKAM_ELIXIR_VAULT_H - -#include "erl_nif.h" - -ERL_NIF_TERM default_init(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); - -ERL_NIF_TERM sha256(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); - -ERL_NIF_TERM secret_generate(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); - -ERL_NIF_TERM secret_import(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); - -ERL_NIF_TERM secret_export(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); - -ERL_NIF_TERM secret_publickey_get(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); - -ERL_NIF_TERM secret_attributes_get(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); - -ERL_NIF_TERM secret_destroy(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); - -ERL_NIF_TERM ecdh(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); - -ERL_NIF_TERM hkdf_sha256(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); - -ERL_NIF_TERM aead_aes_gcm_encrypt(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); - -ERL_NIF_TERM aead_aes_gcm_decrypt(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); - -ERL_NIF_TERM deinit(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]); - -#endif //OCKAM_ELIXIR_VAULT_H diff --git a/implementations/elixir/ockam/ockam_vault_software/test/test_helper.exs b/implementations/elixir/ockam/ockam_vault_software/test/test_helper.exs deleted file mode 100644 index bc5e817a230..00000000000 --- a/implementations/elixir/ockam/ockam_vault_software/test/test_helper.exs +++ /dev/null @@ -1,5 +0,0 @@ -Application.ensure_all_started(:logger) -Application.ensure_all_started(:ockam) -Application.ensure_all_started(:ockam_vault_software) - -ExUnit.start(capture_log: true, trace: true) diff --git a/implementations/elixir/ockam/ockam_vault_software/test/vault_software_test.exs b/implementations/elixir/ockam/ockam_vault_software/test/vault_software_test.exs deleted file mode 100644 index b4935344226..00000000000 --- a/implementations/elixir/ockam/ockam_vault_software/test/vault_software_test.exs +++ /dev/null @@ -1,244 +0,0 @@ -defmodule Ockam.Vault.Software.Tests do - use ExUnit.Case, async: true - doctest Ockam.Vault.Software - alias Ockam.Vault.Software, as: SoftwareVault - - describe "Ockam.Vault.Software.sha256/2" do - test "can run natively implemented functions" do - {:ok, handle} = SoftwareVault.default_init() - {:ok, hash} = SoftwareVault.sha256(handle, "test") - - assert hash == - <<159, 134, 208, 129, 136, 76, 125, 101, 154, 47, 234, 160, 197, 90, 208, 21, 163, - 191, 79, 27, 43, 11, 130, 44, 209, 93, 108, 21, 176, 240, 10, 8>> - end - end - - describe "Ockam.Vault.Software.secret_generate/2" do - test "can run natively implemented functions" do - {:ok, handle} = SoftwareVault.default_init() - attributes = %{type: :curve25519, persistence: :ephemeral, length: 32} - {:ok, secret} = SoftwareVault.secret_generate(handle, attributes) - assert secret != 0 - end - end - - describe "Ockam.Vault.Software.secret_import/3" do - test "can run natively implemented functions" do - {:ok, handle} = SoftwareVault.default_init() - attributes = %{type: :curve25519, persistence: :ephemeral, length: 32} - - key_data = - <<120, 132, 203, 140, 22, 250, 109, 249, 155, 207, 102, 47, 186, 14, 109, 252, 110, 197, - 217, 163, 147, 242, 36, 234, 91, 58, 252, 218, 244, 55, 133, 86>> - - {:ok, secret} = SoftwareVault.secret_import(handle, attributes, key_data) - assert secret != 0 - end - end - - describe "Ockam.Vault.Software.secret_export/2" do - test "can run natively implemented functions" do - {:ok, handle} = SoftwareVault.default_init() - attributes = %{type: :curve25519, persistence: :ephemeral, length: 32} - - key_data = - <<120, 132, 203, 140, 22, 250, 109, 249, 155, 207, 102, 47, 186, 14, 109, 252, 110, 197, - 217, 163, 147, 242, 36, 234, 91, 58, 252, 218, 244, 55, 133, 86>> - - {:ok, secret} = SoftwareVault.secret_import(handle, attributes, key_data) - - {:ok, data} = SoftwareVault.secret_export(handle, secret) - - assert data == key_data - end - end - - describe "Ockam.Vault.Software.secret_publickey_get/2" do - test "can run natively implemented functions" do - {:ok, handle} = SoftwareVault.default_init() - attributes = %{type: :curve25519, persistence: :ephemeral, length: 32} - - key_data = - <<120, 132, 203, 140, 22, 250, 109, 249, 155, 207, 102, 47, 186, 14, 109, 252, 110, 197, - 217, 163, 147, 242, 36, 234, 91, 58, 252, 218, 244, 55, 133, 86>> - - {:ok, secret} = SoftwareVault.secret_import(handle, attributes, key_data) - - public_key = - <<150, 222, 161, 134, 252, 228, 164, 141, 155, 94, 150, 20, 255, 187, 168, 204, 82, 148, - 227, 235, 101, 45, 106, 171, 61, 223, 40, 223, 225, 181, 77, 102>> - - {:ok, data} = SoftwareVault.secret_publickey_get(handle, secret) - - assert data == public_key - end - end - - describe "Ockam.Vault.Software.secret_attributes_get/2" do - test "can run natively implemented functions" do - {:ok, handle} = SoftwareVault.default_init() - attributes = %{type: :curve25519, persistence: :ephemeral, length: 32} - {:ok, secret} = SoftwareVault.secret_generate(handle, attributes) - - {:ok, attributes} = SoftwareVault.secret_attributes_get(handle, secret) - - assert attributes == %{type: :curve25519, persistence: :ephemeral, length: 32} - end - end - - describe "Ockam.Vault.Software.secret_destroy/2" do - test "can run natively implemented functions" do - {:ok, handle} = SoftwareVault.default_init() - attributes = %{type: :curve25519, persistence: :ephemeral, length: 32} - {:ok, secret} = SoftwareVault.secret_generate(handle, attributes) - - :ok = SoftwareVault.secret_destroy(handle, secret) - end - end - - describe "Ockam.Vault.Software.ecdh/3" do - test "can run natively implemented functions" do - {:ok, handle} = SoftwareVault.default_init() - attributes = %{type: :curve25519, persistence: :ephemeral, length: 32} - - secret_data = - <<136, 150, 7, 173, 189, 63, 35, 127, 17, 37, 185, 84, 167, 243, 90, 61, 140, 73, 183, 46, - 177, 139, 20, 171, 175, 41, 171, 202, 146, 55, 186, 114>> - - {:ok, secret1} = SoftwareVault.secret_import(handle, attributes, secret_data) - - public2 = - <<244, 220, 38, 193, 253, 60, 127, 20, 18, 61, 120, 162, 140, 188, 230, 36, 20, 82, 31, - 186, 20, 207, 112, 14, 88, 119, 23, 20, 119, 179, 226, 95>> - - {:ok, dh} = SoftwareVault.ecdh(handle, secret1, public2) - - {:ok, dh_data} = SoftwareVault.secret_export(handle, dh) - - assert dh_data == - <<174, 139, 240, 140, 226, 187, 236, 169, 59, 89, 38, 171, 165, 29, 32, 47, 148, - 161, 218, 139, 246, 23, 131, 164, 6, 109, 155, 8, 203, 90, 153, 38>> - end - end - - describe "Ockam.Vault.Software.hkdf_sha256/4" do - test "can run natively implemented functions" do - {:ok, handle} = SoftwareVault.default_init() - attributes = %{type: :buffer, persistence: :ephemeral, length: 32} - - salt_data = - <<122, 235, 128, 126, 98, 120, 229, 181, 70, 49, 183, 146, 114, 203, 117, 56, 57, 97, 114, - 156, 206, 162, 68, 171, 40, 228, 128, 217, 198, 93, 57, 93>> - - {:ok, salt} = SoftwareVault.secret_import(handle, attributes, salt_data) - - ikm_data = - <<52, 28, 249, 202, 250, 82, 168, 196, 7, 9, 236, 217, 229, 151, 87, 163, 96, 201, 169, - 224, 128, 160, 192, 242, 238, 41, 189, 157, 200, 196, 78, 144>> - - {:ok, ikm} = SoftwareVault.secret_import(handle, attributes, ikm_data) - - attributes_out1 = %{ - type: :buffer, - persistence: :ephemeral, - length: 32 - } - - attributes_out2 = %{ - type: :buffer, - persistence: :ephemeral, - length: 32 - } - - {:ok, derived_secrets} = - SoftwareVault.hkdf_sha256(handle, salt, ikm, [attributes_out1, attributes_out2]) - - {:ok, data1} = SoftwareVault.secret_export(handle, Enum.at(derived_secrets, 0)) - {:ok, data2} = SoftwareVault.secret_export(handle, Enum.at(derived_secrets, 1)) - - assert data1 == - <<59, 23, 69, 123, 40, 228, 199, 167, 81, 220, 56, 17, 94, 81, 136, 231, 180, 67, - 38, 91, 233, 144, 215, 39, 75, 67, 179, 228, 245, 22, 187, 134>> - - assert data2 == - <<19, 115, 44, 135, 74, 135, 235, 12, 109, 224, 28, 81, 156, 216, 108, 224, 191, - 254, 187, 175, 111, 210, 162, 132, 249, 167, 199, 71, 188, 118, 14, 2>> - end - end - - describe "Ockam.Vault.Software.aead_aes_gcm_encrypt/5" do - test "can run natively implemented functions" do - {:ok, handle} = SoftwareVault.default_init() - attributes = %{type: :aes, persistence: :ephemeral, length: 32} - - key_data = - <<60, 39, 4, 177, 160, 228, 92, 103, 87, 110, 249, 2, 175, 175, 130, 92, 196, 211, 49, - 250, 51, 157, 6, 45, 39, 205, 207, 84, 126, 153, 104, 209>> - - {:ok, key} = SoftwareVault.secret_import(handle, attributes, key_data) - - plain_text = "Hello, nif" - ad = "Token" - nonce = 5 - - {:ok, cipher_text} = SoftwareVault.aead_aes_gcm_encrypt(handle, key, nonce, ad, plain_text) - - assert cipher_text == - <<125, 225, 184, 225, 253, 238, 233, 167, 41, 157, 48, 205, 146, 233, 209, 117, 3, - 243, 166, 199, 19, 203, 229, 132, 96, 13>> - end - end - - describe "Ockam.Vault.Software.aead_aes_gcm_decrypt/5" do - test "can run natively implemented functions" do - {:ok, handle} = SoftwareVault.default_init() - attributes = %{type: :aes, persistence: :ephemeral, length: 32} - - key_data = - <<60, 39, 4, 177, 160, 228, 92, 103, 87, 110, 249, 2, 175, 175, 130, 92, 196, 211, 49, - 250, 51, 157, 6, 45, 39, 205, 207, 84, 126, 153, 104, 209>> - - {:ok, key} = SoftwareVault.secret_import(handle, attributes, key_data) - - plain_text = "Hello, nif" - ad = "Token" - nonce = 5 - - cipher_text = - <<125, 225, 184, 225, 253, 238, 233, 167, 41, 157, 48, 205, 146, 233, 209, 117, 3, 243, - 166, 199, 19, 203, 229, 132, 96, 13>> - - {:ok, decrypted} = SoftwareVault.aead_aes_gcm_decrypt(handle, key, nonce, ad, cipher_text) - - assert plain_text == decrypted - end - end - - describe "Ockam.Vault.Software.aead_aes_gcm_encrypt_decrypt/5" do - test "can run natively implemented functions" do - {:ok, handle} = SoftwareVault.default_init() - attributes = %{type: :aes, persistence: :ephemeral, length: 32} - - {:ok, key} = SoftwareVault.secret_generate(handle, attributes) - - plain_text = "Hello, nif" - ad = "Token" - nonce = 5 - - {:ok, cipher_text} = SoftwareVault.aead_aes_gcm_encrypt(handle, key, nonce, ad, plain_text) - - {:ok, decrypted} = SoftwareVault.aead_aes_gcm_decrypt(handle, key, nonce, ad, cipher_text) - - assert plain_text == decrypted - end - end - - describe "Ockam.Vault.Software.deinit/1" do - test "can run natively implemented functions" do - {:ok, handle} = SoftwareVault.default_init() - - :ok = SoftwareVault.deinit(handle) - end - end -end diff --git a/implementations/elixir/ockam/ockam_vault_software/.formatter.exs b/implementations/elixir/ockam/ockly/.formatter.exs similarity index 61% rename from implementations/elixir/ockam/ockam_vault_software/.formatter.exs rename to implementations/elixir/ockam/ockly/.formatter.exs index 5a0d2db8b99..2e75ea83c38 100644 --- a/implementations/elixir/ockam/ockam_vault_software/.formatter.exs +++ b/implementations/elixir/ockam/ockly/.formatter.exs @@ -1,4 +1,5 @@ # Used by "mix format" [ - inputs: ["{mix,.formatter,.credo}.exs", "{config,lib,test}/**/*.{ex,exs}"] + inputs: ["{mix,.formatter,.credo}.exs", "{config,lib,test}/**/*.{ex,exs}"], + locals_without_parens: [step: 1, step: 2] ] diff --git a/implementations/elixir/ockam/ockly/README.md b/implementations/elixir/ockam/ockly/README.md new file mode 100644 index 00000000000..d3223cd8a8d --- /dev/null +++ b/implementations/elixir/ockam/ockly/README.md @@ -0,0 +1,21 @@ +# Ockly + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `ockly` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:ockly, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at . + diff --git a/implementations/elixir/ockam/ockly/config/config.exs b/implementations/elixir/ockam/ockly/config/config.exs new file mode 100644 index 00000000000..51cd8685e3f --- /dev/null +++ b/implementations/elixir/ockam/ockly/config/config.exs @@ -0,0 +1,6 @@ +## Application configuration used in release as sys.config or mix run +## THIS CONFIGURATION IS NOT LOADED IF THE APP IS LOADED AS A DEPENDENCY + +import Config + +import_config "#{Mix.env()}.exs" diff --git a/implementations/elixir/ockam/ockly/config/dev.exs b/implementations/elixir/ockam/ockly/config/dev.exs new file mode 100644 index 00000000000..becde76932f --- /dev/null +++ b/implementations/elixir/ockam/ockly/config/dev.exs @@ -0,0 +1 @@ +import Config diff --git a/implementations/elixir/ockam/ockly/config/prod.exs b/implementations/elixir/ockam/ockly/config/prod.exs new file mode 100644 index 00000000000..becde76932f --- /dev/null +++ b/implementations/elixir/ockam/ockly/config/prod.exs @@ -0,0 +1 @@ +import Config diff --git a/implementations/elixir/ockam/ockly/config/runtime.exs b/implementations/elixir/ockam/ockly/config/runtime.exs new file mode 100644 index 00000000000..a3706f1d9fe --- /dev/null +++ b/implementations/elixir/ockam/ockly/config/runtime.exs @@ -0,0 +1,9 @@ +import Config + +aws_vault = + case System.fetch_env("OCKAM_VAULT_AWS") do + {:ok, "true"} -> true + :error -> false + end + +config :ockly, aws_vault: aws_vault diff --git a/implementations/elixir/ockam/ockly/config/test.exs b/implementations/elixir/ockam/ockly/config/test.exs new file mode 100644 index 00000000000..becde76932f --- /dev/null +++ b/implementations/elixir/ockam/ockly/config/test.exs @@ -0,0 +1 @@ +import Config diff --git a/implementations/elixir/ockam/ockly/lib/ockly.ex b/implementations/elixir/ockam/ockly/lib/ockly.ex new file mode 100644 index 00000000000..b57a7566e53 --- /dev/null +++ b/implementations/elixir/ockam/ockly/lib/ockly.ex @@ -0,0 +1,12 @@ +defmodule Ockly do + @moduledoc """ + Documentation for `Ockly`. + """ + + def nif_config do + case Application.fetch_env(:ockly, :aws_vault) do + {:ok, true} -> :aws_kms + _ -> nil + end + end +end diff --git a/implementations/elixir/ockam/ockly/lib/ockly/native.ex b/implementations/elixir/ockam/ockly/lib/ockly/native.ex new file mode 100644 index 00000000000..59770d2e640 --- /dev/null +++ b/implementations/elixir/ockam/ockly/lib/ockly/native.ex @@ -0,0 +1,17 @@ +defmodule Ockly.Native do + @moduledoc false + + use Rustler, otp_app: :ockly, crate: "ockly", load_data_fun: {Ockly, :nif_config} + + def create_identity, do: create_identity(nil) + def create_identity(_), do: error() + def check_identity(_), do: error() + def attest_secure_channel_key(_, _), do: error() + def verify_secure_channel_key_attestation(_, _, _), do: error() + def verify_credential(_, _, _), do: error() + def import_signing_secret(_), do: error() + + def issue_credential(_, _, _, _), do: error() + + defp error, do: :erlang.nif_error(:nif_not_loaded) +end diff --git a/implementations/elixir/ockam/ockly/mix.exs b/implementations/elixir/ockam/ockly/mix.exs new file mode 100644 index 00000000000..5d371b6aa57 --- /dev/null +++ b/implementations/elixir/ockam/ockly/mix.exs @@ -0,0 +1,42 @@ +defmodule Ockly.MixProject do + use Mix.Project + + def project do + [ + app: :ockly, + version: "0.1.0", + elixir: "~> 1.13", + start_permanent: Mix.env() == :prod, + deps: deps(), + aliases: aliases() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:rustler, + git: "https://github.com/polvorin/rustler.git", + branch: "fix_local_crate", + sparse: "rustler_mix"}, + {:credo, "~> 1.6", only: [:dev, :test], runtime: false}, + {:hkdf_erlang, "~> 1.0.0"} + ] + end + + defp aliases do + [ + credo: "credo --strict", + "lint.format": "format --check-formatted", + "lint.credo": "credo --strict", + lint: ["lint.format", "lint.credo"] + ] + end +end diff --git a/implementations/elixir/ockam/ockly/mix.lock b/implementations/elixir/ockam/ockly/mix.lock new file mode 100644 index 00000000000..2cf24305d6d --- /dev/null +++ b/implementations/elixir/ockam/ockly/mix.lock @@ -0,0 +1,9 @@ +%{ + "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, + "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, + "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "hkdf_erlang": {:hex, :hkdf_erlang, "1.0.0", "0b681b428805eacf1286e02ece8617e843cdceae7adb5fad43c283c98088fbc2", [:rebar3], [], "hexpm", "d2091d9e0df97613fd149074929a94e3675047f28cca6b1626fe7be60f5d76ac"}, + "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, + "rustler": {:git, "https://github.com/polvorin/rustler.git", "f6ead5802b3b8bf38b3c5fcaf0a0dbdf0e66a221", [branch: "fix_local_crate", sparse: "rustler_mix"]}, + "toml": {:hex, :toml, "0.7.0", "fbcd773caa937d0c7a02c301a1feea25612720ac3fa1ccb8bfd9d30d822911de", [:mix], [], "hexpm", "0690246a2478c1defd100b0c9b89b4ea280a22be9a7b313a8a058a2408a2fa70"}, +} diff --git a/implementations/elixir/ockam/ockly/native/ockly/.cargo/config.toml b/implementations/elixir/ockam/ockly/native/ockly/.cargo/config.toml new file mode 100644 index 00000000000..20f03f3d805 --- /dev/null +++ b/implementations/elixir/ockam/ockly/native/ockly/.cargo/config.toml @@ -0,0 +1,5 @@ +[target.'cfg(target_os = "macos")'] +rustflags = [ + "-C", "link-arg=-undefined", + "-C", "link-arg=dynamic_lookup", +] diff --git a/implementations/elixir/ockam/ockly/native/ockly/.gitignore b/implementations/elixir/ockam/ockly/native/ockly/.gitignore new file mode 100644 index 00000000000..373c8173286 --- /dev/null +++ b/implementations/elixir/ockam/ockly/native/ockly/.gitignore @@ -0,0 +1,3 @@ +/target +# Include .cargo at this level, the config on it is needed by rustler +!.cargo diff --git a/implementations/elixir/ockam/ockly/native/ockly/Cargo.lock b/implementations/elixir/ockam/ockly/native/ockly/Cargo.lock new file mode 100644 index 00000000000..24e95c8a285 --- /dev/null +++ b/implementations/elixir/ockam/ockly/native/ockly/Cargo.lock @@ -0,0 +1,2581 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", + "rand_core", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug", +] + +[[package]] +name = "aes-gcm" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc3be92e19a7ef47457b8e6f90707e12b6ac5d20c6f3866584fa3be0787d839f" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +dependencies = [ + "memchr", +] + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "async-trait" +version = "0.1.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "atomic-polyfill" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ff7eb3f316534d83a8a2c3d1674ace8a5a71198eba31e2e2b597833f699b28" +dependencies = [ + "critical-section", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "aws-config" +version = "0.56.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6b3804dca60326e07205179847f17a4fce45af3a1106939177ad41ac08a6de" +dependencies = [ + "aws-credential-types", + "aws-http", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-client", + "aws-smithy-http", + "aws-smithy-http-tower", + "aws-smithy-json", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http", + "hyper", + "time", + "tokio", + "tower", + "tracing", +] + +[[package]] +name = "aws-credential-types" +version = "0.56.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a66ac8ef5fa9cf01c2d999f39d16812e90ec1467bd382cbbb74ba23ea86201" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "fastrand", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-http" +version = "0.56.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e626370f9ba806ae4c439e49675fd871f5767b093075cdf4fef16cac42ba900" +dependencies = [ + "aws-credential-types", + "aws-smithy-http", + "aws-smithy-types", + "aws-types", + "bytes", + "http", + "http-body", + "lazy_static", + "percent-encoding", + "pin-project-lite", + "tracing", +] + +[[package]] +name = "aws-runtime" +version = "0.56.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ac5cf0ff19c1bca0cea7932e11b239d1025a45696a4f44f72ea86e2b8bdd07" +dependencies = [ + "aws-credential-types", + "aws-http", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "fastrand", + "http", + "percent-encoding", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-kms" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92f134e75581d16a36a6d7e6dd8b6cdef5cbc610e599a8c12df317eb7868c8b" +dependencies = [ + "aws-credential-types", + "aws-http", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-client", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http", + "regex", + "tokio-stream", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47ad6bf01afc00423d781d464220bf69fb6a674ad6629cbbcb06d88cdc2be82" +dependencies = [ + "aws-credential-types", + "aws-http", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-client", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "http", + "regex", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "0.56.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b28f4910bb956b7ab320b62e98096402354eca976c587d1eeccd523d9bac03" +dependencies = [ + "aws-smithy-http", + "form_urlencoded", + "hex", + "hmac", + "http", + "once_cell", + "percent-encoding", + "regex", + "sha2", + "time", + "tracing", +] + +[[package]] +name = "aws-smithy-async" +version = "0.56.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cdb73f85528b9d19c23a496034ac53703955a59323d581c06aa27b4e4e247af" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", + "tokio-stream", +] + +[[package]] +name = "aws-smithy-client" +version = "0.56.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c27b2756264c82f830a91cb4d2d485b2d19ad5bea476d9a966e03d27f27ba59a" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-http-tower", + "aws-smithy-types", + "bytes", + "fastrand", + "http", + "http-body", + "hyper", + "hyper-rustls", + "lazy_static", + "pin-project-lite", + "rustls", + "tokio", + "tower", + "tracing", +] + +[[package]] +name = "aws-smithy-http" +version = "0.56.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54cdcf365d8eee60686885f750a34c190e513677db58bbc466c44c588abf4199" +dependencies = [ + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http", + "http-body", + "hyper", + "once_cell", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-http-tower" +version = "0.56.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "822de399d0ce62829a69dfa8c5cd08efdbe61a7426b953e2268f8b8b52a607bd" +dependencies = [ + "aws-smithy-http", + "aws-smithy-types", + "bytes", + "http", + "http-body", + "pin-project-lite", + "tower", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.56.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1e7ab8fa7ad10c193af7ae56d2420989e9f4758bf03601a342573333ea34f" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-query" +version = "0.56.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28556a3902091c1f768a34f6c998028921bdab8d47d92586f363f14a4a32d047" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "0.56.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "745e096b3553e7e0f40622aa04971ce52765af82bebdeeac53aa6fc82fe801e6" +dependencies = [ + "aws-smithy-async", + "aws-smithy-client", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "http", + "http-body", + "once_cell", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "0.56.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d0ae0c9cfd57944e9711ea610b48a963fb174a53aabacc08c5794a594b1d02" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-types", + "bytes", + "http", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-types" +version = "0.56.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d90dbc8da2f6be461fa3c1906b20af8f79d14968fe47f2b7d29d086f62a51728" +dependencies = [ + "base64-simd", + "itoa", + "num-integer", + "ryu", + "serde", + "time", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.56.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01d2dedcdd8023043716cfeeb3c6c59f2d447fce365d8e194838891794b23b6" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "0.56.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85aa0451bf8af1bf22a4f028d5d28054507a14be43cb8ac0597a8471fba9edfe" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-client", + "aws-smithy-http", + "aws-smithy-types", + "http", + "rustc_version", + "tracing", +] + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "bytes-utils" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e47d3a8076e283f3acd27400535992edb3ba4b5bb72f8891ad8fbe7932a7d4b9" +dependencies = [ + "bytes", + "either", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "const-oid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a232f92a03f37dd7d7dd2adc67166c77e9cd88de5b019b9a9eecfaeaf7bfd481" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f711ade317dd348950a9910f81c5947e3d8907ebd2b83f76203ff1807e6a2bc2" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "platforms", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "delegate" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee5df75c70b95bd3aacc8e2fd098797692fb1d54121019c4de481e42f04c8a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core", + "serde", + "sha2", + "zeroize", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "elliptic-curve" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pem-rfc7468", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "fastrand" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "ghash" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gimli" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "ahash", + "serde", +] + +[[package]] +name = "heapless" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db04bc24a18b9ea980628ecf00e6c0264f3c1426dac36c00cb49b6fbad8b0743" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "spin 0.9.8", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.9", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +dependencies = [ + "futures-util", + "http", + "hyper", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "lmdb-rkv" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447a296f7aca299cfbb50f4e4f3d49451549af655fb7215d7f8c0c3d64bad42b" +dependencies = [ + "bitflags", + "byteorder", + "libc", + "lmdb-rkv-sys", +] + +[[package]] +name = "lmdb-rkv-sys" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61b9ce6b3be08acefa3003c57b7565377432a89ec24476bbe72e11d101f852fe" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e" + +[[package]] +name = "minicbor" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7005aaf257a59ff4de471a9d5538ec868a21586534fff7f85dd97d4043a6139" +dependencies = [ + "minicbor-derive", +] + +[[package]] +name = "minicbor-derive" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1154809406efdb7982841adb6311b3d095b46f78342dd646736122fe6b19e267" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +dependencies = [ + "libc", + "wasi", + "windows-sys", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe" +dependencies = [ + "memchr", +] + +[[package]] +name = "ockam_core" +version = "0.84.0" +dependencies = [ + "async-trait", + "cfg-if", + "core2", + "futures-util", + "hashbrown 0.14.0", + "heapless", + "hex", + "minicbor", + "ockam_macros", + "once_cell", + "rand", + "serde", + "serde_bare", + "subtle", + "tinyvec", + "tracing", + "zeroize", +] + +[[package]] +name = "ockam_executor" +version = "0.52.0" +dependencies = [ + "crossbeam-queue", + "futures", + "heapless", + "ockam_core", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "ockam_identity" +version = "0.79.0" +dependencies = [ + "arrayref", + "async-trait", + "cfg-if", + "delegate", + "group", + "heapless", + "hex", + "lmdb-rkv", + "minicbor", + "ockam_core", + "ockam_macros", + "ockam_node", + "ockam_vault", + "rand", + "serde", + "serde-big-array", + "serde_bare", + "sha2", + "subtle", + "time", + "tokio-retry", + "tracing", +] + +[[package]] +name = "ockam_macros" +version = "0.31.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "ockam_node" +version = "0.87.0" +dependencies = [ + "cfg-if", + "fs2", + "futures", + "minicbor", + "ockam_core", + "ockam_executor", + "ockam_macros", + "ockam_transport_core", + "serde", + "serde_bare", + "serde_json", + "tokio", + "tracing", + "tracing-error", + "tracing-subscriber", +] + +[[package]] +name = "ockam_transport_core" +version = "0.57.0" +dependencies = [ + "ockam_core", + "tracing", +] + +[[package]] +name = "ockam_vault" +version = "0.80.0" +dependencies = [ + "aes-gcm", + "arrayref", + "cfg-if", + "ed25519-dalek", + "hex", + "hkdf", + "minicbor", + "ockam_core", + "ockam_macros", + "ockam_node", + "p256", + "rand", + "serde", + "serde_cbor", + "sha2", + "static_assertions", + "tracing", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "ockam_vault_aws" +version = "0.5.0" +dependencies = [ + "aws-config", + "aws-sdk-kms", + "delegate", + "ockam_core", + "ockam_macros", + "ockam_node", + "ockam_vault", + "p256", + "sha2", + "thiserror", + "tracing", +] + +[[package]] +name = "ockly" +version = "0.1.0" +dependencies = [ + "lazy_static", + "minicbor", + "ockam_identity", + "ockam_vault", + "ockam_vault_aws", + "rustler", + "tokio", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "outref" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "platforms" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4503fa043bf02cee09a9582e9554b4c6403b2ef55e4612e96561d294419429f8" + +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "primeorder" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c2fcef82c0ec6eefcc179b978446c399b3cdf73c392c35604e399eee6df1ee3" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.3.7", + "regex-syntax 0.7.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustler" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0884cb623b9f43d3e2c51f9071c5e96a5acf3e6e6007866812884ff0cb983f1e" +dependencies = [ + "lazy_static", + "rustler_codegen", + "rustler_sys", +] + +[[package]] +name = "rustler_codegen" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e277af754f2560cf4c4ebedb68c1a735292fb354505c6133e47ec406e699cf" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "rustler_sys" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a7c0740e5322b64e2b952d8f0edce5f90fcf6f6fe74cca3f6e78eb3de5ea858" +dependencies = [ + "regex", + "unreachable", +] + +[[package]] +name = "rustls" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" + +[[package]] +name = "serde" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_bare" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51c55386eed0f1ae957b091dc2ca8122f287b60c79c774cbe3d5f2b69fded660" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.188" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "serde_json" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" +dependencies = [ + "deranged", + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2 0.5.3", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "tokio-retry" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f" +dependencies = [ + "pin-project", + "rand", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "universal-hash" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +dependencies = [ + "void", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "uuid" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.29", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "x25519-dalek" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + +[[package]] +name = "xmlparser" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] diff --git a/implementations/elixir/ockam/ockly/native/ockly/Cargo.toml b/implementations/elixir/ockam/ockly/native/ockly/Cargo.toml new file mode 100644 index 00000000000..632e96ee716 --- /dev/null +++ b/implementations/elixir/ockam/ockly/native/ockly/Cargo.toml @@ -0,0 +1,22 @@ +[workspace] + +[package] +name = "ockly" +version = "0.1.0" +authors = [] +edition = "2021" + +[lib] +name = "ockly" +path = "src/lib.rs" +crate-type = ["cdylib"] + +[dependencies] +lazy_static = "1.4.0" +minicbor = { version = "0.19.0", features = ["alloc", "derive"] } +ockam_identity = { path = "../../../../../rust/ockam/ockam_identity" } +ockam_vault = { path = "../../../../../rust/ockam/ockam_vault" } +ockam_vault_aws = { path = "../../../../../rust/ockam/ockam_vault_aws" } +# Enable credentials-sso feature in ockam_vault_aws for use on sso environments (like dev machines) +rustler = "0.29.1" +tokio = "1.31.0" diff --git a/implementations/elixir/ockam/ockly/native/ockly/README.md b/implementations/elixir/ockam/ockly/native/ockly/README.md new file mode 100644 index 00000000000..e0cb3afc5d5 --- /dev/null +++ b/implementations/elixir/ockam/ockly/native/ockly/README.md @@ -0,0 +1,20 @@ +# NIF for Elixir.Ockly + +## To build the NIF module: + +- Your NIF will now build along with your project. + +## To load the NIF: + +```elixir +defmodule Ockly do + use Rustler, otp_app: :ockly, crate: "ockly" + + # When your NIF is loaded, it will override this function. + def add(_a, _b), do: :erlang.nif_error(:nif_not_loaded) +end +``` + +## Examples + +[This](https://github.com/rusterlium/NifIo) is a complete example of a NIF written in Rust. diff --git a/implementations/elixir/ockam/ockly/native/ockly/src/lib.rs b/implementations/elixir/ockam/ockly/native/ockly/src/lib.rs new file mode 100644 index 00000000000..b26ef30b542 --- /dev/null +++ b/implementations/elixir/ockam/ockly/native/ockly/src/lib.rs @@ -0,0 +1,360 @@ +use std::{ + future::Future, + ops::Deref, + str::FromStr, + sync::{Arc, RwLock}, + time::Duration, +}; + +use lazy_static::lazy_static; +use ockam_identity::{ + models::{PurposeKeyAttestation, PurposePublicKey, SchemaId}, + utils::AttributesBuilder, + Identifier, Identities, Purpose, Vault, +}; +use ockam_vault::SecretType; +use ockam_vault::{PublicKey, Secret, SoftwareSigningVault}; +use ockam_vault_aws::AwsSigningVault; +use rustler::{Atom, Binary, Env, Error, NewBinary, NifResult}; +use std::clone::Clone; +use std::collections::HashMap; +use tokio::{runtime::Runtime, task}; + +lazy_static! { + static ref RUNTIME: Arc = Arc::new(Runtime::new().unwrap()); + static ref IDENTITIES: RwLock>> = RwLock::new(None); + static ref SIGNING_MEMORY_VAULT: RwLock>> = RwLock::new(None); +} + +mod atoms { + rustler::atoms! { + credential_decode_error, + credential_encode_error, + credential_issuing_error, + identity_import_error, + credential_verification_failed, + invalid_identifier, + utf8_error, + attest_error, + attestation_encode_error, + attestation_decode_error, + purpose_key_type_not_supported, + invalid_attestation, + invalid_state, + invalid_secret, + no_memory_vault, + aws_kms + } +} + +/// . +fn get_runtime() -> Arc { + RUNTIME.clone() +} + +fn block_future(f: F) -> ::Output +where + F: Future, +{ + let rt = get_runtime(); + task::block_in_place(move || { + let local = task::LocalSet::new(); + local.block_on(&rt, f) + }) +} + +fn identities_ref() -> NifResult> { + let r = IDENTITIES.read().unwrap(); //TODO + r.clone() + .ok_or_else(|| Error::Term(Box::new(atoms::invalid_state()))) +} + +#[rustler::nif] +fn create_identity(env: Env, existing_key: Option) -> NifResult<(Binary, Binary)> { + let identities_ref = identities_ref()?; + let identity = block_future(async move { + if let Some(key) = existing_key { + identities_ref + .identities_creation() + .identity_builder() + .with_existing_key(key, SecretType::Ed25519) + .build() + .await + } else { + if SIGNING_MEMORY_VAULT.read().unwrap().is_some() { + identities_ref.identities_creation().identity_builder().with_random_key(SecretType::Ed25519).build().await + } else { + // AWS vault uses NistP256 keys + identities_ref.identities_creation().identity_builder().with_random_key(SecretType::NistP256).build().await + } + } + }) + .map_err(|_| Error::BadArg)?; + + let exported = identity.export().map_err(|_| Error::BadArg)?; + let id = identity.identifier().to_string(); + let mut binary = NewBinary::new(env, id.len()); + binary.copy_from_slice(id.as_bytes()); + let mut exp_binary = NewBinary::new(env, exported.len()); + exp_binary.copy_from_slice(&exported); + Ok((binary.into(), exp_binary.into())) +} + +#[rustler::nif] +fn attest_secure_channel_key<'a>( + env: Env<'a>, + identifier: String, + secret: Binary, +) -> NifResult> { + let identities_ref = identities_ref()?; + let identifier = Identifier::from_str(&identifier) + .map_err(|_| Error::Term(Box::new(atoms::invalid_identifier())))?; + let purpose_key = block_future(async move { + let key_id = identities_ref + .purpose_keys() + .purpose_keys_creation() + .vault() + .secure_channel_vault + .import_static_secret( + Secret::new(secret.to_vec()), + ockam_vault::SecretAttributes::X25519, + ) + .await?; + identities_ref + .purpose_keys() + .purpose_keys_creation() + .purpose_key_builder(&identifier, Purpose::SecureChannel) + .with_existing_key(key_id, SecretType::X25519) + .build() + .await + }) + .map_err(|_| Error::Term(Box::new(atoms::attest_error())))?; + let encoded = minicbor::to_vec(purpose_key.attestation()) + .map_err(|_| Error::Term(Box::new(atoms::attestation_encode_error())))?; + let mut exp_binary = NewBinary::new(env, encoded.len()); + exp_binary.copy_from_slice(&encoded); + Ok(exp_binary.into()) +} + +#[rustler::nif] +fn verify_secure_channel_key_attestation( + identity: Binary, + public_key: Binary, + attestation: Binary, +) -> NifResult { + let identities_ref = identities_ref()?; + let attestation: PurposeKeyAttestation = minicbor::decode(&attestation) + .map_err(|_| Error::Term(Box::new(atoms::attestation_decode_error())))?; + let k = PublicKey::new(public_key.as_slice().to_vec(), SecretType::X25519); + block_future(async move { + let identity = identities_ref + .identities_creation() + .import(None, &identity) + .await + .map_err(|_| atoms::identity_import_error())?; + identities_ref + .purpose_keys() + .purpose_keys_verification() + .verify_purpose_key_attestation(Some(identity.identifier()), &attestation) + .await + .map_err(|_| atoms::attest_error()) + .and_then(|data| { + if let PurposePublicKey::SecureChannelStaticKey(x) = data.public_key { + if PublicKey::from(x).eq(&k) { + Ok(true) + } else { + Err(atoms::invalid_attestation()) + } + } else { + Err(atoms::purpose_key_type_not_supported()) + } + }) + }) + .map_err(|reason| Error::Term(Box::new(reason))) +} + +#[rustler::nif] +fn check_identity<'a>(env: Env<'a>, identity: Binary) -> NifResult> { + let identities_ref = identities_ref()?; + let imported_identity = block_future(async move { + identities_ref + .identities_creation() + .import(None, &identity) + .await + .map_err(|_| atoms::identity_import_error()) + }) + .map_err(|reason| Error::Term(Box::new(reason)))?; + let id = imported_identity.identifier().to_string(); + let mut binary = NewBinary::new(env, id.len()); + binary.copy_from_slice(id.as_bytes()); + Ok(binary.into()) +} + +#[rustler::nif] +fn issue_credential<'a>( + env: Env<'a>, + issuer_identity: Binary, + subject_identifier: String, + attrs: HashMap, + duration: u64, +) -> NifResult> { + let identities_ref = identities_ref()?; + let subject_identifier = Identifier::from_str(&subject_identifier) + .map_err(|_| Error::Term(Box::new(atoms::invalid_identifier())))?; + let credential_and_purpose_key = block_future(async move { + let issuer = identities_ref + .identities_creation() + .import(None, &issuer_identity) + .await + .map_err(|_| atoms::identity_import_error())?; + let mut attr_builder = AttributesBuilder::with_schema(SchemaId(0)); + for (key, value) in attrs { + attr_builder = attr_builder.with_attribute(key, value) + } + identities_ref + .credentials() + .credentials_creation() + .issue_credential( + issuer.identifier(), + &subject_identifier, + attr_builder.build(), + Duration::from_secs(duration), + ) + .await + .map_err(|_| atoms::credential_issuing_error()) + }) + .map_err(|reason| Error::Term(Box::new(reason)))?; + let encoded = minicbor::to_vec(credential_and_purpose_key) + .map_err(|_| Error::Term(Box::new(atoms::credential_encode_error())))?; + let mut binary = NewBinary::new(env, encoded.len()); + binary.copy_from_slice(&encoded); + Ok(binary.into()) +} + +#[rustler::nif] +fn verify_credential( + expected_subject: String, + authorities: Vec, + credential: Binary, +) -> NifResult<(u64, HashMap)> { + let identities_ref = identities_ref()?; + let expected_subject = Identifier::from_str(&expected_subject) + .map_err(|_| Error::Term(Box::new(atoms::invalid_identifier())))?; + let attributes = block_future(async move { + let credential_and_purpose_key = + minicbor::decode(&credential).map_err(|_| atoms::credential_decode_error())?; + + let mut authorities_identities = Vec::new(); + for authority in authorities { + let authority = identities_ref + .identities_creation() + .import(None, &authority) + .await + .map_err(|_| atoms::identity_import_error())?; + authorities_identities.push(authority.identifier().clone()); + } + let credential_and_purpose_key_data = identities_ref + .credentials() + .credentials_verification() + .verify_credential( + Some(&expected_subject), + &authorities_identities, + &credential_and_purpose_key, + ) + .await + .map_err(|_| atoms::credential_verification_failed())?; + let mut attr_map = HashMap::new(); + for (k, v) in credential_and_purpose_key_data + .credential_data + .subject_attributes + .map + { + attr_map.insert( + String::from_utf8(k.to_vec()).map_err(|_| atoms::utf8_error())?, + String::from_utf8(v.to_vec()).map_err(|_| atoms::utf8_error())?, + ); + } + Ok(( + *credential_and_purpose_key_data + .credential_data + .expires_at + .deref(), + attr_map, + )) + }); + attributes.map_err(|reason: Atom| Error::Term(Box::new(reason))) +} + +#[rustler::nif] +fn import_signing_secret(secret: Binary) -> NifResult { + let signing_vault = SIGNING_MEMORY_VAULT + .read() + .unwrap() + .clone() + .ok_or_else(|| Error::Term(Box::new(atoms::no_memory_vault())))?; + block_future(async move { + signing_vault + .import_key( + Secret::new(secret.to_vec()), + ockam_vault::SecretAttributes::Ed25519, + ) + .await + .map_err(|_| Error::Term(Box::new(atoms::invalid_secret()))) + }) +} + +fn load_memory_vault() -> bool { + let vault = SoftwareSigningVault::create(); + *SIGNING_MEMORY_VAULT.write().unwrap() = Some(vault.clone()); + let builder = ockam_identity::Identities::builder().with_vault(Vault::new( + vault, + Vault::create_secure_channel_vault(), + Vault::create_credential_vault(), + Vault::create_verifying_vault(), + )); + *IDENTITIES.write().unwrap() = Some(builder.build()); + true +} + +fn load_aws_vault() -> bool { + block_future(async move { + match AwsSigningVault::create().await { + Ok(vault) => { + let aws_vault = Arc::new(vault); + let builder = ockam_identity::Identities::builder().with_vault(Vault::new( + aws_vault.clone(), + Vault::create_secure_channel_vault(), + aws_vault, + Vault::create_verifying_vault(), + )); + *IDENTITIES.write().unwrap() = Some(builder.build()); + true + } + Err(_) => { + false + } + }}) +} + +fn load(_env: rustler::Env, load_data: rustler::Term) -> bool { + if let Ok(r) = load_data.decode::() { + if atoms::aws_kms().eq(&r) { + return load_aws_vault(); + } + } + load_memory_vault() +} + +rustler::init!( + "Elixir.Ockly.Native", + [ + create_identity, + attest_secure_channel_key, + verify_secure_channel_key_attestation, + check_identity, + issue_credential, + verify_credential, + import_signing_secret + ], + load = load +); diff --git a/implementations/elixir/ockam/ockly/test/ockly_test.exs b/implementations/elixir/ockam/ockly/test/ockly_test.exs new file mode 100644 index 00000000000..b7dee869e83 --- /dev/null +++ b/implementations/elixir/ockam/ockly/test/ockly_test.exs @@ -0,0 +1,121 @@ +defmodule OcklyTest do + use ExUnit.Case + doctest Ockly + + test "create identity" do + {id, exported_identity} = Ockly.Native.create_identity() + {pub_key, secret_key} = :crypto.generate_key(:eddh, :x25519) + attestation = Ockly.Native.attest_secure_channel_key(id, secret_key) + + assert Ockly.Native.verify_secure_channel_key_attestation( + exported_identity, + pub_key, + attestation + ) == true + + # attest for another key + assert Ockly.Native.verify_secure_channel_key_attestation(exported_identity, id, attestation) == + {:error, :invalid_attestation} + + # attestation data is junk + assert Ockly.Native.verify_secure_channel_key_attestation(exported_identity, pub_key, pub_key) == + {:error, :attestation_decode_error} + + assert Ockly.Native.check_identity(exported_identity) == id + + {subject_id, _subject_identity} = Ockly.Native.create_identity() + attrs = %{"Some" => "works!", "other" => "yes!"} + credential = Ockly.Native.issue_credential(exported_identity, subject_id, attrs, 60) + + {ttl, verified_attrs} = + Ockly.Native.verify_credential(subject_id, [exported_identity], credential) + + {:error, :credential_verification_failed} = + Ockly.Native.verify_credential(id, [exported_identity], credential) + + assert verified_attrs == attrs + assert ttl == System.os_time(:second) + 60 + end + + test "create identity from existing secret" do + {_pub, secret} = :crypto.generate_key(:eddsa, :ed25519) + key_id = Ockly.Native.import_signing_secret(secret) + {_id, _exported_identity} = Ockly.Native.create_identity(key_id) + end + + test "import identity and existing secret" do + # An existing exported identity (change history) and its signing key. + # It must be possible to import them and use (if memory signing vault is used) + + secret = + <<83, 231, 139, 244, 109, 254, 138, 112, 211, 93, 197, 106, 173, 226, 235, 88, 141, 218, + 113, 168, 209, 229, 28, 241, 69, 249, 106, 70, 50, 54, 218, 217>> + + identity = + <<129, 162, 1, 88, 59, 162, 1, 1, 2, 88, 53, 164, 2, 130, 1, 129, 88, 32, 83, 241, 75, 224, + 25, 93, 231, 146, 168, 52, 2, 192, 228, 60, 198, 200, 216, 60, 101, 169, 165, 128, 75, + 221, 124, 29, 3, 224, 11, 89, 124, 70, 3, 244, 4, 26, 100, 248, 141, 178, 5, 26, 119, 196, + 144, 178, 2, 130, 1, 129, 88, 64, 236, 140, 158, 157, 188, 146, 79, 243, 149, 182, 13, 3, + 100, 174, 45, 5, 37, 208, 240, 3, 205, 7, 29, 61, 74, 44, 28, 166, 51, 161, 201, 36, 211, + 72, 21, 1, 200, 238, 124, 183, 24, 26, 236, 66, 106, 172, 219, 61, 169, 171, 103, 167, 2, + 40, 11, 183, 202, 162, 217, 237, 91, 244, 59, 1>> + + identifier = "I31f064878eb4fc0852d55a0fbb7305270b8fa1d7" + + assert identifier == Ockly.Native.check_identity(identity) + _key_id = Ockly.Native.import_signing_secret(secret) + + # Check that we can use it + {pub_key, secret_key} = :crypto.generate_key(:eddh, :x25519) + attestation = Ockly.Native.attest_secure_channel_key(identifier, secret_key) + + assert Ockly.Native.verify_secure_channel_key_attestation(identity, pub_key, attestation) == + true + end + + test "junk identity" do + assert {:error, :identity_import_error} == Ockly.Native.check_identity("junk") + end + + test "hkdf" do + salt = + <<122, 235, 128, 126, 98, 120, 229, 181, 70, 49, 183, 146, 114, 203, 117, 56, 57, 97, 114, + 156, 206, 162, 68, 171, 40, 228, 128, 217, 198, 93, 57, 93>> + + ikm = + <<52, 28, 249, 202, 250, 82, 168, 196, 7, 9, 236, 217, 229, 151, 87, 163, 96, 201, 169, 224, + 128, 160, 192, 242, 238, 41, 189, 157, 200, 196, 78, 144>> + + <> = :hkdf.derive(:sha256, ikm, "", salt, 64) + + assert k1 == + <<59, 23, 69, 123, 40, 228, 199, 167, 81, 220, 56, 17, 94, 81, 136, 231, 180, 67, 38, + 91, 233, 144, 215, 39, 75, 67, 179, 228, 245, 22, 187, 134>> + + assert k2 == + <<19, 115, 44, 135, 74, 135, 235, 12, 109, 224, 28, 81, 156, 216, 108, 224, 191, 254, + 187, 175, 111, 210, 162, 132, 249, 167, 199, 71, 188, 118, 14, 2>> + end + + test "encrypt" do + key = + <<60, 39, 4, 177, 160, 228, 92, 103, 87, 110, 249, 2, 175, 175, 130, 92, 196, 211, 49, 250, + 51, 157, 6, 45, 39, 205, 207, 84, 126, 153, 104, 209>> + + plain_text = "Hello, nif" + ad = "Token" + nonce = 5 + + {a, b} = :crypto.crypto_one_time_aead(:aes_256_gcm, key, <>, plain_text, ad, true) + cipher_text = <> + + assert cipher_text == + <<125, 225, 184, 225, 253, 238, 233, 167, 41, 157, 48, 205, 146, 233, 209, 117, 3, + 243, 166, 199, 19, 203, 229, 132, 96, 13>> + + size = byte_size(cipher_text) - 16 + <> = cipher_text + decrypted = :crypto.crypto_one_time_aead(:aes_256_gcm, key, <>, c, ad, tag, false) + assert plain_text == decrypted + end +end diff --git a/implementations/elixir/ockam/ockly/test/test_helper.exs b/implementations/elixir/ockam/ockly/test/test_helper.exs new file mode 100644 index 00000000000..869559e709e --- /dev/null +++ b/implementations/elixir/ockam/ockly/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/implementations/rust/ockam/ockam_api/src/identity.rs b/implementations/rust/ockam/ockam_api/src/identity.rs index 33ed1b25385..b1ac194916e 100644 --- a/implementations/rust/ockam/ockam_api/src/identity.rs +++ b/implementations/rust/ockam/ockam_api/src/identity.rs @@ -1,7 +1,3 @@ -pub mod models; - mod enrollment_ticket; -mod identity_service; pub use enrollment_ticket::*; -pub use identity_service::*; diff --git a/implementations/rust/ockam/ockam_api/src/identity/identity_service.rs b/implementations/rust/ockam/ockam_api/src/identity/identity_service.rs deleted file mode 100644 index ffa01a8609d..00000000000 --- a/implementations/rust/ockam/ockam_api/src/identity/identity_service.rs +++ /dev/null @@ -1,231 +0,0 @@ -use core::convert::Infallible; - -use minicbor::encode::Write; -use minicbor::{Decoder, Encode}; -use ockam::identity::IdentityHistoryComparison; -use tracing::trace; - -use crate::identity::models::{ - CompareIdentityChangeHistoryRequest, CreateResponse, ValidateIdentityChangeHistoryRequest, - ValidateIdentityChangeHistoryResponse, -}; -use ockam_core::api::{Error, Id, Method, Request, Response, Status}; -use ockam_core::{Result, Routed, Worker}; -use ockam_node::Context; - -use crate::nodes::service::NodeIdentities; - -/// Vault Service Worker -pub struct IdentityService { - node_identities: NodeIdentities, -} - -impl IdentityService { - pub async fn new(node_identities: NodeIdentities) -> Result { - Ok(Self { node_identities }) - } -} - -impl IdentityService { - fn response_for_bad_request(req: &Request, msg: &str, enc: W) -> Result<()> - where - W: Write, - { - let error = Error::new(req.path()).with_message(msg); - - let error = if let Some(m) = req.method() { - error.with_method(m) - } else { - error - }; - - Response::bad_request(req.id()).body(error).encode(enc)?; - - Ok(()) - } - - fn ok_response(req: &Request, body: Option, enc: W) -> Result<()> - where - W: Write, - B: Encode<()>, - { - Response::ok(req.id()).body(body).encode(enc)?; - - Ok(()) - } - - fn response_with_error( - req: Option<&Request>, - status: Status, - error: &str, - enc: W, - ) -> Result<()> - where - W: Write, - { - let (path, req_id) = match req { - None => ("", Id::fresh()), - Some(req) => (req.path(), req.id()), - }; - - let error = Error::new(path).with_message(error); - - Response::builder(req_id, status).body(error).encode(enc)?; - - Ok(()) - } - - async fn handle_request( - &mut self, - req: &Request, - dec: &mut Decoder<'_>, - enc: W, - ) -> Result<()> - where - W: Write, - { - trace! { - target: "ockam_identity::service", - id = %req.id(), - method = ?req.method(), - path = %req.path(), - body = %req.has_body(), - "request" - } - - let method = match req.method() { - Some(m) => m, - None => return Self::response_for_bad_request(req, "empty method", enc), - }; - - use Method::*; - - match method { - Get => match req.path_segments::<2>().as_slice() { - [identity_name] => { - match self - .node_identities - .get_identity(identity_name.to_string()) - .await? - { - Some(identity) => { - let body = CreateResponse::new( - identity.export()?, - identity.identifier().clone(), - ); - Self::ok_response(req, Some(body), enc) - } - None => Self::response_for_bad_request(req, "unknown identity", enc), - } - } - _ => Self::response_for_bad_request(req, "unknown path", enc), - }, - Post => match req.path_segments::<2>().as_slice() { - [""] => { - let identity = self - .node_identities - .get_default_identities_creation() - .await? - .create_identity() - .await?; - let body = - CreateResponse::new(identity.export()?, identity.identifier().clone()); - - Self::ok_response(req, Some(body), enc) - } - ["actions", "validate_identity_change_history"] => { - if !req.has_body() { - return Self::response_for_bad_request(req, "empty body", enc); - } - - let args = dec.decode::()?; - let identities_creation = self - .node_identities - .get_default_identities_creation() - .await?; - let identity = identities_creation.import(None, args.identity()).await?; - - let body = - ValidateIdentityChangeHistoryResponse::new(identity.identifier().clone()); - - Self::ok_response(req, Some(body), enc) - } - ["actions", "compare_identity_change_history"] => { - if !req.has_body() { - return Self::response_for_bad_request(req, "empty body", enc); - } - - let args = dec.decode::()?; - - let identities_creation = self - .node_identities - .get_default_identities_creation() - .await?; - - let current_identity = identities_creation - .import(None, args.current_identity()) - .await?; - - let body = if args.known_identity().is_empty() { - IdentityHistoryComparison::Newer - } else { - let known_identity = identities_creation - .import(Some(current_identity.identifier()), args.known_identity()) - .await?; - current_identity.compare(&known_identity) - }; - - Self::ok_response(req, Some(body), enc) - } - _ => Self::response_for_bad_request(req, "unknown path", enc), - }, - Put | Patch | Delete => Self::response_for_bad_request(req, "unknown method", enc), - } - } - - async fn on_request(&mut self, data: &[u8]) -> Result> { - let mut buf = Vec::new(); - - let mut dec = Decoder::new(data); - let req: Request = match dec.decode() { - Ok(r) => r, - Err(_) => { - Self::response_with_error( - None, - Status::BadRequest, - "invalid Request structure", - &mut buf, - )?; - - return Ok(buf); - } - }; - - match self.handle_request(&req, &mut dec, &mut buf).await { - Ok(_) => {} - Err(err) => Self::response_with_error( - Some(&req), - Status::InternalServerError, - &err.to_string(), - &mut buf, - )?, - } - - Ok(buf) - } -} - -#[ockam_core::worker] -impl Worker for IdentityService { - type Message = Vec; - type Context = Context; - - async fn handle_message( - &mut self, - ctx: &mut Self::Context, - msg: Routed, - ) -> Result<()> { - let buf = self.on_request(msg.as_body()).await?; - ctx.send(msg.return_route(), buf).await - } -} diff --git a/implementations/rust/ockam/ockam_api/src/identity/models.rs b/implementations/rust/ockam/ockam_api/src/identity/models.rs deleted file mode 100644 index 402577612f4..00000000000 --- a/implementations/rust/ockam/ockam_api/src/identity/models.rs +++ /dev/null @@ -1,110 +0,0 @@ -#![allow(missing_docs)] - -use ockam_core::CowBytes; - -use minicbor::{Decode, Encode}; -use ockam::identity::Identifier; - -#[cfg(feature = "tag")] -use ockam_core::TypeTag; - -#[derive(Debug, Clone, Encode, Decode)] -#[rustfmt::skip] -#[cbor(map)] -pub struct CreateResponse { - #[cfg(feature = "tag")] - #[n(0)] tag: TypeTag<3500430>, - #[n(1)] identity: Vec, - #[n(2)] identity_id: Identifier, -} - -impl CreateResponse { - pub fn new(identity: Vec, identity_id: Identifier) -> Self { - Self { - #[cfg(feature = "tag")] - tag: TypeTag, - identity, - identity_id, - } - } - pub fn identity(&self) -> &[u8] { - &self.identity - } - pub fn identity_id(&self) -> &Identifier { - &self.identity_id - } -} - -#[derive(Debug, Clone, Encode, Decode)] -#[rustfmt::skip] -#[cbor(map)] -pub struct ValidateIdentityChangeHistoryRequest<'a> { - #[cfg(feature = "tag")] - #[n(0)] tag: TypeTag<2556809>, - #[b(1)] identity: CowBytes<'a>, -} - -impl<'a> ValidateIdentityChangeHistoryRequest<'a> { - pub fn new(identity: impl Into>) -> Self { - Self { - #[cfg(feature = "tag")] - tag: TypeTag, - identity: identity.into(), - } - } - pub fn identity(&self) -> &[u8] { - &self.identity - } -} - -#[derive(Debug, Clone, Encode, Decode)] -#[rustfmt::skip] -#[cbor(map)] -pub struct ValidateIdentityChangeHistoryResponse { - #[cfg(feature = "tag")] - #[n(0)] tag: TypeTag<4245404>, - #[n(1)] identity_id: Identifier, -} - -impl ValidateIdentityChangeHistoryResponse { - pub fn new(identity_id: Identifier) -> Self { - Self { - #[cfg(feature = "tag")] - tag: TypeTag, - identity_id, - } - } - pub fn identity_id(&self) -> &Identifier { - &self.identity_id - } -} - -#[derive(Debug, Clone, Encode, Decode)] -#[rustfmt::skip] -#[cbor(map)] -pub struct CompareIdentityChangeHistoryRequest<'a> { - #[cfg(feature = "tag")] - #[n(0)] tag: TypeTag<7300740>, - #[b(1)] current_identity: CowBytes<'a>, - #[b(2)] known_identity: CowBytes<'a>, -} - -impl<'a> CompareIdentityChangeHistoryRequest<'a> { - pub fn new( - current_identity: impl Into>, - known_identity: impl Into>, - ) -> Self { - Self { - #[cfg(feature = "tag")] - tag: TypeTag, - current_identity: current_identity.into(), - known_identity: known_identity.into(), - } - } - pub fn current_identity(&self) -> &[u8] { - &self.current_identity - } - pub fn known_identity(&self) -> &[u8] { - &self.known_identity - } -} diff --git a/implementations/rust/ockam/ockam_api/src/lib.rs b/implementations/rust/ockam/ockam_api/src/lib.rs index 9d9cf4096ed..3456b57ee54 100644 --- a/implementations/rust/ockam/ockam_api/src/lib.rs +++ b/implementations/rust/ockam/ockam_api/src/lib.rs @@ -143,7 +143,6 @@ pub mod port_range; pub mod rpc_proxy_service; pub mod trust_context; pub mod uppercase; -pub mod verifier; mod schema; mod session; @@ -158,7 +157,6 @@ extern crate tracing; pub struct DefaultAddress; impl DefaultAddress { - pub const IDENTITY_SERVICE: &'static str = "identity_service"; pub const AUTHENTICATED_SERVICE: &'static str = "authenticated"; pub const FORWARDING_SERVICE: &'static str = "forwarding_service"; pub const UPPERCASE_SERVICE: &'static str = "uppercase"; @@ -170,7 +168,6 @@ impl DefaultAddress { pub const CREDENTIAL_ISSUER: &'static str = "credential_issuer"; pub const ENROLLMENT_TOKEN_ISSUER: &'static str = "enrollment_token_issuer"; pub const ENROLLMENT_TOKEN_ACCEPTOR: &'static str = "enrollment_token_acceptor"; - pub const VERIFIER: &'static str = "verifier"; pub const OKTA_IDENTITY_PROVIDER: &'static str = "okta"; pub const KAFKA_OUTLET: &'static str = "kafka_outlet"; pub const KAFKA_CONSUMER: &'static str = "kafka_consumer"; @@ -181,8 +178,7 @@ impl DefaultAddress { pub fn is_valid(name: &str) -> bool { matches!( name, - Self::IDENTITY_SERVICE - | Self::AUTHENTICATED_SERVICE + Self::AUTHENTICATED_SERVICE | Self::FORWARDING_SERVICE | Self::UPPERCASE_SERVICE | Self::ECHO_SERVICE @@ -193,7 +189,6 @@ impl DefaultAddress { | Self::CREDENTIAL_ISSUER | Self::ENROLLMENT_TOKEN_ISSUER | Self::ENROLLMENT_TOKEN_ACCEPTOR - | Self::VERIFIER | Self::OKTA_IDENTITY_PROVIDER | Self::KAFKA_CONSUMER | Self::KAFKA_PRODUCER @@ -205,7 +200,6 @@ impl DefaultAddress { pub fn iter() -> impl Iterator { [ - Self::IDENTITY_SERVICE, Self::AUTHENTICATED_SERVICE, Self::FORWARDING_SERVICE, Self::UPPERCASE_SERVICE, @@ -217,7 +211,6 @@ impl DefaultAddress { Self::CREDENTIAL_ISSUER, Self::ENROLLMENT_TOKEN_ISSUER, Self::ENROLLMENT_TOKEN_ACCEPTOR, - Self::VERIFIER, Self::OKTA_IDENTITY_PROVIDER, Self::KAFKA_CONSUMER, Self::KAFKA_PRODUCER, @@ -305,7 +298,6 @@ mod test { #[test] fn test_default_address_is_valid() { assert!(!DefaultAddress::is_valid("foo")); - assert!(DefaultAddress::is_valid(DefaultAddress::IDENTITY_SERVICE)); assert!(DefaultAddress::is_valid( DefaultAddress::AUTHENTICATED_SERVICE )); @@ -329,7 +321,6 @@ mod test { assert!(DefaultAddress::is_valid( DefaultAddress::ENROLLMENT_TOKEN_ACCEPTOR )); - assert!(DefaultAddress::is_valid(DefaultAddress::VERIFIER)); assert!(DefaultAddress::is_valid( DefaultAddress::OKTA_IDENTITY_PROVIDER )); diff --git a/implementations/rust/ockam/ockam_api/src/nodes/registry.rs b/implementations/rust/ockam/ockam_api/src/nodes/registry.rs index ff0004c5daf..387603f9a25 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/registry.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/registry.rs @@ -195,14 +195,12 @@ impl OutletInfo { pub(crate) struct Registry { pub(crate) secure_channels: SecureChannelRegistry, pub(crate) secure_channel_listeners: BTreeMap, - pub(crate) identity_services: BTreeMap, pub(crate) authenticated_services: BTreeMap, pub(crate) okta_identity_provider_services: BTreeMap, pub(crate) uppercase_services: BTreeMap, pub(crate) echoer_services: BTreeMap, pub(crate) kafka_services: BTreeMap, pub(crate) hop_services: BTreeMap, - pub(crate) verifier_services: BTreeMap, pub(crate) credentials_services: BTreeMap, #[cfg(feature = "direct-authenticator")] pub(crate) authenticator_service: BTreeMap, diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service.rs b/implementations/rust/ockam/ockam_api/src/nodes/service.rs index a5543d200c6..5e5a744f513 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service.rs @@ -604,9 +604,6 @@ impl NodeManagerWorker { } // ==*== Services ==*== - (Post, ["node", "services", DefaultAddress::IDENTITY_SERVICE]) => { - encode_request_result(self.start_identity_service(ctx, req, dec).await)? - } (Post, ["node", "services", DefaultAddress::AUTHENTICATED_SERVICE]) => { encode_request_result(self.start_authenticated_service(ctx, req, dec).await)? } @@ -622,9 +619,6 @@ impl NodeManagerWorker { (Post, ["node", "services", DefaultAddress::DIRECT_AUTHENTICATOR]) => { encode_request_result(self.start_authenticator_service(ctx, req, dec).await)? } - (Post, ["node", "services", DefaultAddress::VERIFIER]) => { - encode_request_result(self.start_verifier_service(ctx, req, dec).await)? - } (Post, ["node", "services", DefaultAddress::CREDENTIALS_SERVICE]) => { encode_request_result(self.start_credentials_service(ctx, req, dec).await)? } diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service/node_identities.rs b/implementations/rust/ockam/ockam_api/src/nodes/service/node_identities.rs index 88971f3e808..1a11e5383de 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service/node_identities.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service/node_identities.rs @@ -1,8 +1,5 @@ use ockam::compat::sync::Arc; -use ockam::identity::Identity; -use ockam::identity::Vault; -use ockam::identity::{Identifier, IdentitiesRepository}; -use ockam::identity::{Identities, IdentitiesCreation}; +use ockam::identity::{Identifier, Identities, Vault}; use ockam::Result; use crate::cli_state::traits::StateDirTrait; @@ -27,47 +24,11 @@ impl NodeIdentities { self.identities.vault() } - pub(super) fn identities_repository(&self) -> Arc { - self.identities.repository() - } - - /// Return an identity if it has been created with that name before - pub(crate) async fn get_identity(&self, identity_name: String) -> Result> { - let repository = self.identities_repository(); - - let idt_state = if let Ok(idt_state) = self.cli_state.identities.get(identity_name.as_str()) - { - idt_state - } else { - return Ok(None); - }; - - let change_history = - if let Ok(change_history) = repository.get_identity(&idt_state.identifier()).await { - change_history - } else { - return Ok(None); - }; - - let identity = self - .identities - .identities_creation() - .import_from_change_history(Some(&idt_state.identifier()), change_history) - .await?; - - Ok(Some(identity)) - } - pub(crate) async fn get_identifier(&self, identity_name: String) -> Result { let identity_state = self.cli_state.identities.get(identity_name.as_str())?; Ok(identity_state.identifier()) } - /// Return an identities creation service backed up by the default vault - pub(crate) async fn get_default_identities_creation(&self) -> Result> { - Ok(Arc::new(self.get_identities_creation(None).await?)) - } - /// Return an identities service, possibly backed by a specific vault pub(crate) async fn get_identities( &self, @@ -81,19 +42,6 @@ impl NodeIdentities { .build()) } - /// Return an identities creations service - pub(crate) async fn get_identities_creation( - &self, - vault_name: Option, - ) -> Result { - let vault = self.get_identities_vault(vault_name).await?; - Ok(IdentitiesCreation::new( - self.identities_repository(), - vault.identity_vault, - vault.verifying_vault, - )) - } - /// Return either the default vault or a specific one pub(crate) async fn get_identities_vault(&self, vault_name: Option) -> Result { if let Some(vault) = vault_name { diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service/node_services.rs b/implementations/rust/ockam/ockam_api/src/nodes/service/node_services.rs index 77ba84e0db3..eb616bc634c 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service/node_services.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service/node_services.rs @@ -18,7 +18,6 @@ use crate::authenticator::direct::EnrollmentTokenAuthenticator; use crate::echoer::Echoer; use crate::error::ApiError; use crate::hop::Hop; -use crate::identity::IdentityService; use crate::kafka::{ ConsumerNodeAddr, KafkaInletController, KafkaPortalListener, KafkaSecureChannelControllerImpl, KAFKA_OUTLET_BOOTSTRAP_ADDRESS, KAFKA_OUTLET_INTERCEPTOR_ADDRESS, @@ -28,14 +27,12 @@ use crate::nodes::models::portal::CreateInlet; use crate::nodes::models::services::{ DeleteServiceRequest, ServiceList, ServiceStatus, StartAuthenticatedServiceRequest, StartAuthenticatorRequest, StartCredentialsService, StartEchoerServiceRequest, - StartHopServiceRequest, StartIdentityServiceRequest, StartKafkaConsumerRequest, - StartKafkaDirectRequest, StartKafkaOutletRequest, StartKafkaProducerRequest, - StartOktaIdentityProviderRequest, StartServiceRequest, StartUppercaseServiceRequest, - StartVerifierService, + StartHopServiceRequest, StartKafkaConsumerRequest, StartKafkaDirectRequest, + StartKafkaOutletRequest, StartKafkaProducerRequest, StartOktaIdentityProviderRequest, + StartServiceRequest, StartUppercaseServiceRequest, }; use crate::nodes::registry::{ AuthenticatorServiceInfo, CredentialsServiceInfo, KafkaServiceInfo, KafkaServiceKind, Registry, - VerifierServiceInfo, }; use crate::nodes::NodeManager; use crate::port_range::PortRange; @@ -46,29 +43,6 @@ use crate::{actions, resources}; use super::NodeManagerWorker; impl NodeManager { - pub(super) async fn start_identity_service_impl( - &mut self, - ctx: &Context, - addr: Address, - ) -> Result<()> { - if self.registry.identity_services.contains_key(&addr) { - return Err(ApiError::core("Identity service exists at this address")); - } - - let service = IdentityService::new(self.node_identities()).await?; - - ctx.flow_controls() - .add_consumer(addr.clone(), &self.api_transport_flow_control_id); - - ctx.start_worker(addr.clone(), service).await?; - - self.registry - .identity_services - .insert(addr, Default::default()); - - Ok(()) - } - pub(super) async fn start_credentials_service_impl<'a>( &mut self, ctx: &Context, @@ -374,19 +348,6 @@ impl NodeManager { } impl NodeManagerWorker { - pub(super) async fn start_identity_service( - &mut self, - ctx: &Context, - req: &Request, - dec: &mut Decoder<'_>, - ) -> Result> { - let mut node_manager = self.node_manager.write().await; - let req_body: StartIdentityServiceRequest = dec.decode()?; - let addr = req_body.addr.to_string().into(); - node_manager.start_identity_service_impl(ctx, addr).await?; - Ok(Response::ok(req.id())) - } - pub(super) async fn start_authenticated_service( &mut self, ctx: &Context, @@ -506,34 +467,6 @@ impl NodeManagerWorker { Ok(Response::ok(req.id())) } - pub(super) async fn start_verifier_service( - &mut self, - ctx: &Context, - req: &Request, - dec: &mut Decoder<'_>, - ) -> Result> { - let mut node_manager = self.node_manager.write().await; - let body: StartVerifierService = dec.decode()?; - let addr: Address = body.address().into(); - - if node_manager.registry.verifier_services.contains_key(&addr) { - return Err(ApiError::core("Verifier service exists at this address").into()); - } - - ctx.flow_controls() - .add_consumer(addr.clone(), &node_manager.api_transport_flow_control_id); - - let vs = crate::verifier::Verifier::new(node_manager.identities()); - ctx.start_worker(addr.clone(), vs).await?; - - node_manager - .registry - .verifier_services - .insert(addr, VerifierServiceInfo::default()); - - Ok(Response::ok(req.id())) - } - pub(super) async fn start_credentials_service( &mut self, ctx: &Context, @@ -988,12 +921,6 @@ impl NodeManagerWorker { fn list_services_impl(registry: &Registry) -> Vec { let mut list = Vec::new(); - registry.identity_services.keys().for_each(|addr| { - list.push(ServiceStatus::new( - addr.address(), - DefaultAddress::IDENTITY_SERVICE, - )) - }); registry.authenticated_services.keys().for_each(|addr| { list.push(ServiceStatus::new( addr.address(), @@ -1018,9 +945,6 @@ impl NodeManagerWorker { DefaultAddress::HOP_SERVICE, )) }); - registry.verifier_services.keys().for_each(|addr| { - list.push(ServiceStatus::new(addr.address(), DefaultAddress::VERIFIER)) - }); registry.credentials_services.keys().for_each(|addr| { list.push(ServiceStatus::new( addr.address(), diff --git a/implementations/rust/ockam/ockam_api/src/verifier.rs b/implementations/rust/ockam/ockam_api/src/verifier.rs deleted file mode 100644 index 70e0a4220df..00000000000 --- a/implementations/rust/ockam/ockam_api/src/verifier.rs +++ /dev/null @@ -1,126 +0,0 @@ -pub mod types; - -use crate::verifier::types::{VerifyRequest, VerifyResponse}; -use either::Either; -use minicbor::Decoder; -use ockam::identity::models::{CredentialAndPurposeKey, PurposeKeyAttestationData, VersionedData}; -use ockam::identity::{CredentialAndPurposeKeyData, Identities}; -use ockam_core::api::{Error, Request, Response, ResponseBuilder}; -use ockam_core::api::{Id, Method}; -use ockam_core::compat::sync::Arc; -use ockam_core::{self, api, Result, Routed, Worker}; -use ockam_node::Context; -use tracing::trace; - -pub struct Verifier { - identities: Arc, -} - -#[ockam_core::worker] -impl Worker for Verifier { - type Context = Context; - type Message = Vec; - - async fn handle_message(&mut self, c: &mut Context, m: Routed) -> Result<()> { - let r = self.on_request(m.as_body()).await?; - c.send(m.return_route(), r).await - } -} - -impl Verifier { - pub fn new(identities: Arc) -> Self { - Self { identities } - } - - async fn on_request(&mut self, data: &[u8]) -> Result> { - let mut dec = Decoder::new(data); - - let req: Request = match dec.decode() { - Ok(rq) => rq, - Err(e) => { - let err = Error::default().with_message(e.to_string()); - return Ok(Response::bad_request(Id::default()).body(err).to_vec()?); - } - }; - - trace! { - target: "ockam_api::verifier", - id = %req.id(), - method = ?req.method(), - path = %req.path(), - body = %req.has_body(), - "request" - } - - let res = match req.method() { - Some(Method::Post) => match req.path_segments::<2>().as_slice() { - ["verify"] => { - let vr: VerifyRequest = dec.decode()?; - let cr: CredentialAndPurposeKey = minicbor::decode(vr.credential())?; - match self.verify(req.id(), &vr, &cr).await { - Ok(Either::Left(err)) => err.to_vec()?, - Ok(Either::Right(dat)) => { - let exp = dat.credential_data.expires_at; - Response::ok(req.id()) - .body(VerifyResponse::new( - dat.credential_data.subject_attributes, - exp, - )) - .to_vec()? - } - Err(err) => { - let err_body = Error::new(req.path()) - .with_message(format!("Unable to verify credential. {}", err)); - Response::internal_error(req.id()).body(err_body).to_vec()? - } - } - } - _ => api::unknown_path(&req).to_vec()?, - }, - _ => api::invalid_method(&req).to_vec()?, - }; - - Ok(res) - } - - async fn verify<'a>( - &self, - id: Id, - req: &'a VerifyRequest<'a>, - cre: &CredentialAndPurposeKey, - ) -> Result, CredentialAndPurposeKeyData>> { - let versioned_data: VersionedData = - minicbor::decode(cre.purpose_key_attestation.data.as_slice())?; - let data: PurposeKeyAttestationData = minicbor::decode(&versioned_data.data)?; - - let authority = if let Some(ident) = req.authority(&data.subject) { - // FIXME: Put Authority separately to avoid decoding the data manually here - self.identities - .identities_creation() - .import(Some(&data.subject), ident) - .await? - .identifier() - .clone() - } else { - let err = Error::new("/verify").with_message("unauthorised issuer"); - return Ok(Either::Left(Response::unauthorized(id).body(err))); - }; - - let data = match self - .identities - .credentials() - .credentials_verification() - .verify_credential(Some(req.subject()), &[authority], cre) - .await - { - Ok(data) => data, - Err(err) => { - let err = Error::new("/verify") - .with_message(format!("error verifying a credential: {err}")); - return Ok(Either::Left(Response::forbidden(id).body(err))); - } - }; - - Ok(Either::Right(data)) - } -} diff --git a/implementations/rust/ockam/ockam_api/src/verifier/types.rs b/implementations/rust/ockam/ockam_api/src/verifier/types.rs deleted file mode 100644 index d2d0a46b59f..00000000000 --- a/implementations/rust/ockam/ockam_api/src/verifier/types.rs +++ /dev/null @@ -1,85 +0,0 @@ -use minicbor::{Decode, Encode}; -use ockam::identity::Identifier; -use ockam::identity::{Attributes, TimestampInSeconds}; -use ockam_core::compat::borrow::Cow; -use ockam_core::CowBytes; -use std::collections::BTreeMap; - -#[cfg(feature = "tag")] -use ockam_core::TypeTag; - -#[derive(Debug, Decode, Encode)] -#[rustfmt::skip] -#[cbor(map)] -pub struct VerifyRequest<'a> { - #[cfg(feature = "tag")] - #[n(0)] tag: TypeTag<4592146>, - #[b(1)] cred: CowBytes<'a>, - #[n(2)] subj: Identifier, - #[b(3)] auth: BTreeMap> -} - -#[derive(Debug, Decode, Encode)] -#[rustfmt::skip] -#[cbor(map)] -pub struct VerifyResponse { - #[cfg(feature = "tag")] - #[n(0)] tag: TypeTag<6845123>, - #[b(1)] attrs: Attributes, - #[n(2)] expires: TimestampInSeconds -} - -impl<'a> VerifyRequest<'a> { - pub fn new>>(cred: C, subj: Identifier) -> Self { - Self { - #[cfg(feature = "tag")] - tag: TypeTag, - cred: CowBytes(cred.into()), - subj, - auth: BTreeMap::new(), - } - } - - pub fn with_authority(mut self, id: Identifier, identity: T) -> Self - where - T: Into>, - { - self.auth.insert(id, CowBytes(identity.into())); - self - } - - pub fn credential(&self) -> &[u8] { - &self.cred - } - - pub fn subject(&self) -> &Identifier { - &self.subj - } - - pub fn authorities(&self) -> &BTreeMap> { - &self.auth - } - - pub fn authority(&self, id: &Identifier) -> Option<&CowBytes<'a>> { - self.auth.get(id) - } -} - -impl VerifyResponse { - pub fn new(attrs: Attributes, expires: TimestampInSeconds) -> Self { - Self { - #[cfg(feature = "tag")] - tag: TypeTag, - attrs, - expires, - } - } - - pub fn attributes(&self) -> &Attributes { - &self.attrs - } - - pub fn expires_at(&self) -> TimestampInSeconds { - self.expires - } -} diff --git a/implementations/rust/ockam/ockam_api/tests/identity.rs b/implementations/rust/ockam/ockam_api/tests/identity.rs deleted file mode 100644 index 281650bba6c..00000000000 --- a/implementations/rust/ockam/ockam_api/tests/identity.rs +++ /dev/null @@ -1,129 +0,0 @@ -use minicbor::Decoder; - -use ockam::identity::identity::IdentityHistoryComparison; -use ockam::node; -use ockam_api::cli_state::CliState; -use ockam_api::identity::models::*; -use ockam_api::identity::IdentityService; -use ockam_api::nodes::service::NodeIdentities; -use ockam_core::api::{Request, Response, Status}; -use ockam_core::errcode::{Kind, Origin}; -use ockam_core::{route, AsyncTryClone, Error, Result}; -use ockam_node::Context; - -async fn create_identity(ctx: &mut Context, service_address: &str) -> Result<(Vec, String)> { - let req = Request::post("").to_vec()?; - - let receiving_buf: Vec = ctx.send_and_receive(route![service_address], req).await?; - let mut dec = Decoder::new(&receiving_buf); - - let res: Response = dec.decode()?; - - if let Some(Status::Ok) = res.status() { - } else { - return Err(Error::new( - Origin::Identity, - Kind::Other, - "consistency error", - )); - } - - let res: CreateResponse = dec.decode()?; - - Ok((res.identity().to_vec(), res.identity_id().to_string())) -} - -async fn validate_identity_change_history( - ctx: &mut Context, - identity: &[u8], - service_address: &str, -) -> Result { - let body = ValidateIdentityChangeHistoryRequest::new(identity); - let req = Request::post("actions/validate_identity_change_history") - .body(body) - .to_vec()?; - - let receiving_buf: Vec = ctx.send_and_receive(route![service_address], req).await?; - let mut dec = Decoder::new(&receiving_buf); - - let res: Response = dec.decode()?; - - if let Some(Status::Ok) = res.status() { - } else { - return Err(Error::new( - Origin::Identity, - Kind::Other, - "consistency error", - )); - } - - let res: ValidateIdentityChangeHistoryResponse = dec.decode()?; - - Ok(res.identity_id().to_string()) -} - -async fn compare_identity_change_history( - ctx: &mut Context, - current_identity: &[u8], - known_identity: &[u8], - service_address: &str, -) -> Result { - let body = CompareIdentityChangeHistoryRequest::new(current_identity, known_identity); - let req = Request::post("actions/compare_identity_change_history") - .body(body) - .to_vec()?; - - let receiving_buf: Vec = ctx.send_and_receive(route![service_address], req).await?; - let mut dec = Decoder::new(&receiving_buf); - - let res: Response = dec.decode()?; - - if let Some(Status::Ok) = res.status() { - } else { - return Err(Error::new( - Origin::Identity, - Kind::Other, - "consistency error", - )); - } - - let res: IdentityHistoryComparison = dec.decode()?; - - Ok(res) -} - -#[ockam_macros::test] -async fn full_flow(ctx: &mut Context) -> Result<()> { - let cli_state = CliState::test().unwrap(); - let node1 = node(ctx.async_try_clone().await?); - let node2 = node(ctx.async_try_clone().await?); - - // Start services - ctx.start_worker( - "1", - IdentityService::new(NodeIdentities::new(node1.identities(), cli_state.clone())).await?, - ) - .await?; - ctx.start_worker( - "2", - IdentityService::new(NodeIdentities::new(node2.identities(), cli_state)).await?, - ) - .await?; - - let (identity1, _identity_id1) = create_identity(ctx, "1").await?; - let (identity2, _identity_id2) = create_identity(ctx, "2").await?; - - // Identity is updated here - let _identity_id1 = validate_identity_change_history(ctx, &identity1, "2").await?; - let _identity_id2 = validate_identity_change_history(ctx, &identity2, "1").await?; - - let comparison1 = compare_identity_change_history(ctx, &identity2, &[], "1").await?; - let comparison2 = compare_identity_change_history(ctx, &identity1, &[], "2").await?; - - assert_eq!(comparison1, IdentityHistoryComparison::Newer); - assert_eq!(comparison2, IdentityHistoryComparison::Newer); - - ctx.stop().await?; - - Ok(()) -} diff --git a/implementations/rust/ockam/ockam_command/src/node/create.rs b/implementations/rust/ockam/ockam_command/src/node/create.rs index fbb8d6bd9d7..ea077403859 100644 --- a/implementations/rust/ockam/ockam_command/src/node/create.rs +++ b/implementations/rust/ockam/ockam_command/src/node/create.rs @@ -396,13 +396,6 @@ async fn start_services(ctx: &Context, cfg: &Config) -> miette::Result<()> { } }; - if let Some(cfg) = config.identity { - if !cfg.disabled { - println!("starting identity service ..."); - let req = api::start_identity_service(&cfg.address); - send_req_to_node_manager(ctx, req).await?; - } - } if let Some(cfg) = config.secure_channel_listener { if !cfg.disabled { let adr = Address::from((LOCAL, cfg.address)); @@ -412,13 +405,6 @@ async fn start_services(ctx: &Context, cfg: &Config) -> miette::Result<()> { secure_channel_listener::create_listener(ctx, adr, ids, identity, route![]).await?; } } - if let Some(cfg) = config.verifier { - if !cfg.disabled { - println!("starting verifier service ..."); - let req = api::start_verifier_service(&cfg.address); - send_req_to_node_manager(ctx, req).await?; - } - } if let Some(cfg) = config.authenticator { if !cfg.disabled { println!("starting authenticator service ..."); diff --git a/implementations/rust/ockam/ockam_command/src/service/config.rs b/implementations/rust/ockam/ockam_command/src/service/config.rs index 70f46519c33..fb7fb73ed83 100644 --- a/implementations/rust/ockam/ockam_command/src/service/config.rs +++ b/implementations/rust/ockam/ockam_command/src/service/config.rs @@ -8,15 +8,6 @@ use ockam_api::DefaultAddress; use crate::Result; -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct IdentityConfig { - #[serde(default = "identity_default_addr")] - pub(crate) address: String, - - #[serde(default)] - pub(crate) disabled: bool, -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SecureChannelListenerConfig { #[serde(default = "sec_listener_default_addr")] @@ -32,15 +23,6 @@ pub struct SecureChannelListenerConfig { pub(crate) identity: Option, } -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct VerifierConfig { - #[serde(default = "verifier_default_addr")] - pub(crate) address: String, - - #[serde(default)] - pub(crate) disabled: bool, -} - #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AuthenticatorConfig { #[serde(default = "authenticator_default_addr")] @@ -71,9 +53,7 @@ pub struct OktaIdentityProviderConfig { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServiceConfigs { - pub(crate) identity: Option, pub(crate) secure_channel_listener: Option, - pub(crate) verifier: Option, pub(crate) authenticator: Option, pub(crate) okta_identity_provider: Option, } @@ -95,18 +75,10 @@ impl Config { } } -fn identity_default_addr() -> String { - DefaultAddress::IDENTITY_SERVICE.to_string() -} - fn sec_listener_default_addr() -> String { DefaultAddress::SECURE_CHANNEL_LISTENER.to_string() } -fn verifier_default_addr() -> String { - DefaultAddress::VERIFIER.to_string() -} - fn authenticator_default_addr() -> String { DefaultAddress::DIRECT_AUTHENTICATOR.to_string() } diff --git a/implementations/rust/ockam/ockam_command/src/service/start.rs b/implementations/rust/ockam/ockam_command/src/service/start.rs index 76c1752b0a6..b53917692e3 100644 --- a/implementations/rust/ockam/ockam_command/src/service/start.rs +++ b/implementations/rust/ockam/ockam_command/src/service/start.rs @@ -28,18 +28,10 @@ pub enum StartSubCommand { #[arg(long, default_value_t = hop_default_addr())] addr: String, }, - Identity { - #[arg(long, default_value_t = identity_default_addr())] - addr: String, - }, Authenticated { #[arg(long, default_value_t = authenticated_default_addr())] addr: String, }, - Verifier { - #[arg(long, default_value_t = verifier_default_addr())] - addr: String, - }, Credentials { #[arg(long)] identity: String, @@ -63,18 +55,10 @@ fn hop_default_addr() -> String { DefaultAddress::HOP_SERVICE.to_string() } -fn identity_default_addr() -> String { - DefaultAddress::IDENTITY_SERVICE.to_string() -} - fn authenticated_default_addr() -> String { DefaultAddress::AUTHENTICATED_SERVICE.to_string() } -fn verifier_default_addr() -> String { - DefaultAddress::VERIFIER.to_string() -} - fn credentials_default_addr() -> String { DefaultAddress::CREDENTIALS_SERVICE.to_string() } @@ -104,19 +88,11 @@ async fn run_impl(ctx: Context, opts: CommandGlobalOpts, cmd: StartCommand) -> m start_hop_service(&mut rpc, &addr).await?; addr } - StartSubCommand::Identity { addr, .. } => { - start_identity_service(&mut rpc, &addr).await?; - addr - } StartSubCommand::Authenticated { addr, .. } => { let req = api::start_authenticated_service(&addr); start_service_impl(&mut rpc, "Authenticated", req).await?; addr } - StartSubCommand::Verifier { addr, .. } => { - start_verifier_service(&mut rpc, &addr).await?; - addr - } StartSubCommand::Credentials { identity, addr, @@ -167,18 +143,6 @@ pub async fn start_hop_service<'a>(rpc: &mut Rpc, serv_addr: &str) -> Result<()> start_service_impl(rpc, "Hop", req).await } -/// Public so `ockam_command::node::create` can use it. -pub async fn start_identity_service<'a>(rpc: &mut Rpc, serv_addr: &str) -> Result<()> { - let req = api::start_identity_service(serv_addr); - start_service_impl(rpc, "Identity", req).await -} - -/// Public so `ockam_command::node::create` can use it. -pub async fn start_verifier_service<'a>(rpc: &mut Rpc, serv_addr: &str) -> Result<()> { - let req = api::start_verifier_service(serv_addr); - start_service_impl(rpc, "Verifier", req).await -} - /// Public so `ockam_command::node::create` can use it. #[allow(clippy::too_many_arguments)] pub async fn start_authenticator_service<'a>( diff --git a/implementations/rust/ockam/ockam_command/src/util/api.rs b/implementations/rust/ockam/ockam_command/src/util/api.rs index c2546b6a120..861ebdd28e8 100644 --- a/implementations/rust/ockam/ockam_command/src/util/api.rs +++ b/implementations/rust/ockam/ockam_command/src/util/api.rs @@ -15,8 +15,7 @@ use ockam_api::cloud::{BareCloudRequestWrapper, CloudRequestWrapper}; use ockam_api::nodes::models::flow_controls::AddConsumer; use ockam_api::nodes::models::services::{ StartAuthenticatedServiceRequest, StartAuthenticatorRequest, StartCredentialsService, - StartHopServiceRequest, StartIdentityServiceRequest, StartOktaIdentityProviderRequest, - StartVerifierService, + StartHopServiceRequest, StartOktaIdentityProviderRequest, }; use ockam_api::nodes::*; use ockam_api::trust_context::TrustContextConfigBuilder; @@ -136,12 +135,6 @@ pub(crate) fn start_hop_service(addr: &str) -> RequestBuilder RequestBuilder { - let payload = StartIdentityServiceRequest::new(addr); - Request::post(node_service(DefaultAddress::IDENTITY_SERVICE)).body(payload) -} - /// Construct a request to start an Authenticated Service pub(crate) fn start_authenticated_service( addr: &str, @@ -150,12 +143,6 @@ pub(crate) fn start_authenticated_service( Request::post(node_service(DefaultAddress::AUTHENTICATED_SERVICE)).body(payload) } -/// Construct a request to start a Verifier Service -pub(crate) fn start_verifier_service(addr: &str) -> RequestBuilder { - let payload = StartVerifierService::new(addr); - Request::post(node_service(DefaultAddress::VERIFIER)).body(payload) -} - /// Construct a request to start a Credential Service pub(crate) fn start_credentials_service( public_identity: &str, diff --git a/implementations/rust/ockam/ockam_command/tests/bats/nodes.bats b/implementations/rust/ockam/ockam_command/tests/bats/nodes.bats index 5ad200586e8..bab60178c3c 100644 --- a/implementations/rust/ockam/ockam_command/tests/bats/nodes.bats +++ b/implementations/rust/ockam/ockam_command/tests/bats/nodes.bats @@ -50,18 +50,10 @@ force_kill_node() { run_success "$OCKAM" node create n1 assert_success - # Check we can start service, but only once with the same name - run_success "$OCKAM" service start identity --addr my_identity --at n1 - run_failure "$OCKAM" service start identity --addr my_identity --at n1 - # Check we can start service, but only once with the same name run_success "$OCKAM" service start authenticated --addr my_authenticated --at n1 run_failure "$OCKAM" service start authenticated --addr my_authenticated --at n1 - # Check we can start service, but only once with the same name - run_success "$OCKAM" service start verifier --addr my_verifier --at n1 - run_failure "$OCKAM" service start verifier --addr my_verifier --at n1 - # Check we can start service, but only once with the same name run_success "$OCKAM" service start credentials --addr my_credentials --at n1 --identity 81a201583ba20101025835a4028201815820984249b1a11c6933002d02019f408ec0bdb7f3058068227a472986ea588ec67003f4041a64e49a5e051a77b09d5e02820181584002c2cc20acf3d7d59d67c420c3c29d4ebb1ebe483bfaba7fb046f59de96284ebfb570d17539e5d4989b74f22af12261b9c1d5eecf731e2d19907b092f6c47d04 run_failure "$OCKAM" service start credentials --addr my_credentials --at n1 --identity 81a201583ba20101025835a4028201815820984249b1a11c6933002d02019f408ec0bdb7f3058068227a472986ea588ec67003f4041a64e49a5e051a77b09d5e02820181584002c2cc20acf3d7d59d67c420c3c29d4ebb1ebe483bfaba7fb046f59de96284ebfb570d17539e5d4989b74f22af12261b9c1d5eecf731e2d19907b092f6c47d04 diff --git a/implementations/rust/ockam/ockam_ffi/.cargo/config.toml b/implementations/rust/ockam/ockam_ffi/.cargo/config.toml deleted file mode 100644 index c8173208dd8..00000000000 --- a/implementations/rust/ockam/ockam_ffi/.cargo/config.toml +++ /dev/null @@ -1,9 +0,0 @@ -# Note that this config format has a few fields that also appear in `Cargo.toml` -# (like `profile.release.lto`, as below), but is largely different. Notably, it -# only applies for builds from this directory (or that are invoked with -# `--manifest-path` pointing at it). -# -# See https://doc.rust-lang.org/cargo/reference/config.html for details - -[profile.release] -lto = true diff --git a/implementations/rust/ockam/ockam_ffi/include/ockam/vault.h b/implementations/rust/ockam/ockam_ffi/include/ockam/vault.h deleted file mode 100644 index 90bd550723e..00000000000 --- a/implementations/rust/ockam/ockam_ffi/include/ockam/vault.h +++ /dev/null @@ -1,260 +0,0 @@ -// Created by Ockam Developers - -#ifndef RUST_VAULT_H -#define RUST_VAULT_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct { - int64_t handle; - uint8_t vault_type; -} ockam_vault_t; - -typedef uint64_t ockam_vault_secret_t; - -/** - * @struct ockam_vault_extern_error_t - * @brief Represents an error that occurred in one of the `ockam_vault` functions. - * - * In the case of an error, resources associated with this error (the `domain` string) - * must be released using @ref ockam_vault_free_error (which is a no-op if an error did - * not occur) in order to avoid a memory leak. - */ -typedef struct { - int32_t code; - const char *domain; -} ockam_vault_extern_error_t; - -/** - * @enum ockam_vault_secret_t - * @brief Supported secret types for AES and Elliptic Curves. - */ -typedef enum { - OCKAM_VAULT_SECRET_TYPE_BUFFER = 0, - OCKAM_VAULT_SECRET_TYPE_AES_KEY, - OCKAM_VAULT_SECRET_TYPE_CURVE25519_PRIVATEKEY, - OCKAM_VAULT_SECRET_TYPE_P256_PRIVATEKEY, -} ockam_vault_secret_type_t; - -/** - * @enum ockam_vault_secret_persistence_t - * @brief Types of secrets vault can handle. - */ -typedef enum { - OCKAM_VAULT_SECRET_EPHEMERAL = 0, - OCKAM_VAULT_SECRET_PERSISTENT = 1, -} ockam_vault_secret_persistence_t; - -/** - * @struct ockam_vault_secret_attributes_t - * @brief Attributes for a specific ockam vault secret. - */ -typedef struct { - uint8_t type; - uint8_t persistence; - uint32_t length; -} ockam_vault_secret_attributes_t; - -/** - * @brief Initialize the specified ockam vault object - * @param vault[out] The ockam vault object to initialize with the default vault. - * @return an error, which should be freed using @ref ockam_vault_free_error. - */ -ockam_vault_extern_error_t ockam_vault_default_init(ockam_vault_t* vault); - -/** - * @brief Compute a SHA-256 hash based on input data. - * @param vault[in] Vault object to use for SHA-256. - * @param input[in] Buffer containing data to run through SHA-256. - * @param input_length[in] Length of the data to run through SHA-256. - * @param digest[out] Buffer to place the resulting SHA-256 hash in. Must be 32 bytes. - * @return an error, which should be freed using @ref ockam_vault_free_error. - */ -ockam_vault_extern_error_t ockam_vault_sha256(ockam_vault_t vault, - const uint8_t* input, - uint32_t input_length, - uint8_t* digest); - -/** - * @brief Generate an ockam secret. Attributes struct must specify the configuration for the type of secret to - * generate. For EC keys and AES keys, length is ignored. - * @param vault[in] Vault object to use for generating a secret key. - * @param secret[out] Pointer to an ockam secret object to be populated with a handle to the secret - * @param attributes[in] Desired attributes for the secret to be generated. - * @return an error, which should be freed using @ref ockam_vault_free_error. - */ -ockam_vault_extern_error_t ockam_vault_secret_generate(ockam_vault_t vault, - ockam_vault_secret_t* secret, - ockam_vault_secret_attributes_t attributes); - -/** - * @brief Import the specified data into the supplied ockam vault secret. - * @param vault[in] Vault object to use for generating a secret key. - * @param secret[out] Pointer to an ockam secret object to be populated with input data. - * @param attributes[in] Desired attributes for the secret being imported. - * @param input[in] Data to load into the supplied secret. - * @param input_length[in] Length of data to load into the secret. - * @return an error, which should be freed using @ref ockam_vault_free_error. - */ - -ockam_vault_extern_error_t ockam_vault_secret_import(ockam_vault_t vault, - ockam_vault_secret_t* secret, - ockam_vault_secret_attributes_t attributes, - const uint8_t* input, - uint32_t input_length); - -/** - * @brief Export data from an ockam vault secret into the supplied output buffer. - * @param vault[in] Vault object to use for exporting secret data. - * @param secret[in] Ockam vault secret to export data from. - * @param output_buffer[out] Buffer to place the exported secret data in. - * @param output_buffer_size[in] Size of the output buffer. - * @param output_buffer_length[out] Amount of data placed in the output buffer. - * @return an error, which should be freed using @ref ockam_vault_free_error. - */ -ockam_vault_extern_error_t ockam_vault_secret_export(ockam_vault_t vault, - ockam_vault_secret_t secret, - uint8_t* output_buffer, - uint32_t output_buffer_size, - uint32_t* output_buffer_length); - -/** - * @brief Retrieve the public key from an ockam vault secret. - * @param vault[in] Vault object to use for exporting the public key - * @param secret[in] Ockam vault secret to export the public key for. - * @param output_buffer[out] Buffer to place the public key in. - * @param output_buffer_size[in] Size of the output buffer. - * @param output_buffer_length[out] Amount of data placed in the output buffer. - * @return an error, which should be freed using @ref ockam_vault_free_error. - */ -ockam_vault_extern_error_t ockam_vault_secret_publickey_get(ockam_vault_t vault, - ockam_vault_secret_t secret, - uint8_t* output_buffer, - uint32_t output_buffer_size, - uint32_t* output_buffer_length); - -/** - * @brief Retrieve the attributes for a specified secret - * @param vault[in] Vault object to use for retrieving ockam vault secret attributes. - * @param secret[in] Ockam vault secret to get attributes for. - * @param secret_attributes[out] Pointer to the attributes for the specified secret. - * @return an error, which should be freed using @ref ockam_vault_free_error. - */ -ockam_vault_extern_error_t ockam_vault_secret_attributes_get(ockam_vault_t vault, - uint64_t secret, - ockam_vault_secret_attributes_t* attributes); - -/** - * @brief Delete an ockam vault secret. - * @param vault[in] Vault object to use for deleting the ockam vault secret. - * @param secret[in] Ockam vault secret to delete. - * @return an error, which should be freed using @ref ockam_vault_free_error. - */ -ockam_vault_extern_error_t ockam_vault_secret_destroy(ockam_vault_t vault, ockam_vault_secret_t secret); - -/** -* @brief Perform an ECDH operation on the supplied ockam vault secret and peer_publickey. The result is another -* ockam vault secret of type unknown. -* @param vault[in] Vault object to use for encryption. -* @param privatekey[in] The ockam vault secret to use for the private key of ECDH. -* @param peer_publickey[in] Public key data to use for ECDH. -* @param peer_publickey_length[in] Length of the public key. -* @param shared_secret[out] Resulting shared secret from a successful ECDH operation. Invalid if ECDH failed. -* @return an error, which should be freed using @ref ockam_vault_free_error. -*/ -ockam_vault_extern_error_t ockam_vault_ecdh(ockam_vault_t vault, - ockam_vault_secret_t privatekey, - const uint8_t* peer_publickey, - uint32_t peer_publickey_length, - ockam_vault_secret_t* shared_secret); - -/** - * @brief Perform an HMAC-SHA256 based key derivation function on the supplied salt and input key material. - * @param vault[in] Vault object to use for encryption. - * @param salt[in] Ockam vault secret containing the salt for HKDF. - * @param input_key_material[in] Ockam vault secret containing input key material to use for HKDF. - * @param derived_outputs_attributes[in] Attributes of output secrets. - * @param derived_outputs_count[in] Length of outputs attributes array. - * @param derived_outputs[out] Array of ockam vault secrets resulting from HKDF. - * @return an error, which should be freed using @ref ockam_vault_free_error. - */ -ockam_vault_extern_error_t ockam_vault_hkdf_sha256(ockam_vault_t vault, - ockam_vault_secret_t salt, - const ockam_vault_secret_t* input_key_material, - const ockam_vault_secret_attributes_t* derived_outputs_attributes, - uint8_t derived_outputs_count, - ockam_vault_secret_t* derived_outputs); - -/** - * @brief Encrypt a payload using AES-GCM. - * @param vault[in] Vault object to use for encryption. - * @param key[in] Ockam secret key to use for encryption. - * @param nonce[in] Nonce value to use for encryption. - * @param additional_data[in] Additional data to use for encryption. - * @param additional_data_length[in] Length of the additional data. - * @param plaintext[in] Buffer containing plaintext data to encrypt. - * @param plaintext_length[in] Length of plaintext data to encrypt. - * @param ciphertext_and_tag[in] Buffer containing the generated ciphertext and tag data. - * @param ciphertext_and_tag_size[in] Size of the ciphertext + tag buffer. Must be plaintext_size + 16. - * @param ciphertext_and_tag_length[out] Amount of data placed in the ciphertext + tag buffer. - * @return an error, which should be freed using @ref ockam_vault_free_error. - */ -ockam_vault_extern_error_t ockam_vault_aead_aes_gcm_encrypt(ockam_vault_t vault, - ockam_vault_secret_t key, - uint64_t nonce, - const uint8_t* additional_data, - uint32_t additional_data_length, - const uint8_t* plaintext, - uint32_t plaintext_length, - uint8_t* ciphertext_and_tag, - uint32_t ciphertext_and_tag_size, - uint32_t* ciphertext_and_tag_length); - -/** - * @brief Decrypt a payload using AES-GCM. - * @param vault[in] Vault object to use for decryption. - * @param key[in] Ockam secret key to use for decryption. - * @param nonce[in] Nonce value to use for decryption. - * @param additional_data[in] Additional data to use for decryption. - * @param additional_data_length[in] Length of the additional data. - * @param ciphertext_and_tag[in] The ciphertext + tag data to decrypt. - * @param ciphertext_and_tag_length[in] Length of the ciphertext + tag data to decrypt. - * @param plaintext[out] Buffer to place the decrypted data in. - * @param plaintext_size[in] Size of the plaintext buffer. Must be ciphertext_tag_size - 16. - * @param plaintext_length[out] Amount of data placed in the plaintext buffer. - * @return an error, which should be freed using @ref ockam_vault_free_error. - */ -ockam_vault_extern_error_t ockam_vault_aead_aes_gcm_decrypt(ockam_vault_t vault, - ockam_vault_secret_t key, - uint64_t nonce, - const uint8_t* additional_data, - uint32_t additional_data_length, - const uint8_t* ciphertext_and_tag, - uint32_t ciphertext_and_tag_length, - uint8_t* plaintext, - uint32_t plaintext_size, - uint32_t* plaintext_length); - -/** - * @brief Deinitialize the specified ockam vault object - * @param vault[in] The ockam vault object to deinitialize. - * @return an error, which should be freed using @ref ockam_vault_free_error. - */ -ockam_vault_extern_error_t ockam_vault_deinit(ockam_vault_t vault); - -/** - * @brief Free any resources associated with a @ref ockam_vault_extern_error_t. - * @param error[in] the error to free. - */ -void ockam_vault_free_error(ockam_vault_extern_error_t *error); - -#ifdef __cplusplus -} // extern "C" -#endif - -#endif // RUST_VAULT_H diff --git a/implementations/rust/ockam/ockam_ffi/src/error.rs b/implementations/rust/ockam/ockam_ffi/src/error.rs deleted file mode 100644 index 93865d35ddf..00000000000 --- a/implementations/rust/ockam/ockam_ffi/src/error.rs +++ /dev/null @@ -1,129 +0,0 @@ -use ockam_core::{ - errcode::{Kind, Origin}, - Error, -}; -use std::ffi::CString; -use std::os::raw::c_char; - -#[repr(C)] -#[derive(Debug, PartialEq, Eq)] -/// Error type relating to FFI specific failures. -pub struct FfiOckamError { - code: i32, - domain: *const c_char, -} - -impl FfiOckamError { - /// Create a new error. - pub fn new(code: i32, domain: &str) -> Self { - Self { - code, - // TODO: Should this graciously fail? - domain: CString::new(domain.as_bytes()).unwrap().into_raw(), - } - } - - /// No error. - pub fn none() -> Self { - Self { - code: 0, - domain: std::ptr::null(), - } - } -} - -/// Represents the failures that can occur in an Ockam FFI Vault. -#[derive(Clone, Copy, Debug)] -pub enum FfiError { - /// Persistence is not supported for this Vault implementation. - VaultDoesNotSupportPersistence = 1, - - /// An underlying filesystem error prevented Vault creation. - ErrorCreatingFilesystemVault, - - /// Invalid parameter. - InvalidParam, - - /// Entry not found. - EntryNotFound, - - /// Unknown public key type. - UnknownPublicKeyType, - - /// Invalid string. - InvalidString, - - /// Buffer is too small. - BufferTooSmall, - - /// A public key is invalid. - InvalidPublicKey, - - /// No such Vault. - VaultNotFound, - - /// Ownership error. - OwnershipError, - - /// Caught a panic (which would be UB if we let it unwind across the FFI). - UnexpectedPanic, -} -impl ockam_core::compat::error::Error for FfiError {} -impl From for Error { - #[track_caller] - fn from(err: FfiError) -> Self { - Error::new(Origin::Other, Kind::Other, err) - } -} -impl core::fmt::Display for FfiError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::VaultDoesNotSupportPersistence => write!( - f, - "persistence is not supported for this Vault implementation." - ), - Self::ErrorCreatingFilesystemVault => write!( - f, - "an underlying filesystem error prevented Vault creation." - ), - Self::InvalidParam => write!(f, "invalid parameter."), - Self::EntryNotFound => write!(f, "entry not found."), - Self::UnknownPublicKeyType => write!(f, "unknown public key type."), - Self::InvalidString => write!(f, "invalid string."), - Self::BufferTooSmall => write!(f, "buffer is too small."), - Self::InvalidPublicKey => write!(f, "a public key is invalid."), - Self::VaultNotFound => write!(f, "no such Vault."), - Self::OwnershipError => write!(f, "ownership error."), - Self::UnexpectedPanic => write!( - f, - "caught a panic (which would be UB if we let it unwind across the FFI)." - ), - } - } -} - -impl From for FfiOckamError { - fn from(err: Error) -> Self { - Self::new( - err.code().origin as i32 * 10_000 + err.code().kind as i32, - "unknown", - ) - } -} - -impl From for FfiOckamError { - fn from(err: FfiError) -> Self { - let err2: Error = err.into(); - Self::from(err2) - } -} - -/// # Safety -/// frees `FfiOckamError::domain` if it's non-null -#[no_mangle] -pub unsafe extern "C" fn ockam_vault_free_error(context: &mut FfiOckamError) { - if !context.domain.is_null() { - let _ = CString::from_raw(context.domain as *mut _); - context.domain = core::ptr::null(); - } -} diff --git a/implementations/rust/ockam/ockam_ffi/src/lib.rs b/implementations/rust/ockam/ockam_ffi/src/lib.rs deleted file mode 100644 index 30cf7950234..00000000000 --- a/implementations/rust/ockam/ockam_ffi/src/lib.rs +++ /dev/null @@ -1,22 +0,0 @@ -//! In order to support a variety of cryptographically capable hardware we maintain loose coupling between our protocols and how a specific building block is invoked in a specific hardware. This is achieved using an abstract Vault trait. -//! -//! A concrete implementation of the Vault trait is called an Ockam Vault. Over time, and with help from the Ockam open source community, we plan to add vaults for several TEEs, TPMs, HSMs, and Secure Enclaves. -//! -//! This crate provides the Vault FFI bindings following the "C" calling convention, and generates static and dynamic C linkable libraries. -#![warn( - missing_docs, - trivial_casts, - trivial_numeric_casts, - unused_import_braces, - unused_qualifications -)] -#![allow(clippy::not_unsafe_ptr_arg_deref)] - -mod error; -mod macros; -mod vault; -mod vault_types; - -pub use error::*; -pub use vault::*; -use vault_types::*; diff --git a/implementations/rust/ockam/ockam_ffi/src/macros.rs b/implementations/rust/ockam/ockam_ffi/src/macros.rs deleted file mode 100644 index eede075cb74..00000000000 --- a/implementations/rust/ockam/ockam_ffi/src/macros.rs +++ /dev/null @@ -1,14 +0,0 @@ -/// Safety macro which ensures a buffer is not null and not empty. -#[macro_export] -macro_rules! check_buffer { - ($buffer:expr) => { - if $buffer.is_null() { - return Err(FfiError::InvalidParam.into()); - } - }; - ($buffer:expr, $length:expr) => { - if $buffer.is_null() || $length == 0 { - return Err(FfiError::InvalidParam.into()); - } - }; -} diff --git a/implementations/rust/ockam/ockam_ffi/src/vault.rs b/implementations/rust/ockam/ockam_ffi/src/vault.rs deleted file mode 100644 index fb8601ba023..00000000000 --- a/implementations/rust/ockam/ockam_ffi/src/vault.rs +++ /dev/null @@ -1,569 +0,0 @@ -use crate::vault_types::{FfiSecretAttributes, SecretKeyHandle}; -use crate::{check_buffer, FfiError, FfiOckamError}; -use crate::{FfiVaultFatPointer, FfiVaultType}; - -use ockam_core::compat::collections::BTreeMap; -use ockam_core::compat::sync::{Arc, RwLock}; -use ockam_core::{Error, Result}; -use ockam_vault::{ - KeyId, PublicKey, Secret, SecretAttributes, SecureChannelVault, SoftwareSecureChannelVault, - SoftwareVerifyingVault, -}; - -use core::{future::Future, result::Result as StdResult, slice}; -use lazy_static::lazy_static; -use tokio::{runtime::Runtime, task}; - -#[derive(Default)] -struct SecretsMapping { - mapping: BTreeMap, - last_index: u64, -} - -impl SecretsMapping { - fn insert(&mut self, key_id: KeyId) -> u64 { - self.last_index += 1; - - self.mapping.insert(self.last_index, key_id); - - self.last_index - } - - fn get(&self, index: u64) -> Result { - Ok(self - .mapping - .get(&index) - .cloned() - .ok_or(FfiError::EntryNotFound)?) - } - - fn take(&mut self, index: u64) -> Result { - Ok(self.mapping.remove(&index).ok_or(FfiError::EntryNotFound)?) - } -} - -#[derive(Clone)] -struct VaultEntry { - secure_channel_vault: Arc, - // TODO: Remove sha256 from FFI and remove that dependency - verifying_vault: Arc, - secrets_mapping: Arc>, -} - -impl Default for VaultEntry { - fn default() -> Self { - Self { - secure_channel_vault: SoftwareSecureChannelVault::create(), - verifying_vault: SoftwareVerifyingVault::create(), - secrets_mapping: Default::default(), - } - } -} - -impl VaultEntry { - fn insert(&self, key_id: KeyId) -> u64 { - self.secrets_mapping.write().unwrap().insert(key_id) - } - - fn get(&self, index: u64) -> Result { - self.secrets_mapping.read().unwrap().get(index) - } - - fn take(&self, index: u64) -> Result { - self.secrets_mapping.write().unwrap().take(index) - } -} - -lazy_static! { - static ref SOFTWARE_VAULTS: RwLock> = RwLock::new(vec![]); - static ref RUNTIME: Arc = Arc::new(Runtime::new().unwrap()); -} - -fn get_runtime() -> Arc { - RUNTIME.clone() -} - -fn block_future(f: F) -> ::Output -where - F: Future, -{ - let rt = get_runtime(); - task::block_in_place(move || { - let local = task::LocalSet::new(); - local.block_on(&rt, f) - }) -} - -fn get_vault_entry(context: FfiVaultFatPointer) -> Result { - match context.vault_type() { - FfiVaultType::Software => { - let item = SOFTWARE_VAULTS - .read() - .unwrap() - .get(context.handle() as usize) - .ok_or(FfiError::VaultNotFound)? - .clone(); - - Ok(item) - } - } -} - -/// Create and return a default Ockam Vault. -#[no_mangle] -pub extern "C" fn ockam_vault_default_init(context: &mut FfiVaultFatPointer) -> FfiOckamError { - handle_panics(|| { - // TODO: handle logging - - let mut write_lock = SOFTWARE_VAULTS.write().unwrap(); - write_lock.push(Default::default()); - let handle = write_lock.len() - 1; - drop(write_lock); - - *context = FfiVaultFatPointer::new(handle as u64, FfiVaultType::Software); - - Ok(()) - }) -} - -/// Compute the SHA-256 hash on `input` and put the result in `digest`. -/// `digest` must be 32 bytes in length. -#[no_mangle] -pub extern "C" fn ockam_vault_sha256( - context: FfiVaultFatPointer, - input: *const u8, - input_length: u32, - digest: *mut u8, -) -> FfiOckamError { - handle_panics(|| { - check_buffer!(input); - check_buffer!(digest); - - let input = unsafe { core::slice::from_raw_parts(input, input_length as usize) }; - - let entry = get_vault_entry(context)?; - - let res = entry.verifying_vault.sha256_sync(input)?; - - unsafe { - std::ptr::copy_nonoverlapping(res.as_ptr(), digest, res.len()); - } - Ok(()) - }) -} - -/// Generate a secret key with the specific attributes. -/// Returns a handle for the secret. -#[no_mangle] -pub extern "C" fn ockam_vault_secret_generate( - context: FfiVaultFatPointer, - secret: &mut SecretKeyHandle, - attributes: FfiSecretAttributes, -) -> FfiOckamError { - handle_panics(|| { - let entry = get_vault_entry(context)?; - let atts = attributes.try_into()?; - - let entry_clone = entry.clone(); - let key_id = block_future(async move { - entry_clone - .secure_channel_vault - .generate_ephemeral_secret(atts) - .await - })?; - - *secret = entry.insert(key_id); - - Ok(()) - }) -} - -/// Import a secret key with the specific handle and attributes. -#[no_mangle] -pub extern "C" fn ockam_vault_secret_import( - context: FfiVaultFatPointer, - secret: &mut SecretKeyHandle, - attributes: FfiSecretAttributes, - input: *mut u8, - input_length: u32, -) -> FfiOckamError { - handle_panics(|| { - check_buffer!(input, input_length); - let entry = get_vault_entry(context)?; - let atts = attributes.try_into()?; - let secret_data = unsafe { core::slice::from_raw_parts(input, input_length as usize) }; - let secret_data = Secret::new(secret_data.to_vec()); - - let entry_clone = entry.clone(); - let key_id = block_future(async move { - entry_clone - .secure_channel_vault - .import_ephemeral_secret(secret_data, atts) - .await - })?; - - *secret = entry.insert(key_id); - - Ok(()) - }) -} - -/// Export a secret key with the specific handle to the `output_buffer`. -#[no_mangle] -pub extern "C" fn ockam_vault_secret_export( - context: FfiVaultFatPointer, - secret: SecretKeyHandle, - output_buffer: *mut u8, - output_buffer_size: u32, - output_buffer_length: &mut u32, -) -> FfiOckamError { - *output_buffer_length = 0; - handle_panics(|| { - let entry = get_vault_entry(context)?; - let key_id = entry.get(secret)?; - let key = entry.secure_channel_vault.get_ephemeral_secret(&key_id)?; - let key = key.secret().as_ref(); - if output_buffer_size < key.len() as u32 { - return Err(FfiError::BufferTooSmall.into()); - } - *output_buffer_length = key.len() as u32; - - unsafe { - std::ptr::copy_nonoverlapping(key.as_ptr(), output_buffer, key.len()); - }; - - Ok(()) - }) -} - -/// Get the public key, given a secret key, and copy it to the output buffer. -#[no_mangle] -pub extern "C" fn ockam_vault_secret_publickey_get( - context: FfiVaultFatPointer, - secret: SecretKeyHandle, - output_buffer: *mut u8, - output_buffer_size: u32, - output_buffer_length: &mut u32, -) -> FfiOckamError { - *output_buffer_length = 0; - handle_panics(|| { - let entry = get_vault_entry(context)?; - let key_id = entry.get(secret)?; - - let key = - block_future(async move { entry.secure_channel_vault.get_public_key(&key_id).await })?; - - if output_buffer_size < key.data().len() as u32 { - return Err(FfiError::BufferTooSmall.into()); - } - *output_buffer_length = key.data().len() as u32; - - unsafe { - std::ptr::copy_nonoverlapping(key.data().as_ptr(), output_buffer, key.data().len()); - }; - - Ok(()) - }) -} - -/// Retrieve the attributes for a specified secret. -#[no_mangle] -pub extern "C" fn ockam_vault_secret_attributes_get( - context: FfiVaultFatPointer, - secret: SecretKeyHandle, - attributes: &mut FfiSecretAttributes, -) -> FfiOckamError { - handle_panics(|| { - let entry = get_vault_entry(context)?; - let key_id = entry.get(secret)?; - - *attributes = block_future(async move { - entry - .secure_channel_vault - .get_secret_attributes(&key_id) - .await - })? - .into(); - - Ok(()) - }) -} - -/// Delete an ockam vault secret. -#[no_mangle] -pub extern "C" fn ockam_vault_secret_destroy( - context: FfiVaultFatPointer, - secret: SecretKeyHandle, -) -> FfiOckamError { - handle_panics(|| { - let entry = get_vault_entry(context)?; - let key_id = entry.take(secret)?; - - block_future(async move { entry.secure_channel_vault.delete_secret(key_id).await })?; - - Ok(()) - }) -} - -/// Perform an ECDH operation on the supplied Ockam Vault `secret` and `peer_publickey`. The result -/// is an Ockam Vault secret of unknown type. -#[no_mangle] -pub extern "C" fn ockam_vault_ecdh( - context: FfiVaultFatPointer, - secret: SecretKeyHandle, - peer_publickey: *const u8, - peer_publickey_length: u32, - shared_secret: &mut SecretKeyHandle, -) -> FfiOckamError { - handle_panics(|| { - check_buffer!(peer_publickey, peer_publickey_length); - - let peer_publickey = - unsafe { core::slice::from_raw_parts(peer_publickey, peer_publickey_length as usize) }; - let entry = get_vault_entry(context)?; - let key_id = entry.get(secret)?; - - *shared_secret = block_future(async move { - let atts = entry - .secure_channel_vault - .get_secret_attributes(&key_id) - .await?; - let pubkey = PublicKey::new(peer_publickey.to_vec(), atts.secret_type()); - let shared_ctx = entry - .secure_channel_vault - .ec_diffie_hellman(&key_id, &pubkey) - .await?; - let index = entry.insert(shared_ctx); - Ok::(index) - })?; - Ok(()) - }) -} - -/// Perform an HMAC-SHA256 based key derivation function on the supplied salt and input key -/// material. -#[no_mangle] -pub extern "C" fn ockam_vault_hkdf_sha256( - context: FfiVaultFatPointer, - salt: SecretKeyHandle, - input_key_material: *const SecretKeyHandle, - derived_outputs_attributes: *const FfiSecretAttributes, - derived_outputs_count: u8, - derived_outputs: *mut SecretKeyHandle, -) -> FfiOckamError { - handle_panics(|| { - let derived_outputs_count = derived_outputs_count as usize; - - let entry = get_vault_entry(context)?; - let salt_key_id = entry.get(salt)?; - let ikm_key_id = if input_key_material.is_null() { - None - } else { - let ctx = unsafe { entry.get(*input_key_material)? }; - Some(ctx) - }; - let ikm_key_id = ikm_key_id.as_ref(); - - let array: &[FfiSecretAttributes] = - unsafe { slice::from_raw_parts(derived_outputs_attributes, derived_outputs_count) }; - - let mut output_attributes = Vec::::with_capacity(array.len()); - for x in array.iter() { - output_attributes.push(SecretAttributes::try_from(*x)?); - } - - let entry_clone = entry.clone(); - let hkdf_output = block_future(async move { - // TODO: Hardcoded to be empty for now because any changes - // to the C layer requires an API change. - // This change was necessary to implement Enrollment since the info string is not - // left blank for that protocol, but is blank for the XX key exchange pattern. - // If we agree to change the API, then this wouldn't be hardcoded but received - // from a parameter in the C API. Elixir and other consumers would be expected - // to pass the appropriate flag. The other option is to not expose the vault - // directly since it may confuse users about what to pass here and - // I don't like the idea of yelling at consumers through comments. - // Instead the vault could be encapsulated in channels and key exchanges. - // Either way, I don't want to change the API until this decision is finalized. - entry_clone - .secure_channel_vault - .hkdf_sha256(&salt_key_id, b"", ikm_key_id, output_attributes) - .await - })?; - - let hkdf_output: Vec = - hkdf_output.into_iter().map(|x| entry.insert(x)).collect(); - - unsafe { - std::ptr::copy_nonoverlapping( - hkdf_output.as_ptr(), - derived_outputs, - derived_outputs_count, - ) - }; - - Ok(()) - }) -} - -/// Encrypt a payload using AES-GCM. -#[no_mangle] -pub extern "C" fn ockam_vault_aead_aes_gcm_encrypt( - context: FfiVaultFatPointer, - secret: SecretKeyHandle, - nonce: u64, - additional_data: *const u8, - additional_data_length: u32, - plaintext: *const u8, - plaintext_length: u32, - ciphertext_and_tag: &mut u8, - ciphertext_and_tag_size: u32, - ciphertext_and_tag_length: &mut u32, -) -> FfiOckamError { - *ciphertext_and_tag_length = 0; - handle_panics(|| { - check_buffer!(additional_data); - check_buffer!(plaintext); - - let additional_data = unsafe { - core::slice::from_raw_parts(additional_data, additional_data_length as usize) - }; - - let plaintext = - unsafe { core::slice::from_raw_parts(plaintext, plaintext_length as usize) }; - - let entry = get_vault_entry(context)?; - let key_id = entry.get(secret)?; - let mut nonce_vec = vec![0; 12 - 8]; - nonce_vec.extend_from_slice(&nonce.to_be_bytes()); - - let ciphertext = block_future(async move { - entry - .secure_channel_vault - .aead_aes_gcm_encrypt(&key_id, plaintext, &nonce_vec, additional_data) - .await - })?; - - if ciphertext_and_tag_size < ciphertext.len() as u32 { - return Err(FfiError::BufferTooSmall.into()); - } - *ciphertext_and_tag_length = ciphertext.len() as u32; - - unsafe { - std::ptr::copy_nonoverlapping(ciphertext.as_ptr(), ciphertext_and_tag, ciphertext.len()) - }; - - Ok(()) - }) -} - -/// Decrypt a payload using AES-GCM. -#[no_mangle] -pub extern "C" fn ockam_vault_aead_aes_gcm_decrypt( - context: FfiVaultFatPointer, - secret: SecretKeyHandle, - nonce: u64, - additional_data: *const u8, - additional_data_length: u32, - ciphertext_and_tag: *const u8, - ciphertext_and_tag_length: u32, - plaintext: &mut u8, - plaintext_size: u32, - plaintext_length: &mut u32, -) -> FfiOckamError { - *plaintext_length = 0; - handle_panics(|| { - check_buffer!(ciphertext_and_tag, ciphertext_and_tag_length); - check_buffer!(additional_data); - - let additional_data = unsafe { - core::slice::from_raw_parts(additional_data, additional_data_length as usize) - }; - - let ciphertext_and_tag = unsafe { - core::slice::from_raw_parts(ciphertext_and_tag, ciphertext_and_tag_length as usize) - }; - - let entry = get_vault_entry(context)?; - let key_id = entry.get(secret)?; - let mut nonce_vec = vec![0; 12 - 8]; - nonce_vec.extend_from_slice(&nonce.to_be_bytes()); - - let plain = block_future(async move { - entry - .secure_channel_vault - .aead_aes_gcm_decrypt(&key_id, ciphertext_and_tag, &nonce_vec, additional_data) - .await - })?; - - if plaintext_size < plain.len() as u32 { - return Err(FfiError::BufferTooSmall.into()); - } - *plaintext_length = plain.len() as u32; - - unsafe { std::ptr::copy_nonoverlapping(plain.as_ptr(), plaintext, plain.len()) }; - - Ok(()) - }) -} - -/// De-initialize an Ockam Vault. -#[no_mangle] -pub extern "C" fn ockam_vault_deinit(context: FfiVaultFatPointer) -> FfiOckamError { - handle_panics(|| { - block_future(async move { - match context.vault_type() { - FfiVaultType::Software => { - let handle = context.handle() as usize; - let mut v = SOFTWARE_VAULTS.write().unwrap(); - if handle < v.len() { - v.remove(handle); - Ok(()) - } else { - Err(FfiError::VaultNotFound) - } - } - } - })?; - Ok(()) - }) -} - -fn handle_panics(f: F) -> FfiOckamError -where - F: FnOnce() -> StdResult<(), FfiOckamError>, -{ - let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)); - match result { - // No error. - Ok(Ok(())) => FfiOckamError::none(), - // Failed with a specific ockam error: - Ok(Err(e)) => e, - // Panicked - Err(e) => { - // Force an abort if either: - // - // - `e` panics during its `Drop` impl. - // - `FfiOckamError::from(FfiError)` panics. - // - // Both of these are extremely unlikely, but possible. - let panic_guard = AbortOnDrop; - drop(e); - let ret = FfiOckamError::from(FfiError::UnexpectedPanic); - core::mem::forget(panic_guard); - ret - } - } -} - -/// Aborts on drop, used to guard against panics in a section of code. -/// -/// Correct usage should `mem::forget` this struct after the non-panicking -/// section. -struct AbortOnDrop; -impl Drop for AbortOnDrop { - fn drop(&mut self) { - eprintln!("Panic from error drop, aborting!"); - std::process::abort(); - } -} diff --git a/implementations/rust/ockam/ockam_ffi/src/vault_types.rs b/implementations/rust/ockam/ockam_ffi/src/vault_types.rs deleted file mode 100644 index 6566c4b18a9..00000000000 --- a/implementations/rust/ockam/ockam_ffi/src/vault_types.rs +++ /dev/null @@ -1,98 +0,0 @@ -#![allow(conflicting_repr_hints)] - -use crate::FfiError; -use ockam_vault::constants::AES256_SECRET_LENGTH_U32; -use ockam_vault::{SecretAttributes, SecretType}; - -/// Represents a handle id for the secret key -pub type SecretKeyHandle = u64; - -#[repr(C, u8)] -#[derive(Clone, Copy, Debug)] -pub enum FfiVaultType { - Software = 1, -} - -#[repr(C)] -#[derive(Clone, Copy, Debug)] -pub struct FfiVaultFatPointer { - handle: u64, - vault_type: FfiVaultType, -} - -impl FfiVaultFatPointer { - pub fn handle(&self) -> u64 { - self.handle - } - pub fn vault_type(&self) -> FfiVaultType { - self.vault_type - } -} - -impl FfiVaultFatPointer { - pub fn new(handle: u64, vault_type: FfiVaultType) -> Self { - FfiVaultFatPointer { handle, vault_type } - } -} - -#[derive(Clone, Copy, Debug)] -#[repr(C)] -pub struct FfiSecretAttributes { - stype: u8, - persistence: u8, - length: u32, -} - -impl FfiSecretAttributes { - pub fn stype(&self) -> u8 { - self.stype - } - pub fn persistence(&self) -> u8 { - self.persistence - } - pub fn length(&self) -> u32 { - self.length - } -} - -impl FfiSecretAttributes { - pub fn new(stype: u8, length: u32) -> Self { - Self { - stype, - persistence: 0, - length, - } - } -} - -impl From for FfiSecretAttributes { - fn from(attrs: SecretAttributes) -> Self { - let stype = match attrs.secret_type() { - SecretType::Buffer => 0, - SecretType::Aes => 1, - SecretType::X25519 => 2, - SecretType::Ed25519 => 3, - SecretType::NistP256 => 4, - }; - - Self::new(stype, attrs.length()) - } -} - -impl TryFrom for SecretAttributes { - type Error = FfiError; - - fn try_from(attrs: FfiSecretAttributes) -> Result { - match attrs.stype() { - 0 => Ok(SecretAttributes::Buffer(attrs.length)), - 1 => Ok(if attrs.length == AES256_SECRET_LENGTH_U32 { - SecretAttributes::Aes256 - } else { - SecretAttributes::Aes128 - }), - 2 => Ok(SecretAttributes::X25519), - 3 => Ok(SecretAttributes::Ed25519), - _ => Err(FfiError::InvalidParam), - } - } -} diff --git a/implementations/rust/ockam/ockam_vault_aws/Cargo.toml b/implementations/rust/ockam/ockam_vault_aws/Cargo.toml index dfe96743be0..078da860774 100644 --- a/implementations/rust/ockam/ockam_vault_aws/Cargo.toml +++ b/implementations/rust/ockam/ockam_vault_aws/Cargo.toml @@ -50,6 +50,9 @@ no_std = [ # Feature: "alloc" enables support for heap allocation (implied by `feature = "std"`) alloc = ["ockam_core/alloc", "ockam_node/alloc", "ockam_vault/alloc"] +# Feature: "credentials-sso" enables support for sso on aws-config +credentials-sso = ["aws-config/credentials-sso"] + [dependencies] aws-config = { version = "0.56.1", default-features = false, features = ["rustls"] } aws-sdk-kms = { version = "0.30.0", default-features = false, features = ["rustls"] } From ce06e3aa7d7d662b5ec48629b4c55bab324aa9c8 Mon Sep 17 00:00:00 2001 From: Pablo Polvorin Date: Sat, 16 Sep 2023 12:59:20 -0300 Subject: [PATCH 3/5] fix(elixir): fix format on list_member_ids response --- .../credential/authenticator/direct/client.ex | 2 +- .../ockam/ockly/native/ockly/src/lib.rs | 34 ++++++++++++------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/implementations/elixir/ockam/ockam/lib/ockam/credential/authenticator/direct/client.ex b/implementations/elixir/ockam/ockam/lib/ockam/credential/authenticator/direct/client.ex index 1c5a5dc214a..12ca54d3513 100644 --- a/implementations/elixir/ockam/ockam/lib/ockam/credential/authenticator/direct/client.ex +++ b/implementations/elixir/ockam/ockam/lib/ockam/credential/authenticator/direct/client.ex @@ -95,7 +95,7 @@ defmodule Ockam.Credential.Authenticator.Direct.Client do def list_member_ids(api_route) do case ApiClient.sync_request(:get, "/member_ids", "", api_route) do {:ok, %ApiResponse{status: 200, body: response}} -> - Ockam.TypedCBOR.decode_strict({:list, :string}, response) + Ockam.TypedCBOR.decode_strict({:list, :binary}, response) {:ok, %ApiResponse{status: status, body: body}} -> {:error, {:api_error, status, body}} diff --git a/implementations/elixir/ockam/ockly/native/ockly/src/lib.rs b/implementations/elixir/ockam/ockly/native/ockly/src/lib.rs index b26ef30b542..2e445641cf3 100644 --- a/implementations/elixir/ockam/ockly/native/ockly/src/lib.rs +++ b/implementations/elixir/ockam/ockly/native/ockly/src/lib.rs @@ -34,6 +34,8 @@ mod atoms { identity_import_error, credential_verification_failed, invalid_identifier, + identity_creation_error, + identity_export_error, utf8_error, attest_error, attestation_encode_error, @@ -72,26 +74,33 @@ fn identities_ref() -> NifResult> { #[rustler::nif] fn create_identity(env: Env, existing_key: Option) -> NifResult<(Binary, Binary)> { let identities_ref = identities_ref()?; + let secret_type = if SIGNING_MEMORY_VAULT.read().unwrap().is_some() { + SecretType::Ed25519 + } else { + SecretType::NistP256 + }; let identity = block_future(async move { if let Some(key) = existing_key { identities_ref .identities_creation() .identity_builder() - .with_existing_key(key, SecretType::Ed25519) + .with_existing_key(key, secret_type) .build() .await } else { - if SIGNING_MEMORY_VAULT.read().unwrap().is_some() { - identities_ref.identities_creation().identity_builder().with_random_key(SecretType::Ed25519).build().await - } else { - // AWS vault uses NistP256 keys - identities_ref.identities_creation().identity_builder().with_random_key(SecretType::NistP256).build().await - } + identities_ref + .identities_creation() + .identity_builder() + .with_random_key(secret_type) + .build() + .await } }) - .map_err(|_| Error::BadArg)?; + .map_err(|_| Error::Term(Box::new(atoms::identity_creation_error())))?; - let exported = identity.export().map_err(|_| Error::BadArg)?; + let exported = identity + .export() + .map_err(|_| Error::Term(Box::new(atoms::identity_export_error())))?; let id = identity.identifier().to_string(); let mut binary = NewBinary::new(env, id.len()); binary.copy_from_slice(id.as_bytes()); @@ -330,10 +339,9 @@ fn load_aws_vault() -> bool { *IDENTITIES.write().unwrap() = Some(builder.build()); true } - Err(_) => { - false - } - }}) + Err(_) => false, + } + }) } fn load(_env: rustler::Env, load_data: rustler::Term) -> bool { From f391b081d372159203e608bc2c70f48d06bbc8d1 Mon Sep 17 00:00:00 2001 From: Pablo Polvorin Date: Sun, 17 Sep 2023 20:58:21 -0300 Subject: [PATCH 4/5] fix(elixir): include static key and attestation on identity callbacks --- .../lib/healthcheck/healthcheck.ex | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/implementations/elixir/ockam/ockam_healthcheck/lib/healthcheck/healthcheck.ex b/implementations/elixir/ockam/ockam_healthcheck/lib/healthcheck/healthcheck.ex index 3313f1f8f7f..3595179f39d 100644 --- a/implementations/elixir/ockam/ockam_healthcheck/lib/healthcheck/healthcheck.ex +++ b/implementations/elixir/ockam/ockam_healthcheck/lib/healthcheck/healthcheck.ex @@ -160,9 +160,7 @@ defmodule Ockam.Healthcheck do defp connect_secure_channel(tcp_conn, api_worker) do api_route = [tcp_conn, api_worker] - with {:ok, identity} <- get_healthcheck_identity(), - {:ok, keypair} <- SecureChannel.Crypto.generate_dh_keypair(), - {:ok, attestation} <- Ockam.Identity.attest_purpose_key(identity, keypair) do + with {:ok, {identity, keypair, attestation}} <- get_healthcheck_identity() do case SecureChannel.create_channel( [ route: api_route, @@ -249,7 +247,9 @@ defmodule Ockam.Healthcheck do end @spec get_healthcheck_identity() :: - {:ok, identity :: Ockam.Identity.t()} + {:ok, + {identity :: Ockam.Identity.t(), keypair :: map(), + attestation :: Ockam.Identity.PurposeKeyAttestation.t()}} | {:error, reason :: any()} defp get_healthcheck_identity() do case Application.get_env(:ockam_healthcheck, :identity_source) do @@ -276,8 +276,10 @@ defmodule Ockam.Healthcheck do defp identity_from_file(file, secret) do with {:ok, identity_data} <- File.read(file), {:ok, signing_key} <- File.read(secret), - {:ok, identity, _identity_id} <- Ockam.Identity.import(identity_data, signing_key) do - {:ok, identity} + {:ok, identity, _identity_id} <- Ockam.Identity.import(identity_data, signing_key), + {:ok, keypair} <- SecureChannel.Crypto.generate_dh_keypair(), + {:ok, attestation} <- Ockam.Identity.attest_purpose_key(identity, keypair) do + {:ok, {identity, keypair, attestation}} end end @@ -286,19 +288,17 @@ defmodule Ockam.Healthcheck do :none -> generate_and_cache_identity() - identity -> - {:ok, identity} + {identity, keypair, attestation} -> + {:ok, {identity, keypair, attestation}} end end defp generate_and_cache_identity() do - case generate_identity() do - {:ok, identity} -> - :persistent_term.put(:healthcheck_identity, identity) - {:ok, identity} - - {:error, reason} -> - {:error, reason} + with {:ok, identity} <- generate_identity(), + {:ok, keypair} <- SecureChannel.Crypto.generate_dh_keypair(), + {:ok, attestation} <- Ockam.Identity.attest_purpose_key(identity, keypair) do + :persistent_term.put(:healthcheck_identity, {identity, keypair, attestation}) + {:ok, {identity, keypair, attestation}} end end From 7e2a962689f6f894bbac5daa1c57921ec9dfb88f Mon Sep 17 00:00:00 2001 From: Oleksandr Deundiak Date: Tue, 19 Sep 2023 14:53:30 +0200 Subject: [PATCH 5/5] chore(rust): delete `ockam-ffi` directory --- .../rust/ockam/ockam_ffi/CHANGELOG.md | 456 ------------------ .../rust/ockam/ockam_ffi/Cargo.toml | 35 -- .../rust/ockam/ockam_ffi/README.md | 42 -- 3 files changed, 533 deletions(-) delete mode 100644 implementations/rust/ockam/ockam_ffi/CHANGELOG.md delete mode 100644 implementations/rust/ockam/ockam_ffi/Cargo.toml delete mode 100644 implementations/rust/ockam/ockam_ffi/README.md diff --git a/implementations/rust/ockam/ockam_ffi/CHANGELOG.md b/implementations/rust/ockam/ockam_ffi/CHANGELOG.md deleted file mode 100644 index 363a27ab026..00000000000 --- a/implementations/rust/ockam/ockam_ffi/CHANGELOG.md +++ /dev/null @@ -1,456 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## 0.78.0 - 2023-09-13 - -### Changed - -- Updated dependencies - -## 0.77.0 - 2023-09-06 - -### Changed - -- Updated dependencies - -## 0.76.0 - 2023-06-26 - -### Changed - -- Extract a full state machine for the secure channel handshake -- Updated dependencies - -## 0.75.0 - 2023-06-09 - -### Changed - -- Updated dependencies - -## 0.74.0 - 2023-05-26 - -### Changed - -- Regroup all the vault related types and traits in the same crate -- Extract the vault_aws crate -- Separated the ephemeral and persistent secrets store interfaces -- Updated dependencies - -### Fixed - -- Fix the vault tests for elixir - -## 0.73.0 - 2023-05-12 - -### Changed - -- Updated dependencies - -## 0.72.0 - 2023-05-04 - -### Added - -- Added a readme template and updated some readmes - -### Changed - -- Updated dependencies - -## 0.71.0 - 2023-04-27 - -### Changed - -- Updated dependencies - -## 0.70.0 - 2023-04-14 - -### Changed - -- Updated dependencies - -## 0.69.0 - 2023-03-28 - -### Changed - -- Updated dependencies - -## 0.68.0 - 2023-03-03 - -### Changed - -- Updated dependencies - -## 0.67.0 - 2023-02-24 - -### Changed - -- Updated dependencies - -## 0.66.0 - 2023-02-09 - -### Changed - -- Updated dependencies - -## 0.65.0 - 2023-01-31 - -### Changed - -- Updated dependencies - -## 0.63.0 - 2022-11-08 - -### Changed - -- Switch to arch agnostic integers for secret length -- Updated dependencies - -### Removed - -- Remove bls support - -## 0.62.0 - 2022-09-21 - -### Changed - -- Switch to arch agnostic integers for secret length -- Updated dependencies - -## 0.61.0 - 2022-09-09 - -### Changed - -- Switch to arch agnostic integers for secret length -- Updated dependencies - -## 0.60.0 - 2022-09-07 - -### Changed - -- Switch to arch agnostic integers for secret length -- Updated dependencies - -## 0.59.0 - 2022-09-05 - -### Changed - -- Updated dependencies - -## 0.58.0 - 2022-08-31 - -### Changed - -- Updated dependencies - -## 0.57.0 - 2022-08-29 - -### Changed - -- Updated dependencies - -## 0.56.0 - 2022-08-17 - -### Changed - -- Updated dependencies - -## 0.55.0 - 2022-08-12 - -### Changed - -- Updated dependencies - -## 0.54.0 - 2022-08-04 - -### Changed - -- Updated dependencies - -## 0.46.0 - 2022-06-06 - -### Changed - -- Switch `Vault` to `String` `KeyId` instead of integer `Secret` -- Updated dependencies - -### Fixed - -- Fix ffi after `Vault` updates - -### Removed - -- Remove `AsRef` from `PublicKey` to avoid confusion - -## 0.45.0 - 2022-05-23 - -### Changed - -- Updated dependencies - -## 0.44.0 - 2022-05-09 - -### Changed - -- Updated dependencies - -## 0.43.0 - 2022-05-05 - -### Changed - -- Updated dependencies - -## 0.42.0 - 2022-04-25 - -### Changed - -- Updated dependencies - -## 0.41.0 - 2022-04-19 - -### Changed - -- Clean up ockam_core import paths -- Run rustfmt -- Rename error2 to error -- Updated dependencies - -### Fixed - -- Errors: fix ockam_ffi -- Fixing lints -- Fix various clippy and rustfmt lints - -### Removed - -- Remove thiserror as it does not support no_std - -## 0.40.0 - 2022-04-11 - -### Changed - -- Vault updates -- Updated dependencies - -## 0.39.0 - 2022-04-04 - -### Changed - -- Updated dependencies - -## 0.38.0 - 2022-03-28 - -### Changed - -- Updated dependencies - -## 0.35.0 - 2022-02-08 - -### Changed - -- Update crate edition to 2021 - -## 0.32.1 - 2022-01-10 - -### Added - -- Add script to rebuild macos native vault lib -- Add ockam_core/bls feature and small fixes - -### Changed - -- Vault updates -- Change uses of `ockam_vault_core::Foo` to use `ockam_core::vault::Foo` across crates -- Improve formatting of `Cargo.toml`s and add `rust-version` 1.56.0 - -## v0.32.0 - 2021-12-06 - -### Removed - -- Remove symlinks to `DEVELOP.md` and `LICENSE` - -## v0.31.0 - 2021-11-22 - - -### Changed - -- Deny warnings in ci, not local development - - -## v0.30.0 - 2021-11-15 -### Changed -- change `Doesnt` to `DoesNot` for enum variants -- Dependencies updated - -## v0.29.0 - 2021-11-08 -### Changed -- Dependencies updated - -## v0.28.0 - 2021-11-01 -### Changed -- catch panics in `extern "C"` functions -- avoid leaking memory on errors in the ffi -- clean up `ockam_ffi`'s handling of `bls` feature -- Dependencies updated - -## v0.27.0 - 2021-10-26 -### Changed -- Dependencies updated - -## v0.26.0 - 2021-10-25 -### Changed -- Update FFI to async. -- Move as many things as possible into a workspace. -- Dependencies updated - -### Removed -- Remove `None` errors from Error enums. - -## v0.25.0 - 2021-10-18 -### Changed -- Make credentials optional (disabled by default) -- Dependencies updated - -## v0.24.0 - 2021-10-11 -### Changed -- Dependencies updated - -## v0.23.0 - 2021-10-04 -### Changed -- Dependencies updated - -## v0.22.0 - 2021-09-27 -### Changed -- Dependencies updated -- Ockam compiles under no_std + alloc. - -## v0.21.0 - 2021-09-20 -### Changed -- Dependencies updated - -## v0.20.0 - 2021-09-14 -### Changed -- Fixed incorrect link in README - -## v0.19.0 - 2021-09-13 -### Changed -- Change `size_t` to `unint3_t` in C header file. -- Revert signature `output_buffer` type to a pointer. -- Dependencies updated. - -## v0.18.0 - 2021-09-03 -### Changed -- Dependencies updated. - -## v0.17.0 - 2021-08-30 -### Changed -- Dependencies updated. - -## v0.16.0 - 2021-08-23 -### Changed -- Replace std:: modules with core:: and alternate implementations -- Dependencies updated. - -## v0.15.0 - 2021-08-16 -### Added -- Implement BLS signature using BBS+. -### Changed -- Dependencies updated. - -## v0.14.0 - 2021-08-09 -### Changed -- Dependencies updated. - -## v0.13.0 - 2021-08-03 -### Changed -- Dependencies updated. - -## v0.12.0 - 2021-07-29 -### Changed -- Dependencies updated. - -## v0.11.0 - 2021-07-26 -### Added -### Changed -- Include stddef.h in ockam_ffi/include/vault.h -- Dependencies updated. - -## v0.10.0 - 2021-07-19 -### Changed -- Dependencies updated. - -## v0.9.0 - 2021-07-12 -### Changed -- Dependencies updated. - -## v0.8.0 - 2021-07-06 -### Added -- Type for `BLS` secrets. -### Changed -- Dependencies updated. - -## v0.7.0 - 2021-06-30 -### Changed -- Dependencies updated. - -## v0.6.0 - 2021-06-21 -### Changed -- Dependencies updated. - -## v0.5.0 - 2021-06-14 -### Changed -- Dependencies updated. - -## v0.4.0 - 2021-05-30 -### Added -### Changed -- Dependency updates. - -## v0.3.0 - 2021-05-17 -### Added -### Changed -- Dependencies updated. -### Deleted - -## v0.2.0 - 2021-05-10 -### Added -### Changed -- Dependencies updated. -### Deleted - -# v0.1.7 - 2021-05-03 -### Changed -- Fix crate metadata. - -# v0.1.7 - 2021-05-03 -### Changed -- Fix crate metadata. - -# v0.1.6 - 2021-05-03 -### Changed -- Dependencies updated. - -# v0.1.5 - 2021-04-26 -### Changed -- Dependencies updated. - -# v0.1.4 - 2021-04-19 -### Changed -- Dependencies updated. - -# v0.1.3 - 2021-04-14 -### Changed -- Dependencies updated. - -# v0.1.2 - 2021-04-13 -### Changed -- Dependencies updated. - -# v0.1.1 - 2021-04-12 -### Changed -- Dependencies updated. - -# v0.1.0 - 2021-04-05 -### Changed -- Initial release. diff --git a/implementations/rust/ockam/ockam_ffi/Cargo.toml b/implementations/rust/ockam/ockam_ffi/Cargo.toml deleted file mode 100644 index da41a3db616..00000000000 --- a/implementations/rust/ockam/ockam_ffi/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "ockam-ffi" -version = "0.78.0" -authors = ["Ockam Developers"] -categories = [ - "cryptography", - "asynchronous", - "authentication", - "external-ffi-bindings", - "api-bindings", -] -edition = "2021" -homepage = "https://github.com/build-trust/ockam" -keywords = ["ockam", "crypto", "ffi", "cryptography", "bindings"] -license = "Apache-2.0" -publish = true -readme = "README.md" -repository = "https://github.com/build-trust/ockam/tree/develop/implementations/rust/ockam/ockam_ffi" -rust-version = "1.56.0" -description = """FFI layer for ockam_vault. -""" - -[lib] -crate-type = ["staticlib"] -path = "src/lib.rs" - -[features] -default = [] - -[dependencies] -futures = { version = "0.3.28" } -lazy_static = "1.4" -ockam_core = { path = "../ockam_core", version = "^0.85.0" } -ockam_vault = { path = "../ockam_vault", version = "^0.81.0" } -tokio = { version = "1.31", features = ["full"] } diff --git a/implementations/rust/ockam/ockam_ffi/README.md b/implementations/rust/ockam/ockam_ffi/README.md deleted file mode 100644 index 38593c25887..00000000000 --- a/implementations/rust/ockam/ockam_ffi/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# ockam-ffi - -[![crate][crate-image]][crate-link] -[![docs][docs-image]][docs-link] -[![license][license-image]][license-link] -[![discuss][discuss-image]][discuss-link] - -Ockam is a library for building devices that communicate securely, privately -and trustfully with cloud services and other devices. - -In order to support a variety of cryptographically capable hardware we maintain loose coupling between our protocols and how a specific building block is invoked in a specific hardware. This is achieved using an abstract Vault trait. - -A concrete implementation of the Vault trait is called an Ockam Vault. Over time, and with help from the Ockam open source community, we plan to add vaults for several TEEs, TPMs, HSMs, and Secure Enclaves. - -This crate provides the Vault FFI bindings following the "C" calling convention, and generates static and dynamic C linkable libraries. - -## Usage - -Add this to your `Cargo.toml`: - -``` -[dependencies] -ockam-ffi = "0.78.0" -``` - -## License - -This code is licensed under the terms of the [Apache License 2.0][license-link]. - -[main-ockam-crate-link]: https://crates.io/crates/ockam - -[crate-image]: https://img.shields.io/crates/v/ockam-ffi.svg -[crate-link]: https://crates.io/crates/ockam-ffi - -[docs-image]: https://docs.rs/ockam-ffi/badge.svg -[docs-link]: https://docs.rs/ockam-ffi - -[license-image]: https://img.shields.io/badge/License-Apache%202.0-green.svg -[license-link]: https://github.com/build-trust/ockam/blob/HEAD/LICENSE - -[discuss-image]: https://img.shields.io/badge/Discuss-Github%20Discussions-ff70b4.svg -[discuss-link]: https://github.com/build-trust/ockam/discussions