1+ <?php
2+ namespace Mastercard \Developer \Interceptors ;
3+
4+ use Mastercard \Developer \Encryption \EncryptionException ;
5+ use Mastercard \Developer \Encryption \FieldLevelEncryption ;
6+ use Mastercard \Developer \Encryption \FieldLevelEncryptionParams ;
7+ use Psr \Http \Message \RequestInterface ;
8+ use Psr \Http \Message \ResponseInterface ;
9+
10+ /**
11+ * Utility class for encrypting RequestInterface and decrypting ResponseInterface payloads (see: https://www.php-fig.org/psr/psr-7/)
12+ * @package Mastercard\Developer\Interceptors
13+ */
14+ class PsrHttpMessageEncryptionInterceptor {
15+
16+ private $ config ;
17+
18+ /**
19+ * PsrHttpMessageEncryptionInterceptor constructor.
20+ * @param $config A FieldLevelEncryptionConfig instance
21+ */
22+ public function __construct ($ config ) {
23+ $ this ->config = $ config ;
24+ }
25+
26+ /**
27+ * Encrypt payloads from RequestInterface objects when needed.
28+ * @param $request A RequestInterface object
29+ * @return The updated RequestInterface object
30+ * @throws EncryptionException
31+ */
32+ public function interceptRequest (RequestInterface &$ request ) {
33+ try {
34+ // Check request actually has a payload
35+ $ body = $ request ->getBody ();
36+ $ payload = $ body ->__toString ();
37+ if (empty ($ payload )) {
38+ // Nothing to encrypt
39+ return $ request ;
40+ }
41+
42+ // Encrypt fields & update headers
43+ if ($ this ->config ->useHttpHeaders ()) {
44+ // Generate encryption params and add them as HTTP headers
45+ $ params = FieldLevelEncryptionParams::generate ($ this ->config );
46+ self ::updateHeader ($ request , $ this ->config ->getIvHeaderName (), $ params ->getIvValue ());
47+ self ::updateHeader ($ request , $ this ->config ->getEncryptedKeyHeaderName (), $ params ->getEncryptedKeyValue ());
48+ self ::updateHeader ($ request , $ this ->config ->getEncryptionCertificateFingerprintHeaderName (), $ params ->getEncryptionCertificateFingerprintValue ());
49+ self ::updateHeader ($ request , $ this ->config ->getEncryptionKeyFingerprintHeaderName (), $ params ->getEncryptionKeyFingerprintValue ());
50+ self ::updateHeader ($ request , $ this ->config ->getOaepPaddingDigestAlgorithmHeaderName (), $ params ->getOaepPaddingDigestAlgorithmValue ());
51+ $ encryptedPayload = FieldLevelEncryption::encryptPayload ($ payload , $ this ->config , $ params );
52+ } else {
53+ // Encryption params will be stored in the payload
54+ $ encryptedPayload = FieldLevelEncryption::encryptPayload ($ payload , $ this ->config );
55+ }
56+
57+ // Update body and content length
58+ $ updatedBody = new PsrStreamInterfaceImpl ();
59+ $ updatedBody ->write ($ encryptedPayload );
60+ $ request = $ request ->withBody ($ updatedBody );
61+ self ::updateHeader ($ request , 'Content-Length ' , $ updatedBody ->getSize ());
62+ return $ request ;
63+
64+ } catch (EncryptionException $ e ) {
65+ throw $ e ;
66+ } catch (\Exception $ e ) {
67+ throw new EncryptionException ('Failed to intercept and encrypt request! ' , $ e );
68+ }
69+ }
70+
71+ /**
72+ * Decrypt payloads from ResponseInterface objects when needed.
73+ * @param $response A ResponseInterface object
74+ * @return The updated ResponseInterface object
75+ * @throws EncryptionException
76+ */
77+ public function interceptResponse (ResponseInterface &$ response ) {
78+ try {
79+ // Read response payload
80+ $ body = $ response ->getBody ();
81+ $ payload = $ body ->__toString ();
82+ if (empty ($ payload )) {
83+ // Nothing to decrypt
84+ return $ response ;
85+ }
86+
87+ // Decrypt fields & update headers
88+ if ($ this ->config ->useHttpHeaders ()) {
89+ // Read encryption params from HTTP headers and delete headers
90+ $ ivValue = self ::readAndRemoveHeader ($ response , $ this ->config ->getIvHeaderName ());
91+ $ encryptedKeyValue = self ::readAndRemoveHeader ($ response , $ this ->config ->getEncryptedKeyHeaderName ());
92+ $ oaepPaddingDigestAlgorithmValue = self ::readAndRemoveHeader ($ response , $ this ->config ->getOaepPaddingDigestAlgorithmHeaderName ());
93+ self ::readAndRemoveHeader ($ response , $ this ->config ->getEncryptionCertificateFingerprintHeaderName ());
94+ self ::readAndRemoveHeader ($ response , $ this ->config ->getEncryptionKeyFingerprintHeaderName ());
95+ $ params = new FieldLevelEncryptionParams ($ this ->config , $ ivValue , $ encryptedKeyValue , $ oaepPaddingDigestAlgorithmValue );
96+ $ decryptedPayload = FieldLevelEncryption::decryptPayload ($ payload , $ this ->config , $ params );
97+ } else {
98+ // Encryption params are stored in the payload
99+ $ decryptedPayload = FieldLevelEncryption::decryptPayload ($ payload , $ this ->config );
100+ }
101+
102+ // Update body and content length
103+ $ updatedBody = new PsrStreamInterfaceImpl ();
104+ $ updatedBody ->write ($ decryptedPayload );
105+ $ response = $ response ->withBody ($ updatedBody );
106+ self ::updateHeader ($ response , 'Content-Length ' , $ updatedBody ->getSize ());
107+ return $ response ;
108+
109+ } catch (EncryptionException $ e ) {
110+ throw $ e ;
111+ } catch (\Exception $ e ) {
112+ throw new EncryptionException ('Failed to intercept and decrypt response! ' , $ e );
113+ }
114+ }
115+
116+ private static function updateHeader (&$ message , $ name , $ value ) {
117+ if (empty ($ name )) {
118+ // Do nothing
119+ return $ message ;
120+ }
121+ $ message = $ message ->withHeader ($ name , $ value );
122+ }
123+
124+ private static function readAndRemoveHeader (&$ message , $ name ) {
125+ if (empty ($ name )) {
126+ return null ;
127+ }
128+ if (!$ message ->hasHeader ($ name )) {
129+ return null ;
130+ }
131+ $ values = $ message ->getHeader ($ name );
132+ $ message = $ message ->withoutHeader ($ name );
133+ return $ values [0 ];
134+ }
135+ }
0 commit comments