Coverage Report

Created: 2026-10-28 07:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibCrypto/ASN1/PEM.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2021, the SerenityOS developers.
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#include <AK/Base64.h>
8
#include <AK/GenericLexer.h>
9
#include <LibCrypto/ASN1/PEM.h>
10
11
namespace Crypto {
12
13
ByteBuffer decode_pem(ReadonlyBytes data)
14
1.23k
{
15
1.23k
    GenericLexer lexer { data };
16
1.23k
    ByteBuffer decoded;
17
18
1.23k
    Vector<u8, 4> buffer;
19
20
    // FIXME: Parse multiple.
21
1.23k
    enum {
22
1.23k
        PreStartData,
23
1.23k
        Started,
24
1.23k
        Ended,
25
1.23k
    } state { PreStartData };
26
1.19M
    while (!lexer.is_eof()) {
27
1.19M
        switch (state) {
28
1.18M
        case PreStartData:
29
1.18M
            if (lexer.consume_specific("-----BEGIN"sv))
30
857
                state = Started;
31
1.18M
            lexer.consume_line();
32
1.18M
            break;
33
7.94k
        case Started: {
34
7.94k
            if (lexer.consume_specific("-----END"sv)) {
35
28
                state = Ended;
36
28
                lexer.consume_line();
37
28
                break;
38
28
            }
39
7.92k
            auto maybe_error = [&]() -> ErrorOr<void> {
40
7.92k
                auto next_line = lexer.consume_line().trim_whitespace(TrimMode::Right);
41
7.92k
                if (!buffer.is_empty()) {
42
10.4k
                    while (buffer.size() < 4 && !next_line.is_empty()) {
43
6.36k
                        buffer.append(next_line[0]);
44
6.36k
                        next_line = next_line.bytes().slice(1);
45
6.36k
                    }
46
4.08k
                    if (buffer.size() != 4)
47
1.31k
                        return {};
48
2.76k
                    auto b64decoded = TRY(decode_base64(StringView { buffer }));
49
2.74k
                    TRY(decoded.try_append(b64decoded.data(), b64decoded.size()));
50
2.74k
                    buffer.clear();
51
2.74k
                }
52
53
6.58k
                auto remainder = next_line.length() % 4;
54
6.58k
                auto end = next_line.length() - remainder;
55
56
6.58k
                buffer.extend(next_line.bytes().slice_from_end(remainder));
57
58
6.58k
                auto b64decoded = TRY(decode_base64(next_line.bytes().trim(end)));
59
6.21k
                TRY(decoded.try_append(b64decoded.data(), b64decoded.size()));
60
61
6.21k
                return {};
62
6.21k
            }();
63
64
7.92k
            if (maybe_error.is_error()) {
65
387
                dbgln("Failed to decode PEM: {}", maybe_error.error());
66
387
                return {};
67
387
            }
68
7.53k
            break;
69
7.92k
        }
70
7.53k
        case Ended:
71
20
            lexer.consume_all();
72
20
            break;
73
0
        default:
74
0
            VERIFY_NOT_REACHED();
75
1.19M
        }
76
1.19M
    }
77
78
848
    if (!buffer.is_empty()) {
79
151
        dbgln("Failed to decode PEM: Invalid length of base64 encoded string");
80
151
        return {};
81
151
    }
82
83
697
    return decoded;
84
848
}
85
86
ErrorOr<Vector<ByteBuffer>> decode_pems(ReadonlyBytes data)
87
0
{
88
0
    GenericLexer lexer { data };
89
0
    ByteBuffer decoded;
90
0
    Vector<ByteBuffer> pems;
91
92
0
    enum {
93
0
        Junk,
94
0
        Parsing,
95
0
    } state { Junk };
96
0
    while (!lexer.is_eof()) {
97
0
        switch (state) {
98
0
        case Junk:
99
0
            if (lexer.consume_specific("-----BEGIN"sv))
100
0
                state = Parsing;
101
0
            lexer.consume_line();
102
0
            break;
103
0
        case Parsing: {
104
0
            if (lexer.consume_specific("-----END"sv)) {
105
0
                state = Junk;
106
0
                lexer.consume_line();
107
0
                TRY(pems.try_append(decoded));
108
0
                decoded.clear();
109
0
                break;
110
0
            }
111
0
            auto b64decoded = TRY(decode_base64(lexer.consume_line().trim_whitespace(TrimMode::Right)));
112
0
            TRY(decoded.try_append(b64decoded.data(), b64decoded.size()));
113
0
            break;
114
0
        }
115
0
        default:
116
0
            VERIFY_NOT_REACHED();
117
0
        }
118
0
    }
119
120
0
    return pems;
121
0
}
122
123
ErrorOr<ByteBuffer> encode_pem(ReadonlyBytes data, PEMType type)
124
0
{
125
0
    ByteBuffer encoded;
126
0
    StringView block_start;
127
0
    StringView block_end;
128
129
0
    switch (type) {
130
0
    case Certificate:
131
0
        block_start = "-----BEGIN CERTIFICATE-----\n"sv;
132
0
        block_end = "-----END CERTIFICATE-----\n"sv;
133
0
        break;
134
0
    case PrivateKey:
135
0
        block_start = "-----BEGIN PRIVATE KEY-----\n"sv;
136
0
        block_end = "-----END PRIVATE KEY-----\n"sv;
137
0
        break;
138
0
    default:
139
0
        VERIFY_NOT_REACHED();
140
0
    }
141
142
0
    auto b64encoded = TRY(encode_base64(data));
143
144
0
    TRY(encoded.try_append(block_start.bytes()));
145
146
0
    size_t to_read = 64;
147
0
    for (size_t i = 0; i < b64encoded.bytes().size(); i += to_read) {
148
0
        if (i + to_read > b64encoded.bytes().size())
149
0
            to_read = b64encoded.bytes().size() - i;
150
0
        TRY(encoded.try_append(b64encoded.bytes().slice(i, to_read)));
151
0
        TRY(encoded.try_append("\n"sv.bytes()));
152
0
    }
153
154
0
    TRY(encoded.try_append(block_end.bytes()));
155
156
0
    return encoded;
157
0
}
158
159
}