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