001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyrighted [year] [name of copyright owner]".
013 *
014 * Copyright 2013-2017 ForgeRock AS.
015 */
016
017package org.forgerock.json.jose.jwk;
018
019import java.math.BigInteger;
020import java.security.GeneralSecurityException;
021import java.security.KeyFactory;
022import java.security.KeyPair;
023import java.security.interfaces.ECPrivateKey;
024import java.security.interfaces.ECPublicKey;
025import java.security.spec.ECPoint;
026import java.security.spec.ECPrivateKeySpec;
027import java.security.spec.ECPublicKeySpec;
028import java.util.List;
029
030import org.forgerock.json.JsonException;
031import org.forgerock.json.JsonValue;
032import org.forgerock.json.jose.jws.SupportedEllipticCurve;
033import org.forgerock.json.jose.utils.BigIntegerUtils;
034import org.forgerock.util.encode.Base64url;
035
036/**
037 * This class implements an Elliptical Curve Json Web Key storage and manipulation class.
038 */
039public class EcJWK extends JWK {
040    /**
041     * The X value for the EC point.
042     */
043    private final static String X = "x";
044
045    /**
046     * The Y value for the EC Point.
047     */
048    private final static String Y = "y";
049
050    /**
051     * The private key value.
052     */
053    private final static String D = "d";
054
055    /**
056     * The Curve of the ECC.
057     */
058    private final static String CURVE = "crv";
059
060    /**
061     * Creates a public EcJWK.
062     * @param publicKey The public key for the JWK
063     * @param use The value of the use JWK parameter
064     * @param kid The key id of the JWK
065     */
066    public EcJWK(ECPublicKey publicKey, KeyUse use, String kid) {
067        this(publicKey, null, use, kid);
068    }
069
070    /**
071     * Creates a public and private EcJWK.
072     * @param publicKey The public key for the JWK
073     * @param privateKey The private key for the JWK
074     * @param use The value of the use JWK parameter
075     * @param kid The key id of the JWK
076     */
077    public EcJWK(ECPublicKey publicKey, ECPrivateKey privateKey, KeyUse use, String kid) {
078        super(KeyType.EC, use, SupportedEllipticCurve.forKey(publicKey).getJwsAlgorithm().name(), kid);
079
080        final int fieldSize = publicKey.getParams().getCurve().getField().getFieldSize();
081        put(X, encodeCoordinate(fieldSize,
082                publicKey.getW().getAffineX()));
083        put(Y, encodeCoordinate(fieldSize,
084                publicKey.getW().getAffineY()));
085        put(CURVE, SupportedEllipticCurve.forKey(publicKey).getStandardName());
086        if (privateKey != null) {
087            put(D, encodeCoordinate(fieldSize, privateKey.getS()));
088        }
089    }
090
091    /**
092     * Creates a public EcJWK.
093     * @param use The value of the use JWK parameter
094     * @param alg The value of the alg JWK parameter
095     * @param kid The key id of the JWK
096     * @param x The unsigned big-endian base64 url encoding of the elliptical curve point x coordinate
097     * @param y The unsigned big-endian base64 url encoding of the elliptical curve point y coordinate
098     * @param curve The known curve to use. For example "NIST P-256".
099     * @param x5u the x509 url for the key
100     * @param x5t the x509 thumbnail for the key
101     * @param x5c the x509 chain as a list of Base64 encoded strings
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     * Creates a public and private EcJWK.
110     * @param use The value of the use JWK parameter
111     * @param alg The value of the alg JWK parameter
112     * @param kid The key id of the JWK
113     * @param x The unsigned big-endian base64 url encoding of the elliptical curve point x coordinate
114     * @param y The unsigned big-endian base64 url encoding of the elliptical curve point y coordinate
115     * @param d The unsigned big-endian base64 url encoding of the d value for the elliptical curve private key
116     * @param curve The known curve to use. For example "NIST P-256".
117     * @param x5u the x509 url for the key
118     * @param x5t the x509 thumbnail for the key
119     * @param x5c the x509 chain as a list of Base64 encoded strings
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     * Gets the unsigned big-endian  base64 url encoding of the elliptical curve point x coordinate.
144     * @return unsigned big-endian  base64 url encoding of the elliptical curve point x coordinate
145     */
146    public String getX() {
147        return get(X).asString();
148    }
149
150    /**
151     * Gets the unsigned big-endian  base64 url encoding of the elliptical curve point y coordinate.
152     * @return the unsigned big-endian  base64 url encoding of the elliptical curve point y coordinate
153     */
154    public String getY() {
155        return get(Y).asString();
156    }
157
158    /**
159     * Gets the unsigned big-endian  base64 url encoding of the d value for the elliptical curve private key.
160     * @return the unsigned big-endian  base64 url encoding of the d value for the elliptical curve private key
161     */
162    public String getD() {
163        return get(D).asString();
164    }
165
166    /**
167     * Gets the known curve to use. For example "NIST P-256".
168     * @return the known curve of the JWK
169     */
170    public String getCurve() {
171        return get(CURVE).asString();
172    }
173
174    /**
175     * Parses a JWK from a string json object.
176     * @param json string json object
177     * @return a EcJWK object
178     */
179    public static EcJWK parse(String json) {
180        JsonValue jwk = new JsonValue(toJsonValue(json));
181        return parse(jwk);
182    }
183
184    /**
185     * Parses a JWK from a JsonValue json object.
186     * @param json JsonValue json object
187     * @return a EcJWK object
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     * Prints the JWK as a String json object.
224     * @return a json string object
225     */
226    public String toJsonString() {
227        return super.toString();
228    }
229
230    /**
231     * Converts the JWK to a ECPublicKey.
232     * @return an ECPublicKey
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     * Converts the JWK to a ECPrivateKey.
250     * @return an ECPrivateKey
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     * Converts the JWK to a KeyPair.
266     * @return an KeyPair
267     */
268    public KeyPair toKeyPair() {
269        return new KeyPair(toECPublicKey(), toECPrivateKey());
270    }
271
272    /**
273     * Decode the unsigned big-endian  base64 url encoding of an elliptical curve point.
274     * @param encodedCoordinate the unsigned big-endian  base64 url encoding of a the elliptical curve point
275     * @return the elliptical curve point
276     */
277    public static BigInteger decodeCoordinate(String encodedCoordinate) {
278        return BigIntegerUtils.base64UrlDecode(encodedCoordinate);
279    }
280
281    /**
282     * Base64url encode the unsigned big-endian representation of an elliptical curve point.
283     * @param fieldSize the EC field size in bits.
284     * @param coordinate the elliptical curve point
285     * @return the unsigned big-endian  base64 url encoding of the elliptical curve point
286     */
287    public static String encodeCoordinate(final int fieldSize, final BigInteger coordinate) {
288        final byte[] bigEndian = BigIntegerUtils.toBytesUnsigned(coordinate);
289
290        /*
291         * fieldSize defines the size of the bytes array output.
292         * Since we need to keep big endian, we need to pad with 0 bits at the beginning if necessary
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            // Same size, we can return directly
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}