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
+ }
0 commit comments