/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 | | } |