PEM   A
last analyzed

Complexity

Total Complexity 11

Size/Duplication

Total Lines 135
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 41
c 1
b 0
f 0
dl 0
loc 135
rs 10
wmc 11

7 Methods

Rating   Name   Duplication   Size   Complexity  
A type() 0 3 1
A data() 0 3 1
A fromString() 0 13 3
A __construct() 0 4 1
A string() 0 7 1
A __toString() 0 3 1
A fromFile() 0 12 3
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
    /**
56
     * Constructor.
57
     *
58
     * @param string $type Content type
59
     * @param string $data Payload
60
     */
61
    public function __construct(
62
        protected string $type,
63
        protected string $data,
64
    ) {
65
    }
66
67
68
    /**
69
     * @return string
70
     */
71
    public function __toString(): string
72
    {
73
        return $this->string();
74
    }
75
76
77
    /**
78
     * Initialize from a PEM-formatted string.
79
     *
80
     * @param string $str
81
     *
82
     * @throws \UnexpectedValueException If string is not valid PEM
83
     *
84
     * @return self
85
     */
86
    public static function fromString(string $str): self
87
    {
88
        if (!preg_match(self::PEM_REGEX, $str, $match)) {
89
            throw new UnexpectedValueException('Not a PEM formatted string.');
90
        }
91
92
        $payload = preg_replace('/\s+/', '', $match[2]);
93
        $data = base64_decode($payload, true);
94
        if (empty($data)) {
95
            throw new UnexpectedValueException('Failed to decode PEM data.');
96
        }
97
98
        return new self($match[1], $data);
99
    }
100
101
102
    /**
103
     * Initialize from a file.
104
     *
105
     * @param string $filename Path to file
106
     *
107
     * @throws \RuntimeException If file reading fails
108
     *
109
     * @return self
110
     */
111
    public static function fromFile(string $filename): self
112
    {
113
        error_clear_last();
114
        $str = @file_get_contents($filename);
115
116
        if (!is_readable($filename) || ($str === false)) {
117
            $e = error_get_last();
118
            $error = $e['message'] ?? "Check that the file exists and can be read.";
119
            throw new IOException(sprintf("File '%s' was not loaded;  %s", $filename, $error));
120
        }
121
122
        return self::fromString($str);
123
    }
124
125
126
    /**
127
     * Get content type.
128
     *
129
     * @return string
130
     */
131
    public function type(): string
132
    {
133
        return $this->type;
134
    }
135
136
137
    /**
138
     * Get payload.
139
     *
140
     * @return string
141
     */
142
    public function data(): string
143
    {
144
        return $this->data;
145
    }
146
147
148
    /**
149
     * Encode to PEM string.
150
     *
151
     * @return string
152
     */
153
    public function string(): string
154
    {
155
        return sprintf(
156
            "-----BEGIN %s-----\n%s\n-----END %s-----",
157
            $this->type,
158
            trim(chunk_split(base64_encode($this->data), 64, "\n")),
159
            $this->type,
160
        );
161
    }
162
}
163