///|
fn pem_der(text : String) -> Bytes raise CryptoError {
  @x509.pem(text) catch {
    e => raise Malformed("PEM: \{e}")
  }
}

///|
fn items(tlv : @asn1.Tlv) -> Array[@asn1.Tlv] raise CryptoError {
  tlv.items() catch {
    e => raise Malformed("DER structure: \{e}")
  }
}

///|
fn algorithm_oid(seq : @asn1.Tlv) -> String raise CryptoError {
  let fields = items(seq)
  if fields.is_empty() {
    raise Malformed("empty algorithm identifier")
  }
  fields[0].oid() catch {
    e => raise Malformed("algorithm OID: \{e}")
  }
}

///|
/// Load an RSA signing key from a PEM string: PKCS#8 (`BEGIN PRIVATE KEY`)
/// or PKCS#1 (`BEGIN RSA PRIVATE KEY`).
pub fn load_private_key_pem(text : String) -> @rsa.PrivateKey raise CryptoError {
  let der = pem_der(text)
  let top = @asn1.only(der[:]) catch {
    e => raise Malformed("private key DER: \{e}")
  }
  let fields = items(top)
  let rsa_seq = if fields.length() == 3 && fields[1].tag == 0x30 {
    let oid = algorithm_oid(fields[1])
    if oid != "1.2.840.113549.1.1.1" {
      raise Unsupported(
        "private key algorithm \{oid} (encrypted keys are not supported)",
      )
    }
    @asn1.only(fields[2].value) catch {
      e => raise Malformed("PKCS#8 RSAPrivateKey: \{e}")
    }
  } else if fields.length() == 9 && fields[1].tag == 0x02 {
    top
  } else {
    raise Malformed("private key: not a PKCS#8 or PKCS#1 RSA structure")
  }
  let rsa_fields = items(rsa_seq)
  if rsa_fields.length() != 9 {
    raise Malformed(
      "RSAPrivateKey: expected 9 fields, got \{rsa_fields.length()}",
    )
  }
  @rsa.PrivateKey::new(
    rsa_fields[1].value,
    rsa_fields[2].value,
    rsa_fields[3].value,
  ) catch {
    _ => raise Operation("RSAPrivateKey carries an unusable key")
  }
}

///|
/// Load an RSA public key from a SubjectPublicKeyInfo PEM string
/// (`BEGIN PUBLIC KEY`).
pub fn load_public_key_pem(text : String) -> @rsa.PublicKey raise CryptoError {
  let der = pem_der(text)
  public_key_from_spki_der(der)
}

///|
/// Build an RSA public key from DER SubjectPublicKeyInfo bytes.
pub fn public_key_from_spki_der(
  der : Bytes,
) -> @rsa.PublicKey raise CryptoError {
  let spki = @x509.Spki::read(der[:]) catch {
    e => raise Malformed("SubjectPublicKeyInfo: \{e}")
  }
  spki.rsa() catch {
    e => raise Unsupported("public key: \{e}")
  }
}