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}