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