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;
}
}