Skip to content

Commit 8c86695

Browse files
authored
Signature: Examine public key when no algorithm passed (#1903)
1 parent 738ea95 commit 8c86695

File tree

2 files changed

+92
-54
lines changed

2 files changed

+92
-54
lines changed

includes/signature/class-http-message-signature.php

Lines changed: 83 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,55 @@
2222
*/
2323
class Http_Message_Signature implements Signature_Standard {
2424

25+
/**
26+
* Signature algorithms.
27+
*
28+
* @var int[][]
29+
*/
30+
private $algorithms = array(
31+
// RSA PKCS#1 v1.5.
32+
'rsa-v1_5-sha256' => array(
33+
'type' => OPENSSL_KEYTYPE_RSA,
34+
'algo' => OPENSSL_ALGO_SHA256,
35+
),
36+
'rsa-v1_5-sha384' => array(
37+
'type' => OPENSSL_KEYTYPE_RSA,
38+
'algo' => OPENSSL_ALGO_SHA384,
39+
),
40+
'rsa-v1_5-sha512' => array(
41+
'type' => OPENSSL_KEYTYPE_RSA,
42+
'algo' => OPENSSL_ALGO_SHA512,
43+
),
44+
45+
// RSA PSS (note: not supported in openssl_verify() until PHP 8.1).
46+
'rsa-pss-sha256' => array(
47+
'type' => OPENSSL_KEYTYPE_RSA,
48+
'algo' => OPENSSL_ALGO_SHA256,
49+
),
50+
'rsa-pss-sha384' => array(
51+
'type' => OPENSSL_KEYTYPE_RSA,
52+
'algo' => OPENSSL_ALGO_SHA384,
53+
),
54+
'rsa-pss-sha512' => array(
55+
'type' => OPENSSL_KEYTYPE_RSA,
56+
'algo' => OPENSSL_ALGO_SHA512,
57+
),
58+
59+
// ECDSA.
60+
'ecdsa-p256-sha256' => array(
61+
'type' => OPENSSL_KEYTYPE_EC,
62+
'algo' => OPENSSL_ALGO_SHA256,
63+
),
64+
'ecdsa-p384-sha384' => array(
65+
'type' => OPENSSL_KEYTYPE_EC,
66+
'algo' => OPENSSL_ALGO_SHA384,
67+
),
68+
'ecdsa-p521-sha512' => array(
69+
'type' => OPENSSL_KEYTYPE_EC,
70+
'algo' => OPENSSL_ALGO_SHA512,
71+
),
72+
);
73+
2574
/**
2675
* Digest algorithms.
2776
*
@@ -248,69 +297,52 @@ private function verify_content_digest( $headers, $body ) {
248297
* @return int|\WP_Error OpenSSL algorithm constant or WP_Error.
249298
*/
250299
private function verify_algorithm( $alg_string, $public_key ) {
251-
$alg_string = \strtolower( $alg_string );
252-
if ( \strpos( $alg_string, 'rsa-pss-' ) === 0 && \version_compare( PHP_VERSION, '8.1.0', '<' ) ) {
253-
return new \WP_Error( 'unsupported_pss', 'RSA-PSS algorithms are not supported.' );
254-
}
255-
256300
$details = \openssl_pkey_get_details( $public_key );
257301
if ( ! isset( $details['type'] ) ) {
258302
return new \WP_Error( 'invalid_key_details', 'Unable to read public key details.' );
259303
}
260304

261-
$map = array(
262-
// RSA PKCS#1 v1.5.
263-
'rsa-v1_5-sha256' => array(
264-
'type' => OPENSSL_KEYTYPE_RSA,
265-
'algo' => OPENSSL_ALGO_SHA256,
266-
),
267-
'rsa-v1_5-sha384' => array(
268-
'type' => OPENSSL_KEYTYPE_RSA,
269-
'algo' => OPENSSL_ALGO_SHA384,
270-
),
271-
'rsa-v1_5-sha512' => array(
272-
'type' => OPENSSL_KEYTYPE_RSA,
273-
'algo' => OPENSSL_ALGO_SHA512,
274-
),
275-
276-
// RSA PSS (note: not supported in openssl_verify() until PHP 8.1).
277-
'rsa-pss-sha256' => array(
278-
'type' => OPENSSL_KEYTYPE_RSA,
279-
'algo' => OPENSSL_ALGO_SHA256,
280-
),
281-
'rsa-pss-sha384' => array(
282-
'type' => OPENSSL_KEYTYPE_RSA,
283-
'algo' => OPENSSL_ALGO_SHA384,
284-
),
285-
'rsa-pss-sha512' => array(
286-
'type' => OPENSSL_KEYTYPE_RSA,
287-
'algo' => OPENSSL_ALGO_SHA512,
288-
),
289-
290-
// ECDSA.
291-
'ecdsa-p256-sha256' => array(
292-
'type' => OPENSSL_KEYTYPE_EC,
293-
'algo' => OPENSSL_ALGO_SHA256,
294-
),
295-
'ecdsa-p384-sha384' => array(
296-
'type' => OPENSSL_KEYTYPE_EC,
297-
'algo' => OPENSSL_ALGO_SHA384,
298-
),
299-
'ecdsa-p521-sha512' => array(
300-
'type' => OPENSSL_KEYTYPE_EC,
301-
'algo' => OPENSSL_ALGO_SHA512,
302-
),
303-
);
305+
// If alg_string is empty, determine algorithm based on public key.
306+
if ( empty( $alg_string ) ) {
307+
switch ( $details['type'] ) {
308+
case \OPENSSL_KEYTYPE_RSA:
309+
$bits = $details['bits'] ?? 2048;
310+
311+
if ( $bits >= 4 * KB_IN_BYTES ) {
312+
return \OPENSSL_ALGO_SHA512;
313+
} elseif ( $bits >= 3 * KB_IN_BYTES ) {
314+
return \OPENSSL_ALGO_SHA384;
315+
} else {
316+
return \OPENSSL_ALGO_SHA256;
317+
}
318+
319+
case \OPENSSL_KEYTYPE_EC:
320+
switch ( $details['ec']['curve_name'] ?? '' ) {
321+
case 'prime256v1':
322+
case 'secp256r1':
323+
return \OPENSSL_ALGO_SHA256;
324+
case 'secp384r1':
325+
return \OPENSSL_ALGO_SHA384;
326+
case 'secp521r1':
327+
return \OPENSSL_ALGO_SHA512;
328+
}
329+
}
330+
}
331+
332+
$alg_string = \strtolower( $alg_string );
333+
if ( \strpos( $alg_string, 'rsa-pss-' ) === 0 && \version_compare( PHP_VERSION, '8.1.0', '<' ) ) {
334+
return new \WP_Error( 'unsupported_pss', 'RSA-PSS algorithms are not supported.' );
335+
}
304336

305-
if ( ! isset( $map[ $alg_string ] ) ) {
337+
if ( ! isset( $this->algorithms[ $alg_string ] ) ) {
306338
return new \WP_Error( 'unsupported_alg', 'Unsupported or unknown alg parameter: ' . $alg_string );
307339
}
308340

309-
if ( $map[ $alg_string ]['type'] !== $details['type'] ) {
341+
if ( $this->algorithms[ $alg_string ]['type'] !== $details['type'] ) {
310342
return new \WP_Error( 'alg_key_mismatch', 'Algorithm does not match public key type.' );
311343
}
312344

313-
return $map[ $alg_string ]['algo'];
345+
return $this->algorithms[ $alg_string ]['algo'];
314346
}
315347

316348
/**

tests/includes/class-test-signature.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,9 @@ public function test_verify_http_signature_rfc9421_algorithms() {
540540
$rsa_keys = self::$test_keys['rsa']['2048'];
541541
$this->verify_rfc9421_signature_with_keys( $rsa_keys, 'rsa-v1_5-sha256' );
542542

543+
$rsa_keys = self::$test_keys['rsa']['2048'];
544+
$this->verify_rfc9421_signature_with_keys( $rsa_keys, '' );
545+
543546
// Test with EC keys.
544547
$ec_keys = self::$test_keys['ec']['prime256v1'];
545548
$this->verify_rfc9421_signature_with_keys( $ec_keys, 'ecdsa-p256-sha256' );
@@ -579,12 +582,15 @@ function () use ( $keys ) {
579582
// Create the signature input components.
580583
$components = array( '@method', '@target-uri', '@authority', 'content-digest', 'date' );
581584
$params_string = \sprintf(
582-
'(%s);created=%d;keyid="https://example.org/author/admin#main-key";alg="%s"',
585+
'(%s);created=%d;keyid="https://example.org/author/admin#main-key"',
583586
'"' . \implode( '" "', $components ) . '"',
584-
\time(),
585-
$algorithm
587+
\time()
586588
);
587589

590+
if ( ! empty( $algorithm ) ) {
591+
$params_string .= ';alg="' . $algorithm . '"';
592+
}
593+
588594
// Create the signature input header value (includes the label).
589595
$signature_input = "sig1=$params_string";
590596

0 commit comments

Comments
 (0)