Passed
Pull Request — master (#43)
by Tim
01:51
created

PEM::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 4
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace SimpleSAML\XMLSecurity\CryptoEncoding;
6
7
use SimpleSAML\XMLSecurity\Exception\IOException;
8
use UnexpectedValueException;
9
10
use function base64_decode;
11
use function base64_encode;
12
use function chunk_split;
13
use function file_get_contents;
14
use function is_readable;
15
use function preg_match;
16
use function preg_replace;
17
use function sprintf;
18
use function trim;
19
20
/**
21
 * Implements PEM file encoding and decoding.
22
 *
23
 * @see https://tools.ietf.org/html/rfc7468
24
 */
25
class PEM
26
{
27
    // well-known PEM types
28
    public const TYPE_CERTIFICATE = 'CERTIFICATE';
29
    public const TYPE_CRL = 'X509 CRL';
30
    public const TYPE_CERTIFICATE_REQUEST = 'CERTIFICATE REQUEST';
31
    public const TYPE_ATTRIBUTE_CERTIFICATE = 'ATTRIBUTE CERTIFICATE';
32
    public const TYPE_PRIVATE_KEY = 'PRIVATE KEY';
33
    public const TYPE_PUBLIC_KEY = 'PUBLIC KEY';
34
    public const TYPE_ENCRYPTED_PRIVATE_KEY = 'ENCRYPTED PRIVATE KEY';
35
    public const TYPE_RSA_PRIVATE_KEY = 'RSA PRIVATE KEY';
36
    public const TYPE_RSA_PUBLIC_KEY = 'RSA PUBLIC KEY';
37
    public const TYPE_EC_PRIVATE_KEY = 'EC PRIVATE KEY';
38
    public const TYPE_PKCS7 = 'PKCS7';
39
    public const TYPE_CMS = 'CMS';
40
41
    /**
42
     * Regular expression to match PEM block.
43
     *
44
     * @var string
45
     */
46
    public const PEM_REGEX =
47
        '/' .
48
        '(?:^|[\r\n])' .                 // line start
49
        '-----BEGIN (.+?)-----[\r\n]+' . // header
50
        '(.+?)' .                        // payload
51
        '[\r\n]+-----END \\1-----' .     // footer
52
        '/ms';
53
54
    /**
55
     * Content type.
56
     *
57
     * @var string
58
     */
59
    protected string $type;
60
61
    /**
62
     * Payload.
63
     *
64
     * @var string
65
     */
66
    protected string $data;
67
68
69
    /**
70
     * Constructor.
71
     *
72
     * @param string $type Content type
73
     * @param string $data Payload
74
     */
75
    public function __construct(string $type, string $data)
76
    {
77
        $this->type = $type;
78
        $this->data = $data;
79
    }
80
81
82
    /**
83
     * @return string
84
     */
85
    public function __toString(): string
86
    {
87
        return $this->string();
88
    }
89
90
91
    /**
92
     * Initialize from a PEM-formatted string.
93
     *
94
     * @param string $str
95
     *
96
     * @throws \UnexpectedValueException If string is not valid PEM
97
     *
98
     * @return self
99
     */
100
    public static function fromString(string $str): self
101
    {
102
        if (!preg_match(self::PEM_REGEX, $str, $match)) {
103
            throw new UnexpectedValueException('Not a PEM formatted string.');
104
        }
105
106
        $payload = preg_replace('/\s+/', '', $match[2]);
107
        $data = base64_decode($payload, true);
108
        if ($data === false) {
109
            throw new UnexpectedValueException('Failed to decode PEM data.');
110
        }
111
112
        return new self($match[1], $data);
113
    }
114
115
116
    /**
117
     * Initialize from a file.
118
     *
119
     * @param string $filename Path to file
120
     *
121
     * @throws \RuntimeException If file reading fails
122
     *
123
     * @return self
124
     */
125
    public static function fromFile(string $filename): self
126
    {
127
        error_clear_last();
128
        $str = @file_get_contents($filename);
129
130
        if (!is_readable($filename) || ($str === false)) {
131
            $e = error_get_last();
132
            $error = $e['message'] ?: "Check that the file exists and can be read.";
133
            throw new IOException(sprintf("File '%s' was not loaded;  %s", $filename, $error));
134
        }
135
136
        return self::fromString($str);
137
    }
138
139
140
    /**
141
     * Get content type.
142
     *
143
     * @return string
144
     */
145
    public function type(): string
146
    {
147
        return $this->type;
148
    }
149
150
151
    /**
152
     * Get payload.
153
     *
154
     * @return string
155
     */
156
    public function data(): string
157
    {
158
        return $this->data;
159
    }
160
161
162
    /**
163
     * Encode to PEM string.
164
     *
165
     * @return string
166
     */
167
    public function string(): string
168
    {
169
        return sprintf(
170
            "-----BEGIN %s-----\n%s\n-----END %s-----",
171
            $this->type,
172
            trim(chunk_split(base64_encode($this->data), 64, "\n")),
173
            $this->type,
174
        );
175
    }
176
}
177