Merge e62ca2295c into 63fbd3b45c
				
					
				
			This commit is contained in:
		
						commit
						96e3b022a9
					
				
							
								
								
									
										10
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -130,19 +130,24 @@ declare module '@vue/runtime-core' { | ||||
|     MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default'] | ||||
|     MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default'] | ||||
|     NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] | ||||
|     NCheckbox: typeof import('naive-ui')['NCheckbox'] | ||||
|     NCode: typeof import('naive-ui')['NCode'] | ||||
|     NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] | ||||
|     NConfigProvider: typeof import('naive-ui')['NConfigProvider'] | ||||
|     NDivider: typeof import('naive-ui')['NDivider'] | ||||
|     NEllipsis: typeof import('naive-ui')['NEllipsis'] | ||||
|     NForm: typeof import('naive-ui')['NForm'] | ||||
|     NFormItem: typeof import('naive-ui')['NFormItem'] | ||||
|     NH1: typeof import('naive-ui')['NH1'] | ||||
|     NH3: typeof import('naive-ui')['NH3'] | ||||
|     NIcon: typeof import('naive-ui')['NIcon'] | ||||
|     NLayout: typeof import('naive-ui')['NLayout'] | ||||
|     NLayoutSider: typeof import('naive-ui')['NLayoutSider'] | ||||
|     NMenu: typeof import('naive-ui')['NMenu'] | ||||
|     NRadio: typeof import('naive-ui')['NRadio'] | ||||
|     NRadioGroup: typeof import('naive-ui')['NRadioGroup'] | ||||
|     NScrollbar: typeof import('naive-ui')['NScrollbar'] | ||||
|     NSpace: typeof import('naive-ui')['NSpace'] | ||||
|     NTable: typeof import('naive-ui')['NTable'] | ||||
|     NSwitch: typeof import('naive-ui')['NSwitch'] | ||||
|     NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] | ||||
|     OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] | ||||
|     PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default'] | ||||
| @ -164,6 +169,7 @@ declare module '@vue/runtime-core' { | ||||
|     SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default'] | ||||
|     SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default'] | ||||
|     SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default'] | ||||
|     SslCertConverter: typeof import('./src/tools/ssl-cert-converter/ssl-cert-converter.vue')['default'] | ||||
|     StringObfuscator: typeof import('./src/tools/string-obfuscator/string-obfuscator.vue')['default'] | ||||
|     SvgPlaceholderGenerator: typeof import('./src/tools/svg-placeholder-generator/svg-placeholder-generator.vue')['default'] | ||||
|     TemperatureConverter: typeof import('./src/tools/temperature-converter/temperature-converter.vue')['default'] | ||||
|  | ||||
| @ -46,6 +46,7 @@ | ||||
|     "@tiptap/vue-3": "2.0.3", | ||||
|     "@types/figlet": "^1.5.8", | ||||
|     "@types/markdown-it": "^13.0.7", | ||||
|     "@types/sshpk": "^1.17.4", | ||||
|     "@vicons/material": "^0.12.0", | ||||
|     "@vicons/tabler": "^0.12.0", | ||||
|     "@vueuse/core": "^10.3.0", | ||||
| @ -69,6 +70,7 @@ | ||||
|     "highlight.js": "^11.7.0", | ||||
|     "iarna-toml-esm": "^3.0.5", | ||||
|     "ibantools": "^4.3.3", | ||||
|     "jks-js": "^1.1.3", | ||||
|     "js-base64": "^3.7.6", | ||||
|     "json5": "^2.2.3", | ||||
|     "jwt-decode": "^3.1.2", | ||||
| @ -89,6 +91,7 @@ | ||||
|     "qrcode": "^1.5.1", | ||||
|     "randexp": "^0.5.3", | ||||
|     "sql-formatter": "^13.0.0", | ||||
|     "sshpk": "^1.18.0", | ||||
|     "ua-parser-js": "^1.0.35", | ||||
|     "ulid": "^2.3.0", | ||||
|     "unicode-emoji-json": "^0.4.0", | ||||
| @ -142,6 +145,7 @@ | ||||
|     "unplugin-icons": "^0.17.0", | ||||
|     "unplugin-vue-components": "^0.25.0", | ||||
|     "vite": "^4.4.9", | ||||
|     "vite-plugin-node-polyfills": "^0.22.0", | ||||
|     "vite-plugin-pwa": "^0.16.0", | ||||
|     "vite-plugin-vue-markdown": "^0.23.5", | ||||
|     "vite-svg-loader": "^4.0.0", | ||||
|  | ||||
							
								
								
									
										736
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										736
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -2,6 +2,7 @@ import { tool as base64FileConverter } from './base64-file-converter'; | ||||
| import { tool as base64StringConverter } from './base64-string-converter'; | ||||
| import { tool as basicAuthGenerator } from './basic-auth-generator'; | ||||
| import { tool as emailNormalizer } from './email-normalizer'; | ||||
| import { tool as sslCertConverter } from './ssl-cert-converter'; | ||||
| 
 | ||||
| import { tool as asciiTextDrawer } from './ascii-text-drawer'; | ||||
| 
 | ||||
| @ -164,7 +165,15 @@ export const toolsByCategory: ToolCategory[] = [ | ||||
|   }, | ||||
|   { | ||||
|     name: 'Network', | ||||
|     components: [ipv4SubnetCalculator, ipv4AddressConverter, ipv4RangeExpander, macAddressLookup, macAddressGenerator, ipv6UlaGenerator], | ||||
|     components: [ | ||||
|       ipv4SubnetCalculator, | ||||
|       ipv4AddressConverter, | ||||
|       ipv4RangeExpander, | ||||
|       macAddressLookup, | ||||
|       macAddressGenerator, | ||||
|       ipv6UlaGenerator, | ||||
|       sslCertConverter, | ||||
|     ], | ||||
|   }, | ||||
|   { | ||||
|     name: 'Math', | ||||
|  | ||||
							
								
								
									
										12
									
								
								src/tools/ssl-cert-converter/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/tools/ssl-cert-converter/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| import { ShieldChevron } from '@vicons/tabler'; | ||||
| import { defineTool } from '../tool'; | ||||
| 
 | ||||
| export const tool = defineTool({ | ||||
|   name: 'SSL Certificate converter', | ||||
|   path: '/ssl-cert-converter', | ||||
|   description: 'Convert SSL Certificate from different formats', | ||||
|   keywords: ['ssl', 'certificate', 'crt', 'pkcs', 'p12', 'pem', 'der', 'jks', 'converter'], | ||||
|   component: () => import('./ssl-cert-converter.vue'), | ||||
|   icon: ShieldChevron, | ||||
|   createdAt: new Date('2024-08-15'), | ||||
| }); | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										119
									
								
								src/tools/ssl-cert-converter/ssl-cert-converter.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								src/tools/ssl-cert-converter/ssl-cert-converter.service.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,119 @@ | ||||
| import type { Buffer } from 'node:buffer'; | ||||
| import { | ||||
|   parseCertificate, | ||||
| } from 'sshpk'; | ||||
| 
 | ||||
| import type { | ||||
|   Certificate, | ||||
|   CertificateFormat, | ||||
| } from 'sshpk'; | ||||
| 
 | ||||
| import * as forge from 'node-forge'; | ||||
| import jks from 'jks-js'; | ||||
| 
 | ||||
| function convertPKCS12ToPem(p12base64: forge.Bytes | forge.util.ByteBuffer, password: string) { | ||||
|   const p12Asn1 = forge.asn1.fromDer(p12base64, false); | ||||
|   const p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, false, password); | ||||
| 
 | ||||
|   const pemKey = getKeyFromP12(p12); | ||||
|   const { pemCertificate, commonName } = getCertificateFromP12(p12); | ||||
| 
 | ||||
|   return { pemKey, pemCertificate, commonName }; | ||||
| } | ||||
| 
 | ||||
| function getKeyFromP12(p12: forge.pkcs12.Pkcs12Pfx) { | ||||
|   const keyData = p12.getBags({ bagType: forge.pki.oids.pkcs8ShroudedKeyBag }); | ||||
|   let pkcs8Key = keyData[forge.pki.oids.pkcs8ShroudedKeyBag]![0]; | ||||
| 
 | ||||
|   if (!pkcs8Key) { | ||||
|     pkcs8Key = keyData[forge.pki.oids.keyBag]![0]; | ||||
|   } | ||||
| 
 | ||||
|   if (!pkcs8Key?.key) { | ||||
|     throw new TypeError('Unable to get private key.'); | ||||
|   } | ||||
| 
 | ||||
|   return forge.pki.privateKeyToPem(pkcs8Key.key); | ||||
| } | ||||
| 
 | ||||
| function getCertificateFromP12(p12: any) { | ||||
|   const certData = p12.getBags({ bagType: forge.pki.oids.certBag }); | ||||
|   const certificate = certData[forge.pki.oids.certBag][0]; | ||||
| 
 | ||||
|   const pemCertificate = forge.pki.certificateToPem(certificate.cert); | ||||
|   const commonName = certificate.cert.subject.attributes[0].value; | ||||
|   return { pemCertificate, commonName }; | ||||
| } | ||||
| 
 | ||||
| export function convertCertificate( | ||||
|   inputKeyOrCertificateValue: string | Buffer, | ||||
|   password: string) { | ||||
|   const canParse = (value: any, parseFunction: (value: any) => any) => { | ||||
|     try { | ||||
|       return parseFunction(value); | ||||
|     } | ||||
|     catch (e: any) { | ||||
|       // console.log(e);
 | ||||
|       return null; | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   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) { | ||||
|     return [{ | ||||
|       alias: '#default', | ||||
|       key: null, | ||||
|       der: canParse(cert, c => c.toBuffer('x509')), | ||||
|       pem: cert.toString('pem'), | ||||
|     }]; | ||||
|   } | ||||
| 
 | ||||
|   const pkcs12 = canParse(inputKeyOrCertificateValue, (value) => { | ||||
|     return convertPKCS12ToPem(forge.util.createBuffer(value, 'raw'), password); | ||||
|   }); | ||||
|   if (pkcs12) { | ||||
|     return [{ | ||||
|       alias: pkcs12.commonName, | ||||
|       key: pkcs12.pemKey, | ||||
|       der: canParse(pkcs12.pemCertificate, pemCert => parseCertificate(pemCert, 'pem').toBuffer('x509')), | ||||
|       pem: pkcs12.pemCertificate, | ||||
|     }]; | ||||
|   } | ||||
| 
 | ||||
|   const parsedJKS = canParse(inputKeyOrCertificateValue, (value) => { | ||||
|     return jks.toPem( | ||||
|       value, | ||||
|       password, | ||||
|     ); | ||||
|   }); | ||||
|   if (parsedJKS) { | ||||
|     return Object.entries(parsedJKS).map(([k, v]) => { | ||||
|       if (typeof v === 'string') { | ||||
|         return { | ||||
|           alias: k, | ||||
|           key: null, | ||||
|           der: canParse(v, pemCert => parseCertificate(pemCert, 'pem').toBuffer('x509')), | ||||
|           pem: v, | ||||
|         }; | ||||
|       } | ||||
|       const { cert, key } = v as { cert: string; key: string }; | ||||
|       return { | ||||
|         alias: k, | ||||
|         key, | ||||
|         der: canParse(cert, pemCert => parseCertificate(pemCert, 'pem').toBuffer('x509')), | ||||
|         pem: cert, | ||||
|       }; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   return null; | ||||
| } | ||||
							
								
								
									
										112
									
								
								src/tools/ssl-cert-converter/ssl-cert-converter.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/tools/ssl-cert-converter/ssl-cert-converter.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,112 @@ | ||||
| <script setup lang="ts"> | ||||
| import { Buffer } from 'node:buffer'; | ||||
| import { convertCertificate } from './ssl-cert-converter.service'; | ||||
| 
 | ||||
| const inputKeyOrCertificate = ref(''); | ||||
| const fileInput = ref() as Ref<Buffer>; | ||||
| const passphrase = ref(''); | ||||
| const inputType = ref<'file' | 'content'>('file'); | ||||
| 
 | ||||
| async function onUpload(file: File) { | ||||
|   if (file) { | ||||
|     fileInput.value = Buffer.from(await file.arrayBuffer()); | ||||
|     inputKeyOrCertificate.value = ''; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function downloadFile(data: ArrayBuffer | string, fileName: string, fileType?: string) { | ||||
|   const blob = new Blob( | ||||
|     [typeof data === 'string' ? new TextEncoder().encode(data) : data], | ||||
|     { type: fileType || 'application/octet-stream' }); | ||||
|   const downloadUrl = URL.createObjectURL(blob); | ||||
|   const a = document.createElement('a'); | ||||
|   a.href = downloadUrl; | ||||
|   a.download = fileName; | ||||
|   document.body.appendChild(a); | ||||
|   a.click(); | ||||
|   URL.revokeObjectURL(downloadUrl); | ||||
| } | ||||
| 
 | ||||
| const convertedCertificates = computed(() => { | ||||
|   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; | ||||
|   } | ||||
| 
 | ||||
|   return convertCertificate(inputKeyOrCertificateValue, passphrase.value); | ||||
| }); | ||||
| </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 PEM, DER, JKS or PKCS#12 file here, or click to select a file" | ||||
|         @file-upload="onUpload" | ||||
|       /> | ||||
| 
 | ||||
|       <c-input-text | ||||
|         v-if="inputType === 'content'" | ||||
|         v-model:value="inputKeyOrCertificate" | ||||
|         label="Paste your Certificate/Store:" | ||||
|         placeholder="Your Certificate/Store..." | ||||
|         multiline | ||||
|         rows="8" | ||||
|         data-test-id="input" | ||||
|       /> | ||||
|     </c-card> | ||||
| 
 | ||||
|     <c-input-text | ||||
|       v-model:value="passphrase" | ||||
|       label="Passphrase (for encrypted certificate/store):" | ||||
|       placeholder="Passphrase (for encrypted certificate/store)..." | ||||
|       type="password" | ||||
|       data-test-id="pass" | ||||
|     /> | ||||
| 
 | ||||
|     <n-divider /> | ||||
| 
 | ||||
|     <c-alert v-if="!convertedCertificates"> | ||||
|       Please provide an input or enter the good password! | ||||
|     </c-alert> | ||||
| 
 | ||||
|     <c-card v-for="(cert, ix) in convertedCertificates" :key="ix" :title="cert.alias"> | ||||
|       <n-form-item v-if="cert.key" label="Key (PEM)"> | ||||
|         <textarea-copyable :value="cert.key" /> | ||||
|       </n-form-item> | ||||
|       <n-form-item v-if="cert.pem" label="Certificate (PEM)"> | ||||
|         <textarea-copyable :value="cert.pem" /> | ||||
|       </n-form-item> | ||||
|       <div flex justify-center gap-1> | ||||
|         <c-button v-if="cert.der" @click="downloadFile(cert.der, `${cert.alias}.der`)"> | ||||
|           Download DER | ||||
|         </c-button> | ||||
|         <c-button v-if="cert.pem" @click="downloadFile(cert.pem, `${cert.alias}.pem`)"> | ||||
|           Download PEM | ||||
|         </c-button> | ||||
|         <c-button v-if="cert.key" @click="downloadFile(cert.key, `${cert.alias}.key.pem`)"> | ||||
|           Download Key (PEM) | ||||
|         </c-button> | ||||
|       </div> | ||||
|     </c-card> | ||||
|   </div> | ||||
| </template> | ||||
| @ -15,6 +15,7 @@ import { VitePWA } from 'vite-plugin-pwa'; | ||||
| import markdown from 'vite-plugin-vue-markdown'; | ||||
| import svgLoader from 'vite-svg-loader'; | ||||
| import { configDefaults } from 'vitest/config'; | ||||
| import { nodePolyfills } from 'vite-plugin-node-polyfills' | ||||
| 
 | ||||
| const baseUrl = process.env.BASE_URL ?? '/'; | ||||
| 
 | ||||
| @ -97,6 +98,7 @@ export default defineConfig({ | ||||
|       resolvers: [NaiveUiResolver(), IconsResolver({ prefix: 'icon' })], | ||||
|     }), | ||||
|     Unocss(), | ||||
|     nodePolyfills(), | ||||
|   ], | ||||
|   base: baseUrl, | ||||
|   resolve: { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user