Combine AIA fields into XCA-style editor
This commit is contained in:
@@ -37,7 +37,7 @@ test("advanced extensions, validation and vault export", async ({ page }) => {
|
||||
await (await field(page, "Country (C)")).fill("DE");
|
||||
await page.locator("#new-key").check();
|
||||
await page.locator("textarea[placeholder='http://example.com/ca.crl']").fill("http://example.com/ca.crl");
|
||||
await page.locator("textarea[placeholder='http://ocsp.example.com']").fill("http://ocsp.example.com");
|
||||
await page.locator("#aia").fill("OCSP;URI:http://ocsp.example.com");
|
||||
await page.locator("textarea[placeholder*='raw']").fill(
|
||||
'{"raw":[{"oid":"1.2.3.4","critical":false,"der_hex":"0c03616263"}]}'
|
||||
);
|
||||
|
||||
@@ -38,6 +38,11 @@ test("advanced extension list editors", async ({ page }) => {
|
||||
const vaultName = unique("List Editors");
|
||||
const certName = unique("constraints.example");
|
||||
const crlUrls = ["https://ca.example.com/root.crl", "https://backup.example.com/root.crl"];
|
||||
const aiaEntries = [
|
||||
["OCSP;URI", "https://ocsp.example.com"],
|
||||
["caIssuers;URI", "https://ca.example.com/issuer.crt"],
|
||||
];
|
||||
const aiaText = aiaEntries.map(([type, value]) => `${type}:${value}`).join(", ");
|
||||
const cpsUrls = ["https://example.com/cps", "https://example.com/policy"];
|
||||
|
||||
await login(page, user);
|
||||
@@ -79,6 +84,24 @@ test("advanced extension list editors", async ({ page }) => {
|
||||
await expect(dialog.getByLabel("Critical", { exact: true })).toBeChecked();
|
||||
await dialog.getByRole("button", { name: "Cancel", exact: true }).click();
|
||||
|
||||
// AIA combines the access method and XCA general-name type in each row.
|
||||
dialog = await openEditor(page, "#edit-aia", "Authority information access");
|
||||
await expect(dialog.getByLabel("Critical", { exact: true })).not.toBeChecked();
|
||||
for (const [type, value] of aiaEntries) {
|
||||
await addRow(dialog, type, value);
|
||||
}
|
||||
await dialog.getByLabel("Critical", { exact: true }).check();
|
||||
await dialog.getByRole("button", { name: "Apply", exact: true }).click();
|
||||
await expect(page.locator("#aia")).toHaveValue(aiaText);
|
||||
|
||||
dialog = await openEditor(page, "#edit-aia", "Authority information access");
|
||||
await expect(dialog.locator(".list-entry")).toHaveCount(2);
|
||||
await expect(dialog.locator(".list-entry").nth(0).locator("select")).toHaveValue("OCSP;URI");
|
||||
await expect(dialog.locator(".list-entry").nth(1).locator("select")).toHaveValue("caIssuers;URI");
|
||||
await expect(dialog.locator(".list-entry").nth(1).locator(".list-value")).toHaveValue(aiaEntries[1][1]);
|
||||
await expect(dialog.getByLabel("Critical", { exact: true })).toBeChecked();
|
||||
await dialog.getByRole("button", { name: "Cancel", exact: true }).click();
|
||||
|
||||
// Name constraints use typed rows and share one extension-level critical flag.
|
||||
dialog = await openEditor(page, "#edit-nc-permitted", "Name constraints · permitted");
|
||||
await expect(dialog.getByLabel("Critical", { exact: true })).toBeChecked();
|
||||
@@ -117,6 +140,7 @@ test("advanced extension list editors", async ({ page }) => {
|
||||
await expect(page.locator("#nc-excluded")).toHaveValue("DNS:.bad.example");
|
||||
await page.getByRole("button", { name: "Validate extensions" }).click();
|
||||
await expect(page.getByText("Extension preview")).toBeVisible();
|
||||
await expect(page.locator("pre.pem")).toContainText("Authority Information Access");
|
||||
await expect(page.locator("pre.pem")).toContainText("Name Constraints");
|
||||
await expect(page.locator("pre.pem")).toContainText("Certificate Policies");
|
||||
|
||||
@@ -129,6 +153,13 @@ test("advanced extension list editors", async ({ page }) => {
|
||||
}, certApiUrl);
|
||||
const extensions = detail.extension_spec;
|
||||
expect(extensions.crl_distribution_points).toEqual({ critical: true, urls: crlUrls });
|
||||
expect(extensions.authority_information_access).toEqual({
|
||||
critical: true,
|
||||
descriptions: aiaEntries.map(([type, value]) => ({
|
||||
method: type.startsWith("OCSP") ? "ocsp" : "ca_issuers",
|
||||
location: { type: "uri", value },
|
||||
})),
|
||||
});
|
||||
expect(extensions.name_constraints).toEqual({
|
||||
critical: true,
|
||||
permitted: [
|
||||
@@ -149,4 +180,9 @@ test("advanced extension list editors", async ({ page }) => {
|
||||
await addRow(dialog, "URI", "https://template.example.com/root.crl");
|
||||
await dialog.getByRole("button", { name: "Apply", exact: true }).click();
|
||||
await expect(page.locator("#crl-urls")).toHaveValue("https://template.example.com/root.crl");
|
||||
|
||||
dialog = await openEditor(page, "#edit-aia", "Authority information access");
|
||||
await addRow(dialog, "OCSP;URI", "https://template.example.com/ocsp");
|
||||
await dialog.getByRole("button", { name: "Apply", exact: true }).click();
|
||||
await expect(page.locator("#aia")).toHaveValue("OCSP;URI:https://template.example.com/ocsp");
|
||||
});
|
||||
|
||||
@@ -9,16 +9,17 @@ use serde_json::{json, Map, Value};
|
||||
use sycamore::prelude::*;
|
||||
|
||||
use crate::components::list_editor::{
|
||||
general_name_entries_to_text, list_editor_dialog, open_list_editor, parse_other_name,
|
||||
split_list, ListEditorConfig, ListEditorFormat, ListEditorSignals,
|
||||
aia_entries_to_text, general_name_entries_to_text, list_editor_dialog, open_list_editor,
|
||||
parse_aia_entries, parse_other_name, split_list, ListEditorConfig, ListEditorFormat,
|
||||
ListEditorSignals,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ExtensionSignals {
|
||||
pub crl_urls: Signal<String>,
|
||||
pub crl_critical: Signal<bool>,
|
||||
pub aia_ocsp: Signal<String>,
|
||||
pub aia_ca_issuers: Signal<String>,
|
||||
pub aia: Signal<String>,
|
||||
pub aia_critical: Signal<bool>,
|
||||
pub nc_permitted: Signal<String>,
|
||||
pub nc_excluded: Signal<String>,
|
||||
pub nc_critical: Signal<bool>,
|
||||
@@ -33,8 +34,7 @@ pub struct ExtensionSignals {
|
||||
|
||||
pub fn extension_fields(s: &ExtensionSignals) -> View {
|
||||
let crl_urls = s.crl_urls;
|
||||
let aia_ocsp = s.aia_ocsp;
|
||||
let aia_ca_issuers = s.aia_ca_issuers;
|
||||
let aia = s.aia;
|
||||
let nc_permitted = s.nc_permitted;
|
||||
let nc_excluded = s.nc_excluded;
|
||||
let policy_oids = s.policy_oids;
|
||||
@@ -45,6 +45,7 @@ pub fn extension_fields(s: &ExtensionSignals) -> View {
|
||||
let advanced_json = s.advanced_json;
|
||||
|
||||
let crl_editor = ListEditorSignals::new();
|
||||
let aia_editor = ListEditorSignals::new();
|
||||
let nc_permitted_editor = ListEditorSignals::new();
|
||||
let nc_excluded_editor = ListEditorSignals::new();
|
||||
let policy_cps_editor = ListEditorSignals::new();
|
||||
@@ -55,6 +56,13 @@ pub fn extension_fields(s: &ExtensionSignals) -> View {
|
||||
critical: Some(s.crl_critical),
|
||||
copy_common_name: false,
|
||||
};
|
||||
let aia_config = ListEditorConfig {
|
||||
id: "aia",
|
||||
title: "Authority information access",
|
||||
format: ListEditorFormat::AuthorityInformationAccess,
|
||||
critical: Some(s.aia_critical),
|
||||
copy_common_name: false,
|
||||
};
|
||||
let nc_permitted_config = ListEditorConfig {
|
||||
id: "nc-permitted",
|
||||
title: "Name constraints · permitted",
|
||||
@@ -77,6 +85,7 @@ pub fn extension_fields(s: &ExtensionSignals) -> View {
|
||||
copy_common_name: false,
|
||||
};
|
||||
let edit_crl = move |_| open_list_editor(crl_editor, crl_urls, crl_config);
|
||||
let edit_aia = move |_| open_list_editor(aia_editor, aia, aia_config);
|
||||
let edit_nc_permitted =
|
||||
move |_| open_list_editor(nc_permitted_editor, nc_permitted, nc_permitted_config);
|
||||
let edit_nc_excluded =
|
||||
@@ -95,13 +104,12 @@ pub fn extension_fields(s: &ExtensionSignals) -> View {
|
||||
button(id="edit-crl-urls", class="btn btn-outline-light", r#type="button", on:click=edit_crl) { "Edit" }
|
||||
}
|
||||
}
|
||||
div(class="col-md-3") {
|
||||
label(class="form-label") { "AIA · OCSP" }
|
||||
textarea(class="form-control", rows="2", placeholder="http://ocsp.example.com", bind:value=aia_ocsp) {}
|
||||
}
|
||||
div(class="col-md-3") {
|
||||
label(class="form-label") { "AIA · caIssuers" }
|
||||
textarea(class="form-control", rows="2", placeholder="http://example.com/ca.crt", bind:value=aia_ca_issuers) {}
|
||||
div(class="col-md-6") {
|
||||
label(class="form-label") { "Authority information access" }
|
||||
div(class="input-group") {
|
||||
textarea(id="aia", class="form-control", rows="2", placeholder="OCSP;URI:http://ocsp.example.com, caIssuers;URI:http://example.com/ca.crt", bind:value=aia) {}
|
||||
button(id="edit-aia", class="btn btn-outline-light", r#type="button", on:click=edit_aia) { "Edit" }
|
||||
}
|
||||
}
|
||||
div(class="col-md-6") {
|
||||
label(class="form-label") { "Name constraints · permitted" }
|
||||
@@ -152,6 +160,7 @@ pub fn extension_fields(s: &ExtensionSignals) -> View {
|
||||
}
|
||||
}
|
||||
(list_editor_dialog(crl_editor, crl_urls, crl_config))
|
||||
(list_editor_dialog(aia_editor, aia, aia_config))
|
||||
(list_editor_dialog(
|
||||
nc_permitted_editor,
|
||||
nc_permitted,
|
||||
@@ -181,17 +190,11 @@ pub fn build_extra_extensions(s: &ExtensionSignals) -> Result<Value, String> {
|
||||
);
|
||||
}
|
||||
|
||||
let mut aia = Vec::new();
|
||||
for url in split_list(&s.aia_ocsp.get_clone()) {
|
||||
aia.push(json!({"method": "ocsp", "location": {"type": "uri", "value": url}}));
|
||||
}
|
||||
for url in split_list(&s.aia_ca_issuers.get_clone()) {
|
||||
aia.push(json!({"method": "ca_issuers", "location": {"type": "uri", "value": url}}));
|
||||
}
|
||||
let aia = parse_aia_entries(&s.aia.get_clone())?;
|
||||
if !aia.is_empty() {
|
||||
extensions.insert(
|
||||
"authority_information_access".to_string(),
|
||||
json!({"critical": false, "descriptions": aia}),
|
||||
json!({"critical": s.aia_critical.get(), "descriptions": aia}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -268,34 +271,18 @@ pub fn load_extra_extensions(s: &ExtensionSignals, spec: &Value) {
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false),
|
||||
);
|
||||
let descriptions = spec
|
||||
.get("authority_information_access")
|
||||
.and_then(|v| v.get("descriptions"))
|
||||
.and_then(|v| v.as_array())
|
||||
let aia = spec.get("authority_information_access");
|
||||
let descriptions = aia
|
||||
.and_then(|value| value.get("descriptions"))
|
||||
.and_then(|value| value.as_array())
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let ocsp = descriptions
|
||||
.iter()
|
||||
.filter(|item| item.get("method").and_then(|v| v.as_str()) == Some("ocsp"))
|
||||
.filter_map(|item| {
|
||||
item.get("location")
|
||||
.and_then(|v| v.get("value"))
|
||||
.and_then(|v| v.as_str())
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
let ca_issuers = descriptions
|
||||
.iter()
|
||||
.filter(|item| item.get("method").and_then(|v| v.as_str()) == Some("ca_issuers"))
|
||||
.filter_map(|item| {
|
||||
item.get("location")
|
||||
.and_then(|v| v.get("value"))
|
||||
.and_then(|v| v.as_str())
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
s.aia_ocsp.set(ocsp);
|
||||
s.aia_ca_issuers.set(ca_issuers);
|
||||
s.aia.set(aia_entries_to_text(&descriptions));
|
||||
s.aia_critical.set(
|
||||
aia.and_then(|value| value.get("critical"))
|
||||
.and_then(|value| value.as_bool())
|
||||
.unwrap_or(false),
|
||||
);
|
||||
|
||||
let nc = spec.get("name_constraints");
|
||||
s.nc_critical.set(
|
||||
|
||||
@@ -44,6 +44,7 @@ impl ListEditorSignals {
|
||||
pub enum ListEditorFormat {
|
||||
GeneralNames,
|
||||
UriList(&'static str),
|
||||
AuthorityInformationAccess,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -88,6 +89,25 @@ pub fn open_list_editor(editor: ListEditorSignals, text: Signal<String>, config:
|
||||
}
|
||||
}
|
||||
}
|
||||
ListEditorFormat::AuthorityInformationAccess => {
|
||||
for entry in parse_aia_entries(&text.get_clone()).unwrap_or_default() {
|
||||
let method =
|
||||
if entry.get("method").and_then(|value| value.as_str()) == Some("ca_issuers") {
|
||||
"caIssuers"
|
||||
} else {
|
||||
"OCSP"
|
||||
};
|
||||
let Some(location) = entry.get("location") else {
|
||||
continue;
|
||||
};
|
||||
let location = general_name_entries_to_text(std::slice::from_ref(location));
|
||||
let Some((kind, value)) = location.split_once(':') else {
|
||||
continue;
|
||||
};
|
||||
rows.push(ListRow::new(next_id, &format!("{method};{kind}"), value));
|
||||
next_id += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
editor.rows.set(rows);
|
||||
editor.next_id.set(next_id);
|
||||
@@ -120,6 +140,7 @@ pub fn list_editor_dialog(
|
||||
let kind = match config.format {
|
||||
ListEditorFormat::GeneralNames => "DNS",
|
||||
ListEditorFormat::UriList(_) => "URI",
|
||||
ListEditorFormat::AuthorityInformationAccess => "OCSP;URI",
|
||||
};
|
||||
rows.update(|rows| rows.push(ListRow::new(id, kind, "")));
|
||||
};
|
||||
@@ -139,11 +160,15 @@ pub fn list_editor_dialog(
|
||||
format!("{}:{}", row.kind.get_clone(), value)
|
||||
}
|
||||
ListEditorFormat::UriList(_) => value.to_string(),
|
||||
ListEditorFormat::AuthorityInformationAccess => {
|
||||
format!("{}:{}", row.kind.get_clone(), value)
|
||||
}
|
||||
});
|
||||
}
|
||||
let separator = match config.format {
|
||||
ListEditorFormat::GeneralNames => ", ",
|
||||
ListEditorFormat::UriList(separator) => separator,
|
||||
ListEditorFormat::AuthorityInformationAccess => ", ",
|
||||
};
|
||||
text.set(entries.join(separator));
|
||||
if let Some(target) = config.critical {
|
||||
@@ -252,6 +277,12 @@ pub fn list_editor_dialog(
|
||||
"For otherName, use <OID>;UTF8:<content> or <OID>;DER:<hex>."
|
||||
}
|
||||
}
|
||||
} else if matches!(config.format, ListEditorFormat::AuthorityInformationAccess) {
|
||||
view! {
|
||||
div(class="form-text mt-2") {
|
||||
"Use OCSP or caIssuers with a URI, email, RID, DNS or IP location."
|
||||
}
|
||||
}
|
||||
} else {
|
||||
view! {}
|
||||
})
|
||||
@@ -286,9 +317,82 @@ fn list_type_options(format: ListEditorFormat) -> View {
|
||||
ListEditorFormat::UriList(_) => view! {
|
||||
option(value="URI") { "URI" }
|
||||
},
|
||||
ListEditorFormat::AuthorityInformationAccess => view! {
|
||||
option(value="OCSP;URI") { "OCSP;URI" }
|
||||
option(value="OCSP;email") { "OCSP;email" }
|
||||
option(value="OCSP;RID") { "OCSP;RID" }
|
||||
option(value="OCSP;DNS") { "OCSP;DNS" }
|
||||
option(value="OCSP;IP") { "OCSP;IP" }
|
||||
option(value="caIssuers;URI") { "caIssuers;URI" }
|
||||
option(value="caIssuers;email") { "caIssuers;email" }
|
||||
option(value="caIssuers;RID") { "caIssuers;RID" }
|
||||
option(value="caIssuers;DNS") { "caIssuers;DNS" }
|
||||
option(value="caIssuers;IP") { "caIssuers;IP" }
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_aia_entries(text: &str) -> Result<Vec<Value>, String> {
|
||||
let mut entries = Vec::new();
|
||||
for chunk in split_list(text) {
|
||||
let (prefix, value) = chunk.split_once(':').ok_or_else(|| {
|
||||
format!("authority information access entry {chunk:?} must use METHOD;TYPE:value")
|
||||
})?;
|
||||
let (method, kind) = prefix.split_once(';').ok_or_else(|| {
|
||||
format!("authority information access entry {chunk:?} must use METHOD;TYPE:value")
|
||||
})?;
|
||||
let method = match method.trim().to_ascii_lowercase().as_str() {
|
||||
"ocsp" => "ocsp",
|
||||
"caissuers" | "ca_issuers" | "ca-issuers" => "ca_issuers",
|
||||
other => {
|
||||
return Err(format!(
|
||||
"unknown authority information access method {other:?}"
|
||||
))
|
||||
}
|
||||
};
|
||||
let kind = match kind.trim().to_ascii_lowercase().as_str() {
|
||||
"uri" => "uri",
|
||||
"email" => "email",
|
||||
"rid" => "rid",
|
||||
"dns" => "dns",
|
||||
"ip" => "ip",
|
||||
other => {
|
||||
return Err(format!(
|
||||
"unknown authority information access location type {other:?}"
|
||||
))
|
||||
}
|
||||
};
|
||||
let location = parse_general_name_entries(&format!("{kind}:{value}"))
|
||||
.into_iter()
|
||||
.next()
|
||||
.ok_or_else(|| format!("authority information access entry {chunk:?} is invalid"))?;
|
||||
entries.push(json!({"method": method, "location": location}));
|
||||
}
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
pub fn aia_entries_to_text(entries: &[Value]) -> String {
|
||||
entries
|
||||
.iter()
|
||||
.filter_map(|entry| {
|
||||
let method = match entry
|
||||
.get("method")
|
||||
.and_then(|value| value.as_str())?
|
||||
.to_ascii_lowercase()
|
||||
.as_str()
|
||||
{
|
||||
"ocsp" => "OCSP",
|
||||
"caissuers" | "ca_issuers" | "ca-issuers" => "caIssuers",
|
||||
_ => return None,
|
||||
};
|
||||
let location =
|
||||
general_name_entries_to_text(std::slice::from_ref(entry.get("location")?));
|
||||
(!location.is_empty()).then(|| format!("{method};{location}"))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
pub fn split_list(text: &str) -> Vec<String> {
|
||||
text.split(|c| c == ',' || c == '\n')
|
||||
.map(|item| item.trim().to_string())
|
||||
@@ -425,4 +529,29 @@ mod tests {
|
||||
assert_eq!(uri_value(&items[0]), "https://one.example/crl");
|
||||
assert_eq!(uri_value(&items[1]), "https://two.example/crl");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aia_entries_round_trip_methods_and_location_types() {
|
||||
let text = "OCSP;URI:https://ocsp.example.com, caIssuers;URI:https://example.com/ca.crt, OCSP;DNS:ocsp.example.com, caIssuers;email:pki@example.com, OCSP;RID:1.2.3.4.5, caIssuers;IP:192.0.2.1";
|
||||
let entries = parse_aia_entries(text).unwrap();
|
||||
|
||||
assert_eq!(entries.len(), 6);
|
||||
assert_eq!(entries[1]["method"], "ca_issuers");
|
||||
assert_eq!(
|
||||
entries[1]["location"],
|
||||
json!({"type": "uri", "value": "https://example.com/ca.crt"})
|
||||
);
|
||||
assert_eq!(
|
||||
entries[4]["location"],
|
||||
json!({"type": "rid", "value": "1.2.3.4.5"})
|
||||
);
|
||||
assert_eq!(aia_entries_to_text(&entries), text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aia_entries_reject_invalid_prefixes() {
|
||||
assert!(parse_aia_entries("OCSP;URI").is_err());
|
||||
assert!(parse_aia_entries("bogus;URI:https://example.com").is_err());
|
||||
assert!(parse_aia_entries("OCSP;UPN:user@example.com").is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ use crate::components::list_editor::{
|
||||
use crate::components::nav::{app_nav, whoami};
|
||||
use crate::components::ui::{page_header, toast_error};
|
||||
use crate::model::{
|
||||
CertificateDetail, CertificateSummary, DnAttribute, KeySummary, Me, OptionsPage, TemplateSummary,
|
||||
CertificateDetail, CertificateSummary, DnAttribute, KeySummary, Me, OptionsPage,
|
||||
TemplateSummary,
|
||||
};
|
||||
|
||||
const DEFAULT_EXPLICIT_DN: [&str; 7] = ["C", "ST", "L", "O", "OU", "CN", "emailAddress"];
|
||||
@@ -134,8 +135,8 @@ pub fn CertificateNewPage(vault_id: String, from_cert: Option<String>) -> View {
|
||||
let ocsp_staple = create_signal(false);
|
||||
let crl_urls = create_signal(String::new());
|
||||
let crl_critical = create_signal(false);
|
||||
let aia_ocsp = create_signal(String::new());
|
||||
let aia_ca_issuers = create_signal(String::new());
|
||||
let aia = create_signal(String::new());
|
||||
let aia_critical = create_signal(false);
|
||||
let nc_permitted = create_signal(String::new());
|
||||
let nc_excluded = create_signal(String::new());
|
||||
let nc_critical = create_signal(true);
|
||||
@@ -149,8 +150,8 @@ pub fn CertificateNewPage(vault_id: String, from_cert: Option<String>) -> View {
|
||||
let ext = ExtensionSignals {
|
||||
crl_urls,
|
||||
crl_critical,
|
||||
aia_ocsp,
|
||||
aia_ca_issuers,
|
||||
aia,
|
||||
aia_critical,
|
||||
nc_permitted,
|
||||
nc_excluded,
|
||||
nc_critical,
|
||||
|
||||
@@ -52,8 +52,8 @@ pub fn TemplateEditorPage(vault_id: String, template_id: Option<String>) -> View
|
||||
let aki = create_signal(true);
|
||||
let crl_urls = create_signal(String::new());
|
||||
let crl_critical = create_signal(false);
|
||||
let aia_ocsp = create_signal(String::new());
|
||||
let aia_ca_issuers = create_signal(String::new());
|
||||
let aia = create_signal(String::new());
|
||||
let aia_critical = create_signal(false);
|
||||
let nc_permitted = create_signal(String::new());
|
||||
let nc_excluded = create_signal(String::new());
|
||||
let nc_critical = create_signal(true);
|
||||
@@ -67,8 +67,8 @@ pub fn TemplateEditorPage(vault_id: String, template_id: Option<String>) -> View
|
||||
let ext = ExtensionSignals {
|
||||
crl_urls,
|
||||
crl_critical,
|
||||
aia_ocsp,
|
||||
aia_ca_issuers,
|
||||
aia,
|
||||
aia_critical,
|
||||
nc_permitted,
|
||||
nc_excluded,
|
||||
nc_critical,
|
||||
|
||||
Reference in New Issue
Block a user