Skip to content

Commit 107f3c1

Browse files
committed
WIP
1 parent 5f68890 commit 107f3c1

File tree

2 files changed

+166
-0
lines changed

2 files changed

+166
-0
lines changed

src/ECPublicKey.php

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
<?php
2+
3+
namespace Firebase\JWT;
4+
5+
class ECPublicKey
6+
{
7+
const ASN1_INTEGER = 0x02;
8+
const ASN1_BIT_STRING = 0x03;
9+
const ASN1_OBJECT_IDENTIFIER = 0x06;
10+
const ASN1_SEQUENCE = 0x10;
11+
const OID = '1.2.840.10045.2.1';
12+
13+
private $data;
14+
15+
private static $curves = [
16+
'P-256' => '1.2.840.10045.3.1.7', // Len: 64
17+
// 'P-384' => '1.3.132.0.34', // Len: 96 (not yet supported)
18+
// 'P-521' => '1.3.132.0.35', // Len: 132 (not supported)
19+
];
20+
21+
public function __construct(array $data)
22+
{
23+
if (isset($data['d'])) {
24+
// The key is actually a private key
25+
throw new \Exception('Key data must be for a public key');
26+
}
27+
28+
if (empty($data['crv'])) {
29+
throw new \Exception('crv not set');
30+
}
31+
32+
if (!isset(self::$curves[$data['crv']])) {
33+
throw new \Exception('Unrecognised or unsupported EC curve');
34+
}
35+
36+
$this->data = $data;
37+
}
38+
39+
public function toPEM()
40+
{
41+
$oid = self::$curves[$this->data['crv']];
42+
$pem =
43+
self::encodeDER(
44+
self::ASN1_SEQUENCE,
45+
self::encodeDER(
46+
self::ASN1_SEQUENCE,
47+
self::encodeDER(
48+
self::ASN1_OBJECT_IDENTIFIER,
49+
self::encodeOID(self::OID)
50+
)
51+
. self::encodeDER(
52+
self::ASN1_OBJECT_IDENTIFIER,
53+
self::encodeOID($oid)
54+
)
55+
) .
56+
self::encodeDER(
57+
self::ASN1_BIT_STRING,
58+
chr(0x00) . chr(0x04)
59+
. JWT::urlsafeB64Decode($this->data['x'])
60+
. JWT::urlsafeB64Decode($this->data['y'])
61+
)
62+
);
63+
64+
return sprintf(
65+
"-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----\n",
66+
wordwrap(base64_encode($pem), 64, "\n", true)
67+
);
68+
}
69+
70+
/**
71+
* Convert an ECDSA signature to an ASN.1 DER sequence
72+
*
73+
* @param string $sig The ECDSA signature to convert
74+
* @return string The encoded DER object
75+
*/
76+
public static function encodeSignature($sig)
77+
{
78+
// Separate the signature into r-value and s-value
79+
list($r, $s) = str_split($sig, (int) (strlen($sig) / 2));
80+
81+
// Trim leading zeros
82+
$r = ltrim($r, "\x00");
83+
$s = ltrim($s, "\x00");
84+
85+
// Convert r-value and s-value from unsigned big-endian integers to
86+
// signed two's complement
87+
if (ord($r[0]) > 0x7f) {
88+
$r = "\x00" . $r;
89+
}
90+
if (ord($s[0]) > 0x7f) {
91+
$s = "\x00" . $s;
92+
}
93+
94+
return self::encodeDER(
95+
self::ASN1_SEQUENCE,
96+
self::encodeDER(self::ASN1_INTEGER, $r) .
97+
self::encodeDER(self::ASN1_INTEGER, $s)
98+
);
99+
}
100+
101+
/**
102+
* Encodes a value into a DER object.
103+
*
104+
* @param int $type DER tag
105+
* @param string $value the value to encode
106+
* @return string the encoded object
107+
*/
108+
private static function encodeDER($type, $value)
109+
{
110+
$tag_header = 0;
111+
if ($type === self::ASN1_SEQUENCE) {
112+
$tag_header |= 0x20;
113+
}
114+
115+
// Type
116+
$der = chr($tag_header | $type);
117+
118+
// Length
119+
$der .= chr(strlen($value));
120+
121+
return $der . $value;
122+
}
123+
124+
/**
125+
* Encodes a string into a DER-encoded OID.
126+
*
127+
* @param string $oid the OID string
128+
* @return string the binary DER-encoded OID
129+
*/
130+
private static function encodeOID($oid)
131+
{
132+
$octets = explode('.', $oid);
133+
134+
// Get the first octet
135+
$oid = chr(array_shift($octets) * 40 + array_shift($octets));
136+
137+
// Iterate over subsequent octets
138+
foreach ($octets as $octet) {
139+
if ($octet == 0) {
140+
$oid .= chr(0x00);
141+
continue;
142+
}
143+
$bin = '';
144+
145+
while ($octet) {
146+
$bin .= chr(0x80 | ($octet & 0x7f));
147+
$octet >>= 7;
148+
}
149+
$bin[0] = $bin[0] & chr(0x7f);
150+
151+
// Convert to big endian if necessary
152+
if (pack('V', 65534) == pack('L', 65534)) {
153+
$oid .= strrev($bin);
154+
} else {
155+
$oid .= $bin;
156+
}
157+
}
158+
159+
return $oid;
160+
}
161+
}

src/JWT.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ public static function decode($jwt, $key, array $allowed_algs = array())
9797
if (!in_array($header->alg, $allowed_algs)) {
9898
throw new UnexpectedValueException('Algorithm not allowed');
9999
}
100+
if ($header->alg === 'ES256') {
101+
// OpenSSL expects an ASN.1 DER sequence for ES256 signatures
102+
$sig = ECPublicKey::encodeSignature($sig);
103+
}
104+
100105
if (is_array($key) || $key instanceof \ArrayAccess) {
101106
if (isset($header->kid)) {
102107
if (!isset($key[$header->kid])) {

0 commit comments

Comments
 (0)