From 1a241d84568ffca5cbd6efa265c48ca79abf786b Mon Sep 17 00:00:00 2001 From: sharevb Date: Sun, 22 Sep 2024 12:18:51 +0200 Subject: [PATCH] feat(new tool): SSL Certificate Converter Fix #1245 Handle JKS, PEM, DER, P12 as input Handle PEM and DER as output --- src/tools/index.ts | 11 +- src/tools/ssl-cert-converter/index.ts | 12 ++ .../ssl-cert-converter.e2e.spec.ts | 15 +++ .../ssl-cert-converter.service.test.ts | 48 +++++++ .../ssl-cert-converter.service.ts | 119 ++++++++++++++++++ .../ssl-cert-converter/ssl-cert-converter.vue | 112 +++++++++++++++++ 6 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 src/tools/ssl-cert-converter/index.ts create mode 100644 src/tools/ssl-cert-converter/ssl-cert-converter.e2e.spec.ts create mode 100644 src/tools/ssl-cert-converter/ssl-cert-converter.service.test.ts create mode 100644 src/tools/ssl-cert-converter/ssl-cert-converter.service.ts create mode 100644 src/tools/ssl-cert-converter/ssl-cert-converter.vue diff --git a/src/tools/index.ts b/src/tools/index.ts index 388cfaf49..60529776f 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -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', diff --git a/src/tools/ssl-cert-converter/index.ts b/src/tools/ssl-cert-converter/index.ts new file mode 100644 index 000000000..4b462b7b9 --- /dev/null +++ b/src/tools/ssl-cert-converter/index.ts @@ -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', 'pem', 'der', 'jks', 'converter'], + component: () => import('./ssl-cert-converter.vue'), + icon: ShieldChevron, + createdAt: new Date('2024-08-15'), +}); diff --git a/src/tools/ssl-cert-converter/ssl-cert-converter.e2e.spec.ts b/src/tools/ssl-cert-converter/ssl-cert-converter.e2e.spec.ts new file mode 100644 index 000000000..cef89730a --- /dev/null +++ b/src/tools/ssl-cert-converter/ssl-cert-converter.e2e.spec.ts @@ -0,0 +1,15 @@ +import { test, expect } from '@playwright/test'; + +test.describe('Tool - Ssl cert converter', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/ssl-cert-converter'); + }); + + test('Has correct title', async ({ page }) => { + await expect(page).toHaveTitle('Ssl cert converter - IT Tools'); + }); + + test('', async ({ page }) => { + + }); +}); \ No newline at end of file diff --git a/src/tools/ssl-cert-converter/ssl-cert-converter.service.test.ts b/src/tools/ssl-cert-converter/ssl-cert-converter.service.test.ts new file mode 100644 index 000000000..271fda4af --- /dev/null +++ b/src/tools/ssl-cert-converter/ssl-cert-converter.service.test.ts @@ -0,0 +1,48 @@ +import { Buffer } from 'node:buffer'; +import { describe, expect, it } from 'vitest'; +import { convertCertificate } from './ssl-cert-converter.service'; + +const formatsData = [ + { + title: 'JKS', + input: Buffer.from( + '/u3+7QAAAAIAAAABAAAAAQAGamtzLWpzAAABeDENGOMAAAB/MH0wDgYKKwYBBAEqAhEBAQUABGstagMT9RkL6lTrGvx2untoFmXM13xjQjMKfxFU/iQHuk3Y44LeB5oP9/e8KEe6nK1NTQhaTRrKyMZGJhs5Oro+TLowYerbBiJJ2DKyBTVjMDCZj8f29hOXpxQpIVv6IEAlFJwL3TQNydxjdgAAAAEABVguNTA5AAAB5jCCAeIwggGHoAMCAQICBCljjXAwDAYIKoZIzj0EAwIFADBmMQ8wDQYDVQQGEwZqa3MtanMxDzANBgNVBAgTBmprcy1qczEPMA0GA1UEBxMGamtzLWpzMQ8wDQYDVQQKEwZsZW5jaHYxDzANBgNVBAsTBmprcy1qczEPMA0GA1UEAxMGamtzLWpzMB4XDTIxMDMxNDE0MDQwNVoXDTIxMDYxMjE0MDQwNVowZjEPMA0GA1UEBhMGamtzLWpzMQ8wDQYDVQQIEwZqa3MtanMxDzANBgNVBAcTBmprcy1qczEPMA0GA1UEChMGbGVuY2h2MQ8wDQYDVQQLEwZqa3MtanMxDzANBgNVBAMTBmprcy1qczBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABEXhUAIulQWBEFCRBqPl7t/FKuI6hzWYpdBpwWZSRiDkp9A0xeIJyHazINyyx2xIDyCvR1vrqIhMuFxJIWxp8DmjITAfMB0GA1UdDgQWBBRpec0VpSfJmWjyzDtiUWk5difuTjAMBggqhkjOPQQDAgUAA0cAMEQCIE2hdtgyJZgO+gGZrCBxSgQ7G/uRugeIDGBR5X9oY2rAAiBgRfCUsTPr5NPeTfuS854/koMCTYrvLEcwcRGD4uBuNe3vG6EIUGVgYuXdiR4aycUoOcEb', + 'base64'), + pass: 'password', + convertedCount: 1, + }, + { + title: 'PEM', + input: Buffer.from( + 'QmFnIEF0dHJpYnV0ZXMKICAgIGxvY2FsS2V5SUQ6IENGIDVDIDAxIEU4IEQyIDYzIERGIEM4IEZFIDMyIERCIEI0IDI4IDJEIDc2IEYzIEQwIDVEIDUyIEQ4IApzdWJqZWN0PS9DPVVTL1NUPUNhbGlmb3JuaWEvTD1TYW4gRnJhbmNpc2NvL089QmFkU1NML0NOPUJhZFNTTCBDbGllbnQgQ2VydGlmaWNhdGUKaXNzdWVyPS9DPVVTL1NUPUNhbGlmb3JuaWEvTD1TYW4gRnJhbmNpc2NvL089QmFkU1NML0NOPUJhZFNTTCBDbGllbnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkKLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVuVENDQW9XZ0F3SUJBZ0lKQU0xSHg0SkoxT2dwTUEwR0NTcUdTSWIzRFFFQkN3VUFNSDR4Q3pBSkJnTlYKQkFZVEFsVlRNUk13RVFZRFZRUUlEQXBEWVd4cFptOXlibWxoTVJZd0ZBWURWUVFIREExVFlXNGdSbkpoYm1OcApjMk52TVE4d0RRWURWUVFLREFaQ1lXUlRVMHd4TVRBdkJnTlZCQU1NS0VKaFpGTlRUQ0JEYkdsbGJuUWdVbTl2CmRDQkRaWEowYVdacFkyRjBaU0JCZFhSb2IzSnBkSGt3SGhjTk1qUXdPREl3TVRZeU5EUXpXaGNOTWpZd09ESXcKTVRZeU5EUXpXakJ2TVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVXTUJRRwpBMVVFQnd3TlUyRnVJRVp5WVc1amFYTmpiekVQTUEwR0ExVUVDZ3dHUW1Ga1UxTk1NU0l3SUFZRFZRUUREQmxDCllXUlRVMHdnUTJ4cFpXNTBJRU5sY25ScFptbGpZWFJsTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEEKTUlJQkNnS0NBUUVBeHpkZkVlc2VUcy9ydWtqbHk2TVNMSE0rUmgwZW5BM0FpNE1qMnNkbDMxeDNTYlBvZW4wOAp1dFZoalBtbHhJVWRraU1HNCtmZmU3TitKdERMRzc1Q2F4WnA5Q3h5dFg3a3l3b29SQkpzUm5RaG1RUGNhOE1SCldBSkJJeit3L0wrM0FGa1RJcVdCZnlUKzFWTzhUVktQa0VwR2RMRG92Wk9telpBQVNpOS9zaitqNmdNN0FhQ2kKRGVaVGYyRVM2NmFiQTVwT3A2MFE2T0Vkd2cvdkNVSmZhcmhLRHBpOXRqM1A2cVRveTlZNERpQlVoT2N0NE1HOAp3NVh3bUtBQytWZm04dGI3dE1pVW9VMHl2S0tPY0w2WVhCWHhCMmtQY09ZeFlOb2JYYXZmVkJFZHdTcmpRN2kvCnMzbzZoa0dRbG05RjdKUEV1VmdibC9KZHdhNjRPWUlxalFJREFRQUJveTB3S3pBSkJnTlZIUk1FQWpBQU1CRUcKQ1dDR1NBR0crRUlCQVFRRUF3SUhnREFMQmdOVkhROEVCQU1DQmVBd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dJQgpBSDlhU2NjMGNQQ2VtUEt3Yk9IY1B1VThON2pINjJoMkxqOGJyZWhlMjZZcXN3dFZYWUN1ZmtLRzFJQkFhNk1XCkxTOHhWVEV0b1ZjOHAwUVZDL3AzTDY1YjB4RG5pUlNCMFArWC9FVWFDNXIzUEE1SmQzVHgzakdBRzgxYjNETVUKMk83L3Jma2ZyZ1hCRUNWQlpRcm1ObVcvWnJpYXI5dmEwKzQwZGFPMTZLUVRRK2tHTDIzdldTWFNKQllJc1FsYgpyU2Y1QWp6eFpXY1Y1aXpUWUZLVjZwWmFsYjNibk8wRmptN2FMWVJZdndqWFhMR2pSb0JYd2xJaVpXRzNWVGJ1CnA5UWlBNEtyS2FvQ2x1bTRCakxtQkVlL1dnK1NVeXdTd2sydkFxUXViVUVtN1l3MmFiL1F3eSttUmIrRTVpaHcKcEhkOUxCaWlGLzBlbVNVbGVJY1NzUWpIend0WWNnQ0M5ZktpYUE1eEhkUXdLY3hOQVRDalZIUUtuMTJpQi92cgpwcVAxRjVHUGhUWk0zWWJ6ZG1wcDdlV0cva2NQRkREQk5CS2xzaXJsR3VRUkVPZ0QweW9pdTg5TG83Q01LV29kClBYbTZrMWxNWS9uRXR0R3VFZm5HR1ZvUUF2SFMvWU5pL3QwVFQ0SlJTMlZBbmZJMGV5K01nVmRCdDVETDMxd3QKa1hIWHh4M2QwV3JmZ0UxSlNvd0NNQ3E4ek1yZGo1UC9vQ0dqYmtsdFJtNnJ2UHZsTmtmRkt0eU42KzV2R0pKMgpWR1BxNnpnTkhmRXVOc1BDRWJ5MExwWm1NSEh6RlhuWjB1d3h6ZnBvVnZqcktYbEFQc1UreGd0K3ZNRzRaZ2VqCjlLTjBmdktnWkZlWWNkZVdsdzR3YThUc0ZadVZVeG1oa3ZsYVJQUlkzenNjCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KQmFnIEF0dHJpYnV0ZXMKICAgIGxvY2FsS2V5SUQ6IENGIDVDIDAxIEU4IEQyIDYzIERGIEM4IEZFIDMyIERCIEI0IDI4IDJEIDc2IEYzIEQwIDVEIDUyIEQ4IApLZXkgQXR0cmlidXRlczogPE5vIEF0dHJpYnV0ZXM+Ci0tLS0tQkVHSU4gRU5DUllQVEVEIFBSSVZBVEUgS0VZLS0tLS0KTUlJRkRqQkFCZ2txaGtpRzl3MEJCUTB3TXpBYkJna3Foa2lHOXcwQkJRd3dEZ1FJcjh6aStFNGwyTElDQWdnQQpNQlFHQ0NxR1NJYjNEUU1IQkFoSEpQUStQVHd3eHdTQ0JNaXVObWlCSGErQmIyWVdGUW1WUzZRYmJBMnlEZFROCmtSamp5K3locG5QSW1UU0plb28xY000UWJRL3owR2c5cUdxZDhNeThKMmtIcFQvNVRWK2VsOHBrbkRtYXYxdG0KMTMwMGlzOXdYd3ZxOUFlTWhSbXJlaFMyeldLYmJDSmk2YkNZczQ2NkZSRy8vRWM5eWZhcUN4blpVVzNhTGlXeApnV2xMeHQyRFRhN1hiTEU2Q1hWaEo4UUNnWGJQcVRRWTYxM1V3KzlRY2xod2t6dXZXYWNwQk5jU2N4dEZhTWZtCkp3UmxWZEdJdUxReDRXckFtUUxjU3J0RlhjMGJwZ1ljRjhqaEsxSTVUaElGNFFCWERlS2FGZFVDbG80NW9XNWIKb0djNXpwd1oraVA3eW96d2c5TnpySTYyam5FMXZBdEtnOWlsQzN0czR4MU9MMktsRXFycUJzbTRLU3c0ZTB6dgpTWUJJRUJuTEFsL0NlK0NLUGZ2TnpKTXpWVHV2Y0MrRGNOUXlmZ2N5bWlVZ2N1SHlkdFFGRi81cU55bjhIT2d2CnoxYm1qR0FBaHJaTHNyajdVWVA3a1FGMzliUlNiZy9Wa1dOa0Z5V2p2REJMc05iMTBXVy8xZEg1ZmJ3YzU4TmcKOTZrT2d3RFJWYWZuVEhtaDI5TzB6QXVMMUFsK0xPUEpnb3JGN0hHYzJneG5vVXEvYk1RREx6MFlSUHJ3eEVnbgpHNnRObTI3THgrZkdvaUdpMnFFUXRWVGErMGl2em1XS3pQbTUxOUU0VU5SR2ZhZGQrdjMrcXBKY3UyR1ZOb0E0Cmdvc2RNbUpYL3kvTkVXSk5kWndHTWpOaGluQW5obDdkL3ZJeVR4aHlvQVhLNExVRkdhWU01L1FYMkFzVjB2ZFUKMTJZWDhnd2ZKMVBYYXhqRGx2ZXp6dTErZTVHbTF5MjI1a0NKU1BNaWpaNE1uMzJnV2lFY0QvN3Vod2dQWkROTQo2clozaWl3K01aaDhqQkQveTBxdTNIdHV4ck0vclRTeERkZVFIb0QydWdnK0lZK0VhMmxsWjI0Sms2Ri9tOG5vCkY2Z3JDODdneEVXdzZIVW8xcE4zVVRzU2l4WCtxaG42eVUzYUJuRWpya0hwQ2ZqOEtNUXV4dXR4cWZDZk1jWmIKaVJpcXIvTmpkYjZ5dUk4OW52OGxVcHRHYjZMZUVEOTc2MGNyNjVhMGh3Ky9rcWZSd0VzUlhRbndYNzEyWHRIRgpVVWlTcmRVd1pGOGdBUFF0dWxmR0tiekllelFVaG9meHFLdUg3NDBtZzFsWmcrNzc2aGtOUGMzOVNNYkJVOEZ5CkYwTllORmhmdnJjbUdIY0Q2b0grZHJvUDJlODhQVXBDU0lHQ0lscm16eHJSOE0rRVR1d1FCOFIyeVFrM1NsN0QKTkh4cXk5RU1KUldVT0JpdmovYjk5QUFkTjE5eWRrZG94VXhOMFlSbWVRMXJGelpvU25OMFBUTjJNSWUvT2lOUApYeEhJZkZIckpSR1NPZm4wU0E0WDdqWHAwZllvUnFMVkJOOGN4bmJLNG5Ma2tob3pVc3BPUGtNVjBONEpLNDBBCkFmc05QTkUyWk1XYmNPbThoZ0ROMk8rVTl5c1NpTnB6akpoOURTWm5OOGR0d1V6NW1WVllaTm1hMExUbXpBME0KMmxKb1JlRGpJUFNaYWE0Q2tWcHhQK29OT1hCN0FMOW1BTnFxc0F4TjhiSjg1UUlSaXRDSEw5YW5oa1lsWGQyNwp1Sld6RWhJbGh6MjM4ZVlmL09KOUNQVkhoMVdmZHZlRndUM0Z4ZXZYaVNlcnVCT1M4UXp3QXJ5eGVxL0l4VmNqCnRSV25OQ2VVQTlqa3BJRktRd05CZ294K2tzU3BNN2t3eXd6dHFNV2F3aHNlSUNCMk9MTDBpaksyVVFaeHp4bTYKQnkvUmt0Qlg0RzI5Wk0wU05uOThNaXdKdW9TMHFvRlg4bVc2MExMWWdtMzZpazR1NTJvWm1CdzNScDVhZGF6cQo4UXREeEs2WDB0bVRUYVVteXB1SmIwUEdRN202c0dVc2h5SnNUTWd3Yi8zREljUFRZaENSRVVOeVVMdlk2MmhSCko5Q3c2aURGRVMzSDNjcjQ1QlRLZHJ3K1R1amFRRWdOUUJpMXNRVTFWUlVmVm5LNURhUjNkU2tEMTBxWmR5Q0EKV0JFPQotLS0tLUVORCBFTkNSWVBURUQgUFJJVkFURSBLRVktLS0tLQo=', + 'base64').toString('binary'), + + pass: '', + convertedCount: 1, + }, + { + title: 'DER', + input: Buffer.from( + 'MIIC+zCCAeOgAwIBAgIJAOxwUeP5dw46MA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNzA1MzExNDExNTNaFw00NDEwMTYxNDExNTNaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALEPP29L/6fGvrA6wVD1kaPMS5o2O8h7AwIgnV8Fne74q/5NyMWrd/s5jAzug+yMRqLD8UbrA2s9vwKwkflKxHFZeIh1BtttTd31HmyPiE5tqp2/N2SqIm6bVb/8R4+i6CkW+K8SWk4+hQbEeAaAzNxq1oH/QKqkq1CzVsTjoLiUW2+dWw1iC0ZGefoablyICuIfBBH4OSK5KOKA8oKdOHoucYMOHXbXvsJVaNP+wJa8+qaDot45s2tliDljX06xNzOwSWSGzFbjiyEm5YuhO1VEIojQ+fe4EfToD00OrC5eTUI2BQ5eJ7FA8udjqAE86xmj9f63GxTbHvUyXx3ve8MCAwEAAaNQME4wHQYDVR0OBBYEFLW83bDG7ZWwluyWcueSk5A/hCk6MB8GA1UdIwQYMBaAFLW83bDG7ZWwluyWcueSk5A/hCk6MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIfVtAO6VOwUQ2zp4QI698uV3ESxYIg1K6kTTWkvXkvYMlG5Wuvsan83V4zCTpclKSqePPr73eN9HkcGEOEXU+zM1eS4RfW8TDj9eLbGt0cBGzWC4gXJiMEx6Rr0VpcnjF5u9Iqty+YWQ/8MH10kTeOojuAtOmcrVe3VwB8bDx+PwHd0ZZCTWxi+qU/ST5jOEokiihaW68aHOc6IXgNRChPpI01gZVUFRevaJP5g1Yk41cSegGEPkmNO1X8ps2wrIzV28Ih5u7GiW1Gca+MwAAErzf8HsbGwZc56AXnpqKpM55T1cmfwPQe36lyUk8Cz/WzivmwsfbIlGRreK8kwCpc=', + 'base64').toString('binary'), + pass: '', + convertedCount: 1, + }, + { + title: 'P12', + input: Buffer.from( + 'MIIKYQIBAzCCCicGCSqGSIb3DQEHAaCCChgEggoUMIIKEDCCBMcGCSqGSIb3DQEHBqCCBLgwggS0AgEAMIIErQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIclGLT8zIDKsCAggAgIIEgKckUUiFcSayY2qs5jWA/+6JEtVvRNwgkUnjTTQdldO+fPUACJ5Ucbqt4CKwdsFC6DSbmko3Ga6z3GHQ9jNKld/31P/Rty8eWossmRMpIGaYPfaAa5tClll6bfcYDWQw2so7T7X+4YxL0mfOP0iMpypjvsa2WUx+KlzZ4iFnjCmKYWAELkSnmYJNN8LK4mHixMTIKzPbL7VWz7ywrrfL+PKIK4Y3I+Axs+Y4HkHTiLWhIALdjKTbD8qJYrbiowfYzbe8GQxenK4d7I0DLEhFWrqZw0vszYJi1gxBOmwT040exnfZdZvQ9+WwbjAUMd1y1P8uJtBvlfPH2zBmlrsbZ3DABKPqEUKkr2PX2sDekiI6oa9DCheKPQJ2UR+BHbA2bMB+JgS9NbH9aeOP8Fa/29v5NGUTThzxwbBBvz5KIfEsDfUfTj/BjHKt5YWtcIbKVAPR/ePfa4FnH8civ9PPptlz8sdzu2IRmxZWsve9zQ2EN2g4MiemnuSSUMr/hNIOuRWQGXFJ3xKdhHXU4+v7zOmpoX2aKM2GLYxYQDw1qohPiXzC+Fdm4fI9np92hZTIs98hZUSvs1B641brsV0kyDgGmP5tb6O//fp8UwPHfcLRucSKPHh6r21QkbAsKlvR1hf8aG+xs5XwY9CJVfBhxDjjK8XjvHbX4ATN3qXG73M3Dw69S0C0f4tGlq1IL9tHxL8B2TmTAsES/5QnRqNY6q+TBvbuhd3zvhakfuYZPsasLY8dzTq/Piv3mMrF1S3Fb2yVodpDbC/3wLK59Y6BM+QswVWWUq9LNYRftvGHXcrx/jN+mALKQSIY0aTzCMMmVh2Dm3xWFKKBJsF/OOXkCZaSgYcK90NHSZtJT/RTus07FHZNAu246Rd+k2s2bVv6qGzdpbwPA985jYV9wPK7pUwIJ1EPBg45wdpsDpqUSMl6twED+/PmWukWmTxvp+MUeUi7LqpcN47uKdYYQWqzM0+sXXWgJbshshccH/76ZpBzZTxo2C2pFvlDW/5gBtOoo4FzExeK37X3DWKWrOQ7YytGzFruaJ1xm+5RiQMNAKbr8NObBdrKFNEQwfICSJDTGdrJiNmYbBqX1MzkMWLRiZANDCxAJ9G22uu63dWF9aEFoh35UYMmNFhvF5Xo72i0lqkUiWBj2dXPcLLFnXtsHb1bggFV0tfNhyAL6guWAqRPbCsQhL0pjlSxo2ejUi4ZFXaa+ass7BsDZhQORtBF4YJVmXW6gueURg74MndPQSnYddElrw6C2MjrngRLCqcN0Mgyn9CQ3dN0BqRJm41ALcKybhFB2tjobW5pdKf7LfQwiHnVkwy63vpAx5sDiHyOrf/zqyKzD0+elTkWp8PH773CKCP15SLUIwighPHeorlxyasAWyd7G6Q15aB9u0VgSjIFQhZ+eQGI7ARnOVEvleR9ywtggE5uhPp/PIaJRn6QrZUvA27zjuGxqMl/nKy2XgDi7IxSRH36U4wrVhtbkjE9bQSG+KDKq0Kpr7q00tH0+wZQ6qCXxJh6bNyEHxgWBjCCBUEGCSqGSIb3DQEHAaCCBTIEggUuMIIFKjCCBSYGCyqGSIb3DQEMCgECoIIE7jCCBOowHAYKKoZIhvcNAQwBAzAOBAh7wM/3Yj5jjgICCAAEggTIgmujigWG8drzI6EjfpCjI7qtE8ed7zNcbWokHCc3irwOXgEUfNMFT0Rlzp9Sdlr2SlrKh9qfmIwDe/prP1WXqMsk8PdVIrcwEe265BXjrGGRCNI6CwlFfXrgdosXeGJXgEWKg7acVnoCgpC55yDFM8F9gOe4IVgBy5By7s5l6F7GrGMgRiz76eeOdS4ZwRuqC/cUkZimf5Iy5tRJRWA02xePgh1HqfBN+IJgJ1l6jmtAFCwJd5QsDAb0yC+gmi4Herb2H4MiLnkx2YHG38d7tVkVdzCWIrdCDghGNYG6f7Me0Oc0Mtq+tmltiIRl61RGQViyL+TkgUy0HFoSSz50nkHJg07Gj9zY68jBE/pGNCoq06q+kT1q/mMPQG6LF8E/Ba5iQYshNlpp/sABAtqtKH+47oyh+Nz46zlvcQ4/C+yi3/FU0YKib+Jm29EU/Da1VPlXSDFJnTu7b0hb4KL0t1FUnvLxeWqas7EPxOdz1SmZ6fmQdKwkwFT1HqpTxnWkshipb2NchRY6tUmjPJMw2u3RxKblj+hd/QGgFfcxqpsHc//tzoFFuDcgvaVyWCZRVPdvdinrA+5ig2QUH3E6QabzUWX5ZeiiLyV+C01xKLnI1EzEMftKdGF71qJjZLC7/DKLRcVEJ718yhItzPYJu3oRzFyj+ciBp3ihibwoq4YHVNfPMAKM6yuZwFyUy7oW6CJSA3WeCnH29PSywJDAbr/kQRq6YJR0JohIauZ4b9q9x/PaqQ+uBY19ZF5vhYjTJdTZJ51DCHDMw3ZFTZPb7sc3vqruMxNuV2mtMb32OuYeXV7bgRbDu3B2VmdG4H5pA5KGY7ICvy2DvmrH7liZyNRfh8QzmXdgz+deoAGVrK8xmkKc/kfjZwxs3U8ZxhjL7psvSePYgQtCsA9aFFTfBLxyf9Y6d4hD8gcopSs93rh4Iqf98qvpYxwfJobm88F2UiftGUy6QozPVpM6R0Wwos/ioX+4UcfM74eBsqKsrQadCUcXX8qwwn7TQpJhw6CVJVKajqca0fqYT/zOmlk5AFDt8JIDWI0W0lPRd0nqLRgpM9N+4qUQzM83mauIuorQquUWbSJ8l5yGs8LljGZ+PiatRS3VDqr5ppjiQv+7dWLJ5PBiv7bbasxCbsJW1ZwWd/qQ0CaRk/I0SopNHUf6Nf/I5lKsXQVE+8jnmFpgZs8SwrzIaz+Xs7EmFOwFPf6lcwZ6JuC10BRsxUWt1ctjb78dRhB5bUVKec0IllR3X5fZinceCOOR6nNZ/wxrRi9waG5gIrL45Rnk8cn+ONSeG8BiD6Z5wxcXZQ1use8QksFbE2aDKIVhi3gnQnp5r+jxBnEwYrl5u3E98sA5rIHi2cllyLpzmKiqjb3QxC+aEu20EJCdMZ9IiSdXdse1CFboztDNafwWTsvUp1XFUF8f69cDzkbH7x+/E3h6P7HyxmmSEr5Xdwmg4sQAy5sY43lsNPDSa+qfurqRyKMk3eFERFSsvDtR/K8CLsNA4IcQsbWBFdLamfkDZsow8Roei+qAOEX8gXc72RUYKsjhGa7zXScHKCAe+f22KGQVUIhoDZWNOVPVSLZN3vP3hFXmX+ZXNw1kBS29filpHDp3+S1gnCXD33Uh1BnqMSUwIwYJKoZIhvcNAQkVMRYEFCosIveCkCjVTq8eGBQN74TQ5GpZMDEwITAJBgUrDgMCGgUABBSQaubCDTe3BlNcJeUlEZqFCneD6gQI97YoYVQLu8QCAggA', + 'base64').toString('binary'), + pass: 'chef$123', + convertedCount: 1, + }, +]; + +describe('ssl-cert-converter', () => { + for (const format of formatsData) { + const { input, pass, convertedCount, title } = format; + it(`Convert '${title}' format to correct values`, () => { + expect(convertCertificate(input, pass)).toHaveLength(convertedCount); + }); + } +}); diff --git a/src/tools/ssl-cert-converter/ssl-cert-converter.service.ts b/src/tools/ssl-cert-converter/ssl-cert-converter.service.ts new file mode 100644 index 000000000..1270763c6 --- /dev/null +++ b/src/tools/ssl-cert-converter/ssl-cert-converter.service.ts @@ -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 (typeof pkcs8Key === 'undefined') { + pkcs8Key = keyData[forge.pki.oids.keyBag]![0]; + } + + if (typeof pkcs8Key === 'undefined') { + 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; +} diff --git a/src/tools/ssl-cert-converter/ssl-cert-converter.vue b/src/tools/ssl-cert-converter/ssl-cert-converter.vue new file mode 100644 index 000000000..42439d5c2 --- /dev/null +++ b/src/tools/ssl-cert-converter/ssl-cert-converter.vue @@ -0,0 +1,112 @@ + + +