Skip to content

Commit cc4316d

Browse files
committed
Added ES256 sig algo to ACMEv2
1 parent e8d6196 commit cc4316d

3 files changed

Lines changed: 57 additions & 12 deletions

File tree

src/cbang/acmev2/Account.cpp

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ void Account::update() {
127127
if (state != STATE_IDLE || !certsReadyForRenewal()) return;
128128

129129
if (directory.isNull()) state = STATE_GET_DIR;
130-
else if (kid.empty()) state = STATE_REGISTER;
130+
else if (kid.empty()) state = STATE_REGISTER;
131131
else {
132132
currentKeyCert = 0;
133133
state = STATE_NEW_ORDER;
@@ -171,7 +171,8 @@ string Account::getURL(const string &url) const {
171171

172172

173173
string Account::getThumbprint() const {
174-
return URLBase64().encode(Digest::hash(getJWK()->toString(), "sha256"));
174+
string jwk = getJWK()->toString(0, true); // No indent, compact
175+
return URLBase64().encode(Digest::hash(jwk, "sha256"));
175176
}
176177

177178

@@ -183,24 +184,30 @@ string Account::getKeyAuthorization() const {
183184
JSON::ValuePtr Account::getJWK() const {
184185
auto d = SmartPtr(new JSON::Dict);
185186

187+
// Insert order matters (lexicographical by key)
186188
if (key.isRSA()) {
187-
// Insert order matters
188189
d->insert("e", URLBase64().encode(key.getRSA_E().toBinString()));
189190
d->insert("kty", "RSA");
190191
d->insert("n", URLBase64().encode(key.getRSA_N().toBinString()));
191192

193+
} else if (key.isEC()) {
194+
d->insert("crv", "P-256");
195+
d->insert("kty", "EC");
196+
d->insert("x", URLBase64().encode(key.getEC_X().toBinString()));
197+
d->insert("y", URLBase64().encode(key.getEC_Y().toBinString()));
198+
192199
} else THROW("Unsupported key type");
193200

194201
return d;
195202
}
196203

197204

198205
string Account::getProtected(const URI &uri) const {
199-
// TODO Implement ES256 (RFC7518 Section 3.1) and EdDSA var. Ed25519 (RFC8037)
200-
// signature algorithms. See IETF ACME draft "Request Authentication".
201-
206+
// See IETF ACME draft "Request Authentication".
202207
auto d = SmartPtr(new JSON::Dict);
203-
d->insert("alg", "RS256");
208+
209+
if (key.isRSA()) d->insert("alg", "RS256");
210+
else if (key.isEC()) d->insert("alg", "ES256");
204211

205212
if (!kid.empty()) d->insert("kid", kid);
206213
else d->insert("jwk", getJWK());
@@ -215,14 +222,17 @@ string Account::getProtected(const URI &uri) const {
215222

216223
string Account::getSignedRequest(const URI &uri, const string &payload) const {
217224
string protected64 = URLBase64().encode(getProtected(uri));
218-
string payload64 = URLBase64().encode(payload);
219-
string signed64 =
220-
URLBase64().encode(key.signSHA256(protected64 + "." + payload64));
225+
string payload64 = URLBase64().encode(payload);
226+
string hash = Digest::hash(protected64 + "." + payload64, "sha256");
227+
228+
string sig;
229+
if (key.isRSA()) sig = key.sign(hash);
230+
else if (key.isEC()) sig = key.signECP1363(hash);
221231

222232
auto d = SmartPtr(new JSON::Dict);
223233
d->insert("protected", protected64);
224-
d->insert("payload", payload64);
225-
d->insert("signature", signed64);
234+
d->insert("payload", payload64);
235+
d->insert("signature", URLBase64().encode(sig));
226236
return d->toString();
227237
}
228238

src/cbang/openssl/KeyPair.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,18 @@ BigNum KeyPair::getRSA_N() const {
166166
}
167167

168168

169+
BigNum KeyPair::getEC_X() const {
170+
if (!isEC()) THROW("Not an EC key");
171+
return getParam(OSSL_PKEY_PARAM_EC_PUB_X);
172+
}
173+
174+
175+
BigNum KeyPair::getEC_Y() const {
176+
if (!isEC()) THROW("Not an EC key");
177+
return getParam(OSSL_PKEY_PARAM_EC_PUB_Y);
178+
}
179+
180+
169181
unsigned KeyPair::size() const {return EVP_PKEY_size(key);}
170182

171183

@@ -393,6 +405,26 @@ string KeyPair::signBase64SHA256(const string &data) const {
393405
}
394406

395407

408+
string KeyPair::signECP1363(const string &data) const {
409+
string sder = sign(data);
410+
411+
unsigned char raw_rs[64]; // This will be our P1363 output
412+
auto p = (const unsigned char *)sder.c_str();
413+
auto sig = d2i_ECDSA_SIG(0, &p, sder.length());
414+
415+
const BIGNUM *r, *s;
416+
ECDSA_SIG_get0(sig, &r, &s);
417+
418+
// BN_bn2binpad ensures exactly 32 bytes with leading zeros
419+
BN_bn2binpad(r, raw_rs, 32); // Fill first 32 bytes
420+
BN_bn2binpad(s, raw_rs + 32, 32); // Fill last 32 bytes
421+
422+
ECDSA_SIG_free(sig);
423+
424+
return string((char *)raw_rs, 64);
425+
}
426+
427+
396428
void KeyPair::verify(const string &signature, const string &data) const {
397429
KeyContext ctx(*this);
398430
ctx.verifyInit();

src/cbang/openssl/KeyPair.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ namespace cb {
7676
BigNum getParam(const char *id) const;
7777
BigNum getRSA_E() const;
7878
BigNum getRSA_N() const;
79+
BigNum getEC_X() const;
80+
BigNum getEC_Y() const;
7981

8082
unsigned size() const; ///< In bytes
8183

@@ -124,6 +126,7 @@ namespace cb {
124126
std::string sign(const std::string &data) const;
125127
std::string signSHA256(const std::string &data) const;
126128
std::string signBase64SHA256(const std::string &data) const;
129+
std::string signECP1363(const std::string &data) const;
127130
void verify(const std::string &signature, const std::string &data) const;
128131
void verifyBase64SHA256(const std::string &sig64,
129132
const std::string &data) const;

0 commit comments

Comments
 (0)