diff --git a/pkg/utils/cert/x509/x509.go b/pkg/utils/cert/x509/x509.go index 38040a8b..bb0ebb45 100644 --- a/pkg/utils/cert/x509/x509.go +++ b/pkg/utils/cert/x509/x509.go @@ -2,8 +2,11 @@ import ( "crypto/x509" + "encoding/asn1" ) +var oidSubjectAlternativeNameExtension = asn1.ObjectIdentifier{2, 5, 29, 17} + // 返回指定 x509.Certificate 对象的主题名称。 // 如果主题名称为空,则返回第一个主题替代名称。 // @@ -38,18 +41,21 @@ func GetSubjectAltNames(cert *x509.Certificate) []string { sans := make([]string, 0) if cert != nil { - for _, dnsName := range cert.DNSNames { - sans = append(sans, dnsName) - } - for _, ipAddr := range cert.IPAddresses { - sans = append(sans, ipAddr.String()) - } - for _, email := range cert.EmailAddresses { - sans = append(sans, email) - } - for _, uri := range cert.URIs { - if uri != nil { - sans = append(sans, uri.String()) + // 注意,这里不直接使用 `DNSNames`、`IPAddresses` 等字段,以保证原始顺序不变 + for _, ext := range cert.Extensions { + if ext.Id.Equal(oidSubjectAlternativeNameExtension) { + var raw asn1.RawValue + _, err := asn1.Unmarshal(ext.Value, &raw) + if err != nil { + continue + } + + var seq asn1.RawValue + if _, err := asn1.Unmarshal(raw.Bytes, &seq); err != nil { + continue + } + + sans = append(sans, string(seq.Bytes)) } } } diff --git a/ui/package-lock.json b/ui/package-lock.json index 8356360d..c6eb4331 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -14,6 +14,7 @@ "@flowgram.ai/document": "0.5.6", "@flowgram.ai/fixed-layout-editor": "0.5.6", "@flowgram.ai/minimap-plugin": "0.5.6", + "@peculiar/x509": "^1.14.2", "@tabler/icons-react": "^3.36.0", "@uiw/codemirror-extensions-basic-setup": "^4.25.4", "@uiw/codemirror-theme-vscode": "^4.25.4", @@ -37,6 +38,7 @@ "react-i18next": "^16.5.0", "react-router": "^7.11.0", "react-router-dom": "^7.11.0", + "reflect-metadata": "^0.2.2", "tailwind-merge": "^3.4.0", "yaml": "^2.8.2", "zod": "^4.2.1", @@ -3177,6 +3179,154 @@ "@tybys/wasm-util": "^0.9.0" } }, + "node_modules/@peculiar/asn1-cms": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@peculiar/asn1-cms/-/asn1-cms-2.6.0.tgz", + "integrity": "sha512-2uZqP+ggSncESeUF/9Su8rWqGclEfEiz1SyU02WX5fUONFfkjzS2Z/F1Li0ofSmf4JqYXIOdCAZqIXAIBAT1OA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "@peculiar/asn1-x509-attr": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-csr": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@peculiar/asn1-csr/-/asn1-csr-2.6.0.tgz", + "integrity": "sha512-BeWIu5VpTIhfRysfEp73SGbwjjoLL/JWXhJ/9mo4vXnz3tRGm+NGm3KNcRzQ9VMVqwYS2RHlolz21svzRXIHPQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-ecc": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@peculiar/asn1-ecc/-/asn1-ecc-2.6.0.tgz", + "integrity": "sha512-FF3LMGq6SfAOwUG2sKpPXblibn6XnEIKa+SryvUl5Pik+WR9rmRA3OCiwz8R3lVXnYnyRkSZsSLdml8H3UiOcw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pfx": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@peculiar/asn1-pfx/-/asn1-pfx-2.6.0.tgz", + "integrity": "sha512-rtUvtf+tyKGgokHHmZzeUojRZJYPxoD/jaN1+VAB4kKR7tXrnDCA/RAWXAIhMJJC+7W27IIRGe9djvxKgsldCQ==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-pkcs8": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs8": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.0.tgz", + "integrity": "sha512-KyQ4D8G/NrS7Fw3XCJrngxmjwO/3htnA0lL9gDICvEQ+GJ+EPFqldcJQTwPIdvx98Tua+WjkdKHSC0/Km7T+lA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-pkcs9": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.0.tgz", + "integrity": "sha512-b78OQ6OciW0aqZxdzliXGYHASeCvvw5caqidbpQRYW2mBtXIX2WhofNXTEe7NyxTb0P6J62kAAWLwn0HuMF1Fw==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-pfx": "^2.6.0", + "@peculiar/asn1-pkcs8": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "@peculiar/asn1-x509-attr": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-rsa": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@peculiar/asn1-rsa/-/asn1-rsa-2.6.0.tgz", + "integrity": "sha512-Nu4C19tsrTsCp9fDrH+sdcOKoVfdfoQQ7S3VqjJU6vedR7tY3RLkQ5oguOIB3zFW33USDUuYZnPEQYySlgha4w==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", + "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", + "license": "MIT", + "dependencies": { + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@peculiar/asn1-x509/-/asn1-x509-2.6.0.tgz", + "integrity": "sha512-uzYbPEpoQiBoTq0/+jZtpM6Gq6zADBx+JNFP3yqRgziWBxQ/Dt/HcuvRfm9zJTPdRcBqPNdaRHTVwpyiq6iNMA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "asn1js": "^3.0.6", + "pvtsutils": "^1.3.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/asn1-x509-attr": { + "version": "2.6.0", + "resolved": "https://registry.npmmirror.com/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.0.tgz", + "integrity": "sha512-MuIAXFX3/dc8gmoZBkwJWxUWOSvG4MMDntXhrOZpJVMkYX+MYc/rUAU2uJOved9iJEoiUx7//3D8oG83a78UJA==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "asn1js": "^3.0.6", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/x509": { + "version": "1.14.2", + "resolved": "https://registry.npmmirror.com/@peculiar/x509/-/x509-1.14.2.tgz", + "integrity": "sha512-r2w1Hg6pODDs0zfAKHkSS5HLkOLSeburtcgwvlLLWWCixw+MmW3U6kD5ddyvc2Y2YdbGuVwCF2S2ASoU1cFAag==", + "license": "MIT", + "dependencies": { + "@peculiar/asn1-cms": "^2.6.0", + "@peculiar/asn1-csr": "^2.6.0", + "@peculiar/asn1-ecc": "^2.6.0", + "@peculiar/asn1-pkcs9": "^2.6.0", + "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-schema": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.0", + "pvtsutils": "^1.3.6", + "reflect-metadata": "^0.2.2", + "tslib": "^2.8.1", + "tsyringe": "^4.10.0" + }, + "engines": { + "node": ">=22.0.0" + } + }, "node_modules/@phosphor/algorithm": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/@phosphor/algorithm/-/algorithm-1.2.0.tgz", @@ -5616,6 +5766,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asn1js": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/asn1js/-/asn1js-3.0.7.tgz", + "integrity": "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==", + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -8625,6 +8789,24 @@ "node": ">=6" } }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmmirror.com/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmmirror.com/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/radash": { "version": "12.1.1", "resolved": "https://registry.npmmirror.com/radash/-/radash-12.1.1.tgz", @@ -8766,7 +8948,8 @@ "node_modules/reflect-metadata": { "version": "0.2.2", "resolved": "https://registry.npmmirror.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" }, "node_modules/reflect.getprototypeof": { "version": "1.0.9", @@ -9596,6 +9779,24 @@ "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, + "node_modules/tsyringe": { + "version": "4.10.0", + "resolved": "https://registry.npmmirror.com/tsyringe/-/tsyringe-4.10.0.tgz", + "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", + "license": "MIT", + "dependencies": { + "tslib": "^1.9.3" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/tsyringe/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz", diff --git a/ui/package.json b/ui/package.json index fd125b88..3cf82f58 100644 --- a/ui/package.json +++ b/ui/package.json @@ -16,6 +16,7 @@ "@flowgram.ai/document": "0.5.6", "@flowgram.ai/fixed-layout-editor": "0.5.6", "@flowgram.ai/minimap-plugin": "0.5.6", + "@peculiar/x509": "^1.14.2", "@tabler/icons-react": "^3.36.0", "@uiw/codemirror-extensions-basic-setup": "^4.25.4", "@uiw/codemirror-theme-vscode": "^4.25.4", @@ -39,6 +40,7 @@ "react-i18next": "^16.5.0", "react-router": "^7.11.0", "react-router-dom": "^7.11.0", + "reflect-metadata": "^0.2.2", "tailwind-merge": "^3.4.0", "yaml": "^2.8.2", "zod": "^4.2.1", diff --git a/ui/src/App.tsx b/ui/src/App.tsx index a6f894fd..a29e9d17 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -1,4 +1,5 @@ -import { useEffect, useLayoutEffect, useState } from "react"; +import "reflect-metadata"; +import { useEffect, useLayoutEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { RouterProvider } from "react-router-dom"; import { Alert, App, ConfigProvider, type ThemeConfig, theme } from "antd"; diff --git a/ui/src/components/workflow/designer/forms/BizApplyNodeConfigForm.tsx b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigForm.tsx index 15307f2c..2d672d5c 100644 --- a/ui/src/components/workflow/designer/forms/BizApplyNodeConfigForm.tsx +++ b/ui/src/components/workflow/designer/forms/BizApplyNodeConfigForm.tsx @@ -227,12 +227,12 @@ const BizApplyNodeConfigForm = ({ node, ...props }: BizApplyNodeConfigFormProps) value: resp.data.keyAlgorithm, }, ]); - } catch (e) { + } catch (err) { formInst.setFields([ { name: "keyContent", value: value, - errors: [getErrMsg(e)], + errors: [getErrMsg(err)], }, ]); } diff --git a/ui/src/components/workflow/designer/forms/BizUploadNodeConfigForm.tsx b/ui/src/components/workflow/designer/forms/BizUploadNodeConfigForm.tsx index 2924d44b..46c815e2 100644 --- a/ui/src/components/workflow/designer/forms/BizUploadNodeConfigForm.tsx +++ b/ui/src/components/workflow/designer/forms/BizUploadNodeConfigForm.tsx @@ -13,6 +13,7 @@ import { type WorkflowNodeConfigForBizUpload, defaultNodeConfigForBizUpload } fr import { useAntdForm } from "@/hooks"; import { getErrMsg } from "@/utils/error"; import { isUrlWithHttpOrHttps } from "@/utils/validator"; +import { getSubjectAltNames as getX509SubjectAltNames } from "@/utils/x509"; import { NodeFormContextProvider } from "./_context"; import { NodeType } from "../nodes/typings"; @@ -46,42 +47,40 @@ const BizUploadNodeConfigForm = ({ node, ...props }: BizUploadNodeConfigFormProp }); const fieldSource = Form.useWatch("source", { form: formInst, preserve: true }); + const fieldCertificate = Form.useWatch("certificate", { form: formInst, preserve: true }); + const fieldName = useMemo(() => { + if (!fieldSource || fieldSource === UPLOAD_SOURCE_FORM) { + return fieldCertificate ? getX509SubjectAltNames(fieldCertificate).join(";") : void 0; + } + return void 0; + }, [fieldSource, fieldCertificate]); const handleSourceChange = (value: string) => { if (value === initialValues?.source) { - formInst.resetFields(["certificate", "privateKey", "name"]); + formInst.resetFields(["certificate", "privateKey"]); } else { setTimeout(() => { formInst.setFieldValue("certificate", ""); formInst.setFieldValue("privateKey", ""); - formInst.setFieldValue("name", ""); }, 0); } }; const handleCertificatePEMChange = async (value: string) => { try { - const resp = await validateCertificate(value); + await validateCertificate(value); formInst.setFields([ - { - name: "name", - value: resp.data.subjectAltNames, - }, { name: "certificate", value: value, }, ]); - } catch (e) { + } catch (err) { formInst.setFields([ - { - name: "name", - value: "", - }, { name: "certificate", value: value, - errors: [getErrMsg(e)], + errors: [getErrMsg(err)], }, ]); } @@ -96,12 +95,12 @@ const BizUploadNodeConfigForm = ({ node, ...props }: BizUploadNodeConfigFormProp value: value, }, ]); - } catch (e) { + } catch (err) { formInst.setFields([ { name: "privateKey", value: value, - errors: [getErrMsg(e)], + errors: [getErrMsg(err)], }, ]); } @@ -120,8 +119,8 @@ const BizUploadNodeConfigForm = ({ node, ...props }: BizUploadNodeConfigFormProp - - + + diff --git a/ui/src/components/workflow/designer/nodes/BizUploadNodeRegistry.tsx b/ui/src/components/workflow/designer/nodes/BizUploadNodeRegistry.tsx index 3b2efb88..0111e558 100644 --- a/ui/src/components/workflow/designer/nodes/BizUploadNodeRegistry.tsx +++ b/ui/src/components/workflow/designer/nodes/BizUploadNodeRegistry.tsx @@ -3,6 +3,7 @@ import { FeedbackLevel, Field } from "@flowgram.ai/fixed-layout-editor"; import { IconCloudUpload } from "@tabler/icons-react"; import { newNode } from "@/domain/workflow"; +import { getSubjectAltNames as getX509SubjectAltNames } from "@/utils/x509"; import { BaseNode } from "./_shared"; import { NodeKindType, type NodeRegistry, NodeType } from "./typings"; @@ -47,8 +48,10 @@ export const BizUploadNodeRegistry: NodeRegistry = { {({ field: { value: fieldSource } }) => ( <> {fieldSource == null || fieldSource === "" || fieldSource === "form" ? ( - name="config.name"> - {({ field: { value: fieldName } }) => <>{fieldName || t("workflow.detail.design.editor.placeholder")}} + name="config.certificate"> + {({ field: { value: fieldCertificate } }) => ( + <>{(fieldCertificate ? getX509SubjectAltNames(fieldCertificate).join(";") : "") || t("workflow.detail.design.editor.placeholder")} + )} ) : ( name="config.certificate"> diff --git a/ui/src/domain/workflow.ts b/ui/src/domain/workflow.ts index 56623dbb..e0b91979 100644 --- a/ui/src/domain/workflow.ts +++ b/ui/src/domain/workflow.ts @@ -125,7 +125,6 @@ export const defaultNodeConfigForBizApply = (): Partial { + const certX509 = new X509Certificate(certPEM); + if (certX509 == null) throw new Error("Could not parse X.509 certificate. Maybe it is not in PEM format?"); + return certX509; +}; + +export const getSubjectAltNames = (cert: string | X509Certificate): string[] => { + const subjectAltNames: string[] = []; + + try { + const certX509 = X509Certificate.isAsnEncoded(cert) ? parseX509Certificate(cert) : cert; + if (certX509 == null) return []; + + const sanExt = certX509.getExtension(SubjectAlternativeNameExtension); + subjectAltNames.push(...(sanExt?.names?.items?.map((san) => san.value) || [])); + } catch (err) { + console.error(err); + } + + return subjectAltNames; +};