Completed
Push — master ( 027ea9...6fcfe6 )
by Florent
05:03
created

Signer::sign()   B

Complexity

Conditions 3
Paths 4

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 25
rs 8.8571
cc 3
eloc 15
nc 4
nop 5
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\Algorithm\JWAManagerInterface;
16
use Jose\Algorithm\Signature\SignatureInterface;
17
use Jose\Behaviour\HasJWAManager;
18
use Jose\Behaviour\HasKeyChecker;
19
use Jose\Behaviour\HasPayloadConverter;
20
use Jose\Object\JWKInterface;
21
use Jose\Object\SignatureInstructionInterface;
22
use Jose\Payload\PayloadConverterManagerInterface;
23
use Jose\Util\Converter;
24
25
/**
26
 */
27
final class Signer implements SignerInterface
28
{
29
    use HasKeyChecker;
30
    use HasJWAManager;
31
    use HasPayloadConverter;
32
33
    /**
34
     * Signer constructor.
35
     *
36
     * @param \Jose\Algorithm\JWAManagerInterface            $jwa_manager
37
     * @param \Jose\Payload\PayloadConverterManagerInterface $payload_converter_manager
38
     */
39
    public function __construct(JWAManagerInterface $jwa_manager, PayloadConverterManagerInterface $payload_converter_manager)
40
    {
41
        $this->setJWAManager($jwa_manager);
42
        $this->setPayloadConverter($payload_converter_manager);
43
    }
44
45
    /**
46
     * {@inheritdoc}
47
     */
48
    public function sign($input, array $instructions, $serialization, $detached_signature = false, &$detached_payload = null)
49
    {
50
        $additional_header = [];
51
        $this->checkSerializationMode($serialization);
52
        $input = $this->getPayloadConverter()->convertPayloadToString($additional_header, $input);
53
        $this->checkInstructions($instructions, $serialization);
54
55
        $jwt_payload = Base64Url::encode($input);
56
57
        $signatures = [
58
            'payload'    => $jwt_payload,
59
            'signatures' => [],
60
        ];
61
62
        foreach ($instructions as $instruction) {
63
            $signatures['signatures'][] = $this->computeSignature($instruction, $jwt_payload, $additional_header);
64
        }
65
66
        if (true === $detached_signature) {
67
            $detached_payload = $signatures['payload'];
68
            unset($signatures['payload']);
69
        }
70
71
        return Converter::convert($signatures, $serialization);
72
    }
73
74
    /**
75
     * @param \Jose\Object\SignatureInstructionInterface $instruction
76
     * @param string                                     $jwt_payload
77
     * @param array                                      $additional_header
78
     *
79
     * @return array
80
     */
81
    protected function computeSignature(SignatureInstructionInterface $instruction, $jwt_payload, array $additional_header)
82
    {
83
        $protected_header = array_merge($instruction->getProtectedHeader(), $additional_header);
84
        $unprotected_header = $instruction->getUnprotectedHeader();
85
        $complete_header = array_merge($protected_header, $protected_header);
86
87
        $jwt_protected_header = empty($protected_header) ? null : Base64Url::encode(json_encode($protected_header));
88
89
        $signature_algorithm = $this->getSignatureAlgorithm($complete_header, $instruction->getKey());
90
91
        if (!$this->checkKeyUsage($instruction->getKey(), 'signature')) {
92
            throw new \InvalidArgumentException('Key cannot be used to sign');
93
        }
94
95
        $signature = $signature_algorithm->sign($instruction->getKey(), $jwt_protected_header.'.'.$jwt_payload);
96
97
        $jwt_signature = Base64Url::encode($signature);
98
99
        $result = [
100
            'signature' => $jwt_signature,
101
        ];
102
        if (null !== $protected_header) {
103
            $result['protected'] = $jwt_protected_header;
104
        }
105
        if (!empty($unprotected_header)) {
106
            $result['header'] = $unprotected_header;
107
        }
108
109
        return $result;
110
    }
111
112
    /**
113
     * @param array                     $complete_header The complete header
114
     * @param \Jose\Object\JWKInterface $key
115
     *
116
     * @return \Jose\Algorithm\Signature\SignatureInterface
117
     */
118
    protected function getSignatureAlgorithm(array $complete_header, JWKInterface $key)
119
    {
120
        if (!array_key_exists('alg', $complete_header)) {
121
            throw new \InvalidArgumentException('No "alg" parameter set in the header.');
122
        }
123
        if ($key->has('alg') && $key->get('alg') !== $complete_header['alg']) {
124
            throw new \InvalidArgumentException(sprintf('The algorithm "%s" is allowed with this key.', $complete_header['alg']));
125
        }
126
127
        $signature_algorithm = $this->getJWAManager()->getAlgorithm($complete_header['alg']);
128
        if (!$signature_algorithm instanceof SignatureInterface) {
129
            throw new \InvalidArgumentException(sprintf('The algorithm "%s" is not supported.', $complete_header['alg']));
130
        }
131
132
        return $signature_algorithm;
133
    }
134
135
    /**
136
     * @param string $serialization
137
     *
138
     * @throws \InvalidArgumentException
139
     */
140
    protected function checkSerializationMode($serialization)
141
    {
142
        if (!in_array($serialization, JSONSerializationModes::getSupportedSerializationModes())) {
143
            throw new \InvalidArgumentException(sprintf('The serialization method "%s" is not supported.', $serialization));
144
        }
145
    }
146
147
    /**
148
     * @param array  $instructions
149
     * @param string $serialization
150
     *
151
     * @throws \InvalidArgumentException
152
     */
153
    protected function checkInstructions(array $instructions, $serialization)
154
    {
155
        if (empty($instructions)) {
156
            throw new \InvalidArgumentException('No instruction.');
157
        }
158
        foreach ($instructions as $instruction) {
159
            if (!$instruction instanceof SignatureInstructionInterface) {
160
                throw new \InvalidArgumentException('Bad instruction. Must implement SignatureInstructionInterface.');
161
            }
162
            if (!empty($instruction->getUnprotectedHeader()) && JSONSerializationModes::JSON_COMPACT_SERIALIZATION === $serialization) {
163
                throw new \InvalidArgumentException('Cannot create Compact Json Serialization representation: unprotected header cannot be kept');
164
            }
165
        }
166
    }
167
}
168