Merge d0582b3142 into b47d132839
				
					
				
			This commit is contained in:
		
						commit
						f6bfd2f850
					
				
							
								
								
									
										1
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -33,6 +33,7 @@ declare module '@vue/runtime-core' { | |||||||
|     CCollapse: typeof import('./src/ui/c-collapse/c-collapse.vue')['default'] |     CCollapse: typeof import('./src/ui/c-collapse/c-collapse.vue')['default'] | ||||||
|     'CCollapse.demo': typeof import('./src/ui/c-collapse/c-collapse.demo.vue')['default'] |     'CCollapse.demo': typeof import('./src/ui/c-collapse/c-collapse.demo.vue')['default'] | ||||||
|     CDiffEditor: typeof import('./src/ui/c-diff-editor/c-diff-editor.vue')['default'] |     CDiffEditor: typeof import('./src/ui/c-diff-editor/c-diff-editor.vue')['default'] | ||||||
|  |     CertificateKeyParser: typeof import('./src/tools/certificate-key-parser/certificate-key-parser.vue')['default'] | ||||||
|     CFileUpload: typeof import('./src/ui/c-file-upload/c-file-upload.vue')['default'] |     CFileUpload: typeof import('./src/ui/c-file-upload/c-file-upload.vue')['default'] | ||||||
|     'CFileUpload.demo': typeof import('./src/ui/c-file-upload/c-file-upload.demo.vue')['default'] |     'CFileUpload.demo': typeof import('./src/ui/c-file-upload/c-file-upload.demo.vue')['default'] | ||||||
|     ChmodCalculator: typeof import('./src/tools/chmod-calculator/chmod-calculator.vue')['default'] |     ChmodCalculator: typeof import('./src/tools/chmod-calculator/chmod-calculator.vue')['default'] | ||||||
|  | |||||||
| @ -69,7 +69,7 @@ | |||||||
|     "highlight.js": "^11.7.0", |     "highlight.js": "^11.7.0", | ||||||
|     "iarna-toml-esm": "^3.0.5", |     "iarna-toml-esm": "^3.0.5", | ||||||
|     "ibantools": "^4.3.3", |     "ibantools": "^4.3.3", | ||||||
|     "js-base64": "^3.7.6", |     "js-base64": "^3.7.7", | ||||||
|     "json5": "^2.2.3", |     "json5": "^2.2.3", | ||||||
|     "jwt-decode": "^3.1.2", |     "jwt-decode": "^3.1.2", | ||||||
|     "libphonenumber-js": "^1.10.28", |     "libphonenumber-js": "^1.10.28", | ||||||
| @ -82,6 +82,7 @@ | |||||||
|     "naive-ui": "^2.35.0", |     "naive-ui": "^2.35.0", | ||||||
|     "netmask": "^2.0.2", |     "netmask": "^2.0.2", | ||||||
|     "node-forge": "^1.3.1", |     "node-forge": "^1.3.1", | ||||||
|  |     "openpgp": "^5.11.1", | ||||||
|     "oui-data": "^1.0.10", |     "oui-data": "^1.0.10", | ||||||
|     "pdf-signature-reader": "^1.4.2", |     "pdf-signature-reader": "^1.4.2", | ||||||
|     "pinia": "^2.0.34", |     "pinia": "^2.0.34", | ||||||
| @ -89,6 +90,7 @@ | |||||||
|     "qrcode": "^1.5.1", |     "qrcode": "^1.5.1", | ||||||
|     "randexp": "^0.5.3", |     "randexp": "^0.5.3", | ||||||
|     "sql-formatter": "^13.0.0", |     "sql-formatter": "^13.0.0", | ||||||
|  |     "sshpk": "^1.18.0", | ||||||
|     "ua-parser-js": "^1.0.35", |     "ua-parser-js": "^1.0.35", | ||||||
|     "ulid": "^2.3.0", |     "ulid": "^2.3.0", | ||||||
|     "unicode-emoji-json": "^0.4.0", |     "unicode-emoji-json": "^0.4.0", | ||||||
| @ -121,6 +123,7 @@ | |||||||
|     "@types/node": "^18.15.11", |     "@types/node": "^18.15.11", | ||||||
|     "@types/node-forge": "^1.3.2", |     "@types/node-forge": "^1.3.2", | ||||||
|     "@types/qrcode": "^1.5.0", |     "@types/qrcode": "^1.5.0", | ||||||
|  |     "@types/sshpk": "^1.17.4", | ||||||
|     "@types/ua-parser-js": "^0.7.36", |     "@types/ua-parser-js": "^0.7.36", | ||||||
|     "@types/uuid": "^9.0.0", |     "@types/uuid": "^9.0.0", | ||||||
|     "@unocss/eslint-config": "^0.57.0", |     "@unocss/eslint-config": "^0.57.0", | ||||||
| @ -142,6 +145,7 @@ | |||||||
|     "unplugin-icons": "^0.17.0", |     "unplugin-icons": "^0.17.0", | ||||||
|     "unplugin-vue-components": "^0.25.0", |     "unplugin-vue-components": "^0.25.0", | ||||||
|     "vite": "^4.4.9", |     "vite": "^4.4.9", | ||||||
|  |     "vite-plugin-node-polyfills": "^0.21.0", | ||||||
|     "vite-plugin-pwa": "^0.16.0", |     "vite-plugin-pwa": "^0.16.0", | ||||||
|     "vite-plugin-vue-markdown": "^0.23.5", |     "vite-plugin-vue-markdown": "^0.23.5", | ||||||
|     "vite-svg-loader": "^4.0.0", |     "vite-svg-loader": "^4.0.0", | ||||||
|  | |||||||
							
								
								
									
										732
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										732
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -2,7 +2,12 @@ | |||||||
| import { useVModel } from '@vueuse/core'; | import { useVModel } from '@vueuse/core'; | ||||||
| import { useCopy } from '@/composable/copy'; | import { useCopy } from '@/composable/copy'; | ||||||
| 
 | 
 | ||||||
| const props = defineProps<{ value: string }>(); | const props = defineProps<{ | ||||||
|  |   value: string | ||||||
|  |   multiline?: boolean | ||||||
|  |   rows?: number | string | ||||||
|  |   autosize?: boolean | ||||||
|  | }>(); | ||||||
| const emit = defineEmits(['update:value']); | const emit = defineEmits(['update:value']); | ||||||
| 
 | 
 | ||||||
| const value = useVModel(props, 'value', emit); | const value = useVModel(props, 'value', emit); | ||||||
| @ -11,7 +16,12 @@ const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : 'Copy to cli | |||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|   <c-input-text v-model:value="value"> |   <c-input-text | ||||||
|  |     v-model:value="value" | ||||||
|  |     :multiline="multiline" | ||||||
|  |     :rows="rows" | ||||||
|  |     :autosize="autosize" | ||||||
|  |   > | ||||||
|     <template #suffix> |     <template #suffix> | ||||||
|       <c-tooltip :tooltip="tooltipText"> |       <c-tooltip :tooltip="tooltipText"> | ||||||
|         <c-button circle variant="text" size="small" @click="copy()"> |         <c-button circle variant="text" size="small" @click="copy()"> | ||||||
|  | |||||||
| @ -17,6 +17,7 @@ const props = withDefaults( | |||||||
|     language?: string |     language?: string | ||||||
|     copyPlacement?: 'top-right' | 'bottom-right' | 'outside' | 'none' |     copyPlacement?: 'top-right' | 'bottom-right' | 'outside' | 'none' | ||||||
|     copyMessage?: string |     copyMessage?: string | ||||||
|  |     wordWrap?: boolean | ||||||
|   }>(), |   }>(), | ||||||
|   { |   { | ||||||
|     followHeightOf: null, |     followHeightOf: null, | ||||||
| @ -49,7 +50,7 @@ const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : copyMessage. | |||||||
|         :style="height ? `min-height: ${height - 40 /* card padding */ + 10 /* negative margin compensation */}px` : ''" |         :style="height ? `min-height: ${height - 40 /* card padding */ + 10 /* negative margin compensation */}px` : ''" | ||||||
|       > |       > | ||||||
|         <n-config-provider :hljs="hljs"> |         <n-config-provider :hljs="hljs"> | ||||||
|           <n-code :code="value" :language="language" :trim="false" data-test-id="area-content" /> |           <n-code :code="value" :language="language" :word-wrap="wordWrap" :trim="false" data-test-id="area-content" /> | ||||||
|         </n-config-provider> |         </n-config-provider> | ||||||
|       </n-scrollbar> |       </n-scrollbar> | ||||||
|       <div absolute right-10px top-10px> |       <div absolute right-10px top-10px> | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| import { extension as getExtensionFromMimeType, extension as getMimeTypeFromExtension } from 'mime-types'; | import { extension as getExtensionFromMimeType, extension as getMimeTypeFromExtension } from 'mime-types'; | ||||||
| import type { Ref } from 'vue'; | import type { MaybeRef, Ref } from 'vue'; | ||||||
| import _ from 'lodash'; | import _ from 'lodash'; | ||||||
|  | import { get } from '@vueuse/core'; | ||||||
| 
 | 
 | ||||||
| export { | export { | ||||||
|   getMimeTypeFromBase64, |   getMimeTypeFromBase64, | ||||||
| @ -75,21 +76,11 @@ function downloadFromBase64({ sourceValue, filename, extension, fileMimeType }: | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function useDownloadFileFromBase64( | function useDownloadFileFromBase64( | ||||||
|   { source, filename, extension, fileMimeType }: |  | ||||||
|   { source: Ref<string>; filename?: string; extension?: string; fileMimeType?: string }) { |  | ||||||
|   return { |  | ||||||
|     download() { |  | ||||||
|       downloadFromBase64({ sourceValue: source.value, filename, extension, fileMimeType }); |  | ||||||
|     }, |  | ||||||
|   }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function useDownloadFileFromBase64Refs( |  | ||||||
|   { source, filename, extension }: |   { source, filename, extension }: | ||||||
|   { source: Ref<string>; filename?: Ref<string>; extension?: Ref<string> }) { |   { source: MaybeRef<string>; filename?: MaybeRef<string>; extension?: MaybeRef<string> }) { | ||||||
|   return { |   return { | ||||||
|     download() { |     download() { | ||||||
|       downloadFromBase64({ sourceValue: source.value, filename: filename?.value, extension: extension?.value }); |       downloadFromBase64({ sourceValue: get(source), filename: get(filename), extension: get(extension) }); | ||||||
|     }, |     }, | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
| @ -116,3 +107,13 @@ function previewImageFromBase64(base64String: string): HTMLImageElement { | |||||||
| 
 | 
 | ||||||
|   return img; |   return img; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | function useDownloadFileFromBase64Refs( | ||||||
|  |   { source, filename, extension }: | ||||||
|  |   { source: Ref<string>; filename?: Ref<string>; extension?: Ref<string> }) { | ||||||
|  |   return { | ||||||
|  |     download() { | ||||||
|  |       downloadFromBase64({ sourceValue: source.value, filename: filename?.value, extension: extension?.value }); | ||||||
|  |     }, | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										333
									
								
								src/tools/certificate-key-parser/certificate-key-parser.infos.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										333
									
								
								src/tools/certificate-key-parser/certificate-key-parser.infos.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,333 @@ | |||||||
|  | import type { | ||||||
|  |   Certificate, | ||||||
|  |   Fingerprint, | ||||||
|  |   Key, | ||||||
|  |   PrivateKey, Signature, | ||||||
|  | } from 'sshpk'; | ||||||
|  | import type * as openpgp from 'openpgp'; | ||||||
|  | import * as forge from 'node-forge'; | ||||||
|  | 
 | ||||||
|  | export interface LabelValue { | ||||||
|  |   label: string | ||||||
|  |   value: string | ||||||
|  |   multiline?: boolean | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function onErrorReturnErrorMessage(func: () => any) { | ||||||
|  |   try { | ||||||
|  |     return func(); | ||||||
|  |   } | ||||||
|  |   catch (e: any) { | ||||||
|  |     return e.toString(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function buf2Hex(buffer: ArrayBuffer) { // buffer is an ArrayBuffer
 | ||||||
|  |   return [...new Uint8Array(buffer)] | ||||||
|  |     .map(x => x.toString(16).padStart(2, '0')) | ||||||
|  |     .join(''); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function getPublicKeyLabelValues(publicKey: Key) { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       label: 'Type:', | ||||||
|  |       value: 'Public Key', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Key Type:', | ||||||
|  |       value: publicKey.type, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Size:', | ||||||
|  |       value: publicKey.size, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Comment:', | ||||||
|  |       value: publicKey.comment, | ||||||
|  |       multiline: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Curve:', | ||||||
|  |       value: publicKey.curve ?? 'none', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Fingerprint (sha256):', | ||||||
|  |       value: onErrorReturnErrorMessage(() => publicKey.fingerprint('sha256')), | ||||||
|  |       multiline: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Fingerprint (sha512):', | ||||||
|  |       value: onErrorReturnErrorMessage(() => publicKey.fingerprint('sha512')), | ||||||
|  |       multiline: true, | ||||||
|  |     }, | ||||||
|  |   ] as LabelValue[]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function getPrivateKeyLabelValues(privateKey: PrivateKey) { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       label: 'Type:', | ||||||
|  |       value: 'Private Key', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Key Type:', | ||||||
|  |       value: privateKey.type, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Size:', | ||||||
|  |       value: privateKey.size, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Comment:', | ||||||
|  |       value: privateKey.comment, | ||||||
|  |       multiline: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Curve:', | ||||||
|  |       value: privateKey.curve, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Fingerprint (sha256):', | ||||||
|  |       value: onErrorReturnErrorMessage(() => privateKey.fingerprint('sha256')), | ||||||
|  |       multiline: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Fingerprint (sha512):', | ||||||
|  |       value: onErrorReturnErrorMessage(() => privateKey.fingerprint('sha512')), | ||||||
|  |       multiline: true, | ||||||
|  |     }, | ||||||
|  |   ] as LabelValue[]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function getCertificateLabelValues(cert: Certificate) { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       label: 'Type:', | ||||||
|  |       value: 'Certificate', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Subjects:', | ||||||
|  |       value: cert.subjects?.map(s => s.toString()).join('\n'), | ||||||
|  |       multiline: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Issuer:', | ||||||
|  |       value: cert.issuer.toString(), | ||||||
|  |       multiline: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Subject Key:', | ||||||
|  |       value: onErrorReturnErrorMessage(() => cert.subjectKey?.toString('ssh')), | ||||||
|  |       multiline: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Subject Key Type:', | ||||||
|  |       value: cert.subjectKey?.type, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Subject Size:', | ||||||
|  |       value: cert.subjectKey?.size, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Subject Comment:', | ||||||
|  |       value: cert.subjectKey?.comment, | ||||||
|  |       multiline: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Subject Curve:', | ||||||
|  |       value: cert.subjectKey?.curve ?? 'none', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Issuer Key:', | ||||||
|  |       value: onErrorReturnErrorMessage(() => cert.issuerKey?.toString('ssh')), | ||||||
|  |       multiline: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Serial:', | ||||||
|  |       value: buf2Hex(cert.serial), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Purposes:', | ||||||
|  |       value: cert.purposes?.join(', '), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Extensions:', | ||||||
|  |       value: JSON.stringify(cert.getExtensions(), null, 2), | ||||||
|  |       multiline: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Fingerprint (sha256):', | ||||||
|  |       value: onErrorReturnErrorMessage(() => cert.fingerprint('sha256')), | ||||||
|  |       multiline: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Fingerprint (sha512):', | ||||||
|  |       value: onErrorReturnErrorMessage(() => cert.fingerprint('sha512')), | ||||||
|  |       multiline: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Certificate (pem):', | ||||||
|  |       value: onErrorReturnErrorMessage(() => cert.toString('pem')), | ||||||
|  |       multiline: true, | ||||||
|  |     }, | ||||||
|  |   ] as LabelValue[]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getPGPPublicKeyLabelValuesAsync(pgpPublicKey: openpgp.Key) { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       label: 'Type:', | ||||||
|  |       value: 'PGP Public Key', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Creation Time:', | ||||||
|  |       value: pgpPublicKey.getCreationTime().toString(), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Expiration Time:', | ||||||
|  |       value: (await pgpPublicKey.getExpirationTime())?.toString() || '', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Algorithm Info:', | ||||||
|  |       value: JSON.stringify(pgpPublicKey.getAlgorithmInfo()), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Fingerprint:', | ||||||
|  |       value: pgpPublicKey.getFingerprint(), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'User ID(s):', | ||||||
|  |       value: pgpPublicKey.getUserIDs().join(', '), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Key ID(s):', | ||||||
|  |       value: pgpPublicKey.getKeyIDs().map(k => k.toHex()).join(' ; '), | ||||||
|  |     }, | ||||||
|  |   ] as LabelValue[]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export async function getPGPPrivateKeyLabelValuesAsync(pgpPrivateKey: openpgp.Key) { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       label: 'Type:', | ||||||
|  |       value: 'PGP Private Key', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Creation Time:', | ||||||
|  |       value: pgpPrivateKey.getCreationTime().toString(), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Expiration Time:', | ||||||
|  |       value: (await pgpPrivateKey.getExpirationTime())?.toString() || '', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Algorithm Info:', | ||||||
|  |       value: JSON.stringify(pgpPrivateKey.getAlgorithmInfo()), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Fingerprint:', | ||||||
|  |       value: pgpPrivateKey.getFingerprint(), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'User ID(s):', | ||||||
|  |       value: pgpPrivateKey.getUserIDs().join(', '), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Key ID(s):', | ||||||
|  |       value: pgpPrivateKey.getKeyIDs().map(k => k.toHex()).join(' ; '), | ||||||
|  |     }, | ||||||
|  |   ] as LabelValue[]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function getCSRLabelValues(csr: forge.pki.Certificate) { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       label: 'Type:', | ||||||
|  |       value: 'Certificate Signing Request', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Subject:', | ||||||
|  |       value: csr.subject?.attributes?.map(a => JSON.stringify(a, null, 2)).join('\n'), | ||||||
|  |       multiline: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Issuer:', | ||||||
|  |       value: csr.issuer?.toString(), | ||||||
|  |       multiline: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Validity:', | ||||||
|  |       value: JSON.stringify(csr.validity, null, 2), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Signature:', | ||||||
|  |       value: csr.signature, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Signature Oid:', | ||||||
|  |       value: csr.signatureOid?.toString(), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Signature parameters:', | ||||||
|  |       value: JSON.stringify(csr.signatureParameters, null, 2), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Signing info:', | ||||||
|  |       value: JSON.stringify(csr.siginfo, null, 2), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Serial:', | ||||||
|  |       value: csr.serialNumber?.toString(), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Extensions:', | ||||||
|  |       value: JSON.stringify(csr.extensions, null, 2), | ||||||
|  |       multiline: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Public Key:', | ||||||
|  |       value: onErrorReturnErrorMessage(() => forge.pki.publicKeyToPem(csr.publicKey)), | ||||||
|  |       multiline: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Public Key Fingerprint:', | ||||||
|  |       value: onErrorReturnErrorMessage(() => forge.pki.getPublicKeyFingerprint(csr.publicKey)?.toHex()), | ||||||
|  |       multiline: true, | ||||||
|  |     }, | ||||||
|  |   ] as LabelValue[]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function getFingerprintLabelValues(fingerprint: Fingerprint) { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       label: 'Type:', | ||||||
|  |       value: 'Fingerprint', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Fingerprint (hex):', | ||||||
|  |       value: fingerprint.toString('hex'), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Fingerprint (base64):', | ||||||
|  |       value: fingerprint.toString('base64'), | ||||||
|  |     }, | ||||||
|  |   ] as LabelValue[]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function getSignatureLabelValues(signature: Signature) { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       label: 'Type:', | ||||||
|  |       value: 'Signature', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Fingerprint (asn1):', | ||||||
|  |       value: signature.toString('asn1'), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       label: 'Fingerprint (ssh):', | ||||||
|  |       value: signature.toString('ssh'), | ||||||
|  |     }, | ||||||
|  |   ] as LabelValue[]; | ||||||
|  | } | ||||||
| @ -0,0 +1,201 @@ | |||||||
|  | import { describe, expect, it } from 'vitest'; | ||||||
|  | import { getKeyOrCertificateInfosAsync } from './certificate-key-parser.service'; | ||||||
|  | 
 | ||||||
|  | const encryptedPrivateKey = /* NOSONAR */ `-----BEGIN ENCRYPTED PRIVATE KEY-----
 | ||||||
|  | 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 | ||||||
|  | oimmuNBEFKy1F1rSP3NQvwcZVw== | ||||||
|  | -----END ENCRYPTED PRIVATE KEY----- | ||||||
|  |     `;
 | ||||||
|  | const openSSHPrivateKey = /* NOSONAR */ `-----BEGIN OPENSSH PRIVATE KEY-----
 | ||||||
|  | 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 | ||||||
|  | 4WRkAc1Ru/KtrlMAAAAAAQID | ||||||
|  | -----END OPENSSH PRIVATE KEY-----       | ||||||
|  |     `;
 | ||||||
|  | 
 | ||||||
|  | const formatsData = [ | ||||||
|  |   { | ||||||
|  |     input: ` | ||||||
|  | -----BEGIN PGP PUBLIC KEY BLOCK----- | ||||||
|  | 
 | ||||||
|  | xjMEZkHgaRYJKwYBBAHaRw8BAQdAdCmEzdpkjMzOoNkzgDFk/CHd+6uYAWkZ | ||||||
|  | BPbjEzTJWtfNAMKMBBAWCgA+BYJmQeBpBAsJBwgJkEDKj7jnGr9wAxUICgQW | ||||||
|  | AAIBAhkBApsDAh4BFiEExfkkog7+aHz7TqepQMqPuOcav3AAAJaaAQCayvFQ | ||||||
|  | jxFbC7oOzX+8wOV8gmXVXXqI5dtLQYY3SeyqmwD/ftVwwe6Prl0vVFyLB/5y | ||||||
|  | lIpAti8AK1Lv8hIezzOx4QDOOARmQeBpEgorBgEEAZdVAQUBAQdA2jU3Rmt7 | ||||||
|  | nMFvqyjgKdVjK5o2CQI2vJiSzn8cfV1piEgDAQgHwngEGBYKACoFgmZB4GkJ | ||||||
|  | kEDKj7jnGr9wApsMFiEExfkkog7+aHz7TqepQMqPuOcav3AAABI0AQCMW4Hg | ||||||
|  | FuIaZk9LVQsUmNknj4a70fzwDYWUYvq0C1iy/QD+KXvLKfcmky5OXJA7RsRV | ||||||
|  | SN2a4SE4c8FH22uyirzyUww= | ||||||
|  | =w51K | ||||||
|  | -----END PGP PUBLIC KEY BLOCK-----       | ||||||
|  |     `,
 | ||||||
|  |     pass: '', | ||||||
|  |     type: 'PGP Public Key', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     // NOSONAR
 | ||||||
|  |     input: ` | ||||||
|  | -----BEGIN PGP PRIVATE KEY BLOCK----- | ||||||
|  | 
 | ||||||
|  | xVgEZkHgaRYJKwYBBAHaRw8BAQdAdCmEzdpkjMzOoNkzgDFk/CHd+6uYAWkZ | ||||||
|  | BPbjEzTJWtcAAQDXcDgEziqd9ZO/OpoyblRRxAOgPq2y8zTitwTz+ixX7RCe | ||||||
|  | zQDCjAQQFgoAPgWCZkHgaQQLCQcICZBAyo+45xq/cAMVCAoEFgACAQIZAQKb | ||||||
|  | AwIeARYhBMX5JKIO/mh8+06nqUDKj7jnGr9wAACWmgEAmsrxUI8RWwu6Ds1/ | ||||||
|  | vMDlfIJl1V16iOXbS0GGN0nsqpsA/37VcMHuj65dL1Rciwf+cpSKQLYvACtS | ||||||
|  | 7/ISHs8zseEAx10EZkHgaRIKKwYBBAGXVQEFAQEHQNo1N0Zre5zBb6so4CnV | ||||||
|  | YyuaNgkCNryYks5/HH1daYhIAwEIBwAA/2PxYHVWBmkLD9eiFDLJ0EtspWQ+ | ||||||
|  | JKui86xylduxQWngEIrCeAQYFgoAKgWCZkHgaQmQQMqPuOcav3ACmwwWIQTF | ||||||
|  | +SSiDv5ofPtOp6lAyo+45xq/cAAAEjQBAIxbgeAW4hpmT0tVCxSY2SePhrvR | ||||||
|  | /PANhZRi+rQLWLL9AP4pe8sp9yaTLk5ckDtGxFVI3ZrhIThzwUfba7KKvPJT | ||||||
|  | DA== | ||||||
|  | =hSgY | ||||||
|  | -----END PGP PRIVATE KEY BLOCK-----       | ||||||
|  |     `,
 | ||||||
|  |     pass: '', | ||||||
|  |     type: 'PGP Private Key', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     input: ` | ||||||
|  | -----BEGIN CERTIFICATE REQUEST----- | ||||||
|  | MIIClTCCAX0CAQAwUDERMA8GA1UEAxMIdGVzdC5jb20xDzANBgNVBAYTBkZyYW5j | ||||||
|  | ZTELMAkGA1UECBMCRlIxDjAMBgNVBAcTBVBhcmlzMQ0wCwYDVQQKEwRUZXN0MIIB | ||||||
|  | IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnIUCiMFffkHGuAhwTaW84uh7 | ||||||
|  | /03KcfQAZB/bZKqLaxtcBGryTB1gHFSsl3uFlW+tIcjsboWnQ2JB0J1Z5Fp4a6IA | ||||||
|  | /62S6GTo6dAd9f73TR+P2vQghZOtiCoc7CN2KlosIx/EWMcMjq+CBzLRjjOOR8tX | ||||||
|  | Yn4ZAhPInO1ZGPMEpfEEfn44aJFRGaMy4KEU+RpTzFKFW6bialvKC3yGPegQ4wcz | ||||||
|  | AqvyUc9WUwG53HYLSJHldg8tZnpiJBNUh8mXiIiw51MFJ4Q9RVnz9vuoHgC6FmUv | ||||||
|  | qlg/R4gjGGfjDhAIUtz+Y98Dl+xfLmD+EzY7KQ1ur412BvQ8rXankNGLA2ea+wID | ||||||
|  | AQABoAAwDQYJKoZIhvcNAQEFBQADggEBABdtkhFSwgaXZWTcKrz6oarvuaQkrjvs | ||||||
|  | Nk9lUs1h/dfhJpnE3iZA0CuNp5PVQRdC2g+/37r21/udjNFdrX1Rm6/ldG0b2xDu | ||||||
|  | nQYZcLpIVB0fZ2TB+FHthmGw175I2niWIfNJQhIqnWJXi8unkGTMP2cD6j3axtMi | ||||||
|  | K8MUVPhWmL11ojEXItG35AU79G6GhFxel9wIByqsXreCUyOcrpYCHy2Fv85ivdE1 | ||||||
|  | JyEQ2tE/f+cKwNg4yJNFoCoHSSFRn61F12J4m2nwpQ77VfD66oVkWtk/gYMrwx0d | ||||||
|  | 4FlJNs+NtZDlcM7fJLNo7YsMdne7hl4aL6WG96kdWdxYEt/2dl3WXbY= | ||||||
|  | -----END CERTIFICATE REQUEST----- | ||||||
|  |     `,
 | ||||||
|  |     pass: '', | ||||||
|  |     type: 'Certificate Signing Request', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     input: ` | ||||||
|  | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDivroAl2ckHCwoPJOvhCLNBgxWFoW1wFwxxW4kdspb8O0b3zmgDZWElF9wHPwwo1L2JMtGSCrjKhNUIiLkUNVTgSdBssygt83aiDo+mBJIvn1s9S9ih6enpFLWJHrTp5VqQf8nogiJp0cxYGsuMKIKF/lT9u+5KRRBaLRgsf2Ejo3D5L0x3DMGh+yFll3DD1ji52tYRV18avFa/mCOVqq7FhkCd9pgIjQ+CH2/WcQAsW585WLWidRlrF27K+wuhCdeLA/Zx9ZewFiaN9WJNMANO8dMsIX0gf7qF4aTZYmSiyCJqOYyVz7VTtez6vlknSKS3SKjJ083weIudWKHqUQ3 | ||||||
|  |     `,
 | ||||||
|  |     pass: '', | ||||||
|  |     type: 'Public Key', | ||||||
|  |     title: 'ssh-rsa Public Key', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     input: openSSHPrivateKey, | ||||||
|  |     pass: '', | ||||||
|  |     type: 'Private Key', | ||||||
|  |     title: 'Unencrypted Private Key', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     input: ` | ||||||
|  | SHA256:qflg623OemnYEHDwUafq+XuMoB0UdJ+Ks44kHcWxDyM | ||||||
|  |     `,
 | ||||||
|  |     pass: '', | ||||||
|  |     type: 'Fingerprint', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     input: ` | ||||||
|  | -----BEGIN CERTIFICATE----- | ||||||
|  | MIIDQDCCAiigAwIBAgIJK59gK0GUbZO3MA0GCSqGSIb3DQEBBQUAMFAxETAPBgNV | ||||||
|  | BAMTCHRlc3QuY29tMQ8wDQYDVQQGEwZGcmFuY2UxCzAJBgNVBAgTAkZSMQ4wDAYD | ||||||
|  | VQQHEwVQYXJpczENMAsGA1UEChMEVGVzdDAeFw0yNDA1MTMwOTQ4MTVaFw0yNTA1 | ||||||
|  | MTMwOTQ4MTVaMFAxETAPBgNVBAMTCHRlc3QuY29tMQ8wDQYDVQQGEwZGcmFuY2Ux | ||||||
|  | CzAJBgNVBAgTAkZSMQ4wDAYDVQQHEwVQYXJpczENMAsGA1UEChMEVGVzdDCCASIw | ||||||
|  | DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMKrE3C9tUtmvHZkIwEBf1h5N7KC | ||||||
|  | FoXowfNZxKK7SHWcnQjBdv0ziqsU+GmcUqUD1GymMcBweqw4TQVg0a/UwdYIUTuQ | ||||||
|  | GXGx4ULCXKHv/NfmVSWcMsOZHAR4m/yEzTB/ZjKMSrqnIWyOdusDMRn4VRoAtrKO | ||||||
|  | /FM+SDJ6wvnJ/jNoZJXktq9avYduEi+heNekIF6NYM9clzm9Ff3Evf89KuigBcsu | ||||||
|  | rgL+S8PjotCwxMgzOWV4/paeeQluqYeU94prWIASS/D3elH7qFTAUnafBICFN2zs | ||||||
|  | XWY6ZFCR8QrDI5F/8KELq/3BaLQBxpIi9SmADLWqnPOu+6H5rzr2YV8LaxMCAwEA | ||||||
|  | AaMdMBswDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAvQwDQYJKoZIhvcNAQEFBQAD | ||||||
|  | ggEBAKhrUNnWe0VmgefvfwsAqrbk0Z6PwaibIl/l5I9oh1qM01J9BFHpvomhcLxu | ||||||
|  | cmIpD6nAqtkNyvsXtFAnZG3WNaf45yyd153wSa0QnrNo2GRH9quktm4DaRIIP7qq | ||||||
|  | EdtApYCeT16LvAGYUH3ubCdom8w6DkukLg8qMrXMywSZlx85jlJfifPvMKsJmm/a | ||||||
|  | QAq1H3cYaaj0DocF1rCP+hLzvsuM7UwS2JOK8Mw49kYPBTbCVmRDOE1rhlDIO8Kw | ||||||
|  | V7CCFr4NXsyRlM0TpKdspOmxJiyxmk6DoVgp9PeqfoyDAC9TJU0VJ6A2x+AfjK4O | ||||||
|  | Wg6xDXMx6dk6Rhh8yqGrmx05QM8= | ||||||
|  | -----END CERTIFICATE----- | ||||||
|  |     `,
 | ||||||
|  |     pass: '', | ||||||
|  |     type: 'Certificate', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     input: ` | ||||||
|  | c6:b9:73:b8:68:49:33:ad:27:51:bb:6c:16:e7:9c:da:dd:e3:92:15 | ||||||
|  |     `,
 | ||||||
|  |     pass: '', | ||||||
|  |     type: 'Fingerprint', | ||||||
|  |     title: 'HEX Fingerprint', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     input: encryptedPrivateKey, | ||||||
|  |     pass: 'test', | ||||||
|  |     type: 'Private Key', | ||||||
|  |     title: 'Encrypted Private Key', | ||||||
|  |   }, | ||||||
|  | ]; | ||||||
|  | 
 | ||||||
|  | describe('certificate-key-parser', () => { | ||||||
|  |   for (const format of formatsData) { | ||||||
|  |     const { input, pass, type, title } = format; | ||||||
|  |     it(`Parse '${title ?? type}' format with right type (${type})`, async () => { | ||||||
|  |       const { values } = await getKeyOrCertificateInfosAsync(input, pass); | ||||||
|  |       const result_type = values.find(v => v.label === 'Type:')?.value; | ||||||
|  | 
 | ||||||
|  |       expect(result_type).toBe(type); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | }); | ||||||
| @ -0,0 +1,129 @@ | |||||||
|  | import type { Buffer } from 'node:buffer'; | ||||||
|  | import { | ||||||
|  |   parseCertificate, parseFingerprint, | ||||||
|  |   parseKey, | ||||||
|  |   parsePrivateKey, | ||||||
|  |   parseSignature, | ||||||
|  | } from 'sshpk'; | ||||||
|  | import type { | ||||||
|  |   AlgorithmType, | ||||||
|  |   Certificate, | ||||||
|  |   CertificateFormat, | ||||||
|  |   Fingerprint, | ||||||
|  |   Key, | ||||||
|  |   PrivateKey, Signature, SignatureFormatType, | ||||||
|  | } from 'sshpk'; | ||||||
|  | import { Base64 } from 'js-base64'; | ||||||
|  | import * as openpgp from 'openpgp'; | ||||||
|  | import * as forge from 'node-forge'; | ||||||
|  | import { type LabelValue, getCSRLabelValues, getCertificateLabelValues, getFingerprintLabelValues, getPGPPrivateKeyLabelValuesAsync, getPGPPublicKeyLabelValuesAsync, getPrivateKeyLabelValues, getPublicKeyLabelValues, getSignatureLabelValues } from './certificate-key-parser.infos'; | ||||||
|  | 
 | ||||||
|  | export async function getKeyOrCertificateInfosAsync(keyOrCertificateValue: string | Buffer, passphrase: string) { | ||||||
|  |   try { | ||||||
|  |     const canParse = (value: string | Buffer, parseFunction: (value: string | Buffer) => any) => { | ||||||
|  |       try { | ||||||
|  |         return parseFunction(value); | ||||||
|  |       } | ||||||
|  |       catch { | ||||||
|  |         return null; | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |     const canParseAsync = async (value: string | Buffer, parseFunction: (value: string | Buffer) => Promise<any>) => { | ||||||
|  |       try { | ||||||
|  |         return await parseFunction(value); | ||||||
|  |       } | ||||||
|  |       catch { | ||||||
|  |         return null; | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     const inputKeyOrCertificateValue = (typeof keyOrCertificateValue === 'string' ? keyOrCertificateValue?.trim() : keyOrCertificateValue); | ||||||
|  | 
 | ||||||
|  |     const privateKey = canParse(inputKeyOrCertificateValue, | ||||||
|  |       value => parsePrivateKey(value, 'auto', { passphrase })) as PrivateKey; | ||||||
|  |     if (privateKey) { | ||||||
|  |       return { | ||||||
|  |         values: getPrivateKeyLabelValues(privateKey), | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const publicKey = canParse(inputKeyOrCertificateValue, parseKey) as Key; | ||||||
|  |     if (publicKey) { | ||||||
|  |       return { values: getPublicKeyLabelValues(publicKey) }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const pgpPrivateKey = await canParseAsync(inputKeyOrCertificateValue, value => openpgp.readPrivateKey({ armoredKey: value.toString() })) as openpgp.Key; | ||||||
|  |     if (pgpPrivateKey) { | ||||||
|  |       return { values: await getPGPPrivateKeyLabelValuesAsync(pgpPrivateKey) }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const pgpPublicKey = await canParseAsync(inputKeyOrCertificateValue, value => openpgp.readKey({ armoredKey: value.toString() })) as openpgp.Key; | ||||||
|  |     if (pgpPublicKey) { | ||||||
|  |       return { values: await getPGPPublicKeyLabelValuesAsync(pgpPublicKey) }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const cert = canParse(inputKeyOrCertificateValue, (value) => { | ||||||
|  |       for (const format of ['openssh', 'pem', 'x509']) { | ||||||
|  |         try { | ||||||
|  |           return parseCertificate(value, format as CertificateFormat); | ||||||
|  |         } | ||||||
|  |         catch {} | ||||||
|  |       } | ||||||
|  |       return null; | ||||||
|  |     }) as Certificate; | ||||||
|  |     if (cert) { | ||||||
|  |       let certificateX509DER = ''; | ||||||
|  |       try { | ||||||
|  |         certificateX509DER = Base64.fromUint8Array(cert.toBuffer('x509')); | ||||||
|  |       } | ||||||
|  |       catch {} | ||||||
|  | 
 | ||||||
|  |       return { values: getCertificateLabelValues(cert), certificateX509DER }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const csr = canParse(inputKeyOrCertificateValue, (value) => { | ||||||
|  |       return forge.pki.certificationRequestFromPem(value.toString(), false, false); | ||||||
|  |     }) as forge.pki.Certificate; | ||||||
|  |     if (csr) { | ||||||
|  |       return { values: getCSRLabelValues(csr) }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const fingerprint = canParse(inputKeyOrCertificateValue, value => parseFingerprint(value.toString())) as Fingerprint; | ||||||
|  |     if (fingerprint) { | ||||||
|  |       return { values: getFingerprintLabelValues(fingerprint) }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const signature = canParse(inputKeyOrCertificateValue, (value) => { | ||||||
|  |       //
 | ||||||
|  |       for (const algo of ['dsa', 'rsa', 'ecdsa', 'ed25519']) { | ||||||
|  |         for (const format of ['asn1', 'ssh', 'raw']) { | ||||||
|  |           try { | ||||||
|  |             return parseSignature(value, algo as AlgorithmType, format as SignatureFormatType); | ||||||
|  |           } | ||||||
|  |           catch {} | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       return null; | ||||||
|  |     }) as Signature; | ||||||
|  |     if (signature) { | ||||||
|  |       return { values: getSignatureLabelValues(signature) }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return { | ||||||
|  |       values: [ | ||||||
|  |         { | ||||||
|  |           label: 'Type:', | ||||||
|  |           value: 'Unknown format or invalid passphrase', | ||||||
|  |         }], | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |   catch (e: any) { | ||||||
|  |     return { | ||||||
|  |       values: [ | ||||||
|  |         { | ||||||
|  |           label: 'Error:', | ||||||
|  |           value: e.toString(), | ||||||
|  |         }] as LabelValue[], | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										120
									
								
								src/tools/certificate-key-parser/certificate-key-parser.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								src/tools/certificate-key-parser/certificate-key-parser.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,120 @@ | |||||||
|  | <script setup lang="ts"> | ||||||
|  | import { Buffer } from 'node:buffer'; | ||||||
|  | 
 | ||||||
|  | import { getKeyOrCertificateInfosAsync } from './certificate-key-parser.service'; | ||||||
|  | import { type LabelValue } from './certificate-key-parser.infos'; | ||||||
|  | import { useDownloadFileFromBase64 } from '@/composable/downloadBase64'; | ||||||
|  | 
 | ||||||
|  | const inputKeyOrCertificate = ref(''); | ||||||
|  | const passphrase = ref(''); | ||||||
|  | const fileInput = ref() as Ref<Buffer>; | ||||||
|  | const inputType = ref<'file' | 'content'>('file'); | ||||||
|  | 
 | ||||||
|  | async function onUpload(file: File) { | ||||||
|  |   if (file) { | ||||||
|  |     fileInput.value = Buffer.from(await file.arrayBuffer()); | ||||||
|  |     inputKeyOrCertificate.value = ''; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const certificateX509DER = ref(''); | ||||||
|  | const { download: downloadX509DER } = useDownloadFileFromBase64( | ||||||
|  |   { | ||||||
|  |     source: certificateX509DER, | ||||||
|  |     extension: 'der', | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  | function downloadX509DERFile() { | ||||||
|  |   if (certificateX509DER.value === '') { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   try { | ||||||
|  |     downloadX509DER(); | ||||||
|  |   } | ||||||
|  |   catch (_) { | ||||||
|  |     // | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const parsedSections = computedAsync<LabelValue[]>(async () => { | ||||||
|  |   const inputContent = inputKeyOrCertificate.value; | ||||||
|  |   const file = fileInput.value; | ||||||
|  |   let inputKeyOrCertificateValue: string | Buffer = ''; | ||||||
|  |   if (inputType.value === 'file' && file) { | ||||||
|  |     inputKeyOrCertificateValue = file; | ||||||
|  |   } | ||||||
|  |   else if (inputType.value === 'content' && inputContent) { | ||||||
|  |     inputKeyOrCertificateValue = inputContent; | ||||||
|  |   } | ||||||
|  |   const { values, certificateX509DER: certPEM } = await getKeyOrCertificateInfosAsync(inputKeyOrCertificateValue, passphrase.value); | ||||||
|  |   certificateX509DER.value = certPEM || ''; | ||||||
|  |   return values; | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <div> | ||||||
|  |     <c-card> | ||||||
|  |       <n-radio-group v-model:value="inputType" name="radiogroup" mb-2 flex justify-center> | ||||||
|  |         <n-space> | ||||||
|  |           <n-radio | ||||||
|  |             value="file" | ||||||
|  |             label="File" | ||||||
|  |           /> | ||||||
|  |           <n-radio | ||||||
|  |             value="content" | ||||||
|  |             label="Content" | ||||||
|  |           /> | ||||||
|  |         </n-space> | ||||||
|  |       </n-radio-group> | ||||||
|  | 
 | ||||||
|  |       <c-file-upload | ||||||
|  |         v-if="inputType === 'file'" | ||||||
|  |         title="Drag and drop a Certificate file here, or click to select a Certificate file" | ||||||
|  |         @file-upload="onUpload" | ||||||
|  |       /> | ||||||
|  | 
 | ||||||
|  |       <c-input-text | ||||||
|  |         v-if="inputType === 'content'" | ||||||
|  |         v-model:value="inputKeyOrCertificate" | ||||||
|  |         label="Paste your Public Key / Private Key / Signature / Fingerprint / Certificate:" | ||||||
|  |         placeholder="Your Public Key / Private Key / Signature / Fingerprint / Certificate..." | ||||||
|  |         multiline | ||||||
|  |         rows="8" | ||||||
|  |         data-test-id="input" | ||||||
|  |       /> | ||||||
|  |     </c-card> | ||||||
|  | 
 | ||||||
|  |     <c-input-text | ||||||
|  |       v-model:value="passphrase" | ||||||
|  |       label="Passphrase (for encrypted keys):" | ||||||
|  |       placeholder="Passphrase (for encrypted keys)..." | ||||||
|  |       type="password" | ||||||
|  |       data-test-id="pass" | ||||||
|  |     /> | ||||||
|  | 
 | ||||||
|  |     <n-divider /> | ||||||
|  | 
 | ||||||
|  |     <input-copyable | ||||||
|  |       v-for="{ label, value, multiline } of parsedSections" | ||||||
|  |       :key="label" | ||||||
|  |       :label="label" | ||||||
|  |       :data-test-id="label" | ||||||
|  |       label-position="left" | ||||||
|  |       label-width="100px" | ||||||
|  |       label-align="right" | ||||||
|  | 
 | ||||||
|  |       autosize mb-2 | ||||||
|  |       :multiline="multiline" | ||||||
|  |       :value="value" | ||||||
|  |       placeholder="Not Set" | ||||||
|  |     /> | ||||||
|  | 
 | ||||||
|  |     <div v-if="certificateX509DER !== ''" flex justify-center> | ||||||
|  |       <c-button @click="downloadX509DERFile()"> | ||||||
|  |         Download X509 DER certificate | ||||||
|  |       </c-button> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
							
								
								
									
										12
									
								
								src/tools/certificate-key-parser/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/tools/certificate-key-parser/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | import { FileCertificate } from '@vicons/tabler'; | ||||||
|  | import { defineTool } from '../tool'; | ||||||
|  | 
 | ||||||
|  | export const tool = defineTool({ | ||||||
|  |   name: 'Certificate/Key parser', | ||||||
|  |   path: '/certificate-key-parser', | ||||||
|  |   description: 'Parse Key and Certificate', | ||||||
|  |   keywords: ['certificate', 'key', 'parser'], | ||||||
|  |   component: () => import('./certificate-key-parser.vue'), | ||||||
|  |   icon: FileCertificate, | ||||||
|  |   createdAt: new Date('2024-02-22'), | ||||||
|  | }); | ||||||
| @ -6,6 +6,7 @@ import { tool as emailNormalizer } from './email-normalizer'; | |||||||
| import { tool as asciiTextDrawer } from './ascii-text-drawer'; | import { tool as asciiTextDrawer } from './ascii-text-drawer'; | ||||||
| 
 | 
 | ||||||
| import { tool as textToUnicode } from './text-to-unicode'; | import { tool as textToUnicode } from './text-to-unicode'; | ||||||
|  | import { tool as certificateKeyParser } from './certificate-key-parser'; | ||||||
| import { tool as safelinkDecoder } from './safelink-decoder'; | import { tool as safelinkDecoder } from './safelink-decoder'; | ||||||
| import { tool as xmlToJson } from './xml-to-json'; | import { tool as xmlToJson } from './xml-to-json'; | ||||||
| import { tool as jsonToXml } from './json-to-xml'; | import { tool as jsonToXml } from './json-to-xml'; | ||||||
| @ -91,7 +92,20 @@ import { tool as yamlViewer } from './yaml-viewer'; | |||||||
| export const toolsByCategory: ToolCategory[] = [ | export const toolsByCategory: ToolCategory[] = [ | ||||||
|   { |   { | ||||||
|     name: 'Crypto', |     name: 'Crypto', | ||||||
|     components: [tokenGenerator, hashText, bcrypt, uuidGenerator, ulidGenerator, cypher, bip39, hmacGenerator, rsaKeyPairGenerator, passwordStrengthAnalyser, pdfSignatureChecker], |     components: [ | ||||||
|  |       tokenGenerator, | ||||||
|  |       hashText, | ||||||
|  |       bcrypt, | ||||||
|  |       uuidGenerator, | ||||||
|  |       ulidGenerator, | ||||||
|  |       cypher, | ||||||
|  |       bip39, | ||||||
|  |       hmacGenerator, | ||||||
|  |       rsaKeyPairGenerator, | ||||||
|  |       certificateKeyParser, | ||||||
|  |       passwordStrengthAnalyser, | ||||||
|  |       pdfSignatureChecker, | ||||||
|  |     ], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|     name: 'Converter', |     name: 'Converter', | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| import { resolve } from 'node:path'; | import { resolve } from 'node:path'; | ||||||
| import { URL, fileURLToPath } from 'node:url'; | import { URL, fileURLToPath } from 'node:url'; | ||||||
|  | import { nodePolyfills } from 'vite-plugin-node-polyfills'; | ||||||
| 
 | 
 | ||||||
| import VueI18n from '@intlify/unplugin-vue-i18n/vite'; | import VueI18n from '@intlify/unplugin-vue-i18n/vite'; | ||||||
| import vue from '@vitejs/plugin-vue'; | import vue from '@vitejs/plugin-vue'; | ||||||
| @ -97,6 +98,7 @@ export default defineConfig({ | |||||||
|       resolvers: [NaiveUiResolver(), IconsResolver({ prefix: 'icon' })], |       resolvers: [NaiveUiResolver(), IconsResolver({ prefix: 'icon' })], | ||||||
|     }), |     }), | ||||||
|     Unocss(), |     Unocss(), | ||||||
|  |     nodePolyfills(), | ||||||
|   ], |   ], | ||||||
|   base: baseUrl, |   base: baseUrl, | ||||||
|   resolve: { |   resolve: { | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user