From 72d2340ab47170e86e0c9822da1cf32d157ca43c Mon Sep 17 00:00:00 2001 From: Martin Auer Date: Wed, 17 Jul 2024 20:14:28 +0200 Subject: [PATCH] fix: x509 domain extraction and leaf certificate extraction (#1955) Signed-off-by: Martin Auer --- .../__tests__/SdJwtVcService.test.ts | 39 ++++++++++++++++--- .../sd-jwt-vc/__tests__/sdjwtvc.fixtures.ts | 9 ++++- packages/core/src/modules/x509/X509Service.ts | 2 +- packages/core/src/utils/domain.ts | 35 +++-------------- 4 files changed, 48 insertions(+), 37 deletions(-) diff --git a/packages/core/src/modules/sd-jwt-vc/__tests__/SdJwtVcService.test.ts b/packages/core/src/modules/sd-jwt-vc/__tests__/SdJwtVcService.test.ts index 8770ec94c1..8fa430b367 100644 --- a/packages/core/src/modules/sd-jwt-vc/__tests__/SdJwtVcService.test.ts +++ b/packages/core/src/modules/sd-jwt-vc/__tests__/SdJwtVcService.test.ts @@ -15,6 +15,7 @@ import { complexSdJwtVcPresentation, contentChangedSdJwtVc, expiredSdJwtVc, + funkeX509, notBeforeInFutureSdJwtVc, sdJwtVcWithSingleDisclosure, sdJwtVcWithSingleDisclosurePresentation, @@ -27,10 +28,11 @@ import { } from './sdjwtvc.fixtures' import { - CredoError, Agent, + CredoError, DidKey, DidsModule, + getDomainFromUrl, getJwkFromKey, JwsService, JwtPayload, @@ -149,7 +151,7 @@ describe('SdJwtVcService', () => { }, issuer: { method: 'x5c', - x5c: [simpleX509.certificate], + x5c: [simpleX509.trustedCertficate], issuer: 'some-issuer', }, }) @@ -168,7 +170,7 @@ describe('SdJwtVcService', () => { }, issuer: { method: 'x5c', - x5c: [simpleX509.certificate], + x5c: [simpleX509.trustedCertficate], issuer: simpleX509.certificateIssuer, }, }) @@ -179,7 +181,7 @@ describe('SdJwtVcService', () => { expect(sdJwtVc.header).toEqual({ typ: 'vc+sd-jwt', alg: 'EdDSA', - x5c: [simpleX509.certificate], + x5c: [simpleX509.trustedCertficate], }) expect(sdJwtVc.prettyClaims).toEqual({ @@ -797,7 +799,7 @@ describe('SdJwtVcService', () => { }) const x509ModuleConfig = agent.context.dependencyManager.resolve(X509ModuleConfig) - await x509ModuleConfig.addTrustedCertificate(simpleX509.certificate) + await x509ModuleConfig.addTrustedCertificate(simpleX509.trustedCertficate) const verificationResult = await sdJwtVcService.verify(agent.context, { compactSdJwtVc: presentation, @@ -846,6 +848,33 @@ describe('SdJwtVcService', () => { }) }) + test('Verify x509 chain protected sd-jwt-vc', async () => { + const x509ModuleConfig = agent.context.dependencyManager.resolve(X509ModuleConfig) + await x509ModuleConfig.addTrustedCertificate(funkeX509.trustedCertificate) + + const verificationResult = await sdJwtVcService.verify(agent.context, { + compactSdJwtVc: funkeX509.sdJwtVc, + requiredClaimKeys: ['issuing_country'], + }) + + const sdJwtIss = verificationResult.sdJwtVc?.payload.iss + expect(sdJwtIss).toEqual('https://demo.pid-issuer.bundesdruckerei.de/c') + expect(getDomainFromUrl(sdJwtIss as string)).toEqual('demo.pid-issuer.bundesdruckerei.de') + + expect(verificationResult).toEqual({ + isValid: false, + error: new CredoError('JWT expired at 1718707804'), + sdJwtVc: expect.any(Object), + verification: { + isSignatureValid: true, + areRequiredClaimsIncluded: true, + isValid: false, + isValidJwtPayload: false, + isStatusValid: true, + }, + }) + }) + test('Verify sd-jwt-vc with status where credential is not revoked', async () => { const sdJwtVcService = agent.dependencyManager.resolve(SdJwtVcService) diff --git a/packages/core/src/modules/sd-jwt-vc/__tests__/sdjwtvc.fixtures.ts b/packages/core/src/modules/sd-jwt-vc/__tests__/sdjwtvc.fixtures.ts index 24acaa2986..55794adb92 100644 --- a/packages/core/src/modules/sd-jwt-vc/__tests__/sdjwtvc.fixtures.ts +++ b/packages/core/src/modules/sd-jwt-vc/__tests__/sdjwtvc.fixtures.ts @@ -30,11 +30,18 @@ export const simpleJwtVc = export const simpleX509 = { sdJwtVc: 'eyJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFZERTQSIsIng1YyI6WyJNSUhlTUlHUm9BTUNBUUlDRUIwYW80ZVVBZUhrQjg2dzhmSUVuR2N3QlFZREsyVndNQUF3SGhjTk1qUXdOekUyTVRNek5URTNXaGNOTWpReE1ESXpNVEkwTlRNeVdqQUFNQ293QlFZREsyVndBeUVBMWMra1AwdFlodlN2LzJCdzdvSlFiQ1dZT2JUY0IyS1VPVHB3K0x0TG85dWpJVEFmTUIwR0ExVWRFUVFXTUJTR0VtaDBkSEJ6T2k4dmFYTnpkV1Z5TG1OdmJUQUZCZ01yWlhBRFFRQkU0SmFrbTh2bjI1NUI4ZEFneWdiaFIwWlBTZkNFbmdGdWlXREJkeUFYalc2YWhpdDZtOGlsZW05MDhreGsyeUpOZ2hUSVNCbERod2tmcmx5UFJ4NE0iXX0.eyJjbGFpbSI6InNvbWUtY2xhaW0iLCJ2Y3QiOiJJZGVudGl0eUNyZWRlbnRpYWwiLCJjbmYiOnsiandrIjp7Imt0eSI6Ik9LUCIsImNydiI6IkVkMjU1MTkiLCJ4Ijoib0VOVnN4T1VpSDU0WDh3SkxhVmtpY0NSazAwd0JJUTRzUmdiazU0TjhNbyJ9fSwiaXNzIjoiaHR0cHM6Ly9pc3N1ZXIuY29tIiwiaWF0IjoxNjk4MTUxNTMyfQ.d254hz7u-mziOtFA3yA9tVNNDP5_6eJL-owg9prcr1jzVnYoiRyjPvzY7NuKuDqN5PeOaZ2x5GFYp7VwYX5RBw~', - certificate: + trustedCertficate: 'MIHeMIGRoAMCAQICEB0ao4eUAeHkB86w8fIEnGcwBQYDK2VwMAAwHhcNMjQwNzE2MTMzNTE3WhcNMjQxMDIzMTI0NTMyWjAAMCowBQYDK2VwAyEA1c+kP0tYhvSv/2Bw7oJQbCWYObTcB2KUOTpw+LtLo9ujITAfMB0GA1UdEQQWMBSGEmh0dHBzOi8vaXNzdWVyLmNvbTAFBgMrZXADQQBE4Jakm8vn255B8dAgygbhR0ZPSfCEngFuiWDBdyAXjW6ahit6m8ilem908kxk2yJNghTISBlDhwkfrlyPRx4M', certificateIssuer: 'https://issuer.com', } +export const funkeX509 = { + sdJwtVc: + 'eyJ4NWMiOlsiTUlJQ2REQ0NBaHVnQXdJQkFnSUJBakFLQmdncWhrak9QUVFEQWpDQmlERUxNQWtHQTFVRUJoTUNSRVV4RHpBTkJnTlZCQWNNQmtKbGNteHBiakVkTUJzR0ExVUVDZ3dVUW5WdVpHVnpaSEoxWTJ0bGNtVnBJRWR0WWtneEVUQVBCZ05WQkFzTUNGUWdRMU1nU1VSRk1UWXdOQVlEVlFRRERDMVRVRkpKVGtRZ1JuVnVhMlVnUlZWRVNTQlhZV3hzWlhRZ1VISnZkRzkwZVhCbElFbHpjM1ZwYm1jZ1EwRXdIaGNOTWpRd05UTXhNRGd4TXpFM1doY05NalV3TnpBMU1EZ3hNekUzV2pCc01Rc3dDUVlEVlFRR0V3SkVSVEVkTUJzR0ExVUVDZ3dVUW5WdVpHVnpaSEoxWTJ0bGNtVnBJRWR0WWtneENqQUlCZ05WQkFzTUFVa3hNakF3QmdOVkJBTU1LVk5RVWtsT1JDQkdkVzVyWlNCRlZVUkpJRmRoYkd4bGRDQlFjbTkwYjNSNWNHVWdTWE56ZFdWeU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRU9GQnE0WU1LZzR3NWZUaWZzeXR3QnVKZi83RTdWaFJQWGlObTUyUzNxMUVUSWdCZFh5REsza1Z4R3hnZUhQaXZMUDN1dU12UzZpREVjN3FNeG12ZHVLT0JrRENCalRBZEJnTlZIUTRFRmdRVWlQaENrTEVyRFhQTFcyL0owV1ZlZ2h5dyttSXdEQVlEVlIwVEFRSC9CQUl3QURBT0JnTlZIUThCQWY4RUJBTUNCNEF3TFFZRFZSMFJCQ1l3SklJaVpHVnRieTV3YVdRdGFYTnpkV1Z5TG1KMWJtUmxjMlJ5ZFdOclpYSmxhUzVrWlRBZkJnTlZIU01FR0RBV2dCVFVWaGpBaVRqb0RsaUVHTWwyWXIrcnU4V1F2akFLQmdncWhrak9QUVFEQWdOSEFEQkVBaUFiZjVUemtjUXpoZldvSW95aTFWTjdkOEk5QnNGS20xTVdsdVJwaDJieUdRSWdLWWtkck5mMnhYUGpWU2JqVy9VLzVTNXZBRUM1WHhjT2FudXNPQnJvQmJVPSIsIk1JSUNlVENDQWlDZ0F3SUJBZ0lVQjVFOVFWWnRtVVljRHRDaktCL0gzVlF2NzJnd0NnWUlLb1pJemowRUF3SXdnWWd4Q3pBSkJnTlZCQVlUQWtSRk1ROHdEUVlEVlFRSERBWkNaWEpzYVc0eEhUQWJCZ05WQkFvTUZFSjFibVJsYzJSeWRXTnJaWEpsYVNCSGJXSklNUkV3RHdZRFZRUUxEQWhVSUVOVElFbEVSVEUyTURRR0ExVUVBd3d0VTFCU1NVNUVJRVoxYm10bElFVlZSRWtnVjJGc2JHVjBJRkJ5YjNSdmRIbHdaU0JKYzNOMWFXNW5JRU5CTUI0WERUSTBNRFV6TVRBMk5EZ3dPVm9YRFRNME1EVXlPVEEyTkRnd09Wb3dnWWd4Q3pBSkJnTlZCQVlUQWtSRk1ROHdEUVlEVlFRSERBWkNaWEpzYVc0eEhUQWJCZ05WQkFvTUZFSjFibVJsYzJSeWRXTnJaWEpsYVNCSGJXSklNUkV3RHdZRFZRUUxEQWhVSUVOVElFbEVSVEUyTURRR0ExVUVBd3d0VTFCU1NVNUVJRVoxYm10bElFVlZSRWtnVjJGc2JHVjBJRkJ5YjNSdmRIbHdaU0JKYzNOMWFXNW5JRU5CTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFWUd6ZHdGRG5jNytLbjVpYkF2Q09NOGtlNzdWUXhxZk1jd1pMOElhSUErV0NST2NDZm1ZL2dpSDkycU1ydTVwL2t5T2l2RTBSQy9JYmRNT052RG9VeWFObU1HUXdIUVlEVlIwT0JCWUVGTlJXR01DSk9PZ09XSVFZeVhaaXY2dTd4WkMrTUI4R0ExVWRJd1FZTUJhQUZOUldHTUNKT09nT1dJUVl5WFppdjZ1N3haQytNQklHQTFVZEV3RUIvd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUgvQkFRREFnR0dNQW9HQ0NxR1NNNDlCQU1DQTBjQU1FUUNJR0VtN3drWktIdC9hdGI0TWRGblhXNnlybndNVVQydTEzNmdkdGwxMFk2aEFpQnVURnF2Vll0aDFyYnh6Q1AweFdaSG1RSzlrVnl4bjhHUGZYMjdFSXp6c3c9PSJdLCJraWQiOiJNSUdVTUlHT3BJR0xNSUdJTVFzd0NRWURWUVFHRXdKRVJURVBNQTBHQTFVRUJ3d0dRbVZ5YkdsdU1SMHdHd1lEVlFRS0RCUkNkVzVrWlhOa2NuVmphMlZ5WldrZ1IyMWlTREVSTUE4R0ExVUVDd3dJVkNCRFV5QkpSRVV4TmpBMEJnTlZCQU1NTFZOUVVrbE9SQ0JHZFc1clpTQkZWVVJKSUZkaGJHeGxkQ0JRY205MGIzUjVjR1VnU1hOemRXbHVaeUJEUVFJQkFnPT0iLCJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFUzI1NiJ9.eyJwbGFjZV9vZl9iaXJ0aCI6eyJfc2QiOlsiZVNReDlVNFI4S2dKUnJISWZOYTdPeUhTR2I3WTh3bFNaWkc0MXVwWFN2ayJdfSwiX3NkIjpbIkdxZVRITW1mWFNyLVRpWHNscGVidTd2ay1aTzBnYkJ3N0ktbWtPR2k4UlEiLCIzMlpvemdIRl9kVFhRTVZMRWl6d0ZzelJ5b0IxVks5bHNnakFHaHlUdnBnIiwib2htY1pCTE1JUFRhOWN2cHVNdXRzcWRZdVFhaTRiR2RibmFJZDRSeDBQayIsIndQQ0lfbzQtbFhWVzkySkVCQkJPazUwUWVISGQtYWl4WGJzOWJ5dGhueWMiLCJqaTJ3VXd6RWJCWmFGOHRtLWRLaUZSVTZpbVp0bTU1ZGFOZkVaYW9xWTBzIiwidk5CS2RKNUVpakFuZWwxaVM4ZGFLS0J5TWd6d3JSelBzTU9vUEZTWkJQQSIsInlfeG5PUGVNU2M5Y2FjZ25VanBYYkFFeW9OZXVIZVpDYkdJdi1WYXU1YWsiXSwiYWRkcmVzcyI6eyJfc2QiOlsiWjhZbDBCalFaZGZ3LWZtbzRCZGR0SzVxMEM3Vk9OLXJ5ZjBUbEZwVUtZayIsIktWbUtzNnBCR0VsNHZTTTJrTF9RZ1FFTEdqLU5mR084blJGbkx0NWdlRlkiLCJabHJibS1BMHdMTURSVWQ0Z1o5SDRSUWJaR3BUMVpRNG5ZSGlxdFI2WU9NIl19LCJpc3N1aW5nX2NvdW50cnkiOiJERSIsInZjdCI6InVybjpldS5ldXJvcGEuZWMuZXVkaTpwaWQ6MSIsImlzc3VpbmdfYXV0aG9yaXR5IjoiREUiLCJfc2RfYWxnIjoic2hhLTI1NiIsImlzcyI6Imh0dHBzOi8vZGVtby5waWQtaXNzdWVyLmJ1bmRlc2RydWNrZXJlaS5kZS9jIiwiY25mIjp7Imp3ayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2IiwieCI6IlU2WDhQb3FpelozLVByTm93UlVRaDduLXR3bTlabzF3VTh0WDkxUEVtXzgiLCJ5IjoidGZjZHFnS3JVVTIyR2VFV0U3NDlMYThkSFlFTGJmSkdDWVFfYVFSM3BPcyJ9fSwiZXhwIjoxNzE4NzA3ODA0LCJpYXQiOjE3MTc0OTgyMDQsImFnZV9lcXVhbF9vcl9vdmVyIjp7Il9zZCI6WyJMOVN3ZS04cHUxZ1llS0ltdUNlQ1RKcTYwODQzeWlSeVV1Rlh0aUdSb1lzIiwiVWFWeWlOSlZzeFR1dHlJVG9rWXVsUDJ2cjVaaHEzRUVRMWNrZ1ptNHRHUSIsIlhGeWVmQ09vZHFJdkkyQm91Rl9hQVBVMF9tZDlaLUI3a1BvaXd4NTdYRk0iLCJsUXlnaFhaclY0dnRROUtwVVpKNnB6R0g5VlRIRUI5MDkzVjdjQmYySEQ4IiwiX2NzVjc3OEdSdEd1dlB1dlJ3Vi11SFhCeklvUTNPMEY0bVB1UHVlWGpYTSIsInptYm1xU2gwMTlNZzF3QVZpdWJyaWxNSW00UnpsTWhuc041Wi1kQkVIeWsiXX19.eX-vYUtV2FcJ2Qq8mlQybSlwmXjZ6jCzKdav-G5xR1mFOQFcLOWZ1j3dPRmdFCat3MWqsHbx6eaZjue4tmDF2Q~WyJKNm55NUdXVzlJZHNXQURKblNEQnN3IiwiZmFtaWx5X25hbWUiLCJNVVNURVJNQU5OIl0~WyJqcnVqMGV4RjdOVElGWEptOUJWQ1VnIiwiZ2l2ZW5fbmFtZSIsIkVSSUtBIl0~WyJRRkJBbnFIaUo3Y1A1MHBDak1IdUFBIiwiYmlydGhkYXRlIiwiMTk2NC0wOC0xMiJd~WyI1MmQxQVNmbmE3N1JERXE3bnR4ODFnIiwiYWdlX2JpcnRoX3llYXIiLDE5NjRd~WyJiZmRSdEl5NV9kRWloc3BRTmwzNDNBIiwiYWdlX2luX3llYXJzIiw1OV0~WyJXbG43VmxHTXhaXzRlSkc0dmNkYTRnIiwiYmlydGhfZmFtaWx5X25hbWUiLCJHQUJMRVIiXQ~WyJBUFdMWlRVOHRzS3NNWXBzazhtclVnIiwibmF0aW9uYWxpdGllcyIsWyJERSJdXQ~WyJOaE9LaFRNdEhxU2pzdmV1ekFLbWpBIiwiMTIiLHRydWVd~WyJFTlFGWURlTEV0bEo3cDhzdEFMSDJRIiwiMTQiLHRydWVd~WyJtNE5QUHFxY0VxVm4tWVNyQkFNMHVBIiwiMTYiLHRydWVd~WyJNT1R4eG1kUk05alhoX3dHbzZTQXpRIiwiMTgiLHRydWVd~WyJNZ3oySzRqTzFJVlZCU015RTNoZ1JRIiwiMjEiLHRydWVd~WyJNN0tjb0JIMjZPVzhPZUFicmZMakx3IiwiNjUiLGZhbHNlXQ~WyJRaW5JUERxdnJFem5Talp1MkZqOUl3IiwibG9jYWxpdHkiLCJCRVJMSU4iXQ~WyJxVFg0Si1URFBqTHh3TElhU3NZLWlBIiwibG9jYWxpdHkiLCJLw5ZMTiJd~WyJFSlNfdFlyVGI4VWVYWXJGY3pkNUd3IiwicG9zdGFsX2NvZGUiLCI1MTE0NyJd~WyJRZFhXN3BaWkVpV1ZLbXFZOGg1d0VBIiwic3RyZWV0X2FkZHJlc3MiLCJIRUlERVNUUkHhup5FIDE3Il0~', + trustedCertificate: + 'MIICdDCCAhugAwIBAgIBAjAKBggqhkjOPQQDAjCBiDELMAkGA1UEBhMCREUxDzANBgNVBAcMBkJlcmxpbjEdMBsGA1UECgwUQnVuZGVzZHJ1Y2tlcmVpIEdtYkgxETAPBgNVBAsMCFQgQ1MgSURFMTYwNAYDVQQDDC1TUFJJTkQgRnVua2UgRVVESSBXYWxsZXQgUHJvdG90eXBlIElzc3VpbmcgQ0EwHhcNMjQwNTMxMDgxMzE3WhcNMjUwNzA1MDgxMzE3WjBsMQswCQYDVQQGEwJERTEdMBsGA1UECgwUQnVuZGVzZHJ1Y2tlcmVpIEdtYkgxCjAIBgNVBAsMAUkxMjAwBgNVBAMMKVNQUklORCBGdW5rZSBFVURJIFdhbGxldCBQcm90b3R5cGUgSXNzdWVyMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOFBq4YMKg4w5fTifsytwBuJf/7E7VhRPXiNm52S3q1ETIgBdXyDK3kVxGxgeHPivLP3uuMvS6iDEc7qMxmvduKOBkDCBjTAdBgNVHQ4EFgQUiPhCkLErDXPLW2/J0WVeghyw+mIwDAYDVR0TAQH/BAIwADAOBgNVHQ8BAf8EBAMCB4AwLQYDVR0RBCYwJIIiZGVtby5waWQtaXNzdWVyLmJ1bmRlc2RydWNrZXJlaS5kZTAfBgNVHSMEGDAWgBTUVhjAiTjoDliEGMl2Yr+ru8WQvjAKBggqhkjOPQQDAgNHADBEAiAbf5TzkcQzhfWoIoyi1VN7d8I9BsFKm1MWluRph2byGQIgKYkdrNf2xXPjVSbjW/U/5S5vAEC5XxcOanusOBroBbU=', +} + export const expiredSdJwtVc = 'eyJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFZERTQSIsImtpZCI6IiN6Nk1rdHF0WE5HOENEVVk5UHJydG9TdEZ6ZUNuaHBNbWd4WUwxZ2lrY1czQnp2TlcifQ.eyJjbGFpbSI6InNvbWUtY2xhaW0iLCJ2Y3QiOiJJZGVudGl0eUNyZWRlbnRpYWwiLCJleHAiOjE3MTYxMTE5MTksImlzcyI6ImRpZDprZXk6ejZNa3RxdFhORzhDRFVZOVBycnRvU3RGemVDbmhwTW1neFlMMWdpa2NXM0J6dk5XIiwiaWF0IjoxNjk4MTUxNTMyfQ.hOQ-CnT-iaL2_Dlui0NgVhBk2Lej4_AqDrEK-7bQNT2b6mJkaikvUXdNtg-z7GnCUNrjq35vm5ProqiyYQz_AA~' diff --git a/packages/core/src/modules/x509/X509Service.ts b/packages/core/src/modules/x509/X509Service.ts index fa689ebe85..6ed5f07289 100644 --- a/packages/core/src/modules/x509/X509Service.ts +++ b/packages/core/src/modules/x509/X509Service.ts @@ -105,7 +105,7 @@ export class X509Service { ): X509Certificate { if (certificateChain.length === 0) throw new X509Error('Certificate chain is empty') - const certificate = X509Certificate.fromEncodedCertificate(certificateChain[certificateChain.length - 1]) + const certificate = X509Certificate.fromEncodedCertificate(certificateChain[0]) return certificate } diff --git a/packages/core/src/utils/domain.ts b/packages/core/src/utils/domain.ts index 27456d6b1b..1cc4e794dc 100644 --- a/packages/core/src/utils/domain.ts +++ b/packages/core/src/utils/domain.ts @@ -1,34 +1,9 @@ -const endings = ['/', ':', '?', '#'] -const starters = ['.', '/', '@'] - -/** - * Get domain from url - * https://github.com/bjarneo/extract-domain/blob/master/index.ts - */ export function getDomainFromUrl(url: string): string { - let domainInc: number = 0 - let offsetDomain: number = 0 - let offsetStartSlice: number = 0 - let offsetPath: number = 0 - let len: number = url.length - let i: number = 0 - - while (len-- && ++i) { - if (domainInc && endings.indexOf(url[i]) > -1) break - if (url[i] !== '.') continue - ++domainInc - offsetDomain = i - } - - offsetPath = i - i = offsetDomain - while (i--) { - if (starters.indexOf(url[i]) === -1) continue - offsetStartSlice = i + 1 - break + if (!url.startsWith('https://')) { + throw new Error('URL must start with "https://"') } - if (offsetStartSlice === 0 && offsetPath > 3) return url - if (offsetStartSlice > 0 && offsetStartSlice < 2) return '' - return url.slice(offsetStartSlice, offsetPath) + const regex = /[#/?]/ + const domain = url.substring('https://'.length).split(regex)[0] + return domain }