Completed
Push — master ( 5f00e3...074281 )
by Florent
02:25
created

Signer::checkInstructions()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 14
rs 8.8571
cc 6
eloc 8
nc 5
nop 2
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(
40
        JWAManagerInterface $jwa_manager,
41
        PayloadConverterManagerInterface $payload_converter_manager
42
    ) {
43
        $this->setJWAManager($jwa_manager);
44
        $this->setPayloadConverter($payload_converter_manager);
45
    }
46
47
    /**
48
     * {@inheritdoc}
49
     */
50
    public function sign($input, array $instructions, $serialization = JSONSerializationModes::JSON_COMPACT_SERIALIZATION, $detached_signature = false, &$detached_payload = null)
51
    {
52
        $additional_header = [];
53
        $input = $this->getPayloadConverter()->convertPayloadToString($additional_header, $input);
54
        $this->checkInstructions($instructions, $serialization);
55
56
        $jwt_payload = Base64Url::encode($input);
57
58
        $signatures = [
59
            'payload'    => $jwt_payload,
60
            'signatures' => [],
61
        ];
62
63
        foreach ($instructions as $instruction) {
64
            $signatures['signatures'][] = $this->computeSignature($instruction, $jwt_payload, $additional_header);
65
        }
66
67
        if (true === $detached_signature) {
68
            $detached_payload = $signatures['payload'];
69
            unset($signatures['payload']);
70
        }
71
72
        $prepared = Converter::convert($signatures, $serialization);
73
74
        return is_array($prepared) ? current($prepared) : $prepared;
75
    }
76
77
    /**
78
     * @param \Jose\Object\SignatureInstructionInterface $instruction
79
     * @param string                                     $jwt_payload
80
     * @param array                                      $additional_header
81
     *
82
     * @return array
83
     */
84
    protected function computeSignature(SignatureInstructionInterface $instruction, $jwt_payload, array $additional_header)
85
    {
86
        $protected_header = array_merge($instruction->getProtectedHeader(), $additional_header);
87
        $unprotected_header = $instruction->getUnprotectedHeader();
88
        $complete_header = array_merge($protected_header, $protected_header);
89
90
        $jwt_protected_header = empty($protected_header) ? null : Base64Url::encode(json_encode($protected_header));
91
92
        $signature_algorithm = $this->getSignatureAlgorithm($complete_header, $instruction->getKey());
93
94
        if (!$this->checkKeyUsage($instruction->getKey(), 'signature')) {
95
            throw new \InvalidArgumentException('Key cannot be used to sign');
96
        }
97
98
        $signature = $signature_algorithm->sign($instruction->getKey(), $jwt_protected_header.'.'.$jwt_payload);
99
100
        $jwt_signature = Base64Url::encode($signature);
101
102
        $result = [
103
            'signature' => $jwt_signature,
104
        ];
105
        if (null !== $protected_header) {
106
            $result['protected'] = $jwt_protected_header;
107
        }
108
        if (!empty($unprotected_header)) {
109
            $result['header'] = $unprotected_header;
110
        }
111
112
        return $result;
113
    }
114
115
    /**
116
     * @param array                     $complete_header The complete header
117
     * @param \Jose\Object\JWKInterface $key
118
     *
119
     * @return \Jose\Algorithm\Signature\SignatureInterface
120
     */
121
    protected function getSignatureAlgorithm(array $complete_header, JWKInterface $key)
122
    {
123
        if (!array_key_exists('alg', $complete_header)) {
124
            throw new \InvalidArgumentException('No "alg" parameter set in the header.');
125
        }
126
        if ($key->has('alg') && $key->get('alg') !== $complete_header['alg']) {
127
            throw new \InvalidArgumentException(sprintf('The algorithm "%s" is allowed with this key.', $complete_header['alg']));
128
        }
129
130
        $signature_algorithm = $this->getJWAManager()->getAlgorithm($complete_header['alg']);
131
        if (!$signature_algorithm instanceof SignatureInterface) {
132
            throw new \InvalidArgumentException(sprintf('The algorithm "%s" is not supported.', $complete_header['alg']));
133
        }
134
135
        return $signature_algorithm;
136
    }
137
138
    /**
139
     * @param \Jose\Object\EncryptionInstructionInterface[] $instructions
140
     * @param string                                        $serialization
141
     */
142
    protected function checkInstructions(array $instructions, $serialization)
143
    {
144
        if (empty($instructions)) {
145
            throw new \InvalidArgumentException('No instruction.');
146
        }
147
        if (count($instructions) > 1 && JSONSerializationModes::JSON_SERIALIZATION !== $serialization) {
148
            throw new \InvalidArgumentException('Only one instruction authorized when Compact or Flattened Serialization Overview is selected.');
149
        }
150
        foreach ($instructions as $instruction) {
151
            if (!$instruction instanceof SignatureInstructionInterface) {
152
                throw new \InvalidArgumentException('Bad instruction. Must implement SignatureInstructionInterface.');
153
            }
154
        }
155
    }
156
}
157