#![cfg_attr(not(feature = "std"), no_std)]
#![forbid(unsafe_code)]
#![allow(clippy::incompatible_msrv)]
#[cfg(not(feature = "std"))]
#[macro_use]
extern crate alloc;
mod internal_alloc;
#[cfg(not(feature = "std"))]
use crate::internal_alloc::ToOwned;
use crate::internal_alloc::{String, Vec};
use noxtls_core::{Error, Result};
#[cfg(feature = "std")]
use std::path::Path;
const BASE64_ALPHABET: &[u8; 64] =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
const PEM_LABEL_CERTIFICATE: &str = "CERTIFICATE";
const PEM_LABEL_RSA_PRIVATE_KEY: &str = "RSA PRIVATE KEY";
const PEM_LABEL_RSA_PUBLIC_KEY: &str = "RSA PUBLIC KEY";
const PEM_LABEL_PRIVATE_KEY: &str = "PRIVATE KEY";
const PEM_LABEL_EC_PRIVATE_KEY: &str = "EC PRIVATE KEY";
const PEM_LABEL_PUBLIC_KEY: &str = "PUBLIC KEY";
pub fn certificate_der_to_pem(der: &[u8]) -> Result<String> {
der_to_pem(der, PEM_LABEL_CERTIFICATE)
}
pub fn certificate_pem_to_der(pem: &str) -> Result<Vec<u8>> {
pem_to_der(pem, PEM_LABEL_CERTIFICATE)
}
pub fn certificate_chain_pem_to_der_blocks(pem: &str) -> Result<Vec<Vec<u8>>> {
pem_to_der_blocks(pem, PEM_LABEL_CERTIFICATE)
}
pub fn rsa_private_key_der_to_pem_pkcs1(der: &[u8]) -> Result<String> {
der_to_pem(der, PEM_LABEL_RSA_PRIVATE_KEY)
}
pub fn rsa_private_key_pem_to_der_pkcs1(pem: &str) -> Result<Vec<u8>> {
pem_to_der(pem, PEM_LABEL_RSA_PRIVATE_KEY)
}
pub fn rsa_public_key_der_to_pem_pkcs1(der: &[u8]) -> Result<String> {
der_to_pem(der, PEM_LABEL_RSA_PUBLIC_KEY)
}
pub fn rsa_public_key_pem_to_der_pkcs1(pem: &str) -> Result<Vec<u8>> {
pem_to_der(pem, PEM_LABEL_RSA_PUBLIC_KEY)
}
pub fn private_key_der_to_pem_pkcs8(der: &[u8]) -> Result<String> {
der_to_pem(der, PEM_LABEL_PRIVATE_KEY)
}
pub fn private_key_pem_to_der_pkcs8(pem: &str) -> Result<Vec<u8>> {
pem_to_der(pem, PEM_LABEL_PRIVATE_KEY)
}
pub fn ec_private_key_der_to_pem_sec1(der: &[u8]) -> Result<String> {
der_to_pem(der, PEM_LABEL_EC_PRIVATE_KEY)
}
pub fn ec_private_key_pem_to_der_sec1(pem: &str) -> Result<Vec<u8>> {
pem_to_der(pem, PEM_LABEL_EC_PRIVATE_KEY)
}
pub fn public_key_der_to_pem_spki(der: &[u8]) -> Result<String> {
der_to_pem(der, PEM_LABEL_PUBLIC_KEY)
}
pub fn public_key_pem_to_der_spki(pem: &str) -> Result<Vec<u8>> {
pem_to_der(pem, PEM_LABEL_PUBLIC_KEY)
}
#[cfg(feature = "std")]
pub fn pem_file_to_der(path: &Path, label: &str) -> Result<Vec<u8>> {
let pem = std::fs::read_to_string(path)
.map_err(|_| Error::ParseFailure("failed to read pem file"))?;
pem_to_der(&pem, label)
}
#[cfg(feature = "std")]
pub fn pem_file_to_der_blocks(path: &Path, label: &str) -> Result<Vec<Vec<u8>>> {
let pem = std::fs::read_to_string(path)
.map_err(|_| Error::ParseFailure("failed to read pem file"))?;
pem_to_der_blocks(&pem, label)
}
#[cfg(feature = "std")]
pub fn der_to_pem_file(path: &Path, der: &[u8], label: &str) -> Result<()> {
let pem = der_to_pem(der, label)?;
std::fs::write(path, pem).map_err(|_| Error::ParseFailure("failed to write pem file"))?;
Ok(())
}
#[cfg(feature = "std")]
pub fn der_to_file(path: &Path, der: &[u8]) -> Result<()> {
if der.is_empty() {
return Err(Error::InvalidLength("der input must not be empty"));
}
std::fs::write(path, der).map_err(|_| Error::ParseFailure("failed to write der file"))?;
Ok(())
}
pub fn der_to_pem(der: &[u8], label: &str) -> Result<String> {
if der.is_empty() {
return Err(Error::InvalidLength("der input must not be empty"));
}
if label.is_empty() {
return Err(Error::InvalidLength("pem label must not be empty"));
}
if label.chars().any(char::is_control) {
return Err(Error::InvalidEncoding(
"pem label contains invalid control character",
));
}
let encoded = encode_base64(der);
let mut pem = String::new();
pem.push_str("-----BEGIN ");
pem.push_str(label);
pem.push_str("-----\n");
for chunk in encoded.as_bytes().chunks(64) {
let line =
core::str::from_utf8(chunk).expect("base64 output is always valid ascii and utf-8");
pem.push_str(line);
pem.push('\n');
}
pem.push_str("-----END ");
pem.push_str(label);
pem.push_str("-----\n");
Ok(pem)
}
pub fn pem_to_der(pem: &str, label: &str) -> Result<Vec<u8>> {
let blocks = pem_to_der_blocks(pem, label)?;
if blocks.len() != 1 {
return Err(Error::ParseFailure(
"expected exactly one pem block for requested label",
));
}
Ok(blocks[0].clone())
}
pub fn pem_to_der_blocks(pem: &str, label: &str) -> Result<Vec<Vec<u8>>> {
if pem.is_empty() {
return Err(Error::InvalidLength("pem input must not be empty"));
}
if label.is_empty() {
return Err(Error::InvalidLength("pem label must not be empty"));
}
if label.chars().any(char::is_control) {
return Err(Error::InvalidEncoding(
"pem label contains invalid control character",
));
}
let mut active_label: Option<String> = None;
let mut payload = String::new();
let mut out = Vec::new();
for line in pem.lines() {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
if let Some(begin_label) = parse_pem_marker_label(trimmed, "BEGIN ") {
if active_label.is_some() {
return Err(Error::ParseFailure("nested pem begin marker"));
}
active_label = Some(begin_label.to_owned());
payload.clear();
continue;
}
if let Some(end_label) = parse_pem_marker_label(trimmed, "END ") {
let current_label = active_label
.as_deref()
.ok_or(Error::ParseFailure("pem end marker appears before begin"))?;
if current_label != end_label {
return Err(Error::ParseFailure("pem begin/end label mismatch"));
}
if current_label == label {
if payload.is_empty() {
return Err(Error::InvalidEncoding("pem payload is empty"));
}
out.push(decode_base64(&payload)?);
}
active_label = None;
payload.clear();
continue;
}
if active_label.is_none() {
continue;
}
payload.push_str(trimmed);
}
if active_label.is_some() {
return Err(Error::ParseFailure("pem end marker missing"));
}
if out.is_empty() {
return Err(Error::ParseFailure("pem begin/end markers not found"));
}
Ok(out)
}
fn parse_pem_marker_label<'a>(line: &'a str, marker_kind: &str) -> Option<&'a str> {
let prefix = "-----";
let suffix = "-----";
if !line.starts_with(prefix) || !line.ends_with(suffix) {
return None;
}
let inner = &line[prefix.len()..line.len() - suffix.len()];
let label = inner.strip_prefix(marker_kind)?.trim();
if label.is_empty() {
return None;
}
Some(label)
}
fn encode_base64(input: &[u8]) -> String {
let mut out = String::with_capacity(input.len().div_ceil(3) * 4);
let mut idx = 0_usize;
while idx + 3 <= input.len() {
let n = (u32::from(input[idx]) << 16)
| (u32::from(input[idx + 1]) << 8)
| u32::from(input[idx + 2]);
out.push(BASE64_ALPHABET[((n >> 18) & 0x3F) as usize] as char);
out.push(BASE64_ALPHABET[((n >> 12) & 0x3F) as usize] as char);
out.push(BASE64_ALPHABET[((n >> 6) & 0x3F) as usize] as char);
out.push(BASE64_ALPHABET[(n & 0x3F) as usize] as char);
idx += 3;
}
let rem = input.len() - idx;
if rem == 1 {
let n = u32::from(input[idx]) << 16;
out.push(BASE64_ALPHABET[((n >> 18) & 0x3F) as usize] as char);
out.push(BASE64_ALPHABET[((n >> 12) & 0x3F) as usize] as char);
out.push('=');
out.push('=');
} else if rem == 2 {
let n = (u32::from(input[idx]) << 16) | (u32::from(input[idx + 1]) << 8);
out.push(BASE64_ALPHABET[((n >> 18) & 0x3F) as usize] as char);
out.push(BASE64_ALPHABET[((n >> 12) & 0x3F) as usize] as char);
out.push(BASE64_ALPHABET[((n >> 6) & 0x3F) as usize] as char);
out.push('=');
}
out
}
fn decode_base64(input: &str) -> Result<Vec<u8>> {
if !input.len().is_multiple_of(4) {
return Err(Error::InvalidEncoding(
"pem base64 length must be divisible by 4",
));
}
let bytes = input.as_bytes();
let mut out = Vec::with_capacity((bytes.len() / 4) * 3);
for (chunk_idx, chunk) in bytes.chunks_exact(4).enumerate() {
let is_last = chunk_idx + 1 == bytes.len() / 4;
let mut sextets = [0_u8; 4];
let mut pad_count = 0_u8;
for (i, byte) in chunk.iter().enumerate() {
if *byte == b'=' {
sextets[i] = 0;
pad_count = pad_count.saturating_add(1);
continue;
}
if pad_count != 0 {
return Err(Error::InvalidEncoding("invalid base64 padding order"));
}
sextets[i] = decode_base64_sextet(*byte)?;
}
if pad_count > 2 {
return Err(Error::InvalidEncoding("invalid base64 padding width"));
}
if !is_last && pad_count != 0 {
return Err(Error::InvalidEncoding(
"base64 padding only allowed in final quartet",
));
}
let n = (u32::from(sextets[0]) << 18)
| (u32::from(sextets[1]) << 12)
| (u32::from(sextets[2]) << 6)
| u32::from(sextets[3]);
out.push(((n >> 16) & 0xFF) as u8);
if pad_count < 2 {
out.push(((n >> 8) & 0xFF) as u8);
}
if pad_count == 0 {
out.push((n & 0xFF) as u8);
}
}
Ok(out)
}
fn decode_base64_sextet(byte: u8) -> Result<u8> {
match byte {
b'A'..=b'Z' => Ok(byte - b'A'),
b'a'..=b'z' => Ok(26 + (byte - b'a')),
b'0'..=b'9' => Ok(52 + (byte - b'0')),
b'+' => Ok(62),
b'/' => Ok(63),
_ => Err(Error::InvalidEncoding(
"invalid base64 character in pem payload",
)),
}
}