silk.encoding.pem.

Read and write PEM, the Base64 text armor for keys and certificates (RFC 7468).

When to use#

Use Pem to find the -----BEGIN PRIVATE KEY----- style blocks in text and get their DER bytes, or to wrap DER bytes in such a block, for example for createPrivateKey and export in Node.js. The DER itself is read by the key modules, such as silk.crypto.rsa.

Details#

Pem.find returns the next block at or after an offset as a PemSection of offsets into the input, so nothing is copied. Text before, between, and after blocks is skipped. Inside a block the Base64 may wrap at any width and may hold spaces, tabs, and CR or LF line ends, as OpenSSL allows. RFC 1421 headers such as Proc-Type and DEK-Info, which a legacy encrypted key has, end at the first empty line and can be read with Pem.header. Pem.encode writes such headers when given, then Base64 lines of 64 characters, with LF line ends. Nothing allocates.

Errors#

The functions return a PemError. On an error the output does not change.

Gotchas#

The Base64 of a block must be canonical: padding only at the end, and zero unused bits. A label is not checked against a list; compare Pem.label with the one you expect.

Examples#

Round trip a block#

silk
import silk.encoding.pem { Pem, PemError, PemSection }
import silk.core.result { Result }

pub fn main() -> i32 {
  let mut text: [u8; 64] = [0; 64]
  let length = match Pem.encode(b"TEST", b"", b"hello", &mut text) {
    Result<usize, PemError>.Success { value } => value
    Result<usize, PemError>.Failure { error: _ } => {
      return 1
    }
  }
  if length != Pem.encodedLength(4, 0, 5) {
    return 1
  }
  let section = match Pem.find(&text, 0) {
    Result<PemSection, PemError>.Success { value } => move value
    Result<PemSection, PemError>.Failure { error: _ } => {
      return 1
    }
  }
  let label = Pem.label(&text, &section)
  if label.length != 4 || label[0] != 84 {
    return 1
  }
  let mut bytes: [u8; 8] = [0; 8]
  let decoded = match Pem.decode(&text, &section, &mut bytes) {
    Result<usize, PemError>.Success { value } => value
    Result<usize, PemError>.Failure { error: _ } => {
      return 1
    }
  }
  if decoded != 5 || bytes[0] != 104 || bytes[4] != 111 {
    return 1
  }
  return 0
}

Import as Pem with import silk.encoding.pem { Pem }.

Public declarations: 3.

PemError#

silk
pub union PemError

The reason that a Pem function rejected a call. The output does not change.

NotFound#

silk
NotFound

No -----BEGIN line starts at or after the offset.

Malformed#

silk
Malformed

A block starts but is not well formed: the BEGIN or END line is broken, the labels differ, the END line is missing, or the Base64 is not canonical.

OutputTooSmall#

silk
OutputTooSmall

The output buffer is shorter than the bytes the call writes.

InvalidLabel#

silk
InvalidLabel

The label is empty, or holds a byte other than printable ASCII without -.

Implementation Debug for PemError#

silk
impl Debug for PemError

Operation debug#

silk
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut Writer

Writes the name of the variant, such as NotFound.

PemSection#

silk
pub struct PemSection

One PEM block, as offsets into the text that Pem.find read.

Details#

The label is labelStart to labelEnd, the RFC 1421 headers are headersStart to headersEnd (empty when there are none), and the Base64 body is bodyStart to bodyEnd. The block ends at end, after the END line, where the next search can start.

Field start#

silk
pub start: usize

The offset of the -----BEGIN line.

Field labelStart#

silk
pub labelStart: usize

The offset of the first byte of the label.

Field labelEnd#

silk
pub labelEnd: usize

The offset after the label.

Field headersStart#

silk
pub headersStart: usize

The offset of the first header line.

Field headersEnd#

silk
pub headersEnd: usize

The offset after the headers and the empty line that ends them.

Field bodyStart#

silk
pub bodyStart: usize

The offset of the Base64 body.

Field bodyEnd#

silk
pub bodyEnd: usize

The offset of the -----END line.

Field end#

silk
pub end: usize

The offset after the END line and its line end.

Pem#

silk
pub struct Pem

Functions that find, decode, and encode PEM blocks.

Implementation Pem#

silk
impl Pem

Associated function find#

silk
pub fn find(input: &[u8], offset: usize) -> Result<PemSection, PemError>

Returns the next PEM block that starts at or after offset of input.

Errors#

  • NotFound if no -----BEGIN line starts at or after offset.

  • Malformed if the first such block is broken, or its END line is missing or has another label.

Associated function label#

silk
pub fn label<'a>(input: &'a [u8], section: &PemSection) -> &'a [u8]

Returns the label of section, such as PRIVATE KEY.

Associated function header#

silk
pub fn header<'a>(input: &'a [u8], section: &PemSection, name: &[u8]) -> Option<&'a [u8]>

Returns the value of the RFC 1421 header name of section, such as DEK-Info, without the spaces around it, or None if the block has no such header.

The name compares exactly. A header that continues on the next line is not joined.

Associated function decode#

silk
pub fn decode(input: &[u8], section: &PemSection, output: &mut [u8]) -> Result<usize, PemError>

Decodes the Base64 body of section into output and returns the number of bytes written.

Errors#

  • Malformed if the body is not canonical Base64 once spaces, tabs, CR, and LF are removed.

  • OutputTooSmall if output is shorter than the decoded bytes.

Associated function encodedLength#

silk
pub fn encodedLength(labelLength: usize, headersLength: usize, length: usize) -> usize

Returns the number of bytes that Pem.encode writes for a label of labelLength bytes, headersLength bytes of headers, and length bytes of data.

Associated function encode#

silk
pub fn encode(label: &[u8], headers: &[u8], bytes: &[u8], output: &mut [u8]) -> Result<usize, PemError>

Writes bytes as a PEM block with label label to output and returns the number of bytes written, Pem.encodedLength.

headers is empty, or RFC 1421 header lines that each end with LF, such as Proc-Type: 4,ENCRYPTED\nDEK-Info: AES-256-CBC,...\n; the empty line after them is added. The Base64 lines have 64 characters and LF line ends.

Errors#

  • InvalidLabel if label is empty, or holds a byte outside printable ASCII or a -.

  • Malformed if headers is not empty and does not end with LF, or holds an empty line.

  • OutputTooSmall if output is shorter than Pem.encodedLength.

Category: Encoding.

Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2, x86_64-pc-windows-msvc, wasm64-unknown-unknown.

View the source of this page