Notice - Design Document

Contents

Notice - Design Document#

This document is a design document for a future version of Password Vault.

For existing deployments, please see the current implementation’s page instead.

Overview#

This page describes the full implementation of Password Vault. The content may still change due to an active development.

Secret#

Secret is an information that should only be known by a limited number of people. IPA provides a mechanism to archive, retrieve, share, and recover secrets, and manage the access control. The secret itself will actually be stored securely in the KRA backend.

A secret is a binary data, so anything can be stored as a secret, but there will be a limit imposed by the server. A secret ID is a unique name to identify a secret within a vault (e.g. email, pin).

During archival, the client will generate a random session key and use it to encrypt the secret, then send both the encrypted secret and the session key wrapped with KRA’s transport public key to KRA. KRA will unwrap the session key with the transport private key, then use it to decrypt the secret. Then KRA will encrypt the secret with the storage key and store it in the LDAP backend.

During retrieval, the client will generate a random session key and send it to the server wrapped with KRA’s transport public key. KRA will decrypt the secret stored in the LDAP backend using the storage key, return the secret encrypted using the session key.

Vault#

Vault is a secure place to store data or a collection of secrets. A vault may be privately owned by a user, or shared among users, groups, or services. A user may have multiple vaults.

Vault name is an identifier for the vault that is unique within a vault container (e.g. PrivateVault is unique within /users/testuser). Vault ID is a globally unique identifier which consists of vault name and the container ID (e.g. /users/testuser/PrivateVault).

There are three types of vault which may offer additional level of security:

Standard Vault#

A standard vault will use the KRA’s standard transport and storage encryption as described above. Any authorized vault members, vault owners, or escrow officers can read and write the secrets without having to use a password/keys.

Symmetric Vault#

With symmetric vault the secrets are additionally protected with a symmetric key generated from a vault password. The client will encrypt secrets before sending them to the server and decrypt them after receiving them from the server. Any authorized vault members, vault owners, or escrow officers can read and write the secrets, but they will have to use a shared vault password.

Asymmetric Vault#

With asymmetric vault (drop box) the secrets are additionally protected with asymmetric keys. The client will archive the secrets using the public key, and retrieve using the private key. The public key is available to all vault members, but the private key is only available to vault owners. This way vault members can archive secrets, but only vault owners/escrow officers can retrieve them.

Container#

Container is a hierarchical collection of vaults and other containers. Container name is an identifier for the container that is unique within the parent container (e.g. testuser is unique within /users). Container ID is a globally unique identifier that consists of the container name and the parent container ID (e.g. /users/testuser).

There are several built-in vault containers:

  • root container: /

  • container for user’s private vaults: /users

  • container for group’s private vaults: /groups

  • container for shared vaults: /shared

  • container for service vaults: /services

The root container is owned by the admins group.

The /users container is owned by the admins group. When a user is added, a private container (/users/) will be created automatically for this user. The user will own the container and be able to create private vaults (/users//) in it. When the user is removed, the private container and its contents will be removed automatically.

The /shared container is owned by the admins group. The container has ipausers as a member so all users can access shared vaults. Only the admins can create shared vaults (/shared/).

The /services container is owned by the service admins group so any service administrator can create sub-containers for the servers (/services/) and the service vaults in it (/services//).

Access Control#

User’s role in determines the operations that it can execute:

  • A non-member cannot access the container or vault.

  • A container member can list sub-containers and vaults in the container.

  • A container owner can create and remove sub-containers and vaults in the container, and manage the members and owners of the container, but it cannot remove the container itself.

  • A vault member can list, archive, and retrieve the secrets in a standard vault. With symmetric vault the member will need a vault password in order to archive and retrieve secrets. With asymmetric vault the member can only archive the secrets but not retrieve them since it only has the public key and not the private key.

  • A vault owner can list, archive, and retrieve the secrets like vault members, but it has the private key so it can retrieve secrets from asymmetric vault. The owner can also manage the list of members and owners of the vault, and change the vault password/keys.

  • An escrow officer can recover secrets and reset the vault password.

  • An administrator can do everything except accessing the secrets in symmetric or asymmetric vaults and change/reset the password/keys.

Installation#

First install IPA server:

$ ipa-server-install
...

Then install KRA component:

$ ipa-kra-install
...

Authenticate as an IPA user:

$ kinit testuser
Password for testuser@EXAMPLE.COM: ********

The vault is ready to use.

Container Managerment#

Listing available containers#

Any user can list the sub-containers within a specified container using the following command:

$ ipa vaultcontainer-find [parent ID] [OPTIONS]

If the parent ID is not specified, it will return the user’s private containers:

$ ipa vaultcontainer-find
--------------------------
2 vault containers matched
--------------------------
  Container name: personal
  Container ID: /users/testuser/personal/
  Description: Personal vaults

  Container name: work
  Container ID: /users/testuser/work/
  Description: Work vaults
----------------------------
Number of entries returned 2
----------------------------

If the parent ID is specified, it will return the sub-containers within that container:

$ ipa vaultcontainer-find /services
--------------------------
2 vault containers matched
--------------------------
  Container name: server1.example.com
  Container ID: /services/server1.example.com/
  Description: Vaults of services on server1.example.com

  Container name: server2.example.com
  Container ID: /services/server2.example.com/
  Description: Vaults of services on server2.example.com
----------------------------
Number of entries returned 2
----------------------------

Top-level containers can be listed by searching from the root container:

$ ipa vaultcontainer-find /
--------------------------
3 vault containers matched
--------------------------
  Container name: users
  Container ID: /users/
  Description: Users vault container

  Container name: shared
  Container ID: /shared/
  Description: Shared vault container

  Container name: services
  Container ID: /services/
  Description: Services vault container
----------------------------
Number of entries returned 3
----------------------------

Displaying container info#

Any user can display the container info using the following command:

$ ipa vaultcontainer-show <container ID> [OPTIONS]

To display user’s private container’s info:

$ ipa vaultcontainer-show personal
  Container name: personal
  Container ID: /users/testuser/personal/
  Description: Personal vault container

To display a public container’s info:

$ ipa vaultcontainer-show /services/server.example.com
  Container name: server.example.com
  Container ID: /services/server.example.com/
  Description: Services vault container for server.example.com

Adding a container#

$ ipa vaultcontainer-add <container ID> [OPTIONS]

To add a private container:

$ ipa vaultcontainer-add personal
--------------------------------
Added vault container "personal"
--------------------------------
  Container name: personal
  Container ID: /users/testuser/personal/

To add a public container:

$ ipa vaultcontainer-add /services/server.example.com
------------------------------------------
Added vault container "server.example.com"
------------------------------------------
  Container name: server.example.com
  Container ID: /services/server.example.com/

Modifying a container#

The container owner can modify a container using the following command:

$ ipa vaultcontainer-mod <container ID> [OPTIONS]

For example, to change container description:

$ ipa vaultcontainer-show /services/server.example.com
  Container name: server.example.com
  Container ID: /services/server.example.com/

$ ipa vaultcontainer-mod /services/server.example.com --desc "Services vault container for server.example.com"
---------------------------------------------
Modified vault container "server.example.com"
---------------------------------------------
  Container name: server.example.com
  Container ID: /services/server.example.com/
  Description: Services vault container for server.example.com

Removing a container#

$ ipa vaultcontainer-del <container ID> [OPTIONS]

For example:

$ ipa vaultcontainer-del /services/server.example.com
--------------------------------------------
Deleted vault container "server.example.com"
--------------------------------------------

Adding container member#

A container owner can add a member to the container with the following command:

$ ipa vaultcontainer-add-member <container ID> --users <member ID> [OPTIONS]

For example:

$ ipa vaultcontainer-add-member /services/server.example.com --users testmember
  Container name: server.example.com
  Container ID: /services/server.example.com/
  Member users: testmember
-------------------------
Number of members added 1
-------------------------

Removing container member#

A container owner can remove a member from the container with the following command:

$ ipa vaultcontainer-remove-member <container ID> --users <member ID> [OPTIONS]

For example:

$ ipa vaultcontainer-remove-member /services/server.example.com --users testmember
  Container name: server.example.com
  Container ID: /services/server.example.com/
---------------------------
Number of members removed 1
---------------------------

Adding container owner#

A container owner can add another owner to the container with the following command:

$ ipa vaultcontainer-add-owner <container ID> --users <owner ID> [OPTIONS]

For example:

$ ipa vaultcontainer-add-owner /services/server.example.com --users testowner
  Container name: server.example.com
  Container ID: /services/server.example.com/
-------------------------
Number of members added 1
-------------------------

Removing container owner#

A container owner can remove another owner from the container with the following command:

$ ipa vaultcontainer-remove-owner <container ID> --users <owner ID> [OPTIONS]

For example:

$ ipa vaultcontainer-remove-owner /services/server.example.com --users testowner
  Container name: server.example.com
  Container ID: /services/server.example.com/
---------------------------
Number of members removed 1
---------------------------

Vault Management#

Listing available vaults#

A user can search the vaults that it owns or it’s a member of using the following command:

$ ipa vault-find [container ID] [OPTIONS]

By default the command will list the vaults in the user’s private container:

$ ipa vault-find
---------------
1 entries found
---------------
  Vault name: PrivateVault
  Vault ID: /users/testuser/PrivateVault
  Description: Private vault
  Type: standard
----------------------------
Number of entries returned 1
----------------------------

To find shared vaults, specify -shared:

$ ipa vault-find --shared
---------------
1 entries found
---------------
  Vault name: IPA
  Vault ID: /shared/projects/IPA
  Description: IPA project
  Type: standard
----------------------------
Number of entries returned 1
----------------------------

To find service vaults, specify –services:

$ ipa vault-find --services
---------------
1 entries found
---------------
  Vault name: HTTP
  Vault ID: /services/server.example.com/HTTP
  Description: HTTP service on server.example.com
  Type: standard
----------------------------
Number of entries returned 1
----------------------------

Displaying vault info#

A user can view a particular vault info using the following command:

$ ipa vault-show <vault ID> [OPTIONS]

To display the basic vault info:

$ ipa vault-show /shared/SymmetricVault
  Vault name: SymmetricVault
  Vault ID: /shared/SymmetricVault
  Description: Symmetric vault
  Type: standard

To display the complete vault info:

$ ipa vault-show /shared/SymmetricVault --all
  Vault name: SymmetricVault
  Vault ID: /shared/SymmetricVault
  Description: Symmetric vault
  Type: symmetric
  Secret salt: ....

Creating a new vault#

A container member can create a vault using the following command:

$ ipa vault-add <vault ID> [OPTIONS]

Private vaults can be created by specifying a relative vault ID:

$ ipa vault-add PrivateVault --desc "Private vault"
--------------------------
Added vault "PrivateVault"
--------------------------
  Vault name: PrivateVault
  Vault ID: /users/testuser/PrivateVault
  Description: Private vault
  Type: standard

Non-private vaults can be created by specifying an absolute vault ID:

$ ipa vault-add /shared/SharedVault --desc "Shared vault"
---------------------------------
Added vault "SharedVault"
---------------------------------
  Vault name: SharedVault
  Vault ID: /shared/SharedVault
  Description: Shared vault
  Type: standard

Symmetric vaults can be created by specifying the type and the password. The password can be provided interactively, specified in the command option, or specified in a file.

$ ipa vault-add SymmetricVault --desc "Symmetric vault" --type symmetric
New password: ********
Verify password: ********
----------------------------
Added vault "SymmetricVault"
----------------------------
  Vault name: SymmetricVault
  Vault ID: /users/testuser/SymmetricVault
  Description: Symmetric vault
  Type: symmetric

$ ipa vault-add SymmetricVault --desc "Symmetric vault" --type symmetric --password mypassword
----------------------------
Added vault "SymmetricVault"
----------------------------
  Vault name: SymmetricVault
  Vault ID: /users/testuser/SymmetricVault
  Description: Symmetric vault
  Type: symmetric

$ ipa vault-add SymmetricVault --desc "Symmetric vault" --type symmetric -password-file password.txt
----------------------------
Added vault "SymmetricVault"
----------------------------
  Vault name: SymmetricVault
  Vault ID: /users/testuser/SymmetricVault
  Description: Symmetric vault
  Type: symmetric

Asymmetric vaults can be created by specifying the type and the public key:

$ ipa vault-add AsymmetricVault --desc "Asymmetric vault" --type asymmetric --public-key-file public.pem
-----------------------------
Added vault "AsymmetricVault"
-----------------------------
  Vault name: AsymmetricVault
  Vault ID: /users/testuser/AsymmetricVault
  Description: Asymmetric vault
  Type: asymmetric

Archiving data#

A vault member/owner can archive data using the following command:

$ ipa vault-archive <vault ID> [--in <input file> | --text <text> | --data <base-64 encoded data> | --stdin] [OPTIONS]

With a standard vault the operation can be done directly.

$ ipa vault-archive StandardVault --in secret.txt
----------------------------------------
Archived data into vault "StandardVault"
----------------------------------------

$ ipa vault-archive StandardVault --text "secret"
----------------------------------------
Archived data into vault "StandardVault"
----------------------------------------

$ ipa vault-archive StandardVault --data c2VjcmV0Cg==
----------------------------------------
Archived data into vault "StandardVault"
----------------------------------------

$ echo secret | ipa vault-archive StandardVault --stdin
----------------------------------------
Archived data into vault "StandardVault"
----------------------------------------

With a symmetric vault the operation requires a password:

$ ipa vault-archive SymmetricVault --in secret.txt
Password: ********
-----------------------------------------
Archived data into vault "SymmetricVault"
-----------------------------------------

With an asymmetric vault the operation does not require anything since the vault public key is stored in one of vault attributes.

$ ipa vault-archive AsymmetricVault --in secret.txt
------------------------------------------
Archived data into vault "AsymmetricVault"
------------------------------------------

Retrieving data#

A vault member/owner can be retrieve data using the following command:

$ ipa vault-retrieve <vault ID> [--out <output file> | --stdout] [OPTIONS]

With a standard vault the operation can be done directly.

$ ipa vault-retrieve StandardVault --out secret.txt
-----------------------------------------
Retrieved data from vault "StandardVault"
-----------------------------------------

$ ipa vault-retrieve StandardVault --stdout
secret

With a symmetric vault the operation requires a password:

$ ipa vault-retrieve SymmetricVault --out secret.txt
Password: ********
------------------------------------------
Retrieved data from vault "SymmetricVault"
------------------------------------------

With an asymmetric vault the operation requires a private key:

$ ipa vault-retrieve AsymmetricVault --out secret.txt --private-key-file private.pem
-------------------------------------------
Retrieved data from vault "AsymmetricVault"
-------------------------------------------

Copying a vault#

A container member can copy a vault that it has access to using the following command:

$ ipa vault-add <vault ID> --source-vault-id <source vault ID> [OPTIONS]

Password or private of the source vault is not required to copy, but it is still required to access the secrets.

To copy a private vault into a new private vault:

$ ipa vault-add NewPrivateVault --source-vault-id PrivateVault
-----------------------------
Added vault "NewPrivateVault"
-----------------------------
  Vault name: NewPrivateVault
  Vault ID: /users/testuser/NewPrivateVault
  Type: standard

To copy a private vault into a new shared vault:

$ ipa vault-add /shared/NewSharedVault --source-vault-id PrivateVault
----------------------------
Added vault "NewSharedVault"
----------------------------
  Vault name: NewSharedVault
  Vault ID: /shared/NewSharedVault
  Type: standard

Modifying a vault#

The vault owner can modify a vault using the following command:

$ ipa vault-mod <vault ID> [OPTIONS]

For example, to change vault description:

$ ipa vault-show PrivateVault
  Vault name: PrivateVault
  Vault ID: /users/testuser/PrivateVault
  Description: Private vault
  Type: standard

$ ipa vault-mod PrivateVault --desc "This is a private vault"
-----------------------------
Modified vault "PrivateVault"
-----------------------------
  Vault name: PrivateVault
  Vault ID: /users/testuser/PrivateVault
  Description: This is a private vault
  Type: standard

To convert a symmetric vault into an asymmetric vault the old password and the new public key must be specified:

$ ipa vault-show PrivateVault
  Vault name: PrivateVault
  Vault ID: /users/testuser/PrivateVault
  Description: Private vault
  Type: symmetric

$ ipa vault-mod PrivateVault --type asymmetric --public-key-file public.pem
Password: ********
-----------------------------
Modified vault "PrivateVault"
-----------------------------
  Vault name: PrivateVault
  Vault ID: /users/testuser/PrivateVault
  Description: Private vault
  Type: asymmetric

To convert an asymmetric vault into a symmetric vault the old private key and the new password must be specified:

$ ipa vault-show PrivateVault
  Vault name: PrivateVault
  Vault ID: /users/testuser/PrivateVault
  Description: Private vault
  Type: asymmetric

$ ipa vault-mod PrivateVault --type symmetric --private-key-file private.pem
Password: ********
Verify password: ********
-----------------------------
Modified vault "PrivateVault"
-----------------------------
  Vault name: PrivateVault
  Vault ID: /users/testuser/PrivateVault
  Description: Private vault
  Type: symmetric

Removing a vault#

To remove a vault the owner can execute the following command:

$ ipa vault-del <vault ID> [OPTIONS]

For example:

$ ipa vault-del PrivateVault
----------------------------
Deleted vault "PrivateVault"
----------------------------

Changing vault password#

An owner can change the vault password or keys using the following command.

$ ipa vault-password <vault ID> [OPTIONS]

An owner can change the password of a symmetric vault by providing the old password and the new password:

$ ipa vault-password SymmetricVault
Password: ********
New password: ********
Verify new password: ********
---------------------------------------
Changed "SymmetricVault" vault password
---------------------------------------

An owner can change the keys of an asymmetric vault by providing the old private key and the new public key:

$ ipa vault-password AsymmetricVault --private-key-file private.pem --new-public-key-file new-public.pem
------------------------------------
Changed "AsymmetricVault" vault keys
------------------------------------

Adding vault member#

A vault owner can add members to the vault with the following command:

$ ipa vault-add-member <vault ID> [--users <list of user IDs>] [--groups <list of group IDs>]

For example:

$ ipa vault-add-member MyVault --users testmember
---------------------------------
Added members to "MyVault " vault
---------------------------------

Removing vault member#

A vault owner can remove a member from the vault with the following command:

$ ipa vault-remove-member <vault ID> [--users <list of user IDs>] [--groups <list of group IDs>]

For example:

$ ipa vault-remove-member MyVault --users testmember
-------------------------------------
Removed members from "MyVault " vault
-------------------------------------

Adding vault owner#

An owner can add another owner to the vault with the following command:

$ ipa vault-add-owner <vault ID> [--users <list of user IDs>] [--groups <list of group IDs>]

For example:

$ ipa vault-add-owner MyVault --users testowner
----------------------------------
Added owners from "MyVault " vault
----------------------------------

Removing vault owner#

An owner can remove another owner from the vault with the following command:

$ ipa vault-remove-owner <vault ID> [--users <list of user IDs>] [--groups <list of group IDs>]

For example:

$ ipa vault-remove-owner MyVault --users testowner
------------------------------------
Removed owners from "MyVault " vault
------------------------------------

Secret Management#

Listing secrets in a vault#

A vault member/owner can list the secrets in a vault using the following command:

$ ipa vaultsecret-find <vault ID> [OPTIONS]

With a standard vault the secrets can be listed directly:

$ ipa vaultsecret-find StandardVault
---------------
2 entries found
---------------
  Secret ID: gmail
  Description: Gmail password

  Secret ID: yahoo
  Description: Yahoo! Mail password
----------------------------
Number of entries returned 2
----------------------------

With a symmetric vault the operation requires a password:

$ ipa vaultsecret-find SymmetricVault
Password: ********
---------------
2 entries found
---------------
  Secret ID: gmail
  Description: Gmail password

  Secret ID: yahoo
  Description: Yahoo! Mail password
----------------------------
Number of entries returned 2
----------------------------

A vault owner can list the secrets in an asymmetric vault by providing the vault private key:

$ ipa vaultsecret-find AsymmetricVault --private-key-file private.pem
---------------
2 entries found
---------------
  Secret ID: gmail
  Description: Gmail password

  Secret ID: yahoo
  Description: Yahoo! Mail password
----------------------------
Number of entries returned 2
----------------------------

Adding a secret#

A vault member/owner can add a secret using the following command:

$ ipa vaultsecret-add <vault ID> <secret ID> [OPTIONS]

With a standard vault the operation can be done directly. The secret can be provided interactively, via an input file, or via standard input.

$ ipa vaultsecret-add StandardVault MySecret
Secret: ********
Verify secret: ********
-----------------------------
Added vault secret "MySecret"
-----------------------------
  Secret name: MySecret
  Data: c2VjcmV0

$ ipa vaultsecret-add StandardVault MySecret --in secret.txt
-----------------------------
Added vault secret "MySecret"
-----------------------------
  Secret name: MySecret
  Data: c2VjcmV0

$ echo secret | ipa vaultsecret-add StandardVault MySecret --stdin
-----------------------------
Added vault secret "MySecret"
-----------------------------
  Secret name: MySecret
  Data: c2VjcmV0

With a symmetric vault the operation requires a password:

$ ipa vaultsecret-add SymmetricVault MySecret --in secret.txt
Password: ********
-----------------------------
Added vault secret "MySecret"
-----------------------------
  Secret name: MySecret
  Data: c2VjcmV0

With an asymmetric vault the operation requires a private key.

$ ipa vaultsecret-add AsymmetricVault MySecret --in secret.txt --private-key private-key.pem
-----------------------------
Added vault secret "MySecret"
-----------------------------
  Secret name: MySecret
  Data: c2VjcmV0

Retrieving a secret#

A vault member/owner can be retrieve a secret using the following command:

$ ipa vaultsecret-show <vault ID> <secret ID> [OPTIONS]

With a standard vault the operation can be done directly. The secret can be stored in an output file or directed to standard output.

$ ipa vaultsecret-show StandardVault MySecret
  Secret name: MySecret
  Data: c2VjcmV0

$ ipa vaultsecret-show StandardVault MySecret --out secret.txt

$ ipa vaultsecret-show StandardVault MySecret --stdout
secret

With a symmetric vault the operation requires a password:

$ ipa vaultsecret-show SymmetricVault MySecret --out secret.txt
Password: ********

With an asymmetric vault the operation requires a private key:

$ ipa vaultsecret-show AsymmetricVault MySecret --out secret.txt --private-key-file private.pem

Copying a secret#

Secret can be copied using the following command:

$ ipa vaultsecret-add <vault ID> <secret ID> [--source-vault <source vault ID>] [--source-secret <source secret ID>] [OPTIONS]

The copy operation will be done using the retrieval and archival operations, so depending on the vault types, it may require the password/key of all vaults involved.

To copy a secret into another secret in the same vault:

$ ipa vaultsecret-add StandardVault NewSecret --source-secret MySecret
------------------------------
Added vault secret "NewSecret"
------------------------------

To copy a secret from a vault into another vault:

$ ipa vaultsecret-add /shared/SharedVault MySecret --source-vault PrivateVault
-----------------------------
Added vault secret "MySecret"
-----------------------------

To copy a secret into another vault with a different name:

$ ipa vaultsecret-add /shared/SharedVault NewSecret --source-vault PrivateVault --source-secret MySecret
------------------------------
Added vault secret "NewSecret"
------------------------------

To copy a secret from a symmetric vault into an asymmetric vault (this will replace all secrets in the asymmetric vault):

$ ipa vaultsecret-add AsymmetricVault MySecret --source-vault SymmetricVault
Source Password: ********
-----------------------------
Added vault secret "MySecret"
-----------------------------

To copy a secret from an asymmetric vault into a symmetric vault:

$ ipa vaultsecret-add SymmetricVault MySecret --source-vault AsymmetricVault --source-private-key private-key.pem
Password: ********
-----------------------------
Added vault secret "MySecret"
-----------------------------

Modifying secret attributes#

Secret attributes can be modified using the following command:

$ ipa vaultsecret-mod <vault ID> <secret ID> [OPTIONS]

For example, to modify secret description:

$ ipa vaultsecret-mod StandardVault MySecret --desc "My secret"
--------------------------------
Modified vault secret "MySecret"
--------------------------------
  Secret name: MySecret
  Description: My secret
  Data: c2VjcmV0

Deleting a secret#

A secret can be removed using the following command:

$ ipa vaultsecret-del <vault ID> <secret ID> [OPTIONS]

With a standard vault the operation can be done directly:

$ ipa vaultsecret-del StandardVault MySecret
-------------------------------
Deleted vault secret "MySecret"
-------------------------------

With a symmetric vault the operation requires a vault password:

$ ipa vaultsecret-del SymmetricVault secret
Password: ********
-------------------------------
Deleted vault secret "MySecret"
-------------------------------

With an asymmetric vault the operation requires a vault private key:

$ ipa vaultsecret-del AsymmetricVault secret --private-key private-key.pem
-------------------------------
Deleted vault secret "MySecret"
-------------------------------

Escrow Operations#

Vault encryption key can be escrowed such that if needed the escrow officer can recover the secrets.

Creating a vault with escrow#

An escrowed symmetric vault can be created with the following command:

$ ipa vault-add EscrowedSymmetricVault --type symmetric --escrow-public-key-file escrow-public.pem
New password: ********
Verify password: ********
------------------------------------
Added vault "EscrowedSymmetricVault"
------------------------------------
  Vault name: EscrowedSymmetricVault
  Vault ID: /users/testuser/EscrowedSymmetricVault
  Vault type: symmetric

An escrowed asymmetric vault can be created with the following command:

$ ipa vault-add EscrowedAsymmetricVault --type asymmetric --public-key-file public.pem --escrow-public-key-file escrow-public.pem
-------------------------------------
Added vault "EscrowedAsymmetricVault"
-------------------------------------
  Vault name: EscrowedAsymmetricVault
  Vault ID: /users/testuser/EscrowedAsymmetricVault
  Vault type: asymmetric

Escrowing an existing vault#

A vault owner can escrow an existing symmetric vault by providing the escrow public key:

$ ipa vault-mod SymmetricVault --escrow true --escrow-public-key-file escrow-public.pem
Password: ********
-------------------------------
Modified vault "SymmetricVault"
-------------------------------

A vault owner can escrow an existing asymmetric vault by providing the vault private key and the escrow public key

$ ipa vault-mod AsymmetricVault --escrow true --private-key-file private.pem --escrow-public-key-file escrow-public.pem
--------------------------------
Modified vault "AsymmetricVault"
--------------------------------

A vault owner can unescrow a vault box as follows:

$ ipa vault-mod Vault --escrow-public-key NONE
----------------------
Modified vault "Vault"
----------------------

Recovering an escrowed secret#

An escrow officer can recover the secret by specifying the escrow private key to decrypt the secret key:

$ ipa vault-retrieve EscrowedVault --escrow-private-key-file escrow-private.pem --out secret.txt
-----------------------------------------
Retrieved data from vault "EscrowedVault"
-----------------------------------------

Changing escrowed vault password#

If the current symmetric vault password is known, the owner can change it by providing the old password and the new password. The new secret key will automatically be escrowed.

$ ipa vault-password EscrowedSymmetricVault
Password: *********
New password: *********
Verify password: ********
-------------------------
Password change completed
-------------------------

If the current password is unknown, the owner can request password reset:

$ ipa vault-password EscrowedSymmetricVault --reset
New password: *********
Verify password: ********
-----------------------
Password change pending
-----------------------

The escrow officer can approve the request as follows:

$ ipa vault-password /users/testuser/EscrowedSymmetricVault --approve --escrow-private-key-file escrow-private.pem
-------------------------
Password change completed
-------------------------

If necessary, the escrow officer can reject the request as follows:

$ ipa vault-password /users/testuser/EscrowedSymmetricVault --reject
------------------------
Password change canceled
------------------------

Service Operations#

Creating service vault password#

A service administrator can create a service vault password by archiving a new secret into a private vault:

$ ipa vault-add ldap_password --in password.txt
---------------------------
Added vault "ldap_password"
---------------------------
  Vault name: ldap_password
  Vault ID: /users/admin/ldap_password
  Type: standard

Provisioning service vault password for service instance#

A service administrator can provision the service vault password to a specific service instance using a service vault. To create a service vault:

$ ipa vaultcontainer-add /services/<server name>
$ ipa vault-add /services/<server name>/<service name> --type asymmetric --public-key <service public key>

To copy the service vault password into the service vault:

$ ipa vault-archive /services/<server name>/<service name> --source-vault-id <vault ID>

The command will retrieve the service vault password already archived earlier, then encrypt it with the service instance’s public key. The public key will be obtained from the service certificate that’s already generated previously on the server.

For example:

$ ipa vaultcontainer-add /services/server.example.com
------------------------------------------
Added vault container "server.example.com"
------------------------------------------
  Container name: server.example.com
  Container ID: /services/server.example.com/

$ ipa vault-add /services/server.example.com/LDAP --type asymmetric --public-key-file service-public.pem
------------------
Added vault "LDAP"
------------------
  Vault name: LDAP
  Vault ID: /services/server.example.com/LDAP
  Type: asymmetric

$ ipa vault-archive /services/server.example.com/LDAP --source-vault-id ldap_password
-------------------------------
Archived data into vault "LDAP"
-------------------------------
  Vault name: LDAP
  Vault ID: /services/server.example.com/LDAP
  Type: asymmetric

Retrieving service vault password for service instance#

A service instance can retrieve the service vault password using the service private key stored locally:

$ ipa vault-retrieve /services/server.example.com/LDAP --private-key-file service-private.pem --out password.txt
--------------------------------
Retrieved data from vault "LDAP"
--------------------------------
  Vault name: LDAP
  Vault ID: /services/server.example.com/LDAP
  Type: asymmetric

Changing service vault password#

The service administrator can change the service vault password by archiving a new secret:

$ ipa vault-archive ldap_password --in new_password.txt
----------------------------------------
Archived data into vault "ldap_password"
----------------------------------------
  Vault name: ldap_password
  Vault ID: /users/admin/ldap_password
  Type: standard

The service administrator will need to re-provision the new service vault password to each service instance using the following command:

$ ipa vault-archive /services/server.example.com/LDAP --source-vault-id ldap_password
-------------------------------
Archived data into vault "LDAP"
-------------------------------
  Vault name: LDAP
  Vault ID: /services/server.example.com/LDAP
  Type: asymmetric

This way if there’s a compromised instance the service administrator can isolate it by changing the service vault password and re-provisioning it to non-compromised instances only.

Configuration Management#

Displaying global vault configuration#

A user can view the global vault configuration using the following command:

$ ipa vault-config-show
  Maximum secret size: 1024 bytes
  Maximum vault size: 10 secrets

Modifying global vault configuration#

An administrator can modify the global vault configuration using the following command:

$ ipa vault-config-mod [OPTIONS]

Note that configuration changes will only affect operations executed after the change.

For example, to change the maximum secret size:

$ ipa vault-config-mod --max-secret-size 1024

Crypto Library#

A crypto libray is needed to generate encryption keys and salts, and encrypt/decrypt the secrets in vault. On the server side IPA and Dogtag use the NSS library. On the client side the IPA client will use Python NSS for transport encryption and Python Cryptography for additional client-side encryption.

The Python NSS is a Python interface for NSS. IPA already has a dependency on Python NSS on both client and server.

The Python Cryptography provides a generic interface for various crypto libraries such as OpenSSL and CommonCrypto as backends. Currently it does not support NSS backend, but it may be added in the future.

Currently Python Cryptography is only available in Fedora via pip. Ticket #1114267 will add the package to Fedora.

The X.509 certificate support is supposed to be added in version 0.6.

Generating salt#

Python NSS:

salt = nss.generate_random(salt_length)

Python Cryptography:

salt = os.urandom(salt_length)

Generating password-based symmetric encryption key#

Symmetric encryption key can be generated using a key derivation algorithm such as PBKDF2 which takes a vault password and a randomly generated salt. In order to generate the same key from the same password, the same salt must be used again.

In NSS the PBKDF2 can be invoked using the following C code. However, currently Python NSS does not provide an interface to call these NSS functions.

SECItem password = {
    siBuffer,
    <password>,
    <password length>
};

SECItem salt = {
    siBuffer,
    <salt>,
    <salt length>
};

void *nullptr = NULL;

SECAlgorithmID *algID = PK11_CreatePBEV2AlgorithmID(
    SEC_OID_PKCS5_PBKDF2,
    <hash function>,
    <pseudo random function>,
    <key length>,
    <iteration>,
    &salt);

PK11SlotInfo *slot = PK11_GetBestSlot(SEC_OID_PKCS5_PBKDF2, nullptr);

PK11SymKey *symKey = PK11_PBEKeyGen(
    slot,
    algID,
    &password,
    PR_FALSE,
    nullptr);

SECOID_DestroyAlgorithmID(algID, PR_TRUE);

Python Cryptography (docs):

kdf = PBKDF2HMAC(
    algorithm=hashes.SHA256(),
    length=256,
    salt=salt,
    iterations=100000,
    backend=default_backend()
)

symmetric_key = kdf.derive(vault_password)

If FIPS certification is not required, the scrypt might be a better option.

symmetric_key = pyscrypt.hash(
    vault_password,
    salt=salt,
    N=1024,
    r=1,
    p=1,
    dkLen=256
)

Web Crypto (spec, implementation, example):

var enc_salt = ...

var deriveBitsOp = window.crypto.subtle.deriveBits(
    {
        name : "PBKDF2",
        salt: salt,
        iterations: 100000,
        hash: { name: "SHA-256" }
    },
    vault_password,
    256
);

deriveBitsOp.oncomplete = function(event) {
    symmetric_key = event.target.result;
};

Generating asymmetric key pair#

The asymmetric key pair can be generated using OpenSSL:

$ openssl genrsa -out private.pem 2048
$ openssl rsa -in private.pem -out public.pem -pubout

Then the above PEM files can be loaded into Python Cryptography:

public_key = load_pem_public_key(
    data=public_pem,
    backend=default_backend()
)

private_key = load_pem_private_key(
    data=private_pem,
    password=None,
    backend=default_backend()
)

PyCrypto:

private_key = RSA.generate(2048)
private_pem = private_key.exportKey('PEM')
public_pem = private_key.publickey().exportKey('PEM')

Encryption with symmetric algorithm#

In a symmetric vault the secrets will be encrypted/decrypted using symmetric-key algorithm.

Python NSS:

iv_si = nss.SecItem(salt)
iv_param = nss.param_from_iv(mechanism, iv_si)

encryptor = nss.create_context_by_sym_key(
    mechanism,
    nss.CKA_ENCRYPT,
    symmetric_key,
    iv_param)

encrypted_data = encryptor.cipher_op(data) + encryptor.digest_final()

decryptor = nss.create_context_by_sym_key(
    mechanism,
    nss.CKA_DECRYPT,
    symmetric_key,
    iv_param)

data = decryptor.cipher_op(encrypted_data) + decryptor.digest_final()

Python Cryptography (docs):

f = Fernet(symmetric_key)
encrypted_data = f.encrypt(data)
data = f.decrypt(encrypted_data)

cipher = Cipher(algorithms.AES(symmetric_key), modes.CBC(iv), backend=default_backend())
encryptor = cipher.encryptor()
encrypted_data = encryptor.update(data) + encryptor.finalize()

decryptor = cipher.decryptor()
data = decryptor.update(encrypted_data) + decryptor.finalize()

Web Crypto (spec):

var encryptOp = window.crypto.subtle.encrypt(
    {
        name : "AES-CBC",
        iv: ...
    },
    symmetric_key,
    data
);

encryptOp.oncomplete = function(event) {
    encrypted_data = event.target.result;
};

var decryptOp = window.crypto.subtle.decrypt(
    {
        name : "AES-CBC",
        iv: ...
    },
    symmetric_key,
    encrypted_data
);

decryptOp.oncomplete = function(event) {
    data = event.target.result;
};

Encryption with asymmetric algorithm#

In an asymmetric vault the secrets will be encrypted/decrypted using asymmetric algorithm:

Python NSS:

encryrpted_data = nss.pub_wrap_sym_key(mechanism, public_key, data)

data = ... encrypted_data  ...?

Python Cryptography (docs):

encrypted_data = public_key.encrypt(
    data,
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA1()),
        algorithm=hashes.SHA1(),
        label=None
    )
)

data = private_key.decrypt(
    encrypted_data,
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA1()),
        algorithm=hashes.SHA1(),
        label=None
    )
)

Web Crypto (spec):

var encryptOp = window.crypto.subtle.encrypt(
    {
        name : "RSA-OAEP",
    },
    public_key,
    data
);

encryptOp.oncomplete = function(event) {
    encrypted_data = event.target.result;
};

var decryptOp = window.crypto.subtle.decrypt(
    {
        name : "RSA-OAEP",
    },
    private_key,
    encrypted_data
);

decryptOp.oncomplete = function(event) {
    data = event.target.result;
};

Client API#

The Client API provides the client interface to access the vault services. The client API will be primarily used to implement the CLI, but it can also be used by custom client application.

IPAConnection class#

The IPAConnection class represents a connection to the IPA server. It will be used internally by the client API to wrap the Web Services.

Vault class#

The Vault class represents a vault. It contains vault attributes accessible to the client.

class Vault:
    d

        # basic attributes
        self.id = id
        self.description = None
        self.type = "standard"

        # access control attributes
        self.owners = []
        self.members = []

        # secret attributes
        self.secrets = {}
        self.secret_salt = None
        self.public_key = None

        # escrow attributes
        self.escrow = False
        self.escrow_public_key = None
        self.escrowed_secret_key = None
        self.escrowed_private_key = None

        # password reset attributes
        self.new_secret_salt = None
        self.new_public_key = None
        self.new_escrowed_secret_key = None
        self.new_escrowed_private_key = None

Secret class#

The Secret class represents a secret. It contains secret attributes accessible to the client.

class Secret:
    def __init__(self, id,
            description=None,
            data=None):
        self.id = id
        self.description = description
        self.data = data

VaultClient class#

VaultClient class provides a client interface to access vaults. It uses an IPAConnection object to communicate with the server.

class VaultClient:
    d
        self.connection = connection

For example:

connection = ... connection to IPA server ...
vault_client = VaultClient(connection)

vaultcontianer_find()#

This method returns a list of subcontainers within the provided container. By default each list element will contain the basic attributes of the subcontainer, but additional attributes can be requested as well.

def vaultcontianer_find(self, parent_id=None, attributes=None):
    return self.connection.vaultcontianer_find(parent_id, attributes)

vaultcontainer_get()#

This method returns the attributes of the container specified by the ID. By default it will return the basic attributes, but additional attributes can be requested as well.

def vaultcontainer_get(self, container_id,
        attributes=None):

    return self.connection.vaultcontainer_get(container_id, attributes)

vaultcontainer_add()#

This method creates a new container.

def vaultcontainer_add(self, container_id,
        description=None)

    self.connection.vaultcontainer_add(container_id, description)

vaultcontainer_del()#

This method removes an existing vault.

def vaultcontainer_del(self, container_id):
    self.connection.vaultcontainer_del(container_id)

vault_find()#

This method returns a list of available vaults within the provided container. By default each list element will contain the basic attributes of the vault, but additional attributes can be requested as well.

def vault_find(self, container_id=None, attributes=None):
    return self.connection.vault_find(container_id, attributes)

For example:

vaults = vault_find()
for vault in vaults:
    print vault.id + ": " + vault.description

vault_get()#

This method returns the attributes of the vault specified by the ID. By default it will return the basic attributes, but additional attributes can be requested as well.

def vault_get(self, vault_id,
        attributes=None):

    return self.connection.vault_get(vault_id, attributes)

For example:

vault = vault_get("PrivateVault", attributes=["description", "members"])
print "ID: " + vault.id
print "Description: " + vault.description

print "Members:"
members = vault.members
for member in members:
    print " - " + member

vault_add()#

This method creates a new vault on the server.

def vault_add(self, vault_id,
        description=None,
        data=None,
        type="standard",
        vault_password=None,
        vault_public_key=None,
        vault_private_key=None,
        escrow=False,
        escrow_public_key=None):

    vault = Vault(vault_id)
    vault.description = description
    vault.type = type
    vault.public_key = vault_public_key
    vault.escrow = escrow

    if vault.type == "standard":
        pass

    elif vault.type == "symmetric":

        # generate secret key and salt
        vault.secret_salt = generate_salt()
        vault_secret_key = generate_key(vault_password, vault.secret_salt)

        # encrypt data with vault secret key
        vault.secrets = encrypt(data, vault_secret_key)

    elif vault.type == "asymmetric":
        # encrypt data with vault public key
        vault.secrets = encrypt(data, vault.public_key)

    if vault.escrow:

        vault.escrow_public_key = escrow_public_key

        if vault.type == "symmetric":
            # encrypt vault secret key with escrow public key
            vault.escrowed_secret_key = encrypt(vault_secret_key, vault.escrow_public_key)

        elif vault.type == "asymmetric":
            # encrypt vault private key with escrow public key
            vault.escrowed_private_key = encrypt(vault_private_key, vault.escrow_public_key)

    self.connection.vault_add(vault)

    return vault

A standard vault can be created without specifying a password/key:

vault = vault_add("StandardVault",
    description="Standard vault")

A symmetric vault can be created by specifying the type and the vault password:

vault = vault_add("SymmetricVault",
    description="Symmetric vault",
    type="symmetric",
    vault_password=...)

An escrowed symmetric vault can be created by specifying the type, the vault password, and the escrow public key:

vault = vault_add("EscrowedSymmetricVault",
    description="Escrowed vault",
    type="symmetric",
    vault_password=...,
    escrow=True,
    escrow_public_key=...)

An asymmetric vault can be created by specifying the type and the vault public key:

vault = vault_add("AsymmetricVault",
    description="Asymmetric vault",
    type="asymmetric",
    vault_public_key=...)

An escrowed asymmetric vault can be created by specifying the type, the vault public and private keys, and the escrow public key:

vault = vault_add("EscrowedAsymmetricVault",
    description="Escrowed asymmetric vault",
    type="asymmetric",
    vault_public_key=...,
    vault_private_key=...,
    escrow=True,
    escrow_public_key=...)

vault_update()#

This method stores changes to the vault object to the server.

def vault_update(self, vault):
    self.connection.vault_update(vault)

vault_change_type()#

This method modifies the type of an existing vault.

def vault_change_type(self,
        vault_id=None,
        vault=None,
        type=None,
        vault_password=None,
        vault_public_key=None,
        vault_private_key=None):

    if not vault:
        vault = self.connection.get_vault(vault_id, attributes=[...])

    # retrieve existing data based on the old type
    data = vault_retrieve(
        vault=vault,
        vault_password=vault_password,
        vault_private_key=vault_private_key)

    # change the type
    vault.type = type

    # re-archive the data based on the new type
    vault_archive(
        vault=vault,
        data=data,
        vault_password=vault_password,
        vault_public_key=vault_public_key,
        vault_private_key=vault_private_key)

    if vault_id:
        vault_update(vault)

To convert a standard vault into a symmetric vault:

vault_change_type("Vault",
    type="symmetric",
    vault_password=new_vault_password)

To convert a symmetric vault into an asymmetric vault:

vault_change_type("Vault",
    type="asymmetric",
    vault_password=vault_password,
    vault_public_key=new_vault_public_key,
    vault_private_key=new_vault_private_key)

To convert an asymmetric vault into a standard vault:

vault_change_type("Vault",
    type="standard",
    vault_private_key=vault_private_key)

vault_change_escrow()#

This method modifies the escrow info of an existing vault.

def vault_change_escrow(self,
        vault_id=None,
        vault=None,
        vault_password=None,
        vault_private_key=None,
        escrow=False,
        escrow_public_key=None):

    if not vault:
        vault = vault_get(vault_id, attributes=[...])

    vault.escrow = escrow
    vault.escrow_public_key = escrow_public_key

    if vault.escrow:

        if vault.type == "standard":
            pass

        elif vault.type == "symmetric":

            # generate secret key
            vault_secret_key = generate_key(vault_password, vault.secret_salt)

            # encrypt vault secret key with escrow public key
            vault.escrowed_secret_key = encrypt(vault_secret_key, vault.escrow_public_key)

        elif vault.type == "asymmetric":
            # encrypt vault private key with escrow public key
            vault.escrowed_private_key = encrypt(vault_private_key, vault.escrow_public_key)

    else:
        vault.escrowed_secret_key = None
        vault.escrowed_private_key = None

    if vault_id:
        vault_update(vault)

To escrow a standard vault:

vault_change_escrow("StandardVault",
    escrow=True)

To escrow a symmetric vault:

vault_change_escrow("SymmetricVault",
    escrow=True,
    vault_password=...)

To escrow an asymmetric vault:

vault_change_escrow("AsymmetricVault",
    escrow=True,
    vault_public_key=...,
    vault_private_key=...)

To unescrow a vault:

vault_change_escrow("Vault", escrow=False)

vault_del()#

This method removes an existing vault on the server.

def vault_del(self, vault_id):
    self.connection.vault_del(vault_id)

vault_archive()#

This method archives a blob of data into a vault replacing existing data.

def vault_archive(self,
        vault_id=None,
        vault=None,
        data=None,
        vault_password=None,
        vault_secret_key=None,
        escrow_private_key=None):

    if not vault:
        vault = self.connection.get_vault(vault_id, attributes=[...])

    if vault.type == "standard":
        pass

    elif vault.type == "symmetric":

        if not vault_secret_key:

            if vault_password:
                # generate vault secret key from vault password and secret salt
                vault_secret_key = generate_key(vault_password, vault.secret_salt)

            elif escrow_private_key:
                # decrypt vault secret key with escrow private key
                vault_secret_key = decrypt(vault.escrowed_secret_key, escrow_private_key)

        # encrypt secrets with vault secret key
        encrypted_data = encrypt(data, vault_secret_key)

    elif vault.type == "asymmetric":

        # encrypt secrets with vault public key
        encrypted_data = encrypt(data, vault.public_key)

    vault_send_data(vult_id, encrypted_data)

A member can archive data into a standard vault without any password or key:

vault_client.archive_secrets("StandardVault",
    data="mydata")

A member can archive data into a symmetric vault by providing a vault password:

vault_client.archive_secrets("SymmetricVault",
    data="mydata",
    vault_password=...)

A member can archive data into a symmetric vault by providing a pre-generated vault secret key:

vault_client.archive_secrets("SymmetricVault",
    data="mydata",
    vault_secret_key=...)

An escrow officer can archive data into a symmetric vault by providing an escrow private key:

vault_client.archive_secrets("SymmetricVault",
    data="mydata",
    escrow_private_key=...)

A member can archive data into an asymmetric vault without any password or key:

vault_client.archive_secrets("AsymmetricVault",
    data="mydata")

vault_retrieve()#

This method retrieves a blob of data stored in a vault and decrypt it based on the vault type.

def vault_retrieve(self,
        vault_id=None,
        vault=None,
        vault_password=None,
        vault_secret_key=None,
        vault_private_key=None,
        escrow_private_key=None):

    if not vault:
        vault = self.connection.get_vault(vault_id, attributes=[...])

    encrypted_data = vault_get_data(vault.id);

    if vault.type == "standard":
        data = encrypted_data

    elif vault.type == "symmetric":

        if not vault_secret_key:

            if vault_password:
                # generate vault secret key from vault password and secret salt
                vault_secret_key = generate_key(vault_password, vault.secret_salt)

            elif escrow_private_key:
                # decrypt vault secret key with escrow private key
                vault_secret_key = decrypt(vault.escrowed_secret_key, escrow_private_key)

        # decrypt secrets with vault secret key
        data = decrypt(encrypted_data, vault_secret_key)

    elif vault.type == "asymmetric":

        if escrow_private_key:
            # decrypt vault private key with escrow private key
            vault_private_key = decrypt(vault.escrowed_private_key, escrow_private_key)

        if vault_private_key:
            # decrypt secrets with vault private key
            data = decrypt(encrypted_data, vault_private_key)

        else:
            # return empty data
            data = ''

    return data

A member can retrieve data from a standard vault without any password or key:

data = vault_client.vault_retrieve("StandardVault")

A member can retrieve data from a symmetric vault by providing the vault password:

data = vault_client.vault_retrieve("SymmetricVault", vault_password=...)

A member can retrieve data from a symmetric vault by providing the vault secret key:

data = vault_client.vault_retrieve("SymmetricVault", vault_secret_key=...)

An escrow officer can recover data from an escrowed symmetric vault by providing the escrow private key:

data = vault_client.vault_retrieve("EscrowedSymmetricVault", escrow_private_key=...)

An owner can retrieve secrets from an asymmetric vault by providing the vault private key:

data = vault_client.vault_retrieve("AsymmetricVault", vault_private_key=...)

An escrow officer can recover secrets from an escrowed asymmetric vault by providing the escrow private key:

data = vault_client.vault_retrieve("EscrowedAsymmetricVault", escrow_private_key=...)

vaultsecret_archive()#

This method encrypt a secret based on the vault type and archive it into a collection of secrets in the vault.

def vaultsecret_archive(self,
        vault_id=None,
        vault=None,
        secret_id=None,
        description=None,
        data=None,
        vault_password=None,
        vault_secret_key=None,
        vault_private_key=None,
        escrow_private_key=None):

    # retrieve vault info
    if not vault:
        vault = vault_get(vault_id, attributes=[...])

    # retrieve existing secrets
    json_encoded_secrets = vault_retrieve(
        vault=vault,
        vault_password=vault_password,
        vault_secret_key=vault_secret_key,
        vault_private_key=vault_private_key)
        escrow_private_key=escrow_private_key)

    secrets = json.decode(json_encoded_secrets)

    # add the new secret
    secret = {
        id: secret_id,
        description: description
        data: data
    }
    secrets[secret_id] = secrets

    # re-archive secrets
    json_encoded_secrets = json.encode(secrets)

    vault_archive(
        vault=vault,
        data=json_encoded_secrets,
        vault_password=vault_password,
        vault_secret_key=vault_secret_key,
        escrow_private_key=escrow_private_key)

A member can archive a secret into a standard vault without any password or key:

vaultsecret_archive("StandardVault",
    secret_id="mysecret",
    description="My secret",
    data="secret data")

A member can archive a secret into a symmetric vault by providing a vault password:

vaultsecret_archive("SymmetricVault",
    secret_id="mysecret",
    description="My secret",
    data="secret data",
    vault_password=...)

A member can archive a secret into a symmetric vault by providing a pre-generated vault secret key:

vaultsecret_archive("SymmetricVault",
    secret_id="mysecret",
    description="My secret",
    data="secret data",
    vault_secret_key=...)

An escrow officer can archive a secret into a symmetric vault by providing an escrow private key:

vaultsecret_archive("SymmetricVault",
    secret_id="mysecret",
    description="My secret",
    data="secret data",
    escrow_private_key=...)

An owner can archive a secret into an asymmetric vault by providing a vault private key:

vaultsecret_archive("AsymmetricVault",
    secret_id="mysecret",
    description="My secret",
    data="secret data",
    vault_private_key=...)

vaultsecret_retrieve()#

This method retrieves a secret from a collection of secrets in a vault and decrypt it based on the vault type.

def vaultsecret_retrieve(self,
        vault_id=None,
        vault=None,
        secret_id=None,
        vault_password=None,
        vault_secret_key=None,
        vault_private_key=None,
        escrow_private_key=None):

    if not vault:
        vault = vault_get(vault_id, attributes=[...])

    # retrieve secrets
    json_encoded_secrets = vault_retrieve(
        vault=vault,
        vault_password=vault_password,
        vault_secret_key=vault_secret_key,
        vault_private_key=vault_private_key)
        escrow_private_key=escrow_private_key)

    secrets = json.decode(json_encoded_secrets)

    return secrets[secret_id]

A member can retrieve a secret from a standard vault without any password or key:

secret = vaultsecret_retrieve("StandardVault", secret_id="mysecret")

A member can retrieve a secret from a symmetric vault by providing the vault password:

secret = vaultsecret_retrieve("SymmetricVault", secret_id="mysecret", vault_password=...)

A member can retrieve a secret from a symmetric vault by providing the vault secret key:

secret = vaultsecret_retrieve("SymmetricVault", secret_id="mysecret", vault_secret_key=...)

An escrow officer can recover a secret from an escrowed symmetric vault by providing the escrow private key:

secret = vaultsecret_retrieve("EscrowedSymmetricVault", secret_id="mysecret", escrow_private_key=...)

An owner can retrieve a secret from an asymmetric vault by providing the vault private key:

secret = vault_retrieve("AsymmetricVault", secret_id="mysecret", vault_private_key=...)

An escrow officer can recover a secret from an escrowed asymmetric vault by providing the escrow private key:

secret = vault_retrieve("EscrowedAsymmetricVault", secret_id="mysecret", escrow_private_key=...)

vault_change_password()#

This method will change the vault password if the current password is known, or change the vault private key if the current vault private key is known.

def vault_change_password(self,
        vault_id=None,
        vault=None,
        vault_password=None,
        new_vault_password=None,
        vault_private_key=None,
        new_vault_public_key=None,
        new_vault_private_key=None):

    if not vault:
        vault = vault_get(vault_id, attributes=[...])

    # retrieve secrets with old vault password or private key
    data = vault_retrieve(
        vault=vault,
        vault_password=vault_password,
        vault_private_key=vault_private_key)

    if vault.type == "symmetric":

        # generate vault secret salt
        vault.secret_salt = generate_salt()

        # generate vault secret key from vault password and secret salt
        new_vault_secret_key = generate_key(new_vault_password, vault.secret_salt)

    elif vault.type == "asymmetric":

        # store vault public key
        vault.public_key = new_vault_public_key

    if vault.escrow:

        if vault.type == "symmetric":
            # encrypt vault secret key with escrow public key
            vault.escrowed_secret_key = encrypt(new_vault_secret_key, vault.escrow_public_key)

        elif vault.type == "asymmetric":
            # encrypt vault private key with escrow public key
            vault.escrowed_private_key = encrypt(new_vault_private_key, vault.escrow_public_key)

    # archive data with new vault password or public key
    vault_archive(
        vault=vault,
        data=data,
        vault_secret_key=new_vault_secret_key)

    vault_update(vault)

An owner can change the vault password of a symmetric vault as follows:

vault_client.change_vault_password("SymmetricVault",
    vault_password=...,
    new_vault_password=...)

An owner can change the keys of an asymmetric vault as follows:

vault_client.change_vault_password("AsymmetricVault",
    vault_private_key=...,
    new_vault_public_key=...,
    new_vault_private_key=...)

vault_reset_password()#

This method will request a vault password reset in case the current password or private key is lost. Password reset will only work if the vault is escrowed. The request must be approved by the escrow officer before it will become effective.

def vault_reset_password(self,
        vault_id=None,
        vault=None,
        new_vault_password=None,
        new_vault_public_key=None,
        new_vault_private_key=None):

    if not vault:
        vault = vault_get(vault_id, attributes=[...])

    if vault.type == "symmetric":

        # generate vault secret salt
        vault.new_secret_salt = generate_salt()

        # generate vault secret key from vault password and secret salt
        new_vault_secret_key = generate_key(new_vault_password, vault.new_secret_salt)

    elif vault.type == "asymmetric":

        # store vault public key
        vault.new_public_key = new_vault_public_key

    if vault.escrow:

        if vault.type == "symmetric":
            # encrypt vault secret key with escrow public key
            vault.new_escrowed_secret_key = encrypt(new_vault_secret_key, vault.escrow_public_key)

        elif vault.type == "asymmetric":
            # encrypt vault private key with escrow public key
            vault.new_escrowed_private_key = encrypt(new_vault_private_key, vault.escrow_public_key)

    if vault_id:
        vault_update(vault)

An owner can request a password reset for a symmetric vault as follows:

vault_reset_password("SymmetricVault",
    new_vault_password=...)

An owner can request a key reset for an asymmetric vault as follows:

vault_reset_password("AsymmetricVault",
    new_vault_public_key=...,
    new_vault_private_key=...)

approve_vault_password_reset()#

This method can be used by an escrow officer to reset vault password.

def approve_vault_password_reset(self,
        vault_id=None,
        vault=None,
        escrow_private_key=None):

    if not vault:
        vault = self.connection.get_vault(vault_id, attributes=[...])

    # recover secrets
    secrets = self.retrieve_secrets(vault=vault, escrow_private_key=escrow_private_key)

    vault.secret_salt = vault.new_secret_salt
    vault.escrowed_secret_key = vault.new_escrowed_secret_key
    vault.public_key = vault.new_public_key
    vault.escrowed_private_key = vault.new_escrowed_private_key

    vault.new_secret_salt = None
    vault.new_escrowed_secret_key = None
    vault.new_public_key = None
    vault.new_escrowed_private_key = None

    # re-archive secrets
    self.archive_secrets(vault=vault, secrets=secrets, escrow_private_key=escrow_private_key)

    if vault_id:
        self.connection.update_vault(vault)

reject_vault_password_reset()#

This method can be used by an escrow officer to reject password reset request.

def reject_vault_password_reject(self,
        vault_id=None,
        vault=None):

    if not vault:
        vault = self.connection.get_vault(vault_id, attributes=[...])

    # clear request
    vault.new_secret_salt = None
    vault.new_escrowed_secret_key = None
    vault.new_public_key = None
    vault.new_escrowed_private_key = None

    if vault_id:
        self.connection.update_vault(vault)

Web Services#

The vault web services initially may be implemented using the existing IPA’s XML/JSON RPC framework. In the future it may be converted to use REST API as follows.

Container resource#

PUT /ipa/rest/vaults/#

An admin can use this operation to create a container. This operation will be wrapped in IPAConnection.create_container().

GET /ipa/rest/vaults/#

A user can use this operation to return the container attributes and the list of vaults in the container. This operation will be wrappped in IPAConnection.find_vaults().

Response:

{
    id: "/users/testuser",
    ...
    vaults: [
        {
            id: "/users/testuser/PrivateVault",
            description: "Private vault",
            type: "standard"
        },
        {
            id: "/shared/SharedVault",
            description: "Shared vault",
            type: "standard"
        }
    ],
    totalVaults: 2
}

Vault resource#

GET /ipa/rest/vaults//#

A member can use this operation to get the vault info. This operation will be wrappped in IPAConnection.get_vault().

The client can specify the attributes to return in the query:

attributes=<comma-separated attribute list>

Response:

{
    id: "/users/testuser/PrivateVault",
    description: "Private vault",
    type: "standard",
    secret_salt: ...,
    owners: [ "testuser", "testowner" ],
    members: [ "testmember" ],
    public_key: null,
    escrow_public_key: null,
    escrowed_secret_key: null,
    escrowed_private_key: null
}

POST /ipa/rest/vaults/#

A user can use this operation to add a vault into a container. This operation will be wrappped in IPAConnection.create_vault().

Request:

{
    id: "PrivateVault",
    description: "Private vault",
    type: "standard"
}

The server will return the normalized values of the vault attributes:

{
    id: "/users/testuser/PrivateVault",
    description: "Private vault",
    type: "standard"
}

POST /ipa/rest/vaults//#

An owner can use this operation to modify a vault. This operation will be wrapped in IPAConnection.update_vault().

The client will send the attributes to be modified:

{
    id: "PrivateVault",
    description: "Private vault"
    ...
}

The server will return the normalized vault attributes after modification:

{
    id: "/users/testuser/PrivateVault",
    description: "Private vault",
    type: "standard"
}

DELETE /ipa/rest/vaults/#

An admin can use this operation to remove a container and all vaults in it. This operation will be wrapped in IPAConnection.remove_container().

DELETE /ipa/rest/vaults//#

An owner can use this operation will remove a vault. This operation will be wrapped in IPAConnection.remove_vault().

Secret resource#

The secrets are accessible under the following URL:

/ipa/rest/vaults/<container>/<vault name>/secrets

The secrets are stored as base-64 encoded of encrypted JSON collection. For example:

{
    "secret1": {
        "description": "First secret",
        "data": "Secret data"
    },
    "secret2": {
        "description": "Second secret",
        "data": "Secret data"
    }
}

GET /ipa/rest/vaults///secrets#

A member can use this operation to return the encrypted secrets as base-64 encoded data. This operation will be wrapped in IPAConnection.get_secrets().

Response:

ewogICAg...Qp9Cg==

To retrieve the secrets, the data needs to be base-64 decoded, then decrypted using the vault secret key or vault private key, then deserialized using JSON.

PUT /ipa/rest/vaults///secrets#

A member can use this operation to store base-64 encoded encrypted secrets. This operation will be wrapped in IPAConnection.update_vault_secrets().

Request:

ewogICAg...Qp9Cg==

To store the secrets, the secret collection needs to be serialized into JSON, then encrypted using the vault secret key or the vault public key, then base-64 encoded.

LDAP Directory#

Directory Structure#

The containers and vaults are represented as LDAP entries in a subtree in the IPA directory. The root container is represented by the root entry of the subtree. Sub-containers are represented by entries directly under the parent container. Vaults are represented by entries stored under the container.

<suffix>
+ cn=vaults
   + cn=users
      + cn=<user ID>
         + cn=<private vault name>
         + ...
   + cn=shared
      + cn=<shared vault name>
      + ...
   + cn=services
      + cn=<server name>
          + cn=<service vault name>
          + ...

LDAP Schema#

See also LDAP schema for PKCS#11 data.

Container entry:

dn: cn=<container name>, <parent container DN>
objectClass: top
objectClass: ipaVaultContainer
cn: ...
description: ...
owner: ...
member: ...
maxSecretSize: ...
maxVaultSize: ...

Vault entry:

dn: cn=<vault name>, <container DN>
objectClass: top
objectClass: ipaVault
cn: ...
description: ...
owner: ...
member: ...
ipaVaultType: ...
ipaVaultSalt:: ...
ipaVaultPublicKey:: ...
ipaVaultEscrowPublicKey:: ...
ipaVaultPendingSalt:: ...
ipaVaultPendingPublicKey:: ...
ipaVaultPendingEscrowedSecretKey:: ...
ipaVaultPendingEscrowedPrivateKey:: ...

Access Control Attributes#

The LDAP ACI attributes are used to control the access to the LDAP entries representing the vaults and the containers. The secrets themselves are stored in KRA and accessed by IPA as KRA agent on behalf of IPA users. The IPA user’s access to the secrets will be determined by IPA framework based on the user’s membership or ownership of the vaults and containers, not by LDAP ACI.

The ACI attributes are defined in the root entry of the vault subtree:

dn: cn=vaults, <suffix>
...

################################################################################
# Vault Container ACLs
################################################################################
aci: (target="ldap:///cn=*,cn=users,cn=vaults,<suffix>")
  (targetattr="*")
  (version 3.0; acl "Allow users to create private container";
   allow (add) userdn = "ldap:///uid=($attr.cn),cn=users,cn=accounts,<suffix>";)

aci: (targetfilter="(objectClass=ipaVaultContainer)")
  (targetattr="*")
  (version 3.0; acl "Container members can access the container";
   allow(read, search, compare) userattr="member#USERDN";)
aci: (targetfilter="(objectClass=ipaVaultContainer)")
  (targetattr="*")
  (version 3.0; acl "Indirect container members can access the container";
   allow(read, search, compare) userattr="member#GROUPDN";)

aci: (targetfilter="(objectClass=ipaVaultContainer)")
  (targetattr="*")
  (version 3.0; acl "Container members can access sub-containers";
   allow(read, search, compare) userattr="parent[1].member#USERDN";)
aci: (targetfilter="(objectClass=ipaVaultContainer)")
  (targetattr="*")
  (version 3.0; acl "Indirect container members can access sub-containers";
   allow(read, search, compare) userattr="parent[1].member#GROUPDN";)

aci: (targetfilter="(objectClass=ipaVaultContainer)")
  (targetattr="*")
  (version 3.0; acl "Container owners can modify the container";
   allow(read, search, compare, write) userattr="owner#USERDN";)
aci: (targetfilter="(objectClass=ipaVaultContainer)")
  (targetattr="*")
  (version 3.0; acl "Indirect container owners can modify the container";
   allow(read, search, compare, write) userattr="owner#GROUPDN";)

aci: (targetfilter="(objectClass=ipaVaultContainer)")
  (targetattr="*")
  (version 3.0; acl "Container owners can manage sub-containers";
   allow(read, search, compare, add, delete) userattr="parent[1].owner#USERDN";)
aci: (targetfilter="(objectClass=ipaVaultContainer)")
  (targetattr="*")
  (version 3.0; acl "Indirect container owners can manage sub-containers";
   allow(read, search, compare, add, delete) userattr="parent[1].owner#GROUPDN";)

################################################################################
# Vault ACLs
################################################################################
aci: (targetfilter="(objectClass=ipaVault)")
  (targetattr="*")
  (version 3.0; acl "Container members can access vaults in the container";
   allow(read, search, compare) userattr="parent[1].member#USERDN";)
aci: (targetfilter="(objectClass=ipaVault)")
  (targetattr="*")
  (version 3.0; acl "Indirect container members can access vaults in the container";
   allow(read, search, compare) userattr="parent[1].member#GROUPDN";)

aci: (targetfilter="(objectClass=ipaVault)")
  (targetattr="*")
  (version 3.0; acl "Container owners can manage vaults in the container";
   allow(read, search, compare, add, delete) userattr="parent[1].owner#USERDN";)
aci: (targetfilter="(objectClass=ipaVault)")
  (targetattr="*")
  (version 3.0; acl "Indirect container owners can manage vaults in the container";
   allow(read, search, compare, add, delete) userattr="parent[1].owner#GROUPDN";)

aci: (targetfilter="(objectClass=ipaVault)")
  (targetattr="*")
  (version 3.0; acl "Vault members can access the vault";
   allow(read, search, compare) userattr="member#USERDN";)
aci: (targetfilter="(objectClass=ipaVault)")
  (targetattr="*")
  (version 3.0; acl "Indirect vault members can access the vault";
   allow(read, search, compare) userattr="member#GROUPDN";)

aci: (targetfilter="(objectClass=ipaVault)")
  (targetattr="*")
  (version 3.0; acl "Vault owners can manage the vault";
   allow(read, search, compare, write) userattr="owner#USERDN";)
aci: (targetfilter="(objectClass=ipaVault)")
  (targetattr="*")
  (version 3.0; acl "Indirect vault owners can manage the vault";
   allow(read, search, compare, write) userattr="owner#GROUPDN";)

################################################################################
# Security Officer ACLs
################################################################################
aci: (targetfilter="(objectClass=ipaVaultContainer)")
  (targetattr="*")
  (version 3.0; acl "Security officers can access all container";
   allow(read, search, compare) groupdn="ldap:///cn=security officers,cn=groups,cn=accounts,<suffix>";)
aci: (targetfilter="(objectClass=ipaVault)")
  (targetattr="*")
  (version 3.0; acl "Security officers can access all vaults";
   allow(read, search, compare) groupdn="ldap:///cn=security officers,cn=groups,cn=accounts,<suffix>";)
aci: (targetfilter="(objectClass=ipaVault)")
  (targetattr="ipaVaultEncSalt || ipaVaultPublicKey || ipaVaultEscrowPublicKey || ipaVaultEscrowedEncKey
   || ipaVaultEscrowedPublicKey || ipaVaultNewSecretSalt || ipaVaultNewPublicKey || ipaVaultNewEscrowedSecretKey || ipaVaultNewEscrowedPublicKey")
  (version 3.0; acl "Security officer can reset/change vault password/keys";
   allow(write) groupdn="ldap:///cn=security officers,cn=groups,cn=accounts,<suffix>";)

KRA Service#

Installing KRA service#

The KRA service must be installed on each replica before the vault services can be used properly using the following command:

$ ipa-kra-install -p <Directory Manager password>

Uninstalling KRA service#

If the vault functionality is no longer needed, it can be removed using the following command:

$ ipa-kra-uninstall -p <Directory Manager password> --uninstall

KRA authentication#

IPA is currently accessing CA services as a user that belongs into CA agents group, so this user needs to be added into KRA agents group as well during installation.

All operations against the KRA will be executed as KRA agent using KRA Python client, so the client certificate needs to be stored on IPA server file system with the appropriate file protection.

IPA user’s access to the secrets stored in KRA will be determined by IPA framework based on the user’s membership or ownership of the vaults and containers.

Mapping a vault into KRA#

The encrypted secrets in a vault will be stored as a blob in KRA as a key under the following client key ID:

<namespace>/<vault ID>

The namespace is used to distinguish secrets from different applications stored in the same KRA. For IPA the namespace will be “ipa”.

Archiving secrets in KRA#

Regardless of the vault type, the client will transmit the (possibly) encrypted secrets as generic data to IPA server. The data will be encrypted with a session key that is unique for each transmission. The session key itself will be wrapped with KRA transport public key. The encrypted data and wrapped session key will be sent to IPA server.

class VaultClient():

    def archive_data(self, vault_id, data=None, encrypted_data=None, wrapped_session_key=None, nonce_iv=None):

        if data:
            transport_cert = self.connection.get_transport_cert()
            session_key = generate_session_key()
            nonce_iv = generate_nonce_iv()

            encrypted_data = encrypt(
                data,
                session_key,
                nonce_iv)

            wrapped_session_key = wrap(
                session_key,
                transport_cert)

        self.vault_service.archive_data(
            vault_id,
            encrypted_data,
            wrapped_session_key,
            nonce_iv)

The IPA server will then forward the encrypted data to KRA:

class VaultService():

    def archive_data(self, vault_id, encrypted_data, wrapped_session_key, nonce_iv):

        vault = self.get_vault(vault_id)

        # verify access rights
        user = ...
        roles = get_user_roles(user)

        is_member = user.id in vault.members
        is_owner = user.id in vault.owners
        is_escrow_officer = "Security Officers" in roles

        if not is_member and not is_owner and not is_escrow_officer:
            raise Exception("Unauthorized access")

        # archive data as KRA agent
        key_client = api.Backend.kra.get_key_client()
        client_key_id = "ipa/" + vault_id

        key_client.archive_encrypted_data(
            client_key_id,
            encrypted_data,
            wrapped_session_key,
            nonce_iv)

Retrieving secrets from KRA#

Regardless of the vault type, the client will send the vault ID to IPA server to retrieve the encrypted secrets:

class VaultClient():

    def retrieve_data(self, vault_id, wrapped_session_key=None):

        if not wrapped_session_key:
            transport_cert = self.connection.get_transport_cert()
            session_key = generate_session_key()

            wrapped_session_key = wrap(
                session_key,
                transport_cert)

        data = self.vault_service.retrieve_data(vault_id, wrapped_session_key)

        return data

When IPA server receives this call, it will retrieve the encrypted secrets from KRA and then return it to the client:

class VaultService():

    def retrieve_data(self, vault_id, wrapped_session_key):

        vault = self.get_vault(vault_id)

        # verify access rights
        user = ...
        roles = get_user_roles(user)

        is_member = user.id in vault.members
        is_owner = user.id in vault.owners
        is_escrow_officer = "Security Officers" in roles

        if not is_member and not is_owner and not is_escrow_officer:
            raise Exception("Unauthorized access")

        # retrieve data as KRA agent
        key_client = api.Backend.kra.get_key_client()
        client_key_id = "ipa/" + vault_id

        key_info = key_client.get_active_key_info(client_key_id)
        data = key_client.retrieve_key(key_info.key_id)

        return data

Dependencies#

Testing#

$ ./make-test ipatests.test_xmlrpc.test_vault_plugin

Frequently Asked Questions#

Why use Python Cryptography instead of Python NSS?#

Although IPA and Dogtag have been using Python NSS, the Python Cryptography is simpler to use and it can support various crypto backends. Since there is no strict requirement to use NSS, it’s recommended to use Python Cryptography for new development.

Can different vault members use different vault passwords?#

No. There is only one vault password per vault, and it has to be shared with all members.

Can the secrets in the same vault be archived with different passwords?#

No. There’s only one vault password per vault and it will be used to encrypt all secrets.

Will the secrets in the same vault encrypted with different encryption keys?#

No. All secrets in the same vault are encrypted with a single encryption key which is generated from the vault password and the secret salt.

Can a vault have more than one escrow officer?#

Yes. A vault can be assigned a pair of escrow public and private keys. The escrow public key will be stored as an attribute in the vault such that the owners can use it to encrypt the vault secret/private key to be escrowed. The escrow private key will be stpred by the escrow officers, but it can be shared by multiple escrow officers, so any of the escrow officers will be able to decrypt the vault secret/private key to recover the secrets.

To Do#

  • Merge vault and vault container

  • Add support for renaming users.

  • Add support for symmetric/asymmetric vault without KRA transport encryption.

  • Add attribute to indicate single secret / multiple secrets in a vault.

  • Clarify how the services container is supposed to be used.

  • Online escrow: need a vault to store escrow private key.

  • Add option to generate public & private keys automatically and archive it in KRA.

References#