1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.forgerock.json.jose.jwk;
18
19 import java.math.BigInteger;
20 import java.security.GeneralSecurityException;
21 import java.security.KeyFactory;
22 import java.security.KeyPair;
23 import java.security.interfaces.ECPrivateKey;
24 import java.security.interfaces.ECPublicKey;
25 import java.security.spec.ECPoint;
26 import java.security.spec.ECPrivateKeySpec;
27 import java.security.spec.ECPublicKeySpec;
28 import java.util.List;
29
30 import org.forgerock.json.JsonException;
31 import org.forgerock.json.JsonValue;
32 import org.forgerock.json.jose.jws.SupportedEllipticCurve;
33 import org.forgerock.json.jose.utils.BigIntegerUtils;
34 import org.forgerock.util.encode.Base64url;
35
36
37
38
39 public class EcJWK extends JWK {
40
41
42
43 private final static String X = "x";
44
45
46
47
48 private final static String Y = "y";
49
50
51
52
53 private final static String D = "d";
54
55
56
57
58 private final static String CURVE = "crv";
59
60
61
62
63
64
65
66 public EcJWK(ECPublicKey publicKey, KeyUse use, String kid) {
67 this(publicKey, null, use, kid);
68 }
69
70
71
72
73
74
75
76
77 public EcJWK(ECPublicKey publicKey, ECPrivateKey privateKey, KeyUse use, String kid) {
78 super(KeyType.EC, use, SupportedEllipticCurve.forKey(publicKey).getJwsAlgorithm().name(), kid);
79
80 final int fieldSize = publicKey.getParams().getCurve().getField().getFieldSize();
81 put(X, encodeCoordinate(fieldSize,
82 publicKey.getW().getAffineX()));
83 put(Y, encodeCoordinate(fieldSize,
84 publicKey.getW().getAffineY()));
85 put(CURVE, SupportedEllipticCurve.forKey(publicKey).getStandardName());
86 if (privateKey != null) {
87 put(D, encodeCoordinate(fieldSize, privateKey.getS()));
88 }
89 }
90
91
92
93
94
95
96
97
98
99
100
101
102
103 public EcJWK(KeyUse use, String alg, String kid, String x, String y, String curve, String x5u, String x5t,
104 List<String> x5c) {
105 this(use, alg, kid, x, y, null, curve, x5u, x5t, x5c);
106 }
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121 public EcJWK(KeyUse use, String alg, String kid, String x, String y, String d, String curve,
122 String x5u, String x5t, List<String> x5c) {
123 super(KeyType.EC, use, alg, kid, x5u, x5t, x5c);
124 if (x == null || x.isEmpty()) {
125 throw new JsonException("x is required for an EcJWK");
126 }
127 put(X, x);
128
129 if (y == null || y.isEmpty()) {
130 throw new JsonException("y is required for an EcJWK");
131 }
132 put(Y, y);
133
134 if (curve == null || curve.isEmpty()) {
135 throw new JsonException("curve is required for an EcJWK");
136 }
137 put(CURVE, curve);
138
139 put(D, d);
140 }
141
142
143
144
145
146 public String getX() {
147 return get(X).asString();
148 }
149
150
151
152
153
154 public String getY() {
155 return get(Y).asString();
156 }
157
158
159
160
161
162 public String getD() {
163 return get(D).asString();
164 }
165
166
167
168
169
170 public String getCurve() {
171 return get(CURVE).asString();
172 }
173
174
175
176
177
178
179 public static EcJWK parse(String json) {
180 JsonValue jwk = new JsonValue(toJsonValue(json));
181 return parse(jwk);
182 }
183
184
185
186
187
188
189 public static EcJWK parse(JsonValue json) {
190 if (json == null) {
191 throw new JsonException("Cant parse OctJWK. No json data.");
192 }
193
194 KeyType kty = null;
195 KeyUse use = null;
196 String x = null, y = null, d = null, curve = null, alg = null, kid = null;
197 String x5u = null, x5t = null;
198 List<String> x5c = null;
199
200 kty = KeyType.getKeyType(json.get(KTY).asString());
201
202 if (!kty.equals(KeyType.EC)) {
203 throw new JsonException("Invalid key type. Not an EC JWK");
204 }
205
206 x = json.get(X).asString();
207 y = json.get(Y).asString();
208 d = json.get(D).asString();
209 curve = json.get(CURVE).asString();
210
211 use = KeyUse.getKeyUse(json.get(USE).asString());
212 alg = json.get(ALG).asString();
213 kid = json.get(KID).asString();
214
215 x5u = json.get(X5U).asString();
216 x5t = json.get(X5T).asString();
217 x5c = json.get(X5C).asList(String.class);
218
219 return new EcJWK(use, alg, kid, x, y, d, curve, x5u, x5t, x5c);
220 }
221
222
223
224
225
226 public String toJsonString() {
227 return super.toString();
228 }
229
230
231
232
233
234 public ECPublicKey toECPublicKey() {
235 try {
236 final SupportedEllipticCurve curve = SupportedEllipticCurve.forName(getCurve());
237
238 KeyFactory keyFactory = KeyFactory.getInstance("EC");
239 ECPoint point = new ECPoint(
240 BigIntegerUtils.base64UrlDecode(getX()),
241 BigIntegerUtils.base64UrlDecode(getY()));
242 return (ECPublicKey) keyFactory.generatePublic(new ECPublicKeySpec(point, curve.getParameters()));
243 } catch (GeneralSecurityException e) {
244 throw new JsonException("Unable to create EC Public Key", e);
245 }
246 }
247
248
249
250
251
252 public ECPrivateKey toECPrivateKey() {
253 try {
254 final SupportedEllipticCurve curve = SupportedEllipticCurve.forName(getCurve());
255
256 KeyFactory keyFactory = KeyFactory.getInstance("EC");
257 final BigInteger s = BigIntegerUtils.base64UrlDecode(getD());
258 return (ECPrivateKey) keyFactory.generatePrivate(new ECPrivateKeySpec(s, curve.getParameters()));
259 } catch (GeneralSecurityException e) {
260 throw new JsonException("Unable to create EC Private Key", e);
261 }
262 }
263
264
265
266
267
268 public KeyPair toKeyPair() {
269 return new KeyPair(toECPublicKey(), toECPrivateKey());
270 }
271
272
273
274
275
276
277 public static BigInteger decodeCoordinate(String encodedCoordinate) {
278 return BigIntegerUtils.base64UrlDecode(encodedCoordinate);
279 }
280
281
282
283
284
285
286
287 public static String encodeCoordinate(final int fieldSize, final BigInteger coordinate) {
288 final byte[] bigEndian = BigIntegerUtils.toBytesUnsigned(coordinate);
289
290
291
292
293
294 int bytesToOutput = (fieldSize + 7) / 8;
295
296 if (bigEndian.length > bytesToOutput) {
297 throw new IllegalArgumentException(
298 "The EC field size can't be smaller than the actual elliptic curve points bits size.");
299 }
300 if (bigEndian.length == bytesToOutput) {
301
302 return Base64url.encode(bigEndian);
303 }
304
305 final byte[] bigEndianWithRightFieldSize = new byte[bytesToOutput];
306 System.arraycopy(bigEndian, 0, bigEndianWithRightFieldSize, bytesToOutput - bigEndian.length, bigEndian.length);
307 return Base64url.encode(bigEndianWithRightFieldSize);
308 }
309 }