Skip to content
Merged
Show file tree
Hide file tree
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
6 changes: 5 additions & 1 deletion src/Certes/Acme/ChallengeContext.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Threading.Tasks;
using Certes.Acme.Resource;
using Certes.Jws;

namespace Certes.Acme
{
Expand Down Expand Up @@ -60,7 +61,10 @@ public ChallengeContext(
/// </returns>
public async Task<Challenge> Validate()
{
var resp = await Context.HttpClient.Post<Challenge>(Context, Location, new {}, true);
// RFC 8555 §7.5.1: the challenge-trigger POST carries an empty JSON object.
// Must be a concrete, source-gen-registered type (EmptyObject) — an anonymous
// `new {}` has no JsonTypeInfo under Native AOT and throws in JwsSigner.Sign.
var resp = await Context.HttpClient.Post<Challenge>(Context, Location, new EmptyObject(), true);
return resp.Resource;
}
}
Expand Down
1 change: 1 addition & 0 deletions src/Certes/Json/CertesJsonContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ namespace Certes.Json
[JsonSerializable(typeof(EcJsonWebKey))]
[JsonSerializable(typeof(RsaJsonWebKey))]
[JsonSerializable(typeof(KeyChangePayload))]
[JsonSerializable(typeof(EmptyObject))]
[JsonSerializable(typeof(ExternalAccountBinding))]
[JsonSerializable(typeof(ExternalAccountBindingHeader))]
// ACME resources
Expand Down
10 changes: 10 additions & 0 deletions src/Certes/Jws/JwsHeader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ internal class KeyChangePayload
public object OldKey { get; set; }
}

/// <summary>
/// An empty JWS payload that serializes to <c>{}</c>. Used for requests whose body
/// is an empty JSON object (e.g. the RFC 8555 §7.5.1 challenge-validation trigger).
/// A concrete, source-gen-registered type is required so serialization is AOT-safe;
/// an anonymous <c>new {}</c> has no JsonTypeInfo and throws under Native AOT.
/// </summary>
internal class EmptyObject
{
}

/// <summary>
/// External account binding JWS, embedded into the new-account request.
/// </summary>
Expand Down
25 changes: 22 additions & 3 deletions src/Certes/Jws/JwsSigner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,28 @@ public JwsPayload Sign(
Url = url,
};

var entityJson = payload == null
? ""
: JsonSerializer.Serialize(payload, CertesJsonContext.Default.GetTypeInfo(payload.GetType()));
string entityJson;
if (payload == null)
{
// POST-as-GET: RFC 8555 §6.3 requires an empty (not "{}") payload.
entityJson = "";
}
else
{
var payloadType = payload.GetType();
var typeInfo = CertesJsonContext.Default.GetTypeInfo(payloadType);
if (typeInfo == null)
{
// Anonymous/unregistered types have no source-generated metadata and
// would otherwise surface as an opaque ArgumentNullException from
// System.Text.Json under Native AOT. Point at the real cause instead.
throw new InvalidOperationException(
$"No JSON metadata for payload type '{payloadType}'. Add it to " +
$"{nameof(CertesJsonContext)} (anonymous types are not supported under AOT).");
}

entityJson = JsonSerializer.Serialize(payload, typeInfo);
}
var protectedHeaderJson = JsonSerializer.Serialize(protectedHeader, CertesJsonContext.Default.JwsHeader);

var payloadEncoded = JwsConvert.ToBase64String(Encoding.UTF8.GetBytes(entityJson));
Expand Down
Loading