Sapient Bundle¶
Sapient bundle help you to secure data exchange. It encrypt and sign response of an API. A client can then verify signature and decrypt content.
This bundle include Guzzle middleware to decrypt and verify signature when calling a Sapient API
Content¶
Installation¶
Before starting installation, your application must have PHP 7.2 minimum. This bundle use Sapient library which require libsodium extension. Symfony 3.4 and Synfony 4 are supported.
Symfony 4 with recipe¶
A recipe has been created for this bundle.
Enable contrib repository in composer.
composer config extra.symfony.allow-contrib true
composer install lepiaf/sapient-bundle
By default, recipe will enable and generate a minimal config file. You have to run a command to initialize configuration.
bin/console sapient:configure
It will output configuration. Copy and paste it to config/packages/sapient.yml
sapient:
sign:
public: 'signing-key-public'
private: 'signing-key-private'
name: 'signer-name'
seal:
public: 'seal-key-public'
private: 'seal-key-private'
sealing_public_keys: ~
verifying_public_keys: ~
Now your api is ready. Repeat this process with a client.
Symfony 4 without recipe¶
As usual, install it via composer
composer install lepiaf/sapient-bundle
Enable it in config/bundles.yml
<?php
return [
lepiaf\SapientBundle\SapientBundle::class => ['all' => true],
];
Now bundle is registered. You can run command to generate default configuration.
bin/console sapient:configure
It will output configuration. Copy and paste it to config/packages/sapient.yml
sapient:
sign:
public: 'signing-key-public'
private: 'signing-key-private'
name: 'signer-name'
seal:
public: 'seal-key-public'
private: 'seal-key-private'
sealing_public_keys: ~
verifying_public_keys: ~
Now your api is ready. Repeat this process with a client.
Symfony 3.4 without recipe¶
PHP 7.2 is the only requirement, it can work with symfony 3.4 and below.
Install it via composer
composer install lepiaf/sapient-bundle
Enable bundle in app/AppKernel.php
<?php
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
new lepiaf\SapientBundle\SapientBundle()
);
return $bundles;
}
}
Now bundle is registered. You can run command to generate default configuration.
bin/console sapient:configure
It will output configuration. Copy and paste it to app/config/config.yml
sapient:
sign:
public: 'signing-key-public'
private: 'signing-key-private'
name: 'signer-name'
seal:
public: 'seal-key-public'
private: 'seal-key-private'
sealing_public_keys: ~
verifying_public_keys: ~
Now your api is ready. Repeat this process with a client.
Configuration¶
We will see 3 differents uses case:
- Sign only response of your API. Each client are free to check signature.
- Sign and seal response. Only the client who did request can unseal content of response and can verify the signature.
- Sign and seal request. Only API can unseal content of request and can verify the signature.
Before diving deep, let assume we have an API Alice and a client Bob. API Alice and client Bob have a key pair to sign and seal (see Installation part). Client Bob will do request to API Alice in order to get some information.
Note
To use this bundle, you need to have basic understand of cryptography and asymmetric encryption.
Sign response only¶
Go to configuration file and open it. After installation, you only have a key pair for sign
and seal
.
sapient:
sign:
public: 'G3zo5Zub2o-eyp-g3GYb9JXEzdtIqmFdDOvU5PV6hBk='
private: 'giP81DlS_R3JL4-UnSVbn2I5lm9abv8vA7aLuEdOUB4bfOjlm5vaj57Kn6DcZhv0lcTN20iqYV0M69Tk9XqEGQ=='
host: 'api-alice'
response: true
seal:
public: 'tquhje8C_hNdd85R-CzVq7n7MOLqc5h11GJv7Vo7fgc='
private: 'NoxnlCvhxl8NRfCgIhuxm95IE1Y9QFUHMuvDkrWrnQ4='
response: true
sealing_public_keys: ~
verifying_public_keys: ~
Do a request and you will see your response in clear text. Then check headers of response.
HTTP/1.1 200 OK
Host: localhost:8000
Connection: close
X-Powered-By: PHP/7.2.4-1+ubuntu16.04.1+deb.sury.org+1
Cache-Control: no-cache, private
Date: Sat, 12 May 2018 19:00:19 +0200, Sat, 12 May 2018 17:00:19 GMT
content-type: application/json
Body-Signature-Ed25519: 6sHYDSKwx05QNDe-s2a1tBXxKw2JZxLZwUBpLojEQpqzcGEU1XcaqdaG9_FQTbVkeSa_25vSak8MJcZ8RaoaAg==
Sapient-Signer: api-alice
Two new headers appear:
- Body-Signature-Ed25519 is signature of response. It is used by Sapient library to verify response with public key.
- Sapient-Signer: name of who sign this response. It is usefull when client call more than one API.
For now we have API Alice who sign all their response. It is good but not usefull for now. Let’s configure client Bob to verify signature.
API Alice must give her sign public key to client Bob. As shown in configuration above, it is G3zo5Zub2o-eyp-g3GYb9JXEzdtIqmFdDOvU5PV6hBk=
.
Do not give private key, hence the name, it is private.
Open client Bob configuration file and add API Alice public key.
sapient:
sealing_public_keys: ~
verifying_public_keys:
-
key: 'G3zo5Zub2o-eyp-g3GYb9JXEzdtIqmFdDOvU5PV6hBk='
host: 'api-alice'
I’ve added API Alice sign public key in verifying_public_keys
configuration. It must have the key and name
of signer. Here it is api-alice
.
Client Bob use Guzzle to request API Alice. Sapient bundle comes with Guzzle middleware to make verification easier. You need to enable it.
guzzle_middleware:
verify: true
Here is the final configuration of client Bob.
sapient:
guzzle_middleware:
verify: true
sealing_public_keys: ~
verifying_public_keys:
-
key: 'G3zo5Zub2o-eyp-g3GYb9JXEzdtIqmFdDOvU5PV6hBk='
host: 'api-alice'
Now, every time you will request API Alice, it will verify every signature. If signature cannot be verifyed, an exception will raise. It can be a misconfiguration or an man-in-the-middle.
Sign and seal response¶
This is the most usefull usecase. It sign and seal the response. Only the requester can unseal the
content of the response. It use XChaCha20-Poly1305
algorithm to seal and ED25519
for signature.
Follow part Sign response only first. In this part, we will configure API Alice to seal response for client Bob.
In client Bob configuration file, generate a seal key pair. You can do it easily with bin/console sapient:configure
.
Copy and paste sign and seal part.
sapient:
sign:
public: 'aO8pIZYoGUrPOSJFC1UfH-XE7M19xC-LP-tZwukwFqI='
private: 'nnr3sTDvLfDHtw6suup3LlNh2YYCCCcXvksDpIp5VHVo7ykhligZSs85IkULVR8f5cTszX3EL4s_61nC6TAWog=='
host: 'client-bob'
response: true
seal:
public: 'M2SMMPHg9NOXoX3NgzlWY8iTheyu8qSovnTZpAlIGB0='
private: 'FzyiZAbEuquHUXt-YNF6WOXFB6CVBpyz2ocMMaT0FK8='
response: true
guzzle_middleware:
verify: true
sealing_public_keys: ~
verifying_public_keys:
-
key: 'G3zo5Zub2o-eyp-g3GYb9JXEzdtIqmFdDOvU5PV6hBk='
host: 'api-alice'
As mentioned in introduction of this part, API Alice will seal response. Client Bob use guzzle and Sapient bundle has a middlware to unseal response. Enable it.
sapient:
sign:
public: 'aO8pIZYoGUrPOSJFC1UfH-XE7M19xC-LP-tZwukwFqI='
private: 'nnr3sTDvLfDHtw6suup3LlNh2YYCCCcXvksDpIp5VHVo7ykhligZSs85IkULVR8f5cTszX3EL4s_61nC6TAWog=='
host: 'client-bob'
response: true
seal:
public: 'M2SMMPHg9NOXoX3NgzlWY8iTheyu8qSovnTZpAlIGB0='
private: 'FzyiZAbEuquHUXt-YNF6WOXFB6CVBpyz2ocMMaT0FK8='
response: true
guzzle_middleware:
verify: true
unseal: true
sealing_public_keys: ~
verifying_public_keys:
-
key: 'G3zo5Zub2o-eyp-g3GYb9JXEzdtIqmFdDOvU5PV6hBk='
host: 'api-alice'
Then, you need to enable option guzzle_middleware.requester_host
to add header Sapient-Requester
.
This header is used by API Alice to return a signed and sealed response.
sapient:
sign:
public: 'aO8pIZYoGUrPOSJFC1UfH-XE7M19xC-LP-tZwukwFqI='
private: 'nnr3sTDvLfDHtw6suup3LlNh2YYCCCcXvksDpIp5VHVo7ykhligZSs85IkULVR8f5cTszX3EL4s_61nC6TAWog=='
host: 'client-bob'
response: true
seal:
public: 'M2SMMPHg9NOXoX3NgzlWY8iTheyu8qSovnTZpAlIGB0='
private: 'FzyiZAbEuquHUXt-YNF6WOXFB6CVBpyz2ocMMaT0FK8='
response: true
guzzle_middleware:
verify: true
unseal: true
requester_host: 'client-bob'
sealing_public_keys: ~
verifying_public_keys:
-
key: 'G3zo5Zub2o-eyp-g3GYb9JXEzdtIqmFdDOvU5PV6hBk='
host: 'api-alice'
Now we are done in client Bob configuration. Before updating configuration of API Alice, copy seal public key of client Bob.
In API Alice, add seal public key of client Bob in sealing_public_keys
configuration.
sapient:
sign:
public: 'G3zo5Zub2o-eyp-g3GYb9JXEzdtIqmFdDOvU5PV6hBk='
private: 'giP81DlS_R3JL4-UnSVbn2I5lm9abv8vA7aLuEdOUB4bfOjlm5vaj57Kn6DcZhv0lcTN20iqYV0M69Tk9XqEGQ=='
host: 'api-alice'
response: true
seal:
public: 'tquhje8C_hNdd85R-CzVq7n7MOLqc5h11GJv7Vo7fgc='
private: 'NoxnlCvhxl8NRfCgIhuxm95IE1Y9QFUHMuvDkrWrnQ4='
response: true
sealing_public_keys:
-
host: 'client-bob'
key: 'M2SMMPHg9NOXoX3NgzlWY8iTheyu8qSovnTZpAlIGB0='
verifying_public_keys: ~
Configuration is done for API Alice.
Every time client Bob will request API Alice, API Alice will seal and sign response. Then, client Bob receive response and pass to Guzzle middleware. It unseal and verify signature. If everything is ok, your controller/service will use data as usual. Else it will raise an exception.
To get more information, check library documentation. Sapient is available in container and you can use more functionality.
Sign and seal request¶
To complete our usecase above, we can sign and seal request to api. Then, we have a full confidentiality on request made to api.
Before continuing, you must follow step Sign and seal response part.
Note: for now, it is not possible to sign/seal request without signing and sealing response. It could be possible in future version.
Client Bob want to seal and sign all request to API Alice. Only API Alice can read request from Client Bob.
As we use Guzzle, you can enable an option to automatically sign and seal all request.
sapient:
sign:
public: 'aO8pIZYoGUrPOSJFC1UfH-XE7M19xC-LP-tZwukwFqI='
private: 'nnr3sTDvLfDHtw6suup3LlNh2YYCCCcXvksDpIp5VHVo7ykhligZSs85IkULVR8f5cTszX3EL4s_61nC6TAWog=='
host: 'client-bob'
response: true
seal:
public: 'M2SMMPHg9NOXoX3NgzlWY8iTheyu8qSovnTZpAlIGB0='
private: 'FzyiZAbEuquHUXt-YNF6WOXFB6CVBpyz2ocMMaT0FK8='
response: true
guzzle_middleware:
verify: true
unseal: true
sign_request: true
seal_request: true
requester_host: 'client-bob'
sealing_public_keys: ~
verifying_public_keys:
-
key: 'G3zo5Zub2o-eyp-g3GYb9JXEzdtIqmFdDOvU5PV6hBk='
host: 'api-alice'
Now we have request signed and sealed. But API Alice will not understand it. We need to enable options in API Alice configuration and exchange keys.
There are 2 options: verify_request
and unseal_request
. Enable it.
sapient:
sign:
public: 'G3zo5Zub2o-eyp-g3GYb9JXEzdtIqmFdDOvU5PV6hBk='
private: 'giP81DlS_R3JL4-UnSVbn2I5lm9abv8vA7aLuEdOUB4bfOjlm5vaj57Kn6DcZhv0lcTN20iqYV0M69Tk9XqEGQ=='
host: 'api-alice'
response: true
seal:
public: 'tquhje8C_hNdd85R-CzVq7n7MOLqc5h11GJv7Vo7fgc='
private: 'NoxnlCvhxl8NRfCgIhuxm95IE1Y9QFUHMuvDkrWrnQ4='
response: true
sealing_public_keys:
-
host: 'client-bob'
key: 'M2SMMPHg9NOXoX3NgzlWY8iTheyu8qSovnTZpAlIGB0='
verifying_public_keys: ~
verify_request: true
unseal_request: true
Then, we have to exchange public key. API Alice must send his seal public key to Client Bob. And Client Bob must send his sign public key to API Alice.
In Client Bob configuration, we must have:
sapient:
sign:
public: 'aO8pIZYoGUrPOSJFC1UfH-XE7M19xC-LP-tZwukwFqI='
private: 'nnr3sTDvLfDHtw6suup3LlNh2YYCCCcXvksDpIp5VHVo7ykhligZSs85IkULVR8f5cTszX3EL4s_61nC6TAWog=='
host: 'client-bob'
response: true
seal:
public: 'M2SMMPHg9NOXoX3NgzlWY8iTheyu8qSovnTZpAlIGB0='
private: 'FzyiZAbEuquHUXt-YNF6WOXFB6CVBpyz2ocMMaT0FK8='
response: true
guzzle_middleware:
verify: true
unseal: true
sign_request: true
seal_request: true
requester_host: 'client-bob'
sealing_public_keys:
-
key: 'tquhje8C_hNdd85R-CzVq7n7MOLqc5h11GJv7Vo7fgc='
host: 'api-alice'
verifying_public_keys:
-
key: 'G3zo5Zub2o-eyp-g3GYb9JXEzdtIqmFdDOvU5PV6hBk='
host: 'api-alice'
In API Alice configuration, we must have:
sapient:
sign:
public: 'G3zo5Zub2o-eyp-g3GYb9JXEzdtIqmFdDOvU5PV6hBk='
private: 'giP81DlS_R3JL4-UnSVbn2I5lm9abv8vA7aLuEdOUB4bfOjlm5vaj57Kn6DcZhv0lcTN20iqYV0M69Tk9XqEGQ=='
host: 'api-alice'
response: true
seal:
public: 'tquhje8C_hNdd85R-CzVq7n7MOLqc5h11GJv7Vo7fgc='
private: 'NoxnlCvhxl8NRfCgIhuxm95IE1Y9QFUHMuvDkrWrnQ4='
response: true
sealing_public_keys:
-
host: 'client-bob'
key: 'M2SMMPHg9NOXoX3NgzlWY8iTheyu8qSovnTZpAlIGB0='
verifying_public_keys:
-
host: 'client-bob'
key: 'aO8pIZYoGUrPOSJFC1UfH-XE7M19xC-LP-tZwukwFqI='
verify_request: true
unseal_request: true
Now you are fully configured !
Reference¶
sapient:
sign:
enabled: boolean
public: string
private: string
host: string
response: boolean
seal:
enabled: boolean
public: string
private: string
response: boolean
guzzle_middleware:
unseal: boolean
verify: boolean
sign_request: boolean
seal_request: boolean
requester_host: string
sealing_public_keys:
-
host: string
key: string
verifying_public_keys:
-
host: string
key: string
verify_request: boolean
unseal_request: boolean
Above, you have full configuration reference.
sign¶
Enable signing response. If sign key is present, it must contain public, private and name property. If not present, feature is disabled.
sign.public¶
Required if sign key is present.
It is signing public key string. It is generated by \ParagonIE\Sapient\CryptographyKeys\SigningSecretKey::generate() function.
sign.private¶
Required if sign key is present.
It is signing private key string. It is generated by \ParagonIE\Sapient\CryptographyKeys\SigningSecretKey::generate() function. This key must never be revealed. If it is leaked, you must regenerate a new key pair.
sign.host¶
Required if sign key is present.
Host of who sign response. It is required if client want to verifying signature in response.
sign.response¶
Enable or disable subscriber that sign response.
seal¶
Enable sealing response. Sealing mean that response content is encrypted. Only receiver with sealing private key can decrypt and reveal response content in clear. If seal key is present, it must contain public, private and name property. If not present, feature is disabled.
seal.public¶
Required if seal key is present.
It is sealing public key string. It is generated by \ParagonIE\Sapient\CryptographyKeys\SealingSecretKey::generate() function.
seal.private¶
Required if seal key is present.
It is sealing private key string. It is generated by \ParagonIE\Sapient\CryptographyKeys\SealingSecretKey::generate() function. This key must never be revealed. If it is leaked, you must regenerate a new key pair.
seal.response¶
Enable or disable subscriber that seal response. To use this feature, you must enable sign feature. Without sign feature you will not able to use it. Sealing a response without signing is not secure. It mean your recipient will unseal the response but he will not be sure it was sent by the right sender.
guzzle_middleware¶
This bundle contain Guzzle middleware to decrypt and verify response.
guzzle_middleware.unseal¶
If enable, it will activate Guzzle middleware that decrypt response. By default it is disabled.
You must enable “seal” option and configure a “seal.private” key before using “guzzle_middleware.unseal” feature.
guzzle_middleware.verify¶
If enable, it will activate Guzzle middleware that verify signature in response. By default it is disabled.
Before enabling this option, you must configure verifying_public_keys array.
guzzle_middleware.requester_host¶
This Guzzle middleware will add a header Sapient-Requester
automatically on each request. This
header is used by recipient to choose the right key to encrypt response.
It is optional but highly recommended. If not enable, you must add header manually in Guzzle client configuration.
guzzle_middleware.sign_request¶
If enable, it will activate Guzzle middleware that sign all request. By default it is disabled.
You must enable “sign” option and configure a “sign.private” key before using “guzzle_middleware.sign_request” feature.
guzzle_middleware.seal_request¶
If enable, it will activate Guzzle middleware that seal all request. By default it is disabled.
It use hostname configured in Guzzle client in order to choose public key to seal request.
You must enable: - “seal” option and configure a “seal.private” key before using “guzzle_middleware.seal_request” feature. - “guzzle_middleware.sign_request” option before using “guzzle_middleware.seal_request” feature.
sealing_public_keys¶
List of all sealing public keys used to encrypt response. Your client must give you the value in sapient.seal.public. Each item must contain a key and a name. name must match header value Sapient-Signer.
sapient:
sealing_public_keys:
-
name: "client-bob"
key: "sealing public key of client-bob"
verifying_public_keys¶
List of all verifying public keys used to verify response. Your api must give you the value in sapient.sign.public. Each item must contain a key and a name. name must match header value Sapient-Requester.
sapient:
verifying_public_keys:
-
name: "api-alice"
key: "verifying public key of api-alice"
verify_request¶
Each request received by HttpKernel will enter in subscriber that verify signature. It
check Sapient-Requester
header and fetch the public key in verifying public keys array.
If found, then it verify signature. If signature is invalid, an InvalidMessageException
is raised.
unseal_request¶
Each request received by HttpKernel will enter in subscriber that unseal signature. It use it own private
key to unseal request. If unseal process fail, an InvalidMessageException
is raised.
You must enable “seal” option before using “unseal_request” feature.
\ Sort by:\ best rated\ newest\ oldest\
\\
Add a comment\ (markup):
\``code``
, \ code blocks:::
and an indented block after blank line