mirror of
				https://github.com/LadybirdBrowser/ladybird.git
				synced 2025-11-04 07:10:57 +00:00 
			
		
		
		
	Improve PEM decoding by parsing the header and returning it along the data. Also verify if the header is equal to the footer.
		
			
				
	
	
		
			179 lines
		
	
	
	
		
			5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			179 lines
		
	
	
	
		
			5 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (c) 2021, the SerenityOS developers.
 | 
						|
 *
 | 
						|
 * SPDX-License-Identifier: BSD-2-Clause
 | 
						|
 */
 | 
						|
 | 
						|
#include <AK/Base64.h>
 | 
						|
#include <AK/GenericLexer.h>
 | 
						|
#include <LibCrypto/ASN1/PEM.h>
 | 
						|
 | 
						|
namespace Crypto {
 | 
						|
 | 
						|
static PEMType pem_header_to_type(StringView header)
 | 
						|
{
 | 
						|
    if (header == "CERTIFICATE"sv)
 | 
						|
        return PEMType::Certificate;
 | 
						|
    if (header == "PRIVATE KEY"sv)
 | 
						|
        return PEMType::PrivateKey;
 | 
						|
    if (header == "RSA PRIVATE KEY"sv)
 | 
						|
        return PEMType::RSAPrivateKey;
 | 
						|
    if (header == "PUBLIC KEY"sv)
 | 
						|
        return PEMType::PublicKey;
 | 
						|
    if (header == "RSA PUBLIC KEY"sv)
 | 
						|
        return PEMType::RSAPublicKey;
 | 
						|
    return PEMType::Unknown;
 | 
						|
}
 | 
						|
 | 
						|
DecodedPEM decode_pem(ReadonlyBytes data)
 | 
						|
{
 | 
						|
    GenericLexer lexer { data };
 | 
						|
    DecodedPEM decoded;
 | 
						|
    StringView header_type;
 | 
						|
 | 
						|
    // FIXME: Parse multiple.
 | 
						|
    enum {
 | 
						|
        PreStartData,
 | 
						|
        Started,
 | 
						|
        Ended,
 | 
						|
    } state { PreStartData };
 | 
						|
    while (!lexer.is_eof()) {
 | 
						|
        switch (state) {
 | 
						|
        case PreStartData:
 | 
						|
            if (lexer.consume_specific("-----BEGIN "sv)) {
 | 
						|
                state = Started;
 | 
						|
                header_type = lexer.consume_until("-----");
 | 
						|
            }
 | 
						|
            lexer.consume_line();
 | 
						|
            break;
 | 
						|
        case Started: {
 | 
						|
            if (lexer.consume_specific("-----END "sv)) {
 | 
						|
                state = Ended;
 | 
						|
 | 
						|
                if (lexer.consume_until("-----") != header_type) {
 | 
						|
                    dbgln("PEM type mismatch");
 | 
						|
                    return {};
 | 
						|
                }
 | 
						|
                lexer.consume_line();
 | 
						|
 | 
						|
                decoded.type = pem_header_to_type(header_type);
 | 
						|
                break;
 | 
						|
            }
 | 
						|
            auto b64decoded = decode_base64(lexer.consume_line().trim_whitespace(TrimMode::Right));
 | 
						|
            if (b64decoded.is_error()) {
 | 
						|
                dbgln("Failed to decode PEM: {}", b64decoded.error().string_literal());
 | 
						|
                return {};
 | 
						|
            }
 | 
						|
            if (decoded.data.try_append(b64decoded.value().data(), b64decoded.value().size()).is_error()) {
 | 
						|
                dbgln("Failed to decode PEM, likely OOM condition");
 | 
						|
                return {};
 | 
						|
            }
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        case Ended:
 | 
						|
            lexer.consume_all();
 | 
						|
            break;
 | 
						|
        default:
 | 
						|
            VERIFY_NOT_REACHED();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return decoded;
 | 
						|
}
 | 
						|
 | 
						|
ErrorOr<Vector<DecodedPEM>> decode_pems(ReadonlyBytes data)
 | 
						|
{
 | 
						|
    GenericLexer lexer { data };
 | 
						|
    Vector<DecodedPEM> pems;
 | 
						|
 | 
						|
    DecodedPEM decoded;
 | 
						|
    StringView header_type;
 | 
						|
 | 
						|
    enum {
 | 
						|
        Junk,
 | 
						|
        Parsing,
 | 
						|
    } state { Junk };
 | 
						|
    while (!lexer.is_eof()) {
 | 
						|
        switch (state) {
 | 
						|
        case Junk:
 | 
						|
            if (lexer.consume_specific("-----BEGIN "sv)) {
 | 
						|
                state = Parsing;
 | 
						|
                header_type = lexer.consume_until("-----");
 | 
						|
            }
 | 
						|
            lexer.consume_line();
 | 
						|
            break;
 | 
						|
        case Parsing: {
 | 
						|
            if (lexer.consume_specific("-----END "sv)) {
 | 
						|
                state = Junk;
 | 
						|
 | 
						|
                if (lexer.consume_until("-----") != header_type) {
 | 
						|
                    return Error::from_string_literal("PEM type mismatch");
 | 
						|
                }
 | 
						|
                lexer.consume_line();
 | 
						|
 | 
						|
                TRY(pems.try_append(decoded));
 | 
						|
                decoded = {};
 | 
						|
                header_type = {};
 | 
						|
                break;
 | 
						|
            }
 | 
						|
            auto b64decoded = TRY(decode_base64(lexer.consume_line().trim_whitespace(TrimMode::Right)));
 | 
						|
            TRY(decoded.data.try_append(b64decoded.data(), b64decoded.size()));
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        default:
 | 
						|
            VERIFY_NOT_REACHED();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return pems;
 | 
						|
}
 | 
						|
 | 
						|
ErrorOr<ByteBuffer> encode_pem(ReadonlyBytes data, PEMType type)
 | 
						|
{
 | 
						|
    ByteBuffer encoded;
 | 
						|
    StringView block_start;
 | 
						|
    StringView block_end;
 | 
						|
 | 
						|
    switch (type) {
 | 
						|
    case PEMType::Certificate:
 | 
						|
        block_start = "-----BEGIN CERTIFICATE-----\n"sv;
 | 
						|
        block_end = "-----END CERTIFICATE-----\n"sv;
 | 
						|
        break;
 | 
						|
    case PEMType::PrivateKey:
 | 
						|
        block_start = "-----BEGIN PRIVATE KEY-----\n"sv;
 | 
						|
        block_end = "-----END PRIVATE KEY-----\n"sv;
 | 
						|
        break;
 | 
						|
    case PEMType::RSAPrivateKey:
 | 
						|
        block_start = "-----BEGIN RSA PRIVATE KEY-----\n"sv;
 | 
						|
        block_end = "-----END RSA PRIVATE KEY-----\n"sv;
 | 
						|
        break;
 | 
						|
    case PEMType::PublicKey:
 | 
						|
        block_start = "-----BEGIN PUBLIC KEY-----\n"sv;
 | 
						|
        block_end = "-----END PUBLIC KEY-----\n"sv;
 | 
						|
        break;
 | 
						|
    case PEMType::RSAPublicKey:
 | 
						|
        block_start = "-----BEGIN RSA PUBLIC KEY-----\n"sv;
 | 
						|
        block_end = "-----END RSA PUBLIC KEY-----\n"sv;
 | 
						|
        break;
 | 
						|
    default:
 | 
						|
        VERIFY_NOT_REACHED();
 | 
						|
    }
 | 
						|
 | 
						|
    auto b64encoded = TRY(encode_base64(data));
 | 
						|
 | 
						|
    TRY(encoded.try_append(block_start.bytes()));
 | 
						|
 | 
						|
    size_t to_read = 64;
 | 
						|
    for (size_t i = 0; i < b64encoded.bytes().size(); i += to_read) {
 | 
						|
        if (i + to_read > b64encoded.bytes().size())
 | 
						|
            to_read = b64encoded.bytes().size() - i;
 | 
						|
        TRY(encoded.try_append(b64encoded.bytes().slice(i, to_read)));
 | 
						|
        TRY(encoded.try_append("\n"sv.bytes()));
 | 
						|
    }
 | 
						|
 | 
						|
    TRY(encoded.try_append(block_end.bytes()));
 | 
						|
 | 
						|
    return encoded;
 | 
						|
}
 | 
						|
 | 
						|
}
 |