001package com.nimbusds.jose.jwk; 002 003 004import java.net.URI; 005import java.util.LinkedHashMap; 006import java.util.List; 007import java.text.ParseException; 008import java.util.Set; 009 010import javax.crypto.SecretKey; 011import javax.crypto.spec.SecretKeySpec; 012 013import net.jcip.annotations.Immutable; 014 015import net.minidev.json.JSONObject; 016 017import com.nimbusds.jose.Algorithm; 018import com.nimbusds.jose.JOSEException; 019import com.nimbusds.jose.util.Base64; 020import com.nimbusds.jose.util.Base64URL; 021import com.nimbusds.jose.util.JSONObjectUtils; 022 023 024/** 025 * {@link KeyType#OCT Octet sequence} JSON Web Key (JWK), used to represent 026 * symmetric keys. This class is immutable. 027 * 028 * <p>Octet sequence JWKs should specify the algorithm intended to be used with 029 * the key, unless the application uses other means or convention to determine 030 * the algorithm used. 031 * 032 * <p>Example JSON object representation of an octet sequence JWK: 033 * 034 * <pre> 035 * { 036 * "kty" : "oct", 037 * "alg" : "A128KW", 038 * "k" : "GawgguFyGrWKav7AX4VKUg" 039 * } 040 * </pre> 041 * 042 * @author Justin Richer 043 * @author Vladimir Dzhuvinov 044 * @version 2015-09-28 045 */ 046@Immutable 047public final class OctetSequenceKey extends JWK { 048 049 050 /** 051 * The key value. 052 */ 053 private final Base64URL k; 054 055 056 /** 057 * Builder for constructing octet sequence JWKs. 058 * 059 * <p>Example usage: 060 * 061 * <pre> 062 * OctetSequenceKey key = new OctetSequenceKey.Builder(k). 063 * algorithm(JWSAlgorithm.HS512). 064 * keyID("123"). 065 * build(); 066 * </pre> 067 */ 068 public static class Builder { 069 070 071 /** 072 * The key value. 073 */ 074 private final Base64URL k; 075 076 077 /** 078 * The public key use, optional. 079 */ 080 private KeyUse use; 081 082 083 /** 084 * The key operations, optional. 085 */ 086 private Set<KeyOperation> ops; 087 088 089 /** 090 * The intended JOSE algorithm for the key, optional. 091 */ 092 private Algorithm alg; 093 094 095 /** 096 * The key ID, optional. 097 */ 098 private String kid; 099 100 101 /** 102 * X.509 certificate URL, optional. 103 */ 104 private URI x5u; 105 106 107 /** 108 * X.509 certificate thumbprint, optional. 109 */ 110 private Base64URL x5t; 111 112 113 /** 114 * The X.509 certificate chain, optional. 115 */ 116 private List<Base64> x5c; 117 118 119 /** 120 * Creates a new octet sequence JWK builder. 121 * 122 * @param k The key value. It is represented as the Base64URL 123 * encoding of value's big endian representation. Must 124 * not be {@code null}. 125 */ 126 public Builder(final Base64URL k) { 127 128 if (k == null) { 129 throw new IllegalArgumentException("The key value must not be null"); 130 } 131 132 this.k = k; 133 } 134 135 136 /** 137 * Creates a new octet sequence JWK builder. 138 * 139 * @param key The key value. Must not be empty byte array or 140 * {@code null}. 141 */ 142 public Builder(final byte[] key) { 143 144 this(Base64URL.encode(key)); 145 146 if (key.length == 0) { 147 throw new IllegalArgumentException("The key must have a positive length"); 148 } 149 } 150 151 152 /** 153 * Creates a new octet sequence JWK builder. 154 * 155 * @param secretKey The secret key to represent. Must not be 156 * {@code null}. 157 */ 158 public Builder(final SecretKey secretKey) { 159 160 this(secretKey.getEncoded()); 161 } 162 163 164 /** 165 * Sets the use ({@code use}) of the JWK. 166 * 167 * @param use The key use, {@code null} if not specified or if 168 * the key is intended for signing as well as 169 * encryption. 170 * 171 * @return This builder. 172 */ 173 public Builder keyUse(final KeyUse use) { 174 175 this.use = use; 176 return this; 177 } 178 179 180 /** 181 * Sets the operations ({@code key_ops}) of the JWK (for a 182 * non-public key). 183 * 184 * @param ops The key operations, {@code null} if not 185 * specified. 186 * 187 * @return This builder. 188 */ 189 public Builder keyOperations(final Set<KeyOperation> ops) { 190 191 this.ops = ops; 192 return this; 193 } 194 195 196 /** 197 * Sets the intended JOSE algorithm ({@code alg}) for the JWK. 198 * 199 * @param alg The intended JOSE algorithm, {@code null} if not 200 * specified. 201 * 202 * @return This builder. 203 */ 204 public Builder algorithm(final Algorithm alg) { 205 206 this.alg = alg; 207 return this; 208 } 209 210 /** 211 * Sets the ID ({@code kid}) of the JWK. The key ID can be used 212 * to match a specific key. This can be used, for instance, to 213 * choose a key within a {@link JWKSet} during key rollover. 214 * The key ID may also correspond to a JWS/JWE {@code kid} 215 * header parameter value. 216 * 217 * @param kid The key ID, {@code null} if not specified. 218 * 219 * @return This builder. 220 */ 221 public Builder keyID(final String kid) { 222 223 this.kid = kid; 224 return this; 225 } 226 227 228 /** 229 * Sets the ID ({@code kid}) of the JWK to its SHA-256 JWK 230 * thumbprint (RFC 7638). The key ID can be used to match a 231 * specific key. This can be used, for instance, to choose a 232 * key within a {@link JWKSet} during key rollover. The key ID 233 * may also correspond to a JWS/JWE {@code kid} header 234 * parameter value. 235 * 236 * @return This builder. 237 * 238 * @throws JOSEException If the SHA-256 hash algorithm is not 239 * supported. 240 */ 241 public Builder keyIDFromThumbprint() 242 throws JOSEException { 243 244 return keyIDFromThumbprint("SHA-256"); 245 } 246 247 248 /** 249 * Sets the ID ({@code kid}) of the JWK to its JWK thumbprint 250 * (RFC 7638). The key ID can be used to match a specific key. 251 * This can be used, for instance, to choose a key within a 252 * {@link JWKSet} during key rollover. The key ID may also 253 * correspond to a JWS/JWE {@code kid} header parameter value. 254 * 255 * @param hashAlg The hash algorithm for the JWK thumbprint 256 * computation. Must not be {@code null}. 257 * 258 * @return This builder. 259 * 260 * @throws JOSEException If the hash algorithm is not 261 * supported. 262 */ 263 public Builder keyIDFromThumbprint(final String hashAlg) 264 throws JOSEException { 265 266 // Put mandatory params in sorted order 267 LinkedHashMap<String,String> requiredParams = new LinkedHashMap<>(); 268 requiredParams.put("k", k.toString()); 269 requiredParams.put("kty", KeyType.OCT.getValue()); 270 this.kid = ThumbprintUtils.compute(hashAlg, requiredParams).toString(); 271 return this; 272 } 273 274 275 /** 276 * Sets the X.509 certificate URL ({@code x5u}) of the JWK. 277 * 278 * @param x5u The X.509 certificate URL, {@code null} if not 279 * specified. 280 * 281 * @return This builder. 282 */ 283 public Builder x509CertURL(final URI x5u) { 284 285 this.x5u = x5u; 286 return this; 287 } 288 289 290 /** 291 * Sets the X.509 certificate thumbprint ({@code x5t}) of the 292 * JWK. 293 * 294 * @param x5t The X.509 certificate thumbprint, {@code null} if 295 * not specified. 296 * 297 * @return This builder. 298 */ 299 public Builder x509CertThumbprint(final Base64URL x5t) { 300 301 this.x5t = x5t; 302 return this; 303 } 304 305 /** 306 * Sets the X.509 certificate chain ({@code x5c}) of the JWK. 307 * 308 * @param x5c The X.509 certificate chain as a unmodifiable 309 * list, {@code null} if not specified. 310 * 311 * @return This builder. 312 */ 313 public Builder x509CertChain(final List<Base64> x5c) { 314 315 this.x5c = x5c; 316 return this; 317 } 318 319 /** 320 * Builds a new octet sequence JWK. 321 * 322 * @return The octet sequence JWK. 323 * 324 * @throws IllegalStateException If the JWK parameters were 325 * inconsistently specified. 326 */ 327 public OctetSequenceKey build() { 328 329 try { 330 return new OctetSequenceKey(k, use, ops, alg, kid, x5u, x5t, x5c); 331 332 } catch (IllegalArgumentException e) { 333 334 throw new IllegalStateException(e.getMessage(), e); 335 } 336 } 337 } 338 339 340 /** 341 * Creates a new octet sequence JSON Web Key (JWK) with the specified 342 * parameters. 343 * 344 * @param k The key value. It is represented as the Base64URL 345 * encoding of the value's big endian representation. Must 346 * not be {@code null}. 347 * @param use The key use, {@code null} if not specified or if the key 348 * is intended for signing as well as encryption. 349 * @param ops The key operations, {@code null} if not specified. 350 * @param alg The intended JOSE algorithm for the key, {@code null} if 351 * not specified. 352 * @param kid The key ID. {@code null} if not specified. 353 * @param x5u The X.509 certificate URL, {@code null} if not specified. 354 * @param x5t The X.509 certificate thumbprint, {@code null} if not 355 * specified. 356 * @param x5c The X.509 certificate chain, {@code null} if not 357 * specified. 358 */ 359 public OctetSequenceKey(final Base64URL k, 360 final KeyUse use, final Set<KeyOperation> ops, final Algorithm alg, final String kid, 361 final URI x5u, final Base64URL x5t, final List<Base64> x5c) { 362 363 super(KeyType.OCT, use, ops, alg, kid, x5u, x5t, x5c); 364 365 if (k == null) { 366 throw new IllegalArgumentException("The key value must not be null"); 367 } 368 369 this.k = k; 370 } 371 372 373 /** 374 * Returns the value of this octet sequence key. 375 * 376 * @return The key value. It is represented as the Base64URL encoding 377 * of the value's big endian representation. 378 */ 379 public Base64URL getKeyValue() { 380 381 return k; 382 } 383 384 385 /** 386 * Returns a copy of this octet sequence key value as a byte array. 387 * 388 * @return The key value as a byte array. 389 */ 390 public byte[] toByteArray() { 391 392 return getKeyValue().decode(); 393 } 394 395 396 /** 397 * Returns a secret key representation of this octet sequence key. 398 * 399 * @return The secret key representation, with an algorithm set to 400 * {@code NONE}. 401 */ 402 public SecretKey toSecretKey() { 403 404 return toSecretKey("NONE"); 405 } 406 407 408 /** 409 * Returns a secret key representation of this octet sequence key with 410 * the specified Java Cryptography Architecture (JCA) algorithm. 411 * 412 * @param jcaAlg The JCA algorithm. Must not be {@code null}. 413 * 414 * @return The secret key representation. 415 */ 416 public SecretKey toSecretKey(final String jcaAlg) { 417 418 return new SecretKeySpec(toByteArray(), jcaAlg); 419 } 420 421 422 @Override 423 public LinkedHashMap<String,?> getRequiredParams() { 424 425 // Put mandatory params in sorted order 426 LinkedHashMap<String,String> requiredParams = new LinkedHashMap<>(); 427 requiredParams.put("k", k.toString()); 428 requiredParams.put("kty", getKeyType().toString()); 429 return requiredParams; 430 } 431 432 433 /** 434 * Octet sequence (symmetric) keys are never considered public, this 435 * method always returns {@code true}. 436 * 437 * @return {@code true} 438 */ 439 @Override 440 public boolean isPrivate() { 441 442 return true; 443 } 444 445 446 /** 447 * Octet sequence (symmetric) keys are never considered public, this 448 * method always returns {@code null}. 449 * 450 * @return {@code null} 451 */ 452 @Override 453 public OctetSequenceKey toPublicJWK() { 454 455 return null; 456 } 457 458 459 @Override 460 public JSONObject toJSONObject() { 461 462 JSONObject o = super.toJSONObject(); 463 464 // Append key value 465 o.put("k", k.toString()); 466 467 return o; 468 } 469 470 471 /** 472 * Parses an octet sequence JWK from the specified JSON object string 473 * representation. 474 * 475 * @param s The JSON object string to parse. Must not be {@code null}. 476 * 477 * @return The octet sequence JWK. 478 * 479 * @throws ParseException If the string couldn't be parsed to an octet 480 * sequence JWK. 481 */ 482 public static OctetSequenceKey parse(final String s) 483 throws ParseException { 484 485 return parse(JSONObjectUtils.parseJSONObject(s)); 486 } 487 488 489 /** 490 * Parses an octet sequence JWK from the specified JSON object 491 * representation. 492 * 493 * @param jsonObject The JSON object to parse. Must not be 494 * @code null}. 495 * 496 * @return The octet sequence JWK. 497 * 498 * @throws ParseException If the JSON object couldn't be parsed to an 499 * octet sequence JWK. 500 */ 501 public static OctetSequenceKey parse(final JSONObject jsonObject) 502 throws ParseException { 503 504 // Parse the mandatory parameters first 505 Base64URL k = new Base64URL(JSONObjectUtils.getString(jsonObject, "k")); 506 507 // Check key type 508 KeyType kty = JWKMetadata.parseKeyType(jsonObject); 509 510 if (kty != KeyType.OCT) { 511 512 throw new ParseException("The key type \"kty\" must be oct", 0); 513 } 514 515 return new OctetSequenceKey(k, 516 JWKMetadata.parseKeyUse(jsonObject), 517 JWKMetadata.parseKeyOperations(jsonObject), 518 JWKMetadata.parseAlgorithm(jsonObject), 519 JWKMetadata.parseKeyID(jsonObject), 520 JWKMetadata.parseX509CertURL(jsonObject), 521 JWKMetadata.parseX509CertThumbprint(jsonObject), 522 JWKMetadata.parseX509CertChain(jsonObject)); 523 } 524}