Signer::createStringToSign()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 1 Features 1
Metric Value
c 5
b 1
f 1
dl 0
loc 18
rs 9.4285
cc 1
eloc 11
nc 1
nop 4
1
<?php
2
/**
3
 * Copyright (c) 2011-2014 Amazon Web Services, Inc.
4
 * Copyright (c) 2014 Ryan Parman.
5
 *
6
 * Based on a stripped-down version of the AWS Signature v4 implementation,
7
 * built into the AWS SDK for PHP 3.0. Original authors:
8
 *
9
 * @author Michael Dowling <https://github.com/mtdowling>
10
 * @author Jeremy Lindblom <https://github.com/jeremeamia>
11
 *
12
 * http://opensource.org/licenses/Apache2.0
13
 */
14
15
namespace Skyzyx\Signer;
16
17
use Psr\Log\LoggerAwareInterface;
18
use Psr\Log\LoggerAwareTrait;
19
use Psr\Log\LoggerTrait;
20
use Psr\Log\NullLogger;
21
22
/**
23
 * The Signer class is designed for those who are signing data on behalf of a public-private keypair.
24
 *
25
 * In principle, the "client party" has public key (i.e., `client_id`) has a matching private key
26
 * (i.e., `client_secret`) that can be verified by both the signer, as well as the client, but
27
 * by nobody else as we don't want to make forgeries possible.
28
 *
29
 * The "signing party" has a simple an identifier which acts as an additional piece of entropy in the
30
 * algorithm, and can help differentiate between multiple signing parties if the client party does
31
 * something like try to use the same public-private keypair independently of a signing party
32
 * (as is common with GPG signing).
33
 *
34
 * For example, in the original AWS implementation, the "self key" for AWS was "AWS4".
35
 */
36
class Signer implements SignerInterface, LoggerAwareInterface
37
{
38
    use LoggerAwareTrait;
39
40
41
    /**************************************************************************/
42
    // PROPERTIES
43
44
    /** @var string */
45
    private $self_key;
46
47
    /** @var string */
48
    private $client_id;
49
50
    /** @var string */
51
    private $client_secret;
52
53
    /** @var string */
54
    private $hash_algo = '';
55
56
57
    /**************************************************************************/
58
    // PUBLIC METHODS
59
60
    /**
61
     * Constructs a new instance of this class.
62
     *
63
     * @param string $self_key      A string which identifies the signing party and adds additional entropy.
64
     * @param string $client_id     A string which is the public portion of the keypair identifying the client party.
65
     *                              The pairing of the public and private portions of the keypair should only be known
66
     *                              to the client party and the signing party.
67
     * @param string $client_secret A string which is the private portion of the keypair identifying the client party.
68
     *                              The pairing of the public and private portions of the keypair should only be known
69
     *                              to the client party and the signing party.
70
     * @param string $hash_algo     The hash algorithm to use for signing. Run `hash_algos()` to see what's supported.
71
     *                              The default value is `sha512`.
72
     *
73
     * @see http://php.net/hash_algos
74
     */
75
    public function __construct($self_key, $client_id, $client_secret, $hash_algo = 'sha512')
76
    {
77
        $this->self_key      = $self_key;
78
        $this->client_id     = $client_id;
79
        $this->client_secret = $client_secret;
80
        $this->hash_algo     = $hash_algo;
81
        $this->logger        = new NullLogger();
82
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87
    public function getSelfKey()
88
    {
89
        /** @var string */
90
        return $this->self_key;
91
    }
92
93
    /**
94
     * {@inheritdoc}
95
     */
96
    public function getClientId()
97
    {
98
        /** @var string */
99
        return $this->client_id;
100
    }
101
102
    /**
103
     * {@inheritdoc}
104
     */
105
    public function getClientSecret()
106
    {
107
        /** @var string */
108
        return $this->client_secret;
109
    }
110
111
    /**
112
     * {@inheritdoc}
113
     */
114
    public function sign(array $payload)
115
    {
116
        $scope       = $this->createScope($this->getSelfKey(), $this->getClientId());
117
        $context     = $this->createContext($payload);
118
        $s2s         = $this->createStringToSign($this->getSelfKey(), $this->getClientId(), $scope, $context);
119
        $signing_key = $this->getSigningSalt($this->getSelfKey(), $this->getClientId(), $this->getClientSecret());
120
        $signature   = hash_hmac($this->hash_algo, $s2s, $signing_key);
121
122
        /** @var string */
123
        return $signature;
124
    }
125
126
127
    /**************************************************************************/
128
    // PRIVATE METHODS
129
130
    /**
131
     * Creates the string-to-sign based on a variety of factors.
132
     *
133
     * @param  string $self_key  A string which identifies the signing party and adds additional entropy.
134
     * @param  string $client_id A string which is the public portion of the keypair identifying the client party.
135
     * @param  string $scope     The results of a call to the `createScope()` method.
136
     * @param  string $context   The results of a call to the `createContext()` method.
137
     * @return string The final string to be signed.
138
     */
139
    private function createStringToSign($self_key, $client_id, $scope, $context)
140
    {
141
        $s2s = sprintf(
142
            "SIGNER-HMAC-%s\n%s\n%s\n%s\n%s",
143
            strtoupper($this->hash_algo),
144
            $self_key,
145
            $client_id,
146
            hash($this->hash_algo, $scope),
147
            hash($this->hash_algo, $context)
148
        );
149
150
        $this->logger->debug(__FUNCTION__, [
151
            'string_to_sign' => $s2s,
152
        ]);
153
154
        /** @var string */
155
        return $s2s;
156
    }
157
158
    /**
159
     * An array of key-value pairs representing the data that you want to sign.
160
     * All values must be `scalar`.
161
     *
162
     * @param  array  $payload The data that you want to sign.
163
     * @return string A canonical string representation of the data to sign.
164
     */
165
    private function createContext(array $payload)
166
    {
167
        $canonical_payload = [];
168
169
        foreach ($payload as $k => $v) {
170
            $k = strtolower($k);
171
            $v = strtolower($v);
172
173
            $canonical_payload[$k] = sprintf('%s=%s', $k, $v);
174
        }
175
176
        ksort($canonical_payload);
177
        $signed_headers_string = implode(';', array_keys($canonical_payload));
178
        $canonical_context     = implode("\n", $canonical_payload) . "\n\n" . $signed_headers_string;
179
180
        $this->logger->debug(__FUNCTION__, [
181
            'payload' => $payload,
182
            'canonical_payload' => $canonical_payload,
183
            'signed_headers_string' => $signed_headers_string,
184
            'canonical_context' => $canonical_context,
185
        ]);
186
187
        /** @var string */
188
        return $canonical_context;
189
    }
190
191
    /**
192
     * Gets the salt value that should be used for signing.
193
     *
194
     * @param  string $self_key      A string which identifies the signing party and adds additional entropy.
195
     * @param  string $client_id     A string which is the public portion of the keypair identifying the client party.
196
     * @param  string $client_secret A string which is the private portion of the keypair identifying the client party.
197
     * @return string The signing salt.
198
     */
199
    private function getSigningSalt($self_key, $client_id, $client_secret)
200
    {
201
        $self_key_sign  = hash_hmac($this->hash_algo, $self_key, $client_secret, true);
202
        $client_id_sign = hash_hmac($this->hash_algo, $client_id, $self_key_sign, true);
203
        $salt           = hash_hmac($this->hash_algo, 'signer', $client_id_sign, true);
204
205
        $this->logger->debug(__FUNCTION__, [
206
            'input' => [
207
                'self_key' => $self_key,
208
                'client_id' => $client_id,
209
                'client_secret' => $client_secret,
210
            ],
211
            'output' => [
212
                'self_key_sign' => $self_key_sign,
213
                'client_id_sign' => $client_id_sign,
214
                'salt' => $salt,
215
            ],
216
        ]);
217
218
        /** @var string */
219
        return $salt;
220
    }
221
222
    /**
223
     * Creates the "scope" in which the signature is valid.
224
     *
225
     * @param  string $self_key  A string which identifies the signing party and adds additional entropy.
226
     * @param  string $client_id A string which is the public portion of the keypair identifying the client party.
227
     * @return string The string which represents the scope in which the signature is valid.
228
     */
229
    private function createScope($self_key, $client_id)
230
    {
231
        $scope = sprintf(
232
            '%s/%s/signer',
233
            $self_key,
234
            $client_id
235
        );
236
237
        $this->logger->debug(__FUNCTION__, [
238
            'scope' => $scope,
239
        ]);
240
241
        /** @var string */
242
        return $scope;
243
    }
244
}
245