Add full XCA curve and digest parity
CI / Build and push docker image (push) Successful in 2m16s
CI / Build and push OCSP image (push) Successful in 34s

This commit is contained in:
KCA
2026-10-26 03:45:25 +00:00
parent b910fa706c
commit d5f060cbf0
36 changed files with 4326 additions and 331 deletions
+70 -23
View File
@@ -4,6 +4,10 @@ use sycamore::prelude::*;
use wasm_bindgen_futures::spawn_local;
use crate::api;
use crate::components::crypto::{
add_key_parameters, apply_signature_hash, key_parameter_input, key_type_select,
signature_hash_input,
};
use crate::components::extension_form::{
build_extra_extensions, extension_fields, load_extra_extensions, ExtensionSignals,
};
@@ -14,7 +18,7 @@ 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, TemplateSummary,
CertificateDetail, CertificateSummary, DnAttribute, KeySummary, Me, OptionsPage, TemplateSummary,
};
const DEFAULT_EXPLICIT_DN: [&str; 7] = ["C", "ST", "L", "O", "OU", "CN", "emailAddress"];
@@ -73,6 +77,7 @@ pub fn CertificateNewPage(vault_id: String, from_cert: Option<String>) -> View {
let reactive_root = use_global_scope();
let id: &'static str = Box::leak(vault_id.clone().into_boxed_str());
let me = create_signal(None::<Me>);
let options = create_signal(None::<OptionsPage>);
let keys = create_signal(Vec::<KeySummary>::new());
let signers = create_signal(Vec::<CertificateSummary>::new());
let templates = create_signal(Vec::<TemplateSummary>::new());
@@ -82,6 +87,7 @@ pub fn CertificateNewPage(vault_id: String, from_cert: Option<String>) -> View {
let request_id = query_param("request").unwrap_or_default();
let request_mode = !request_id.is_empty();
let request_id_for_load = request_id.clone();
let request_key_id = create_signal(String::new());
let name = create_signal(String::new());
let comment = create_signal(String::new());
@@ -90,6 +96,7 @@ pub fn CertificateNewPage(vault_id: String, from_cert: Option<String>) -> View {
let new_key = create_signal(false);
let key_type = create_signal("rsa".to_string());
let key_size = create_signal("2048".to_string());
let curve = create_signal("secp256r1".to_string());
let template_id = create_signal(String::new());
let template_apply_mode = create_signal("all".to_string());
let ca_default_template = create_signal(None::<String>);
@@ -256,6 +263,9 @@ pub fn CertificateNewPage(vault_id: String, from_cert: Option<String>) -> View {
if let Some(size) = key.get("size").and_then(|v| v.as_i64()) {
key_size.set(size.to_string());
}
if let Some(value) = key.get("curve").and_then(|v| v.as_str()) {
curve.set(value.to_string());
}
}
if let Some(items) = settings.get("explicit_dn").and_then(|v| v.as_array()) {
let names = items
@@ -277,8 +287,9 @@ pub fn CertificateNewPage(vault_id: String, from_cert: Option<String>) -> View {
}
}
}
if let Ok(options) = api::options(&id).await {
dn_attributes.set(options.dn_attributes);
if let Ok(page) = api::options(id).await {
dn_attributes.set(page.dn_attributes.clone());
options.set(Some(page));
}
if !request_mode {
if let Some(cert_id) = from_cert.clone() {
@@ -311,6 +322,7 @@ pub fn CertificateNewPage(vault_id: String, from_cert: Option<String>) -> View {
if request_mode {
if let Ok(value) = api::request(&id, &request_id_for_load).await {
name.set(format!("{} certificate", value.summary.name));
request_key_id.set(value.summary.key_id.clone().unwrap_or_default());
let subject_json =
serde_json::to_value(&value.subject_entries).unwrap_or(json!([]));
reactive_root.run_in(|| apply_subject_json(&form, &subject_json));
@@ -318,6 +330,34 @@ pub fn CertificateNewPage(vault_id: String, from_cert: Option<String>) -> View {
}
});
let signing_key_type = move || {
let keys = keys.get_clone();
let signer = signer_id.get_clone();
if signer != "self" {
let issuer_key_id = signers
.get_clone()
.into_iter()
.find(|cert| cert.id == signer)
.and_then(|cert| cert.key_id);
return issuer_key_id.and_then(|key_id| {
keys.into_iter()
.find(|key| key.id == key_id)
.map(|key| key.key_type)
});
}
if !request_mode && new_key.get() {
return Some(key_type.get_clone());
}
let subject_key_id = if request_mode {
request_key_id.get_clone()
} else {
key_id.get_clone()
};
keys.into_iter()
.find(|key| key.id == subject_key_id)
.map(|key| key.key_type)
};
let apply_template_id = move |template: String, mode: String| {
if template.is_empty() {
return;
@@ -430,10 +470,14 @@ pub fn CertificateNewPage(vault_id: String, from_cert: Option<String>) -> View {
"comment": comment.get_clone(),
"subject": subject,
"extensions": extensions,
"signature_hash": signature_hash.get_clone(),
"signer_id": if signer == "self" { serde_json::Value::Null } else { json!(signer) },
"validity": validity_value(&form),
});
apply_signature_hash(
&mut payload,
signing_key_type().as_deref(),
&signature_hash.get_clone(),
);
if validity_mode.get_clone() == "absolute" && !no_expiration.get() {
payload["not_before"] = json!(not_before_text.get_clone());
payload["not_after"] = json!(not_after_text.get_clone());
@@ -443,11 +487,17 @@ pub fn CertificateNewPage(vault_id: String, from_cert: Option<String>) -> View {
payload["modify_subject"] = json!(true);
payload["copy_request_extensions"] = json!(false);
} else if new_key.get() {
payload["new_key"] = json!({
let mut generated_key = json!({
"key_type": key_type.get_clone(),
"size": key_size.get_clone().parse::<i64>().unwrap_or(2048),
"name": format!("{} key", name.get_clone()),
});
add_key_parameters(
&mut generated_key,
&key_type.get_clone(),
&key_size.get_clone(),
&curve.get_clone(),
);
payload["new_key"] = generated_key;
} else if !key_id.get_clone().is_empty() {
payload["key_id"] = json!(key_id.get_clone());
} else {
@@ -494,15 +544,18 @@ pub fn CertificateNewPage(vault_id: String, from_cert: Option<String>) -> View {
}
};
let signer = signer_id.get_clone();
let payload = json!({
let mut payload = json!({
"name": if name.get_clone().is_empty() { "preview".to_string() } else { name.get_clone() },
"subject": subject,
"extensions": extensions,
"signature_hash": signature_hash.get_clone(),
"signer_id": if signer == "self" { serde_json::Value::Null } else { json!(signer) },
"validity": validity_value(&form),
});
let mut payload = payload;
apply_signature_hash(
&mut payload,
signing_key_type().as_deref(),
&signature_hash.get_clone(),
);
if validity_mode.get_clone() == "absolute" && !no_expiration.get() {
payload["not_before"] = json!(not_before_text.get_clone());
payload["not_after"] = json!(not_after_text.get_clone());
@@ -602,13 +655,12 @@ pub fn CertificateNewPage(vault_id: String, from_cert: Option<String>) -> View {
}
}
div(class="col-md-6") {
label(class="form-label") { "Signature hash" }
select(class="form-select", bind:value=signature_hash) {
option(value="sha256") { "SHA-256" }
option(value="sha384") { "SHA-384" }
option(value="sha512") { "SHA-512" }
option(value="sha1") { "SHA-1" }
}
(signature_hash_input(
"Signature hash",
options,
signing_key_type,
signature_hash,
))
}
div(class="col-md-6") {
label(class="form-label") { "Template" }
@@ -661,15 +713,10 @@ pub fn CertificateNewPage(vault_id: String, from_cert: Option<String>) -> View {
div(class="row g-3") {
div(class="col-md-4") {
label(class="form-label") { "Key type" }
select(class="form-select", bind:value=key_type) {
option(value="rsa") { "RSA" }
option(value="ec") { "EC" }
option(value="ed25519") { "ED25519" }
}
(key_type_select(options, key_type))
}
div(class="col-md-4") {
label(class="form-label") { "Key size" }
input(class="form-control", bind:value=key_size)
(key_parameter_input(options, key_type, key_size, curve))
}
}
}