SignatureAlgorithmFactory::__construct()   A
last analyzed

Complexity

Conditions 6
Paths 5

Size

Total Lines 18
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 7
c 1
b 0
f 0
nc 5
nop 1
dl 0
loc 18
rs 9.2222
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XMLSecurity\Alg\Signature;
6
7
use SimpleSAML\Assert\Assert;
8
use SimpleSAML\XMLSecurity\Constants as C;
9
use SimpleSAML\XMLSecurity\Exception\BlacklistedAlgorithmException;
10
use SimpleSAML\XMLSecurity\Exception\UnsupportedAlgorithmException;
11
use SimpleSAML\XMLSecurity\Key\KeyInterface;
12
13
use function array_key_exists;
14
use function sprintf;
15
16
/**
17
 * Factory class to create and configure digital signature algorithms.
18
 *
19
 * @package simplesamlphp/xml-security
20
 */
21
final class SignatureAlgorithmFactory
22
{
23
    /**
24
     * A cache of algorithm implementations indexed by algorithm ID.
25
     *
26
     * @var array<string, \SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmInterface>
27
     */
28
    protected static array $cache = [];
29
30
    /**
31
     * Whether the factory has been initialized or not.
32
     *
33
     * @var bool
34
     */
35
    protected static bool $initialized = false;
36
37
    /**
38
     * An array of blacklisted algorithms.
39
     *
40
     * Defaults to RSA-SHA1 & HMAC-SHA1 due to the weakness of SHA1.
41
     *
42
     * @var string[]
43
     */
44
    public const DEFAULT_BLACKLIST = [
45
        C::SIG_RSA_SHA1,
46
        C::SIG_HMAC_SHA1,
47
    ];
48
49
50
    /**
51
     * An array of default algorithms that can be used.
52
     *
53
     * @var class-string[]
54
     */
55
    private const SUPPORTED_DEFAULTS = [
56
        RSA::class,
57
        HMAC::class,
58
    ];
59
60
61
    /**
62
     * Build a factory that creates algorithms.
63
     *
64
     * @param string[] $blacklist A list of algorithms forbidden for their use.
65
     */
66
    public function __construct(
67
        protected array $blacklist = self::DEFAULT_BLACKLIST,
68
    ) {
69
        // initialize the cache for supported algorithms per known implementation
70
        if (!self::$initialized) {
71
            foreach (self::SUPPORTED_DEFAULTS as $algorithm) {
72
                foreach ($algorithm::getSupportedAlgorithms() as $algId) {
73
                    if (array_key_exists($algId, self::$cache) && !array_key_exists($algId, $this->blacklist)) {
74
                        /*
75
                         * If the key existed before initialization, that means someone registered a handler for this
76
                         * algorithm, so we should respect that and skip registering the default here.
77
                         */
78
                        continue;
79
                    }
80
                    self::$cache[$algId] = $algorithm;
81
                }
82
            }
83
            self::$initialized = true;
84
        }
85
    }
86
87
88
    /**
89
     * Get a new object implementing the given digital signature algorithm.
90
     *
91
     * @param string $algId The identifier of the algorithm desired.
92
     * @param \SimpleSAML\XMLSecurity\Key\KeyInterface $key The key to use with the given algorithm.
93
     *
94
     * @return \SimpleSAML\XMLSecurity\Alg\Signature\SignatureAlgorithmInterface An object implementing the given
95
     * algorithm.
96
     *
97
     * @throws \SimpleSAML\XMLSecurity\Exception\UnsupportedAlgorithmException If an error occurs, e.g. the given
98
     * algorithm is blacklisted, unknown or the given key is not suitable for it.
99
     */
100
    public function getAlgorithm(
101
        string $algId,
102
        #[\SensitiveParameter]
103
        KeyInterface $key,
104
    ): SignatureAlgorithmInterface {
105
        Assert::notInArray(
106
            $algId,
107
            $this->blacklist,
108
            sprintf('Blacklisted algorithm: \'%s\'.', $algId),
109
            BlacklistedAlgorithmException::class,
110
        );
111
        Assert::keyExists(
112
            self::$cache,
113
            $algId,
114
            sprintf('Unknown or unsupported algorithm: \'%s\'.', $algId),
115
            UnsupportedAlgorithmException::class,
116
        );
117
118
        return new self::$cache[$algId]($key, $algId);
119
    }
120
121
122
    /**
123
     * Register an implementation of some algorithm(s) for its use.
124
     *
125
     * @param class-string $className
126
     */
127
    public static function registerAlgorithm(string $className): void
128
    {
129
        Assert::implementsInterface(
130
            $className,
131
            SignatureAlgorithmInterface::class,
132
            sprintf(
133
                'Cannot register algorithm "%s", must implement %s.',
134
                $className,
135
                SignatureAlgorithmInterface::class,
136
            ),
137
        );
138
139
        foreach ($className::getSupportedAlgorithms() as $algId) {
140
            self::$cache[$algId] = $className;
141
        }
142
    }
143
}
144