mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
[stdlib] Add PEM Encoding/Decoding Module (#2858)
* [stdlib] Add PEM Encoding/Decoding Module * release notes * Removed some unnecessary macro usages. Fixed memory handling with headers. * Make end of line a parameter. Internal encode method -> function. Use more tmem. Remove t-functions. * Update API --------- Co-authored-by: Christoffer Lerno <christoffer@aegik.com> Co-authored-by: Christoffer Lerno <christoffer.lerno@gmail.com>
This commit is contained in:
@@ -160,7 +160,12 @@ macro bool char_in_set(char c, String set)
|
||||
}
|
||||
|
||||
<*
|
||||
@return "a String which is safe to convert to a ZString"
|
||||
Join together an array of strings via a "joiner" sequence, which is inserted between each element.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use."
|
||||
@param [in] s : "An array of strings to join in sequence."
|
||||
@param [in] joiner : "The string used to join each element of `s`."
|
||||
@return "A single string containing the result, allocated via `allocator`, safe to convert to a ZString."
|
||||
*>
|
||||
fn String join(Allocator allocator, String[] s, String joiner)
|
||||
{
|
||||
@@ -189,6 +194,9 @@ fn String join(Allocator allocator, String[] s, String joiner)
|
||||
return (String)data[:offset];
|
||||
}
|
||||
|
||||
<* Alias for `string::join` using the temp allocator. *>
|
||||
macro String tjoin(String[] s, String joiner) => join(tmem, s, joiner);
|
||||
|
||||
<*
|
||||
Replace all instances of one substring with a different string.
|
||||
|
||||
|
||||
355
lib/std/encoding/pem.c3
Normal file
355
lib/std/encoding/pem.c3
Normal file
@@ -0,0 +1,355 @@
|
||||
// Copyright (c) 2026 Zack Puhl <github@xmit.xyz>. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
//
|
||||
// A module for encoding or decoding PEM blobs [mostly] in accordance with RFCs 1421-1424.
|
||||
// This implementation retains a lot of flexibility in parsing input PEM blobs.
|
||||
//
|
||||
module std::encoding::pem;
|
||||
|
||||
import std::collections, std::encoding::base64;
|
||||
|
||||
<* A safe, default tag to use per RFC 1421's rules. *>
|
||||
const String DEFAULT_TAG = "PRIVACY-ENHANCED MESSAGE";
|
||||
|
||||
<* The set of characters which are considered valid for PEM tags (which appear inside of Encapsulation Boundaries). *>
|
||||
const AsciiCharset TAG_SET @local = ascii::@combine_sets(ascii::ALPHA_UPPER_SET, ascii::NUMBER_SET, ascii::@create_set(" _-/+()"));
|
||||
<* The set of characters which are considered valid for optional PEM headers used. *>
|
||||
const AsciiCharset HEADER_KEY_SET @local = ascii::@combine_sets(ascii::ALPHANUMERIC_SET, ascii::@create_set("!#$%&'*+-.^_`|~"));
|
||||
|
||||
<* All PEM Encapsulation Boundaries must use this delimiter to demarcate the PEM from its surrounding content, if any. *>
|
||||
const String EB_DELIMITER @local = "-----";
|
||||
<* All PEM blobs will start with this Encapsulation Boundary prefix. *>
|
||||
const String PRE_EB_PREFIX @local = EB_DELIMITER +++ "BEGIN ";
|
||||
<* All PEM blobs will terminate with this Encapsulation Boundary prefix. *>
|
||||
const String POST_EB_PREFIX @local = EB_DELIMITER +++ "END ";
|
||||
|
||||
alias PemHeader = String[2];
|
||||
|
||||
<* Specify a set of possible PEM en/decoding faults. *>
|
||||
faultdef
|
||||
BODY_REQUIRED, // encoding: no body given (or too few of them)
|
||||
HEADERS_REQUIRED, // encoding: no headers given (or too few of them)
|
||||
HEADER_KEY_REQUIRED, // encoding: blank header keys are not allowed
|
||||
HEADER_VALUE_REQUIRED, // encoding: blank header values are not allowed
|
||||
INVALID_BODY, // decoding: invalid body, likely bad base64
|
||||
INVALID_FORMAT, // decoding: invalid input formatting - no pre-EB or just plain wrong
|
||||
INVALID_HEADER, // decoding: invalid headers
|
||||
INVALID_HEADER_KEY, // decoding: invalid or empty header key
|
||||
INVALID_PRE_EB, // decoding: invalid pre-EncapsBoundary BEFORE the PEM body
|
||||
INVALID_POST_EB, // decoding: invalid post-EncapsBoundary AFTER the PEM body
|
||||
INVALID_TAG, // decoding: invalid tag within an EB
|
||||
MISMATCHED_TAG, // decoding: the tag from the pre-EB doesn't match that of the post-EB
|
||||
MISSING_BODY, // decoding: missing PEM body base64
|
||||
MISSING_HEADER_KEY, // decoding: the header is missing its key
|
||||
MISSING_HEADER_VALUE, // decoding: the header is missing its value
|
||||
MISSING_POST_EB, // decoding: no post-EB was found to close off the PEM
|
||||
MISSING_TAG, // decoding: no tag was defined or parsed from the EB
|
||||
TAG_REQUIRED, // encoding: no/empty tag given (or too few of them)
|
||||
;
|
||||
|
||||
|
||||
<* Represents a PEM object in memory, with a reference to the body data, tag value, and optional headers. *>
|
||||
struct Pem
|
||||
{
|
||||
<* The allocator associated with the PEM's creation and destruction. *>
|
||||
Allocator allocator;
|
||||
<* A flexible 'tag' value used within the Encapsulation Boundary to denote the type of the PEM. *>
|
||||
String tag;
|
||||
<* A set of optional headers used to provide more context or information about the body of the PEM object. *>
|
||||
LinkedHashMap{String, String} headers;
|
||||
<* The core boy data of the PEM itself - the main values to be transmitted in this format. *>
|
||||
char[] data;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Create a new PEM object from a few inputs. Each input (i.e., tag, data, and headers) is copied to a new memory location.
|
||||
The PEM object itself is not allocated in-memory, but is a simple container that points to each value that _is_.
|
||||
|
||||
Key-Value pairs for headers are provided in sequence as variadic arguments: `"key", "value", "key2", "value2", ...`
|
||||
|
||||
Created PEMs that are not temporary should be destroyed with `Pem.free`.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use when copying the provided PEM object's fields."
|
||||
@param [in] data : "The body data of the PEM."
|
||||
@param [in] tag : "The tag value to use within the PEM's Encapsulation Boundary."
|
||||
|
||||
@return "A new PEM object."
|
||||
*>
|
||||
fn Pem create(Allocator allocator, char[] data, String tag, PemHeader... args)
|
||||
{
|
||||
Pem result = {
|
||||
.allocator = allocator,
|
||||
.tag = tag.copy(allocator),
|
||||
.data = allocator::clone_slice(allocator, data),
|
||||
};
|
||||
result.headers.init(allocator, capacity: max(args.len, 16));
|
||||
foreach (arg : args)
|
||||
{
|
||||
result.add_header(arg[0], arg[1]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Duplicate a `Pem` container and allocate copies of its members using the given allocator.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use when copying the `Pem` members."
|
||||
*>
|
||||
fn Pem Pem.copy(&self, Allocator allocator)
|
||||
{
|
||||
Pem result = create(allocator, self.data, self.tag);
|
||||
self.headers.@each(;String key, String value)
|
||||
{
|
||||
result.add_header(key, value);
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
<*
|
||||
Safely destroys a `Pem` and deallocate all of its members. This should always be explicitly called when not using `tmem`.
|
||||
*>
|
||||
fn void Pem.free(&self)
|
||||
{
|
||||
mem::zero_volatile(self.data);
|
||||
if (self.allocator != tmem)
|
||||
{
|
||||
self.headers.@each(;String key, String value)
|
||||
{
|
||||
allocator::free(self.allocator, value);
|
||||
};
|
||||
self.headers.free();
|
||||
self.tag.free(self.allocator);
|
||||
allocator::free(self.allocator, self.data);
|
||||
}
|
||||
mem::zero_volatile(@as_char_view(*self));
|
||||
}
|
||||
|
||||
fn void Pem.add_header(&self, String key, String value)
|
||||
{
|
||||
(void)self.headers[key].free(self.allocator);
|
||||
self.headers[key] = value.copy(self.allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
Attempt to decode an input string into one or more `Pem` objects. If the input contains any non-PEM or otherwise
|
||||
invalid data, then this will throw an error. Ideally, this function is used to decode PEM files explicitly, lest
|
||||
the caller need to be sure they're only providing PEM data +/- some intermediate whitespace.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use when creating the `Pem` outputs and members."
|
||||
@param [in] input : "The string to parse one or more PEM blobs from."
|
||||
|
||||
@return "An array of decoded `Pem` objects, depending on how many were present in the input (separated optionally by whitespace)."
|
||||
*>
|
||||
fn Pem[]? decode(Allocator allocator, String input) => @pool()
|
||||
{
|
||||
List{Pem} pem_list;
|
||||
pem_list.tinit();
|
||||
|
||||
String[] lines = input.treplace("\r\n", "\n").tsplit("\n");
|
||||
foreach (&line : lines) *line = (*line).trim_right(); // remove any trailing whitespace as this can disrupt parsing (but shouldn't)
|
||||
while (lines.len > 0)
|
||||
{
|
||||
pem_list.push(_decode_single(allocator, &lines)!);
|
||||
while (lines.len > 0 && lines[0].trim().len == 0) lines = lines[1..]; // skip all empty lines in between or after PEM boundaries
|
||||
}
|
||||
return pem_list.to_array(allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
INTERNAL ONLY: Decode one PEM at a time, from pre-EB to its discovered post-EB.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use during decoding to return the result."
|
||||
@param [&inout] lines_io : "A pointer to an input slice to modify as the single PEM is parsed from it."
|
||||
|
||||
*>
|
||||
fn Pem? _decode_single(Allocator allocator, String[]* lines_io) @local
|
||||
{
|
||||
String[] lines = *lines_io; // copy to local var
|
||||
Pem result = { .allocator = allocator };
|
||||
result.headers.init(allocator);
|
||||
defer catch result.free();
|
||||
|
||||
// Remove any preceding whitespace-only lines.
|
||||
while (lines[0].trim().len == 0) lines = lines[1..];
|
||||
|
||||
if (lines.len < 3) return INVALID_FORMAT~; // at least 3 lines (pre-EB, body, post-EB) are always required
|
||||
|
||||
// The Pre-Encapsulation-Boundary must be of the format: -----BEGIN TAG-----, where "TAG" can be any upper-case identifier [A-Z_ -/]
|
||||
String pre_eb = lines[0];
|
||||
if (pre_eb[0:11] != PRE_EB_PREFIX || pre_eb[^5..] != EB_DELIMITER) return INVALID_PRE_EB~;
|
||||
String tag = pre_eb[PRE_EB_PREFIX.len..^6];
|
||||
if (!tag.len || !tag.trim().len) return MISSING_TAG~;
|
||||
foreach (c : tag) if (!TAG_SET.contains(c)) return INVALID_TAG~;
|
||||
result.tag = tag.copy(allocator);
|
||||
|
||||
// The Post-Encapsulation-Boundary is the same, but uses "END", and the extracted tag must match.
|
||||
// Since the input might contain more than one PEM unit, we need to search for the ending encapsulation boundary dynamically.
|
||||
String post_eb;
|
||||
usz endl;
|
||||
for SEARCH_EB: (endl = 1; endl < lines.len; endl++)
|
||||
{
|
||||
if (lines[endl].len > POST_EB_PREFIX.len && lines[endl][0:EB_DELIMITER.len] == EB_DELIMITER)
|
||||
{
|
||||
post_eb = lines[endl];
|
||||
break SEARCH_EB;
|
||||
}
|
||||
}
|
||||
if (!post_eb.len) return MISSING_POST_EB~;
|
||||
if (post_eb[0:9] != POST_EB_PREFIX || post_eb[^5..] != EB_DELIMITER) return INVALID_POST_EB~;
|
||||
String post_tag = post_eb[POST_EB_PREFIX.len..^6];
|
||||
if (post_tag.len != tag.len || post_tag != tag) return MISMATCHED_TAG~;
|
||||
|
||||
// Now that the inner portion is decapsulated, tag is, strip off the boundaries.
|
||||
*lines_io = lines[endl+1..]; // update the iterated slice of lines from the calling context - see: `decode`
|
||||
lines = lines[1:endl-1];
|
||||
|
||||
// while there's a colon+space in the current line, we should assume that this is a key-value header pair
|
||||
while (lines[0].contains(": "))
|
||||
{
|
||||
if (!HEADER_KEY_SET.contains(lines[0][0])) return INVALID_HEADER~; // not a multiline header? error out if the first char is not appropriate
|
||||
String[] marker = lines; // temporary marker
|
||||
usz span = 1; // how many lines this header spans
|
||||
|
||||
// Search for multi-line key-value pairs, indicated by a whitespace character beginning the current line.
|
||||
for (lines = lines[1..]; lines[0].len > 0 && ascii::WHITESPACE_SET.contains(lines[0][0]); lines = lines[1..], span++);
|
||||
foreach (&line : marker[:span]) *line = (*line).trim(); // always trim on both sides
|
||||
|
||||
String full_header = string::tjoin(marker[:span], " "); // join the lines with a single space
|
||||
if (!full_header.contains(": ")) return INVALID_HEADER~; // reassert the presence of this
|
||||
|
||||
// Extract the key and value from the message, then validate.
|
||||
// The header name should match a valid set of characters, but the value doesn't need to conform to anything other than existing
|
||||
String[] kv = full_header.tsplit(": ", max: 2);
|
||||
if (!kv[0].len) return MISSING_HEADER_KEY~;
|
||||
if (!kv[1].len) return MISSING_HEADER_VALUE~;
|
||||
foreach (c : kv[0]) if (!HEADER_KEY_SET.contains(c)) return INVALID_HEADER_KEY~;
|
||||
|
||||
result.add_header(kv[0], kv[1]); // finally, push the values
|
||||
}
|
||||
|
||||
// if any headers were present, the line after the headers MUST BE EMPTY
|
||||
if (result.headers.len() > 0)
|
||||
{
|
||||
if (lines[0].trim().len > 0) return INVALID_FORMAT~; // but we are forgiving about whitespace here
|
||||
lines = lines[1..];
|
||||
}
|
||||
|
||||
// Here, we assume lines[0] is the start of base64 data. This means there must be at least 1 line, of course.
|
||||
if (lines.len < 1) return MISSING_BODY~;
|
||||
|
||||
// ... While the PEM format specifies a 64-character width on all but the last line of the base64 body,
|
||||
// this parser doesn't need to be particular about that as long as the base64 is ok
|
||||
// In this case, the rest of the lines in the set should be base64 and should decode accordingly
|
||||
String to_decode = string::tjoin(lines, "");
|
||||
if (!to_decode.len) return MISSING_BODY~; // paranoia
|
||||
result.data = (base64::decode(allocator, to_decode) ?? INVALID_BODY~)!;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
<*
|
||||
Encodes a single `Pem` object into a new PEM-formatted string.
|
||||
|
||||
@param pem : "The pem object to encode"
|
||||
@param [&inout] allocator : "The allocator to use for allocating the final encoded string."
|
||||
*>
|
||||
fn String? encode_pem(Pem pem, Allocator allocator, bool use_crlf = false)
|
||||
{
|
||||
if (!pem.data.len) return BODY_REQUIRED~;
|
||||
if (!pem.tag.len) return TAG_REQUIRED~;
|
||||
|
||||
DString out;
|
||||
out.tinit();
|
||||
String line_ending = use_crlf ? "\r\n" : "\n";
|
||||
@pool()
|
||||
{
|
||||
out.appendf(PRE_EB_PREFIX +++ "%s" +++ EB_DELIMITER +++ "%s", pem.tag, line_ending);
|
||||
foreach KEY_ITER: (key : pem.headers.tkeys())
|
||||
{
|
||||
if (!key.len) return HEADER_KEY_REQUIRED~;
|
||||
String value = pem.headers[key]!!;
|
||||
if (!value.len) return HEADER_VALUE_REQUIRED~;
|
||||
usz first_line_length = 64 - 2 - key.len;
|
||||
if (value.len <= first_line_length)
|
||||
{
|
||||
out.appendf("%s: %s%s", key, value, line_ending);
|
||||
continue KEY_ITER;
|
||||
}
|
||||
out.appendf("%s: %s%s", key, value[:first_line_length].trim(), line_ending);
|
||||
value = value[first_line_length..];
|
||||
while (value.len > 0)
|
||||
{
|
||||
out.appendf(" %s%s", (value.len >= 63 ? value[:63] : value[..]).trim(), line_ending);
|
||||
value = value.len >= 63 ? value[63..] : {};
|
||||
}
|
||||
}
|
||||
if (pem.headers.len() > 0) out.append(line_ending);
|
||||
String body = base64::tencode(pem.data);
|
||||
while (body.len > 0)
|
||||
{
|
||||
out.appendf("%s%s", body.len >= 64 ? body[:64] : body[..], line_ending);
|
||||
body = body.len >= 64 ? body[64..] : {};
|
||||
}
|
||||
out.appendf(POST_EB_PREFIX +++ "%s" +++ EB_DELIMITER +++ "%s", pem.tag, line_ending);
|
||||
};
|
||||
|
||||
return allocator == tmem ? out.str_view() : out.copy_str(allocator);
|
||||
}
|
||||
|
||||
<*
|
||||
Encodes a set of input data into a `String` containing the PEM-encoded data.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use when creating the final output string."
|
||||
@param [in] data : "The body data for the output PEM."
|
||||
@param [in] tag : "The tag "
|
||||
*>
|
||||
fn String? encode(Allocator allocator, char[] data, String tag, PemHeader... headers, bool use_crlf = false) => @pool()
|
||||
{
|
||||
if (!data.len) return BODY_REQUIRED~;
|
||||
return encode_pem(create(tmem, data, tag, ...headers), allocator, use_crlf);
|
||||
}
|
||||
|
||||
<*
|
||||
Encode many inputs to a single output string that represents chained/sequential PEM objects in the order they were provided.
|
||||
The length of the `bodies` and `tags` array must be equal.
|
||||
If headers are provided, they must be arrays of String objects, matching both the number of tags and the number of bodies.
|
||||
|
||||
@param [&inout] allocator : "The allocator to use when creating the final output string."
|
||||
@param [in] bodies : "An ordered array of binary arrays, each representing the body of a single PEM."
|
||||
@param [in] tags : "An ordered array of tag strings, each representing the tag of a single PEM."
|
||||
|
||||
@return "A new `String`, allocated with `allocator`, that contains all PEM objects in the order they were given."
|
||||
*>
|
||||
fn String? encode_many(Allocator allocator, char[][] bodies, String[] tags, PemHeader[]... pem_headers, bool use_crlf = false)
|
||||
{
|
||||
usz entries = max(bodies.len, tags.len, pem_headers.len);
|
||||
switch
|
||||
{
|
||||
case bodies.len < entries: return BODY_REQUIRED~;
|
||||
case tags.len < entries: return TAG_REQUIRED~;
|
||||
case pem_headers.len > 0 && pem_headers.len < entries: return HEADERS_REQUIRED~;
|
||||
}
|
||||
|
||||
DString out;
|
||||
out.tinit();
|
||||
|
||||
if (!pem_headers.len)
|
||||
{
|
||||
foreach (x, body : bodies) @pool()
|
||||
{
|
||||
out.append(encode(tmem, body, tags[x], use_crlf: use_crlf)!);
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (i, headers : pem_headers) @pool()
|
||||
{
|
||||
out.append(encode(tmem, bodies[i], tags[i], ...headers, use_crlf: use_crlf)!);
|
||||
};
|
||||
}
|
||||
return allocator == tmem ? out.str_view() : out.copy_str(allocator);
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
- Summarize sort macros as generic function wrappers to reduce the amount of generated code. #2831
|
||||
- Remove dependency on temp allocator in String.join.
|
||||
- Remove dependency on temp allocator in File.open.
|
||||
- Added PEM encoding/decoding. #2858
|
||||
|
||||
### Fixes
|
||||
- Add error message if directory with output file name already exists
|
||||
|
||||
283
test/unit/stdlib/encoding/pem.c3
Normal file
283
test/unit/stdlib/encoding/pem.c3
Normal file
@@ -0,0 +1,283 @@
|
||||
// Copyright (c) 2026 Zack Puhl <github@xmit.xyz>. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license
|
||||
// a copy of which can be found in the LICENSE_STDLIB file.
|
||||
module pem_test @test;
|
||||
|
||||
import std::collections::pair, std::encoding::pem, std::io;
|
||||
|
||||
|
||||
fn void decode_single()
|
||||
{
|
||||
Pem[] res = pem::decode(tmem, SINGLE)!!;
|
||||
test::@check(res.len == 1);
|
||||
test::@check(res[0].tag == "CERTIFICATE");
|
||||
test::@check(res[0].headers.len() == 0);
|
||||
test::@check(res[0].data == SINGLE_DATA);
|
||||
}
|
||||
|
||||
fn void encode_single()
|
||||
{
|
||||
test::@check(pem::encode(tmem, SINGLE_DATA, "CERTIFICATE")!![..^2] == SINGLE);
|
||||
test::@check(pem::encode(tmem, SINGLE_DATA, "CERTIFICATE", use_crlf: true)!![..^3] == SINGLE.treplace("\n", "\r\n"));
|
||||
}
|
||||
|
||||
fn void decode_multi()
|
||||
{
|
||||
const Pair{String, String}[] EXPECTED = {
|
||||
{ "Something", "Here" },
|
||||
{ "A-Multiline", "Header Goes in this spot And another line to back it up" },
|
||||
{ "Non-Multi", "line header!" },
|
||||
{ "I'm-trying", "to confuse things: when parsing the content of this file" },
|
||||
{ "done", "now" },
|
||||
};
|
||||
Pem[] res = pem::decode(tmem, MULTI)!!;
|
||||
test::@check(res.len == 2);
|
||||
test::@check(res[0].tag == "THIS IS NOT A REAL PRIVATE KEY");
|
||||
test::@check(res[0].data == MULTI_DATA);
|
||||
test::@check(res[0].headers.len() == 5);
|
||||
foreach (pair : EXPECTED)
|
||||
{
|
||||
String val = res[0].headers[pair.first]!!;
|
||||
test::@check(res[0].headers.has_key(pair.first), "Expected header key '%s' but it wasn't found.", pair.first);
|
||||
test::@check(val == pair.second, "Expected value '%s' for key '%s' but did not get a match (got: '%s').", pair.second, pair.first, val);
|
||||
}
|
||||
test::@check(res[1].tag == "CERTIFICATE");
|
||||
test::@check(res[1].headers.len() == 0);
|
||||
test::@check(res[1].data == SINGLE_DATA);
|
||||
}
|
||||
|
||||
fn void encode_multi()
|
||||
{
|
||||
String result = pem::encode_many(tmem, { SINGLE_DATA, MULTI_DATA }, { "CERTIFICATE", "THING" })!!;
|
||||
test::@check(result == SIMPLE_MULTI, "Got:\n%s\n\nExpected:\n%s\n\n", result, SIMPLE_MULTI);
|
||||
}
|
||||
|
||||
fn void encode_single_with_headers()
|
||||
{
|
||||
String result = pem::encode(tmem, SINGLE_DATA, "CERTIFICATE", {"My-Header", "Example"}, {"This-is-a-longer-header", "with some extra content that will bleed over to the next line."}, {"Last", "Header is single-line."})!!;
|
||||
test::@check(result == SINGLE_WITH_HEADERS, "Got:\n%s\n\nExpected:\n%s\n\n", result, SINGLE_WITH_HEADERS);
|
||||
}
|
||||
|
||||
fn void leak_check_decode()
|
||||
{
|
||||
Pem[] res = pem::decode(mem, MULTI)!!;
|
||||
test::@check(res.len == 2);
|
||||
|
||||
// don't optimize it away >:I
|
||||
io::printfn(res[0].tag);
|
||||
io::printfn(res[1].tag);
|
||||
|
||||
foreach (r : res) r.free();
|
||||
mem::free(res);
|
||||
|
||||
// ==========================================
|
||||
res = pem::decode(mem, SINGLE)!!;
|
||||
test::@check(res.len == 1);
|
||||
io::printfn(res[0].tag);
|
||||
|
||||
foreach (r : res) r.free();
|
||||
mem::free(res);
|
||||
}
|
||||
|
||||
fn void leak_check_encode()
|
||||
{
|
||||
String encoded = pem::encode(mem, SINGLE_DATA, "SOMETHING", { "Key", "Value" }, {"KeyX", "ValueX" })!!;
|
||||
test::@check(encoded.len > 0);
|
||||
io::printfn("ENCODED_SINGLE:\n%s\n", encoded);
|
||||
encoded.free(mem);
|
||||
|
||||
encoded = pem::encode_many(mem, { SINGLE_DATA, MULTI_DATA }, { "CERTIFICATE", "THING" }, (String[2][]) { {"Key1", "Value1"}, {"KeyX", "ValueX"} }, (String[2][]){ {"more", "more"} })!!;
|
||||
test::@check(encoded.len > 0);
|
||||
io::printfn("ENCODED_MULTI:\n%s\n", encoded);
|
||||
encoded.free(mem);
|
||||
}
|
||||
|
||||
fn void decode_errors()
|
||||
{
|
||||
foreach (i, pair : DECODE_ERRORS) test::@error(pem::decode(tmem, pair.first), pair.second);
|
||||
}
|
||||
|
||||
|
||||
// =============================================================================================
|
||||
// These PEM blobs aren't 'real' in the sense that they're completely public. Tbh I kinda swiped
|
||||
// them from another public repository, and the underlying CN is Superfish, Inc. If these were
|
||||
// real exfiltrations at some point in time, then they're widely public as is... No harm done.
|
||||
|
||||
const String SINGLE = `-----BEGIN CERTIFICATE-----
|
||||
MIIC9TCCAl6gAwIBAgIJANL8E4epRNznMA0GCSqGSIb3DQEBBQUAMFsxGDAWBgNV
|
||||
BAoTD1N1cGVyZmlzaCwgSW5jLjELMAkGA1UEBxMCU0YxCzAJBgNVBAgTAkNBMQsw
|
||||
CQYDVQQGEwJVUzEYMBYGA1UEAxMPU3VwZXJmaXNoLCBJbmMuMB4XDTE0MDUxMjE2
|
||||
MjUyNloXDTM0MDUwNzE2MjUyNlowWzEYMBYGA1UEChMPU3VwZXJmaXNoLCBJbmMu
|
||||
MQswCQYDVQQHEwJTRjELMAkGA1UECBMCQ0ExCzAJBgNVBAYTAlVTMRgwFgYDVQQD
|
||||
Ew9TdXBlcmZpc2gsIEluYy4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOjz
|
||||
Shh2Xxk/sc9Y6X9DBwmVgDXFD/5xMSeBmRImIKXfj2r8QlU57gk4idngNsSsAYJb
|
||||
1Tnm+Y8HiN/+7vahFM6pdEXY/fAXVyqC4XouEpNarIrXFWPRt5tVgA9YvBxJ7SBi
|
||||
3bZMpTrrHD2g/3pxptMQeDOuS8Ic/ZJKocPnQaQtAgMBAAGjgcAwgb0wDAYDVR0T
|
||||
BAUwAwEB/zAdBgNVHQ4EFgQU+5izU38URC7o7tUJml4OVoaoNYgwgY0GA1UdIwSB
|
||||
hTCBgoAU+5izU38URC7o7tUJml4OVoaoNYihX6RdMFsxGDAWBgNVBAoTD1N1cGVy
|
||||
ZmlzaCwgSW5jLjELMAkGA1UEBxMCU0YxCzAJBgNVBAgTAkNBMQswCQYDVQQGEwJV
|
||||
UzEYMBYGA1UEAxMPU3VwZXJmaXNoLCBJbmMuggkA0vwTh6lE3OcwDQYJKoZIhvcN
|
||||
AQEFBQADgYEApHyg7ApKx3DEcWjzOyLi3JyN0JL+c35yK1VEmxu0Qusfr76645Oj
|
||||
1IsYwpTws6a9ZTRMzST4GQvFFQra81eLqYbPbMPuhC+FCxkUF5i0DNSWi+kczJXJ
|
||||
TtCqSwGl9t9JEoFqvtW+znZ9TqyLiOMw7TGEUI+88VAqW0qmXnwPcfo=
|
||||
-----END CERTIFICATE-----`;
|
||||
|
||||
const String MULTI =
|
||||
`-----BEGIN THIS IS NOT A REAL PRIVATE KEY-----
|
||||
Something: Here
|
||||
A-Multiline: Header
|
||||
Goes in this spot
|
||||
And another line to back it up
|
||||
Non-Multi: line header!
|
||||
I'm-trying: to confuse things: when parsing
|
||||
the content of this file
|
||||
done: now
|
||||
|
||||
MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIDHHhyAEZQoICAggA
|
||||
MBQGCCqGSIb3DQMHBAiHEg+MCYQ30ASCAoDEvGvFRHvtWOb5Rc0f3lbVKqeUvWSz
|
||||
xQn+rZELHnwb6baolmbFcsi6XkacVzL/EF7Ll4de/CSQ6pZZCCvfDzov0mPOuGve
|
||||
SAe7hbAcol7+JWVfzbnVTblPf0i7mwSvK61cKq7YfcKJ2os/uJGpeX9zraywWyFx
|
||||
f+EdTr348dOez8uHkURyY1cvSHsIdITALkChOonAYT68SVighTeB6xOCwfmsHx+X
|
||||
3Qbhom2YCIxfJiaAoz2/LndCpDaEfOrVrxXFOKXrIbmeDEyjDQj16AVni9uuaj7l
|
||||
NiO3zrrqxsfdVINPaAYRKQnS102jXqkH01z72c/MpMMC6dwZswF5V3R7RSXngyBn
|
||||
1GLxVFHKR753Gt0IDag13Bd8Jt890/v0tE0Kx66jCkRGn+VCq6+bsnh7VpTH/cG5
|
||||
dlFnv56lv2leknu5ghdJHX8YQ6HjnioaaheLA+ORAxqAlD8Itt1/pRBOOMSkutdz
|
||||
d1px9dB2ZBpSoRAOcBwU5aFaw9uu+tXyzrPM3tZomu8ryQYMNlmVgPNDJOz6jPJi
|
||||
jaZHWTS7U6j370oH/B0KTUG/ybrJGFnOmPP4h2u/ugG75EkfotURsvbrWuetQhOi
|
||||
TCH+9nbIcT3pxnTXqI2IRHZXMturQ+6fqlJF3bb9bWarMBuC3KgprqyqXxeM0Sqg
|
||||
VlyKLWwAuMf2Ec7t7ujqaNmVgv6bpwHEbR6njIi7lC7j4w6D2YQ8vacgvS3MB/K0
|
||||
SX54HNVBVuXhAixPtYJ6tOBGm7QFAKaXju0PJ+AljnMEsHRekOs2u42OHBXEWDE8
|
||||
VHw7/lTXWsJkBcQM+g/svyqV4xKHDAixPms2SUwJyKjvEgV+CQok4F/T
|
||||
-----END THIS IS NOT A REAL PRIVATE KEY-----
|
||||
|
||||
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIC9TCCAl6gAwIBAgIJANL8E4epRNznMA0GCSqGSIb3DQEBBQUAMFsxGDAWBgNV
|
||||
BAoTD1N1cGVyZmlzaCwgSW5jLjELMAkGA1UEBxMCU0YxCzAJBgNVBAgTAkNBMQsw
|
||||
CQYDVQQGEwJVUzEYMBYGA1UEAxMPU3VwZXJmaXNoLCBJbmMuMB4XDTE0MDUxMjE2
|
||||
MjUyNloXDTM0MDUwNzE2MjUyNlowWzEYMBYGA1UEChMPU3VwZXJmaXNoLCBJbmMu
|
||||
MQswCQYDVQQHEwJTRjELMAkGA1UECBMCQ0ExCzAJBgNVBAYTAlVTMRgwFgYDVQQD
|
||||
Ew9TdXBlcmZpc2gsIEluYy4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOjz
|
||||
Shh2Xxk/sc9Y6X9DBwmVgDXFD/5xMSeBmRImIKXfj2r8QlU57gk4idngNsSsAYJb
|
||||
1Tnm+Y8HiN/+7vahFM6pdEXY/fAXVyqC4XouEpNarIrXFWPRt5tVgA9YvBxJ7SBi
|
||||
3bZMpTrrHD2g/3pxptMQeDOuS8Ic/ZJKocPnQaQtAgMBAAGjgcAwgb0wDAYDVR0T
|
||||
BAUwAwEB/zAdBgNVHQ4EFgQU+5izU38URC7o7tUJml4OVoaoNYgwgY0GA1UdIwSB
|
||||
hTCBgoAU+5izU38URC7o7tUJml4OVoaoNYihX6RdMFsxGDAWBgNVBAoTD1N1cGVy
|
||||
ZmlzaCwgSW5jLjELMAkGA1UEBxMCU0YxCzAJBgNVBAgTAkNBMQswCQYDVQQGEwJV
|
||||
UzEYMBYGA1UEAxMPU3VwZXJmaXNoLCBJbmMuggkA0vwTh6lE3OcwDQYJKoZIhvcN
|
||||
AQEFBQADgYEApHyg7ApKx3DEcWjzOyLi3JyN0JL+c35yK1VEmxu0Qusfr76645Oj
|
||||
1IsYwpTws6a9ZTRMzST4GQvFFQra81eLqYbPbMPuhC+FCxkUF5i0DNSWi+kczJXJ
|
||||
TtCqSwGl9t9JEoFqvtW+znZ9TqyLiOMw7TGEUI+88VAqW0qmXnwPcfo=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
`;
|
||||
|
||||
const String SIMPLE_MULTI =
|
||||
`-----BEGIN CERTIFICATE-----
|
||||
MIIC9TCCAl6gAwIBAgIJANL8E4epRNznMA0GCSqGSIb3DQEBBQUAMFsxGDAWBgNV
|
||||
BAoTD1N1cGVyZmlzaCwgSW5jLjELMAkGA1UEBxMCU0YxCzAJBgNVBAgTAkNBMQsw
|
||||
CQYDVQQGEwJVUzEYMBYGA1UEAxMPU3VwZXJmaXNoLCBJbmMuMB4XDTE0MDUxMjE2
|
||||
MjUyNloXDTM0MDUwNzE2MjUyNlowWzEYMBYGA1UEChMPU3VwZXJmaXNoLCBJbmMu
|
||||
MQswCQYDVQQHEwJTRjELMAkGA1UECBMCQ0ExCzAJBgNVBAYTAlVTMRgwFgYDVQQD
|
||||
Ew9TdXBlcmZpc2gsIEluYy4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOjz
|
||||
Shh2Xxk/sc9Y6X9DBwmVgDXFD/5xMSeBmRImIKXfj2r8QlU57gk4idngNsSsAYJb
|
||||
1Tnm+Y8HiN/+7vahFM6pdEXY/fAXVyqC4XouEpNarIrXFWPRt5tVgA9YvBxJ7SBi
|
||||
3bZMpTrrHD2g/3pxptMQeDOuS8Ic/ZJKocPnQaQtAgMBAAGjgcAwgb0wDAYDVR0T
|
||||
BAUwAwEB/zAdBgNVHQ4EFgQU+5izU38URC7o7tUJml4OVoaoNYgwgY0GA1UdIwSB
|
||||
hTCBgoAU+5izU38URC7o7tUJml4OVoaoNYihX6RdMFsxGDAWBgNVBAoTD1N1cGVy
|
||||
ZmlzaCwgSW5jLjELMAkGA1UEBxMCU0YxCzAJBgNVBAgTAkNBMQswCQYDVQQGEwJV
|
||||
UzEYMBYGA1UEAxMPU3VwZXJmaXNoLCBJbmMuggkA0vwTh6lE3OcwDQYJKoZIhvcN
|
||||
AQEFBQADgYEApHyg7ApKx3DEcWjzOyLi3JyN0JL+c35yK1VEmxu0Qusfr76645Oj
|
||||
1IsYwpTws6a9ZTRMzST4GQvFFQra81eLqYbPbMPuhC+FCxkUF5i0DNSWi+kczJXJ
|
||||
TtCqSwGl9t9JEoFqvtW+znZ9TqyLiOMw7TGEUI+88VAqW0qmXnwPcfo=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN THING-----
|
||||
MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIDHHhyAEZQoICAggA
|
||||
MBQGCCqGSIb3DQMHBAiHEg+MCYQ30ASCAoDEvGvFRHvtWOb5Rc0f3lbVKqeUvWSz
|
||||
xQn+rZELHnwb6baolmbFcsi6XkacVzL/EF7Ll4de/CSQ6pZZCCvfDzov0mPOuGve
|
||||
SAe7hbAcol7+JWVfzbnVTblPf0i7mwSvK61cKq7YfcKJ2os/uJGpeX9zraywWyFx
|
||||
f+EdTr348dOez8uHkURyY1cvSHsIdITALkChOonAYT68SVighTeB6xOCwfmsHx+X
|
||||
3Qbhom2YCIxfJiaAoz2/LndCpDaEfOrVrxXFOKXrIbmeDEyjDQj16AVni9uuaj7l
|
||||
NiO3zrrqxsfdVINPaAYRKQnS102jXqkH01z72c/MpMMC6dwZswF5V3R7RSXngyBn
|
||||
1GLxVFHKR753Gt0IDag13Bd8Jt890/v0tE0Kx66jCkRGn+VCq6+bsnh7VpTH/cG5
|
||||
dlFnv56lv2leknu5ghdJHX8YQ6HjnioaaheLA+ORAxqAlD8Itt1/pRBOOMSkutdz
|
||||
d1px9dB2ZBpSoRAOcBwU5aFaw9uu+tXyzrPM3tZomu8ryQYMNlmVgPNDJOz6jPJi
|
||||
jaZHWTS7U6j370oH/B0KTUG/ybrJGFnOmPP4h2u/ugG75EkfotURsvbrWuetQhOi
|
||||
TCH+9nbIcT3pxnTXqI2IRHZXMturQ+6fqlJF3bb9bWarMBuC3KgprqyqXxeM0Sqg
|
||||
VlyKLWwAuMf2Ec7t7ujqaNmVgv6bpwHEbR6njIi7lC7j4w6D2YQ8vacgvS3MB/K0
|
||||
SX54HNVBVuXhAixPtYJ6tOBGm7QFAKaXju0PJ+AljnMEsHRekOs2u42OHBXEWDE8
|
||||
VHw7/lTXWsJkBcQM+g/svyqV4xKHDAixPms2SUwJyKjvEgV+CQok4F/T
|
||||
-----END THING-----
|
||||
`;
|
||||
|
||||
const String SINGLE_WITH_HEADERS =
|
||||
`-----BEGIN CERTIFICATE-----
|
||||
My-Header: Example
|
||||
This-is-a-longer-header: with some extra content that will bleed
|
||||
over to the next line.
|
||||
Last: Header is single-line.
|
||||
|
||||
MIIC9TCCAl6gAwIBAgIJANL8E4epRNznMA0GCSqGSIb3DQEBBQUAMFsxGDAWBgNV
|
||||
BAoTD1N1cGVyZmlzaCwgSW5jLjELMAkGA1UEBxMCU0YxCzAJBgNVBAgTAkNBMQsw
|
||||
CQYDVQQGEwJVUzEYMBYGA1UEAxMPU3VwZXJmaXNoLCBJbmMuMB4XDTE0MDUxMjE2
|
||||
MjUyNloXDTM0MDUwNzE2MjUyNlowWzEYMBYGA1UEChMPU3VwZXJmaXNoLCBJbmMu
|
||||
MQswCQYDVQQHEwJTRjELMAkGA1UECBMCQ0ExCzAJBgNVBAYTAlVTMRgwFgYDVQQD
|
||||
Ew9TdXBlcmZpc2gsIEluYy4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOjz
|
||||
Shh2Xxk/sc9Y6X9DBwmVgDXFD/5xMSeBmRImIKXfj2r8QlU57gk4idngNsSsAYJb
|
||||
1Tnm+Y8HiN/+7vahFM6pdEXY/fAXVyqC4XouEpNarIrXFWPRt5tVgA9YvBxJ7SBi
|
||||
3bZMpTrrHD2g/3pxptMQeDOuS8Ic/ZJKocPnQaQtAgMBAAGjgcAwgb0wDAYDVR0T
|
||||
BAUwAwEB/zAdBgNVHQ4EFgQU+5izU38URC7o7tUJml4OVoaoNYgwgY0GA1UdIwSB
|
||||
hTCBgoAU+5izU38URC7o7tUJml4OVoaoNYihX6RdMFsxGDAWBgNVBAoTD1N1cGVy
|
||||
ZmlzaCwgSW5jLjELMAkGA1UEBxMCU0YxCzAJBgNVBAgTAkNBMQswCQYDVQQGEwJV
|
||||
UzEYMBYGA1UEAxMPU3VwZXJmaXNoLCBJbmMuggkA0vwTh6lE3OcwDQYJKoZIhvcN
|
||||
AQEFBQADgYEApHyg7ApKx3DEcWjzOyLi3JyN0JL+c35yK1VEmxu0Qusfr76645Oj
|
||||
1IsYwpTws6a9ZTRMzST4GQvFFQra81eLqYbPbMPuhC+FCxkUF5i0DNSWi+kczJXJ
|
||||
TtCqSwGl9t9JEoFqvtW+znZ9TqyLiOMw7TGEUI+88VAqW0qmXnwPcfo=
|
||||
-----END CERTIFICATE-----
|
||||
`;
|
||||
|
||||
const char[] SINGLE_DATA =
|
||||
x'308202f53082025ea003020102020900d2fc1387a944dce7300d06092a864886f70d0101050500305b31183016060355040a130f5375706572666973682c2049'
|
||||
x'6e632e310b3009060355040713025346310b3009060355040813024341310b3009060355040613025553311830160603550403130f5375706572666973682c20'
|
||||
x'496e632e301e170d3134303531323136323532365a170d3334303530373136323532365a305b31183016060355040a130f5375706572666973682c20496e632e'
|
||||
x'310b3009060355040713025346310b3009060355040813024341310b3009060355040613025553311830160603550403130f5375706572666973682c20496e63'
|
||||
x'2e30819f300d06092a864886f70d010101050003818d0030818902818100e8f34a18765f193fb1cf58e97f430709958035c50ffe7131278199122620a5df8f6a'
|
||||
x'fc425539ee093889d9e036c4ac01825bd539e6f98f0788dffeeef6a114cea97445d8fdf017572a82e17a2e12935aac8ad71563d1b79b55800f58bc1c49ed2062'
|
||||
x'ddb64ca53aeb1c3da0ff7a71a6d3107833ae4bc21cfd924aa1c3e741a42d0203010001a381c03081bd300c0603551d13040530030101ff301d0603551d0e0416'
|
||||
x'0414fb98b3537f14442ee8eed5099a5e0e5686a8358830818d0603551d230481853081828014fb98b3537f14442ee8eed5099a5e0e5686a83588a15fa45d305b'
|
||||
x'31183016060355040a130f5375706572666973682c20496e632e310b3009060355040713025346310b3009060355040813024341310b30090603550406130255'
|
||||
x'53311830160603550403130f5375706572666973682c20496e632e820900d2fc1387a944dce7300d06092a864886f70d010105050003818100a47ca0ec0a4ac7'
|
||||
x'70c47168f33b22e2dc9c8dd092fe737e722b55449b1bb442eb1fafbebae393a3d48b18c294f0b3a6bd65344ccd24f8190bc5150adaf3578ba986cf6cc3ee842f'
|
||||
x'850b19141798b40cd4968be91ccc95c94ed0aa4b01a5f6df4912816abed5bece767d4eac8b88e330ed3184508fbcf1502a5b4aa65e7c0f71fa';
|
||||
|
||||
const char[] MULTI_DATA =
|
||||
x'308202c6304006092a864886f70d01050d3033301b06092a864886f70d01050c300e04080c71e1c80119428202020800301406082a864886f70d030704088712'
|
||||
x'0f8c098437d004820280c4bc6bc5447bed58e6f945cd1fde56d52aa794bd64b3c509fead910b1e7c1be9b6a89666c572c8ba5e469c5732ff105ecb97875efc24'
|
||||
x'90ea9659082bdf0f3a2fd263ceb86bde4807bb85b01ca25efe25655fcdb9d54db94f7f48bb9b04af2bad5c2aaed87dc289da8b3fb891a9797f73adacb05b2171'
|
||||
x'7fe11d4ebdf8f1d39ecfcb8791447263572f487b087484c02e40a13a89c0613ebc4958a0853781eb1382c1f9ac1f1f97dd06e1a26d98088c5f262680a33dbf2e'
|
||||
x'7742a436847cead5af15c538a5eb21b99e0c4ca30d08f5e805678bdbae6a3ee53623b7cebaeac6c7dd54834f6806112909d2d74da35ea907d35cfbd9cfcca4c3'
|
||||
x'02e9dc19b3017957747b4525e7832067d462f15451ca47be771add080da835dc177c26df3dd3fbf4b44d0ac7aea30a44469fe542abaf9bb2787b5694c7fdc1b9'
|
||||
x'765167bf9ea5bf695e927bb98217491d7f1843a1e39e2a1a6a178b03e391031a80943f08b6dd7fa5104e38c4a4bad773775a71f5d076641a52a1100e701c14e5'
|
||||
x'a15ac3dbaefad5f2ceb3ccded6689aef2bc9060c36599580f34324ecfa8cf2628da6475934bb53a8f7ef4a07fc1d0a4d41bfc9bac91859ce98f3f8876bbfba01'
|
||||
x'bbe4491fa2d511b2f6eb5ae7ad4213a24c21fef676c8713de9c674d7a88d8844765732dbab43ee9faa5245ddb6fd6d66ab301b82dca829aeacaa5f178cd12aa0'
|
||||
x'565c8a2d6c00b8c7f611ceedeee8ea68d99582fe9ba701c46d1ea78c88bb942ee3e30e83d9843cbda720bd2dcc07f2b4497e781cd54156e5e1022c4fb5827ab4'
|
||||
x'e0469bb40500a6978eed0f27e0258e7304b0745e90eb36bb8d8e1c15c458313c547c3bfe54d75ac26405c40cfa0fecbf2a95e312870c08b13e6b36494c09c8a8'
|
||||
x'ef12057e090a24e05fd3';
|
||||
|
||||
const Pair{ String, fault }[] DECODE_ERRORS = {
|
||||
{ "heh", pem::INVALID_FORMAT },
|
||||
{ "---BEGIN CERT\nsomething\nsomething\nsomething", pem::INVALID_PRE_EB },
|
||||
{ "\n\n-----BEGIN SAMPLE-----\ntest: test\ntest1: test1\n\nQQ==\n", pem::MISSING_POST_EB },
|
||||
{ "\n\n-----BEGIN SAMPLE-----\ntest: test\ntest1: test1\n\nQQ==\n-----BEGIN TAG-----", pem::INVALID_POST_EB },
|
||||
{ "-----BEGIN SAMPLE-----\ntest: test\ntest1: test1\n\nQQ==\n-----END TAG-----", pem::MISMATCHED_TAG },
|
||||
{ "-----BEGIN -----\ntest: test\ntest1: test1\n\nQQ==\n-----END SAMPLE-----", pem::MISSING_TAG },
|
||||
{ "-----BEGIN SAMPLE-----\n a key STARTING with spaces!: test\ntest1: test1\n\nQQ==\n-----END SAMPLE-----", pem::INVALID_HEADER },
|
||||
{ "-----BEGIN SAMPLE-----\nthis-line-has-no-colon-character\ntest1: test1\n\nQQ==\n-----END SAMPLE-----", pem::INVALID_BODY },
|
||||
{ "-----BEGIN SAMPLE-----\nthis-line-has-a-colon-character:butnospace\ntest1: test1\n\nQQ==\n-----END SAMPLE-----", pem::INVALID_BODY },
|
||||
{ "-----BEGIN SAMPLE-----\na key with spaces!: test\ntest1: test1\n\nQQ==\n-----END SAMPLE-----", pem::INVALID_HEADER_KEY },
|
||||
{ "-----BEGIN SAMPLE-----\n: nokey\ntest1: test1\n\nQQ==\n-----END SAMPLE-----", pem::INVALID_HEADER }, // because ':' isn't in the valid charset, which is checked first
|
||||
{ "-----BEGIN SAMPLE-----\na key with spaces!: \ntest1: test1\n\nQQ==\n-----END SAMPLE-----", pem::INVALID_BODY }, // because the whitespace is trimmed
|
||||
{ "-----BEGIN SAMPLE-----\ntest1: test1\nQQ==\n-----END SAMPLE-----", pem::INVALID_FORMAT }, // missing a blank line between headers/body
|
||||
{ "-----BEGIN SAMPLE-----\ntest1: test1\n\n-----END SAMPLE-----", pem::MISSING_BODY },
|
||||
{ "-----BEGIN SAMPLE-----\ntest1: test1\n\nthis is not base64 data\n-----END SAMPLE-----", pem::INVALID_BODY },
|
||||
};
|
||||
Reference in New Issue
Block a user