PublicKey::makeASN1Segment()   B
last analyzed

Complexity

Conditions 6
Paths 12

Size

Total Lines 32
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 6
eloc 23
c 2
b 0
f 0
nc 12
nop 2
dl 0
loc 32
rs 8.9297
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XMLSecurity\Key;
6
7
use SimpleSAML\Assert\Assert;
8
use SimpleSAML\XMLSecurity\CryptoEncoding\PEM;
9
10
use function base64_encode;
11
use function chr;
12
use function chunk_split;
13
use function ord;
14
use function pack;
15
use function sprintf;
16
17
/**
18
 * A class modeling public keys for their use in asymmetric algorithms.
19
 *
20
 * @package simplesamlphp/xml-security
21
 */
22
class PublicKey extends AsymmetricKey
23
{
24
    /** @var int */
25
    public const ASN1_TYPE_INTEGER = 0x02; // 2
26
27
    /** @var int */
28
    public const ASN1_TYPE_BIT_STRING = 0x03; // 3
29
30
    /** @var int */
31
    public const ASN1_TYPE_SEQUENCE = 0x30; // 16
32
33
    /** @var int */
34
    public const ASN1_SIZE_128 = 0x80; // 128
35
36
    /** @var int */
37
    public const ASN1_SIZE_256 = 0x0100; // 256
38
39
    /** @var int */
40
    public const ASN1_SIZE_65535 = 0x010000; // 65535
41
42
43
    /**
44
     * Create a new public key from the PEM-encoded key material.
45
     *
46
     * @param \SimpleSAML\XMLSecurity\CryptoEncoding\PEM $key The PEM-encoded key material.
47
     */
48
    final public function __construct(
49
        #[\SensitiveParameter]
50
        PEM $key,
51
    ) {
52
        Assert::oneOf(
53
            $key->type(),
54
            [PEM::TYPE_PUBLIC_KEY, PEM::TYPE_RSA_PUBLIC_KEY],
55
            "PEM structure has the wrong type %s.",
56
        );
57
58
        parent::__construct($key);
59
    }
60
61
62
    /**
63
     * Encode data in ASN.1.
64
     *
65
     * @param int $type The type of data.
66
     * @param string $string The data to encode.
67
     *
68
     * @return null|string The encoded data, or null if it was too long.
69
     */
70
    protected static function makeASN1Segment(int $type, string $string): ?string
71
    {
72
        switch ($type) {
73
            case self::ASN1_TYPE_INTEGER:
74
                if (ord($string) > self::ASN1_SIZE_128 - 1) {
75
                    $string = chr(0) . $string;
76
                }
77
                break;
78
            case self::ASN1_TYPE_BIT_STRING:
79
                $string = chr(0) . $string;
80
                break;
81
        }
82
83
        $length = strlen($string);
84
        Assert::lessThan($length, self::ASN1_SIZE_65535);
85
86
        if ($length < self::ASN1_SIZE_128) {
87
            $output = sprintf("%c%c%s", $type, $length, $string);
88
        } elseif ($length < self::ASN1_SIZE_256) {
89
            $output = sprintf("%c%c%c%s", $type, self::ASN1_SIZE_128 + 1, $length, $string);
90
        } else { // ($length < self::ASN1_SIZE_65535)
91
            $output = sprintf(
92
                "%c%c%c%c%s",
93
                $type,
94
                self::ASN1_SIZE_128 + 2,
95
                $length / 0x0100,
96
                $length % 0x0100,
97
                $string,
98
            );
99
        }
100
101
        return $output;
102
    }
103
104
105
    /**
106
     * Create a new public key from its RSA details (modulus and exponent).
107
     *
108
     * @param string $modulus The modulus of the given key.
109
     * @param string $exponent The exponent of the given key.
110
     *
111
     * @return \SimpleSAML\XMLSecurity\Key\PublicKey A new public key with the given modulus and exponent.
112
     */
113
    public static function fromDetails(string $modulus, string $exponent): PublicKey
114
    {
115
        return new static(PEM::fromString(
116
            "-----BEGIN PUBLIC KEY-----\n" .
117
            chunk_split(
118
                base64_encode(
119
                    self::makeASN1Segment(
120
                        self::ASN1_TYPE_SEQUENCE,
121
                        pack("H*", "300D06092A864886F70D0101010500") . // RSA alg id
122
                        self::makeASN1Segment( // bitstring
123
                            self::ASN1_TYPE_BIT_STRING,
124
                            self::makeASN1Segment( // sequence
125
                                self::ASN1_TYPE_SEQUENCE,
126
                                self::makeASN1Segment(self::ASN1_TYPE_INTEGER, $modulus)
127
                                . self::makeASN1Segment(self::ASN1_TYPE_INTEGER, $exponent),
128
                            ),
129
                        ),
130
                    ),
131
                ),
132
                64,
133
                "\n",
134
            ) .
135
            "-----END PUBLIC KEY-----\n",
136
        ));
137
    }
138
139
140
    /**
141
     * Get a new public key from a file.
142
     *
143
     * @param string $file The file where the PEM-encoded private key is stored.
144
     *
145
     * @return static A new public key.
146
     *
147
     * @throws \SimpleSAML\XMLSecurity\Exception\InvalidArgumentException If the file cannot be read.
148
     */
149
    public static function fromFile(string $file): static
150
    {
151
        return new static(PEM::fromFile($file));
152
    }
153
}
154