public async Task ActivateRecoveryMember()

in src/ccf/recovery/ccf-recovery-agent/Controllers/RecoveryMembersController.cs [97:228]


    public async Task<IActionResult> ActivateRecoveryMember(
        [FromRoute] string memberId,
        [FromBody] byte[] content)
    {
        // Verify caller is an active member of the consortium.
        (var err, var payload) = await this.VerifyMemberAuthentication(memberId, content);
        if (err != null)
        {
            return this.BadRequest(err);
        }

        var input = JsonSerializer.Deserialize<ActivateRecoveryMemberInput>(payload)!;
        err = ValidateInput(input);
        if (err != null)
        {
            return this.BadRequest(err);
        }

        this.logger.LogInformation(
            $"Requesting recovery service to activate member {input.MemberName}.");

        var wsConfig = await this.clientManager.GetWsConfig();

        // To complete member activation the steps are:
        // - Request recovery service to generate the sate digest message which would then be
        //   submitted to CCF to get the state digest.
        // - The response from CCF would be sent to recovery service to ack it.
        // - The ack will then be submitted to CCF.
        var svcClient = await this.GetRecoverySvcClient(input.AgentConfig.RecoveryService);

        var recoveryMember = (await svcClient.GetFromJsonAsync<JsonObject>(
            $"members/{input.MemberName}"))!;
        var signingCert = recoveryMember["signingCert"]!.ToString();
        using var cert = X509Certificate2.CreateFromPem(signingCert);
        var recoveryMemberId = cert.GetCertHashString(HashAlgorithmName.SHA256).ToLower();

        // Get attestation report content to send in the request.
        var dataContent = new JsonObject
        {
            ["memberName"] = input.MemberName
        };

        (var data, var signature) =
            this.PrepareSignedData(dataContent, wsConfig.Attestation.PrivateKey);

        JsonObject svcContent = Attestation.PrepareSignedDataRequestContent(
            data,
            signature,
            wsConfig.Attestation.PublicKey,
            wsConfig.Attestation.Report);
        byte[] stateDigestMessage;
        using (var generateDigestResponse = await svcClient.PostAsync(
            $"members/generateStateDigestMessage",
            JsonContent.Create(svcContent)))
        {
            await generateDigestResponse.ValidateStatusCodeAsync(this.logger);

            var body = (await generateDigestResponse.Content.ReadFromJsonAsync<JsonObject>())!;
            var wrappedValue = Convert.FromBase64String(body["message"]!.ToString());
            stateDigestMessage = Attestation.UnwrapRsaOaepAesKwpValue(
                wrappedValue,
                wsConfig.Attestation.PrivateKey);
        }

        JsonObject stateDigest;
        var ccfClient = await this.clientManager.GetCcfClient();
        using (HttpRequestMessage request = Cose.CreateHttpRequestMessage(
            $"gov/members/state-digests/{recoveryMemberId}:update" +
            $"?api-version={this.clientManager.GetGovApiVersion()}",
            stateDigestMessage))
        {
            using HttpResponseMessage stateDigestResponse = await ccfClient.SendAsync(request);
            await stateDigestResponse.ValidateStatusCodeAsync(this.logger);
            stateDigest = (await stateDigestResponse.Content.ReadFromJsonAsync<JsonObject>())!;
        }

        // Get attestation report and the state digest content to send in the request.
        dataContent = new JsonObject
        {
            ["memberName"] = input.MemberName,
            ["stateDigest"] = stateDigest
        };
        (data, signature) =
            this.PrepareSignedData(dataContent, wsConfig.Attestation.PrivateKey);
        svcContent = Attestation.PrepareSignedDataRequestContent(
            data,
            signature,
            wsConfig.Attestation.PublicKey,
            wsConfig.Attestation.Report);

        byte[] stateDigestAckMessage;
        using (var generateAckResponse = await svcClient.PostAsync(
            $"members/generateStateDigestAckMessage",
            JsonContent.Create(svcContent)))
        {
            await generateAckResponse.ValidateStatusCodeAsync(this.logger);

            var body = (await generateAckResponse.Content.ReadFromJsonAsync<JsonObject>())!;
            var wrappedValue = Convert.FromBase64String(body["message"]!.ToString());
            stateDigestAckMessage = Attestation.UnwrapRsaOaepAesKwpValue(
                wrappedValue,
                wsConfig.Attestation.PrivateKey);
        }

        string ackResponse;
        using (HttpRequestMessage request = Cose.CreateHttpRequestMessage(
            $"gov/members/state-digests/{recoveryMemberId}:ack" +
            $"?api-version={this.clientManager.GetGovApiVersion()}",
            stateDigestAckMessage))
        {
            using HttpResponseMessage stateDigestResponse = await ccfClient.SendAsync(request);
            await stateDigestResponse.ValidateStatusCodeAsync(this.logger);
            ackResponse = (await stateDigestResponse.Content.ReadAsStringAsync())!;
        }

        return this.Ok(ackResponse);

        static ODataError? ValidateInput(ActivateRecoveryMemberInput input)
        {
            if (string.IsNullOrEmpty(input.MemberName))
            {
                return new ODataError("InputMissing", "name input is required.");
            }

            if (input.AgentConfig == null)
            {
                return new ODataError("InputMissing", "agentConfig input is required.");
            }

            return null;
        }
    }