diff --git a/packages/core/src/messagingProtocols/mls/E2EIdentityService/Connection/AcmeServer/AcmeService.ts b/packages/core/src/messagingProtocols/mls/E2EIdentityService/Connection/AcmeServer/AcmeService.ts index 8d8d55dfe0..1c1b43f503 100644 --- a/packages/core/src/messagingProtocols/mls/E2EIdentityService/Connection/AcmeServer/AcmeService.ts +++ b/packages/core/src/messagingProtocols/mls/E2EIdentityService/Connection/AcmeServer/AcmeService.ts @@ -48,6 +48,7 @@ import { OidcChallengeResponseSchema, GetCertificateResponseData, GetCertificateResponseSchema, + LocalCertificateRootResponseSchema, } from './schema'; import {AcmeChallenge, AcmeDirectory} from '../../E2EIService.types'; @@ -57,10 +58,16 @@ export class AcmeService { private readonly axiosInstance: AxiosInstance = axios.create(); private readonly url = { DIRECTORY: '/directory', + ROOTS: '/roots.pem', }; constructor(private discoveryUrl: string) {} + private get acmeBaseUrl() { + const {origin} = new URL(this.discoveryUrl); + return origin; + } + // ############ Internal Functions ############ private extractNonce(headers: any): ResponseHeaderNonce['replay-nonce'] { @@ -114,6 +121,12 @@ export class AcmeService { } } + public async getLocalCertificateRoot(): Promise { + const {data} = await this.axiosInstance.get(`${this.acmeBaseUrl}${this.url.ROOTS}`); + const localCertificateRoot = LocalCertificateRootResponseSchema.parse(data); + return localCertificateRoot; + } + public async getInitialNonce(url: AcmeDirectory['newNonce']): GetInitialNonceReturnValue { try { const {headers} = await this.axiosInstance.head(url); diff --git a/packages/core/src/messagingProtocols/mls/E2EIdentityService/Connection/AcmeServer/schema.ts b/packages/core/src/messagingProtocols/mls/E2EIdentityService/Connection/AcmeServer/schema.ts index 2182334ec2..7d47e40b1c 100644 --- a/packages/core/src/messagingProtocols/mls/E2EIdentityService/Connection/AcmeServer/schema.ts +++ b/packages/core/src/messagingProtocols/mls/E2EIdentityService/Connection/AcmeServer/schema.ts @@ -41,6 +41,9 @@ export const DirectoryResponseSchema = z.object({ }); export type DirectoryResponseData = z.infer; +export const LocalCertificateRootResponseSchema = nonOptionalString; +export type LocalCertificateRootResonseData = z.infer; + export const NewAccountResponseSchema = z.object({ status: nonOptionalString, orders: nonOptionalUrl, diff --git a/packages/core/src/messagingProtocols/mls/E2EIdentityService/E2EIServiceExternal.ts b/packages/core/src/messagingProtocols/mls/E2EIdentityService/E2EIServiceExternal.ts index c9ce4230cd..a32d7f72be 100644 --- a/packages/core/src/messagingProtocols/mls/E2EIdentityService/E2EIServiceExternal.ts +++ b/packages/core/src/messagingProtocols/mls/E2EIdentityService/E2EIServiceExternal.ts @@ -22,11 +22,13 @@ import {Decoder} from 'bazinga64'; import {Ciphersuite, CoreCrypto, E2eiConversationState, WireIdentity, DeviceStatus} from '@wireapp/core-crypto'; +import {AcmeService} from './Connection'; import {getE2EIClientId} from './Helper'; import {E2EIStorage} from './Storage/E2EIStorage'; import {ClientService} from '../../../client'; import {parseFullQualifiedClientId} from '../../../util/fullyQualifiedClientIdUtils'; +import {LocalStorageStore} from '../../../util/LocalStorageStore'; export type DeviceIdentity = Omit & {status?: DeviceStatus; deviceId: string}; @@ -125,4 +127,44 @@ export class E2EIServiceExternal { } return typeof client.mls_public_keys.ed25519 !== 'string' || client.mls_public_keys.ed25519.length === 0; } + + private async registerLocalCertificateRoot(connection: AcmeService): Promise { + const localCertificateRoot = await connection.getLocalCertificateRoot(); + await this.coreCryptoClient.e2eiRegisterAcmeCA(localCertificateRoot); + + return localCertificateRoot; + } + + /** + * This function is used to register different server certificates in CoreCrypto. + * + * 1. Root Certificate: This is the root certificate of the server. + * - It must only be registered once. + * - It must be the first certificate to be registered. Nothing else will work + * + * 2. Intermediate Certificate: This is the intermediate certificate of the server. It must be updated every 24 hours. + * - It must be registered after the root certificate. + * - It must be updated every 24 hours. + * + * Both must be registered before the first enrollment. + * + * @param discoveryUrl + */ + public async registerServerCertificates(discoveryUrl: string): Promise { + const ROOT_CA_KEY = 'e2ei_root-registered'; + const store = LocalStorageStore(ROOT_CA_KEY); + const acmeService = new AcmeService(discoveryUrl); + + // Register root certificate if not already registered + if (!store.has(ROOT_CA_KEY)) { + try { + await this.registerLocalCertificateRoot(acmeService); + store.add(ROOT_CA_KEY, 'true'); + } catch (error) { + console.error('Failed to register root certificate', error); + } + } + + // Register intermediate certificate and update it every 24 hours + } }