Completed
Push — master ( f67525...5cf1da )
by Florent
02:34
created

Signer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 10
rs 9.4286
cc 1
eloc 7
nc 1
nop 3
1
<?php
2
3
/*
4
 * The MIT License (MIT)
5
 *
6
 * Copyright (c) 2014-2015 Spomky-Labs
7
 *
8
 * This software may be modified and distributed under the terms
9
 * of the MIT license.  See the LICENSE file for details.
10
 */
11
12
namespace Jose;
13
14
use Base64Url\Base64Url;
15
use Jose\Behaviour\HasJWAManager;
16
use Jose\Behaviour\HasJWTManager;
17
use Jose\Behaviour\HasKeyChecker;
18
use Jose\Behaviour\HasPayloadConverter;
19
use Jose\Operation\SignatureInterface;
20
use Jose\Payload\PayloadConverterManagerInterface;
21
use Jose\Util\Converter;
22
23
/**
24
 */
25
class Signer implements SignerInterface
26
{
27
    use HasKeyChecker;
28
    use HasJWAManager;
29
    use HasJWTManager;
30
    use HasPayloadConverter;
31
32
    /**
33
     * Signer constructor.
34
     *
35
     * @param \Jose\JWTManagerInterface                      $jwt_manager
36
     * @param \Jose\JWAManagerInterface                      $jwa_manager
37
     * @param \Jose\Payload\PayloadConverterManagerInterface $payload_converter_manager
38
     */
39
    public function __construct(
40
        JWTManagerInterface $jwt_manager,
41
        JWAManagerInterface $jwa_manager,
42
        PayloadConverterManagerInterface $payload_converter_manager
43
    )
44
    {
45
        $this->setJWTManager($jwt_manager);
46
        $this->setJWAManager($jwa_manager);
47
        $this->setPayloadConverter($payload_converter_manager);
48
    }
49
50
    /**
51
     * @param $input
52
     */
53
    private function checkInput(&$input)
54
    {
55
        if ($input instanceof JWTInterface) {
56
            return;
57
        }
58
59
        $header = [];
60
        $payload = $this->getPayloadConverter()->convertPayloadToString($header, $input);
61
62
        $jwt = $this->getJWTManager()->createJWT();
63
        $jwt = $jwt->withPayload($payload);
64
        $jwt = $jwt->withProtectedHeader($header);
65
        $input = $jwt;
66
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71
    public function sign($input, array $instructions, $serialization = JSONSerializationModes::JSON_COMPACT_SERIALIZATION, $detached_signature = false, &$detached_payload = null)
72
    {
73
        $this->checkInput($input);
74
        $this->checkInstructions($instructions, $serialization);
75
76
        $jwt_payload = Base64Url::encode($input->getPayload());
77
78
        $signatures = [
79
            'payload'    => $jwt_payload,
80
            'signatures' => [],
81
        ];
82
83
        foreach ($instructions as $instruction) {
84
            $signatures['signatures'][] = $this->computeSignature($instruction, $input, $jwt_payload);
85
        }
86
87
        if (true === $detached_signature) {
88
            $detached_payload = $signatures['payload'];
89
            unset($signatures['payload']);
90
        }
91
92
        $prepared = Converter::convert($signatures, $serialization);
93
94
        return is_array($prepared) ? current($prepared) : $prepared;
95
    }
96
97
    /**
98
     * @param \Jose\SignatureInstructionInterface $instruction
99
     * @param \Jose\JWTInterface                  $input
100
     * @param string                              $jwt_payload
101
     *
102
     * @return array
103
     */
104
    protected function computeSignature(SignatureInstructionInterface $instruction, JWTInterface $input, $jwt_payload)
105
    {
106
        $protected_header = array_merge($input->getProtectedHeader(), $instruction->getProtectedHeader());
107
        $unprotected_header = array_merge($input->getUnprotectedHeader(), $instruction->getUnprotectedHeader());
108
        $complete_header = array_merge($protected_header, $protected_header);
109
110
        $jwt_protected_header = empty($protected_header) ? null : Base64Url::encode(json_encode($protected_header));
111
112
        $signature_algorithm = $this->getSignatureAlgorithm($complete_header, $instruction->getKey());
113
114
        if (!$this->checkKeyUsage($instruction->getKey(), 'signature')) {
115
            throw new \InvalidArgumentException('Key cannot be used to sign');
116
        }
117
118
        $signature = $signature_algorithm->sign($instruction->getKey(), $jwt_protected_header.'.'.$jwt_payload);
119
120
        $jwt_signature = Base64Url::encode($signature);
121
122
        $result = [
123
            'signature' => $jwt_signature,
124
        ];
125
        if (null !== $protected_header) {
126
            $result['protected'] = $jwt_protected_header;
127
        }
128
        if (!empty($unprotected_header)) {
129
            $result['header'] = $unprotected_header;
130
        }
131
132
        return $result;
133
    }
134
135
    /**
136
     * @param array              $complete_header The complete header
137
     * @param \Jose\JWKInterface $key
138
     *
139
     * @return \Jose\Operation\SignatureInterface
140
     */
141
    protected function getSignatureAlgorithm(array $complete_header, JWKInterface $key)
142
    {
143
        if (!array_key_exists('alg', $complete_header)) {
144
            throw new \InvalidArgumentException('No "alg" parameter set in the header.');
145
        }
146
        if (null !== $key->getAlgorithm() && $key->getAlgorithm() !== $complete_header['alg']) {
147
            throw new \InvalidArgumentException(sprintf('The algorithm "%s" is allowed with this key.', $complete_header['alg']));
148
        }
149
150
        $signature_algorithm = $this->getJWAManager()->getAlgorithm($complete_header['alg']);
151
        if (!$signature_algorithm instanceof SignatureInterface) {
152
            throw new \InvalidArgumentException(sprintf('The algorithm "%s" is not supported.', $complete_header['alg']));
153
        }
154
155
        return $signature_algorithm;
156
    }
157
158
    /**
159
     * @param \Jose\EncryptionInstructionInterface[] $instructions
160
     * @param string                                 $serialization
161
     */
162
    protected function checkInstructions(array $instructions, $serialization)
163
    {
164
        if (empty($instructions)) {
165
            throw new \InvalidArgumentException('No instruction.');
166
        }
167
        if (count($instructions) > 1 && JSONSerializationModes::JSON_SERIALIZATION !== $serialization) {
168
            throw new \InvalidArgumentException('Only one instruction authorized when Compact or Flattened Serialization Overview is selected.');
169
        }
170
        foreach ($instructions as $instruction) {
171
            if (!$instruction instanceof SignatureInstructionInterface) {
172
                throw new \InvalidArgumentException('Bad instruction. Must implement SignatureInstructionInterface.');
173
            }
174
        }
175
    }
176
}
177