Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 52 additions & 34 deletions src/Acmebot.App/Acme/AcmeClientFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,53 +63,71 @@ private async Task<AcmeClientContext> CreateClientCoreAsync()
}

var signer = accountKey.GenerateSigner();
var client = new AcmeClient(
_options.Endpoint,
new AcmeClientOptions
{
UserAgent = $"Acmebot/{Constants.ApplicationVersion}"
});
var directory = await client.GetDirectoryAsync();
AcmeAccountHandle accountHandle;
AcmeClient? client = null;

if (account is null)
// The client owns an HttpClient, and both it and the signer are only handed to the caller once
// the context has been fully built. Anything that fails in between (a directory fetch against an
// unreachable ACME endpoint, missing EAB credentials, a state store write) would otherwise leak
// them on every retry.
try
{
var externalAccountBinding = CreateExternalAccountBinding();
client = new AcmeClient(
_options.Endpoint,
new AcmeClientOptions
{
UserAgent = $"Acmebot/{Constants.ApplicationVersion}"
});

if (externalAccountBinding is null && (directory.Metadata?.ExternalAccountRequired ?? false))
var directory = await client.GetDirectoryAsync();
AcmeAccountHandle accountHandle;

if (account is null)
{
throw new PreconditionException("This ACME endpoint requires External Account Binding (EAB). Configure EAB credentials and try again.");
}
var externalAccountBinding = CreateExternalAccountBinding();

accountHandle = await client.CreateAccountAsync(
signer,
new AcmeNewAccountRequest
if (externalAccountBinding is null && (directory.Metadata?.ExternalAccountRequired ?? false))
{
throw new PreconditionException("This ACME endpoint requires External Account Binding (EAB). Configure EAB credentials and try again.");
}

accountHandle = await client.CreateAccountAsync(
signer,
new AcmeNewAccountRequest
{
Contact = contacts,
TermsOfServiceAgreed = true
},
externalAccountBinding);
account = AccountDetails.FromAccountHandle(accountHandle, directory.Metadata?.TermsOfService);

if (isNewAccountKey)
{
Contact = contacts,
TermsOfServiceAgreed = true
},
externalAccountBinding);
account = AccountDetails.FromAccountHandle(accountHandle, directory.Metadata?.TermsOfService);
await stateStore.SaveAsync(accountKey, "account_key.json");
}

if (isNewAccountKey)
await stateStore.SaveAsync(account, "account.json");
}
else
{
await stateStore.SaveAsync(accountKey, "account_key.json");
accountHandle = account.ToAccountHandle(signer);
}

await stateStore.SaveAsync(account, "account.json");
return new AcmeClientContext
{
Client = client,
Directory = directory,
Signer = signer,
Account = accountHandle
};
}
else
catch
{
accountHandle = account.ToAccountHandle(signer);
}
// Same order as AcmeClientContext.Dispose(), which owns these once the context exists.
signer.Dispose();
client?.Dispose();

return new AcmeClientContext
{
Client = client,
Directory = directory,
Signer = signer,
Account = accountHandle
};
throw;
}
}

private AcmeExternalAccountBindingOptions? CreateExternalAccountBinding()
Expand Down