Completed
Push — master ( 8a9942...f209ee )
by Florent
02:39
created

Signer::checkB64HeaderAndCrit()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 10
rs 9.4285
cc 2
eloc 6
nc 2
nop 1
1
<?php
2
3
/*
4
 * The MIT License (MIT)
5
 *
6
 * Copyright (c) 2014-2016 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 Assert\Assertion;
15
use Base64Url\Base64Url;
16
use Jose\Algorithm\SignatureAlgorithmInterface;
17
use Jose\Behaviour\CommonSigningMethods;
18
use Jose\Behaviour\HasJWAManager;
19
use Jose\Behaviour\HasKeyChecker;
20
use Jose\Behaviour\HasLogger;
21
use Jose\Factory\AlgorithmManagerFactory;
22
use Jose\Object\JWKInterface;
23
use Jose\Object\JWSInterface;
24
use Jose\Object\Signature;
25
use Jose\Object\SignatureInterface;
26
use Psr\Log\LoggerInterface;
27
use Psr\Log\LogLevel;
28
29
final class Signer implements SignerInterface
30
{
31
    use HasKeyChecker;
32
    use HasJWAManager;
33
    use HasLogger;
34
    use CommonSigningMethods;
35
36
    /**
37
     * Signer constructor.
38
     *
39
     * @param string[]|\Jose\Algorithm\SignatureAlgorithmInterface[] $signature_algorithms
40
     */
41
    public function __construct(array $signature_algorithms)
42
    {
43
        $this->setSignatureAlgorithms($signature_algorithms);
44
45
        $this->setJWAManager(AlgorithmManagerFactory::createAlgorithmManager($signature_algorithms));
46
    }
47
48
    /**
49
     * {@inheritdoc}
50
     */
51
    public static function createSigner(array $signature_algorithms, LoggerInterface $logger = null)
52
    {
53
        $signer = new self($signature_algorithms);
54
        if (null !== $logger) {
55
            $signer->enableLogging($logger);
56
        }
57
58
        return $signer;
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     */
64
    public function sign(JWSInterface &$jws)
65
    {
66
        $this->log(LogLevel::INFO, 'Trying to sign the JWS object', ['jws' => $jws]);
67
        $nb_signatures = $jws->countSignatures();
68
69
        for ($i = 0; $i < $nb_signatures; $i++) {
70
            $this->computeSignature($jws, $jws->getSignature($i));
71
        }
72
        $this->log(LogLevel::INFO, 'JWS object signed!');
73
    }
74
75
    /**
76
     * @param \Jose\Object\JWSInterface       $jws
77
     * @param \Jose\Object\SignatureInterface $signature
78
     */
79
    private function computeSignature(JWSInterface $jws, SignatureInterface &$signature)
80
    {
81
        $this->log(LogLevel::DEBUG, 'Creation of the signature');
82
        if (null === $signature->getSignatureKey()) {
83
            $this->log(LogLevel::DEBUG, 'The signature key is not set. Aborting.');
84
85
            return;
86
        }
87
        $this->checkKeyUsage($signature->getSignatureKey(), 'signature');
88
89
        $signature_algorithm = $this->getSignatureAlgorithm($signature->getAllHeaders(), $signature->getSignatureKey());
90
91
        $this->log(LogLevel::DEBUG, 'Trying to compute the signature');
92
        $input = $this->getInputToSign($jws, $signature);
93
94
        $value = $signature_algorithm->sign(
95
            $signature->getSignatureKey(),
96
            $input
97
        );
98
99
        $this->log(LogLevel::DEBUG, 'Signature computation done');
100
101
        $signature = Signature::createSignatureFromLoadedData(
102
            $value,
103
            $signature->getEncodedProtectedHeaders(),
104
            $signature->getHeaders()
105
        );
106
107
        $this->log(LogLevel::DEBUG, 'The signature is done', ['signature' => $signature]);
108
    }
109
110
    /**
111
     * @param \Jose\Object\JWSInterface       $jws
112
     * @param \Jose\Object\SignatureInterface $signature
113
     *
114
     * @return string
115
     */
116
    private function getInputToSign(JWSInterface $jws, SignatureInterface $signature)
117
    {
118
        $this->checkB64HeaderAndCrit($signature);
119
        $encoded_protected_headers = $signature->getEncodedProtectedHeaders();
120
        $payload = $jws->getPayload();
121
        if (!$signature->hasProtectedHeader('b64') || true === $signature->getProtectedHeader('b64')) {
122
            $encoded_payload = Base64Url::encode(is_string($payload) ? $payload : json_encode($payload));
123
124
            return sprintf('%s.%s', $encoded_protected_headers, $encoded_payload);
125
        }
126
127
        return sprintf('%s.%s', $encoded_protected_headers, $payload);
128
    }
129
130
    /**
131
     * @param \Jose\Object\SignatureInterface $signature
132
     * 
133
     * @throws \InvalidArgumentException
134
     */
135
    private function checkB64HeaderAndCrit(SignatureInterface $signature)
136
    {
137
        if (!$signature->hasProtectedHeader('b64')) {
138
            return;
139
        }
140
        
141
        Assertion::true($signature->hasProtectedHeader('crit'), 'The protected header parameter "crit" is mandatory when protected header parameter "b64" is set.');
142
        Assertion::isArray($signature->getProtectedHeader('crit'), 'The protected header parameter "crit" must be an array.');
143
        Assertion::inArray('b64', $signature->getProtectedHeader('crit'), 'The protected header parameter "crit" must contain "b64" when protected header parameter "b64" is set.');
144
    }
145
146
    /**
147
     * @param array                     $complete_header The complete header
148
     * @param \Jose\Object\JWKInterface $key
149
     *
150
     * @return \Jose\Algorithm\SignatureAlgorithmInterface
151
     */
152
    private function getSignatureAlgorithm(array $complete_header, JWKInterface $key)
153
    {
154
        $this->log(LogLevel::DEBUG, 'Trying to find the algorithm used to sign');
155
        Assertion::keyExists($complete_header, 'alg', 'No "alg" parameter set in the header.');
156
157
        $this->log(LogLevel::DEBUG, 'The algorithm is {alg}', ['alg' => $complete_header['alg']]);
158
159
        Assertion::false(
160
            $key->has('alg') && $key->get('alg') !== $complete_header['alg'],
161
            sprintf('The algorithm "%s" is not allowed with this key.', $complete_header['alg'])
162
        );
163
164
        $signature_algorithm = $this->getJWAManager()->getAlgorithm($complete_header['alg']);
165
        Assertion::isInstanceOf($signature_algorithm, SignatureAlgorithmInterface::class, sprintf('The algorithm "%s" is not supported.', $complete_header['alg']));
166
167
        $this->log(LogLevel::DEBUG, 'The algorithm {alg} is supported', ['alg' => $complete_header['alg'], 'handler' => $signature_algorithm]);
168
169
        return $signature_algorithm;
170
    }
171
}
172