001/* 002 * nimbus-jose-jwt 003 * 004 * Copyright 2012-2016, Connect2id Ltd. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.jose; 019 020 021import java.text.ParseException; 022 023import net.jcip.annotations.ThreadSafe; 024 025import com.nimbusds.jose.util.Base64URL; 026import com.nimbusds.jose.util.StandardCharset; 027 028 029/** 030 * JSON Web Signature (JWS) secured object. This class is thread-safe. 031 * 032 * @author Vladimir Dzhuvinov 033 * @version 2020-04-16 034 */ 035@ThreadSafe 036public class JWSObject extends JOSEObject { 037 038 039 private static final long serialVersionUID = 1L; 040 041 042 /** 043 * Enumeration of the states of a JSON Web Signature (JWS) object. 044 */ 045 public enum State { 046 047 048 /** 049 * The JWS object is created but not signed yet. 050 */ 051 UNSIGNED, 052 053 054 /** 055 * The JWS object is signed but its signature is not verified. 056 */ 057 SIGNED, 058 059 060 /** 061 * The JWS object is signed and its signature was successfully verified. 062 */ 063 VERIFIED 064 } 065 066 067 /** 068 * The header. 069 */ 070 private final JWSHeader header; 071 072 073 /** 074 * The signing input for this JWS object. 075 */ 076 private final String signingInputString; 077 078 079 /** 080 * The signature, {@code null} if not signed. 081 */ 082 private Base64URL signature; 083 084 085 /** 086 * The JWS object state. 087 */ 088 private State state; 089 090 091 /** 092 * Creates a new to-be-signed JSON Web Signature (JWS) object with the 093 * specified header and payload. The initial state will be 094 * {@link State#UNSIGNED unsigned}. 095 * 096 * @param header The JWS header. Must not be {@code null}. 097 * @param payload The payload. Must not be {@code null}. 098 */ 099 public JWSObject(final JWSHeader header, final Payload payload) { 100 101 if (header == null) { 102 throw new IllegalArgumentException("The JWS header must not be null"); 103 } 104 this.header = header; 105 106 if (payload == null) { 107 throw new IllegalArgumentException("The payload must not be null"); 108 } 109 setPayload(payload); 110 111 signingInputString = composeSigningInput(); 112 signature = null; 113 state = State.UNSIGNED; 114 } 115 116 117 /** 118 * Creates a new signed JSON Web Signature (JWS) object with the 119 * specified serialised parts. The state will be 120 * {@link State#SIGNED signed}. 121 * 122 * @param firstPart The first part, corresponding to the JWS header. 123 * Must not be {@code null}. 124 * @param secondPart The second part, corresponding to the payload. 125 * Must not be {@code null}. 126 * @param thirdPart The third part, corresponding to the signature. 127 * Must not be {@code null}. 128 * 129 * @throws ParseException If parsing of the serialised parts failed. 130 */ 131 public JWSObject(final Base64URL firstPart, final Base64URL secondPart, final Base64URL thirdPart) 132 throws ParseException { 133 this(firstPart, new Payload(secondPart), thirdPart); 134 } 135 136 137 /** 138 * Creates a new signed JSON Web Signature (JWS) object with the 139 * specified serialised parts and payload which can be optionally 140 * unencoded (RFC 7797). The state will be {@link State#SIGNED signed}. 141 * 142 * @param firstPart The first part, corresponding to the JWS header. 143 * Must not be {@code null}. 144 * @param payload The payload. Must not be {@code null}. 145 * @param thirdPart The third part, corresponding to the signature. 146 * Must not be {@code null}. 147 * 148 * @throws ParseException If parsing of the serialised parts failed. 149 */ 150 public JWSObject(final Base64URL firstPart, final Payload payload, final Base64URL thirdPart) 151 throws ParseException { 152 153 if (firstPart == null) { 154 throw new IllegalArgumentException("The first part must not be null"); 155 } 156 try { 157 this.header = JWSHeader.parse(firstPart); 158 } catch (ParseException e) { 159 throw new ParseException("Invalid JWS header: " + e.getMessage(), 0); 160 } 161 162 if (payload == null) { 163 throw new IllegalArgumentException("The payload (second part) must not be null"); 164 } 165 setPayload(payload); 166 167 signingInputString = composeSigningInput(); 168 169 if (thirdPart == null) { 170 throw new IllegalArgumentException("The third part must not be null"); 171 } 172 signature = thirdPart; 173 state = State.SIGNED; // but signature not verified yet! 174 175 if (getHeader().isBase64URLEncodePayload()) { 176 setParsedParts(firstPart, payload.toBase64URL(), thirdPart); 177 } else { 178 setParsedParts(firstPart, new Base64URL(""), thirdPart); 179 } 180 } 181 182 @Override 183 public JWSHeader getHeader() { 184 185 return header; 186 } 187 188 189 /** 190 * Composes the signing input string from the header and payload. 191 * 192 * @return The signing input string. 193 */ 194 private String composeSigningInput() { 195 196 if (header.isBase64URLEncodePayload()) { 197 return getHeader().toBase64URL().toString() + '.' + getPayload().toBase64URL().toString(); 198 } else { 199 return getHeader().toBase64URL().toString() + '.' + getPayload().toString(); 200 } 201 } 202 203 204 /** 205 * Returns the signing input for this JWS object. 206 * 207 * @return The signing input, to be passed to a JWS signer or verifier. 208 */ 209 public byte[] getSigningInput() { 210 211 return signingInputString.getBytes(StandardCharset.UTF_8); 212 } 213 214 215 /** 216 * Returns the signature of this JWS object. 217 * 218 * @return The signature, {@code null} if the JWS object is not signed 219 * yet. 220 */ 221 public Base64URL getSignature() { 222 223 return signature; 224 } 225 226 227 /** 228 * Returns the state of this JWS object. 229 * 230 * @return The state. 231 */ 232 public State getState() { 233 234 return state; 235 } 236 237 238 /** 239 * Ensures the current state is {@link State#UNSIGNED unsigned}. 240 * 241 * @throws IllegalStateException If the current state is not unsigned. 242 */ 243 private void ensureUnsignedState() { 244 245 if (state != State.UNSIGNED) { 246 247 throw new IllegalStateException("The JWS object must be in an unsigned state"); 248 } 249 } 250 251 252 /** 253 * Ensures the current state is {@link State#SIGNED signed} or 254 * {@link State#VERIFIED verified}. 255 * 256 * @throws IllegalStateException If the current state is not signed or 257 * verified. 258 */ 259 private void ensureSignedOrVerifiedState() { 260 261 if (state != State.SIGNED && state != State.VERIFIED) { 262 263 throw new IllegalStateException("The JWS object must be in a signed or verified state"); 264 } 265 } 266 267 268 /** 269 * Ensures the specified JWS signer supports the algorithm of this JWS 270 * object. 271 * 272 * @throws JOSEException If the JWS algorithm is not supported. 273 */ 274 private void ensureJWSSignerSupport(final JWSSigner signer) 275 throws JOSEException { 276 277 if (! signer.supportedJWSAlgorithms().contains(getHeader().getAlgorithm())) { 278 279 throw new JOSEException("The \"" + getHeader().getAlgorithm() + 280 "\" algorithm is not allowed or supported by the JWS signer: Supported algorithms: " + signer.supportedJWSAlgorithms()); 281 } 282 } 283 284 285 /** 286 * Signs this JWS object with the specified signer. The JWS object must 287 * be in a {@link State#UNSIGNED unsigned} state. 288 * 289 * @param signer The JWS signer. Must not be {@code null}. 290 * 291 * @throws IllegalStateException If the JWS object is not in an 292 * {@link State#UNSIGNED unsigned state}. 293 * @throws JOSEException If the JWS object couldn't be signed. 294 */ 295 public synchronized void sign(final JWSSigner signer) 296 throws JOSEException { 297 298 ensureUnsignedState(); 299 300 ensureJWSSignerSupport(signer); 301 302 try { 303 signature = signer.sign(getHeader(), getSigningInput()); 304 305 } catch (JOSEException e) { 306 307 throw e; 308 309 } catch (Exception e) { 310 311 // Prevent throwing unchecked exceptions at this point, 312 // see issue #20 313 throw new JOSEException(e.getMessage(), e); 314 } 315 316 state = State.SIGNED; 317 } 318 319 320 /** 321 * Checks the signature of this JWS object with the specified verifier. 322 * The JWS object must be in a {@link State#SIGNED signed} state. 323 * 324 * @param verifier The JWS verifier. Must not be {@code null}. 325 * 326 * @return {@code true} if the signature was successfully verified, 327 * else {@code false}. 328 * 329 * @throws IllegalStateException If the JWS object is not in a 330 * {@link State#SIGNED signed} or 331 * {@link State#VERIFIED verified state}. 332 * @throws JOSEException If the JWS object couldn't be 333 * verified. 334 */ 335 public synchronized boolean verify(final JWSVerifier verifier) 336 throws JOSEException { 337 338 ensureSignedOrVerifiedState(); 339 340 boolean verified; 341 342 try { 343 verified = verifier.verify(getHeader(), getSigningInput(), getSignature()); 344 345 } catch (JOSEException e) { 346 347 throw e; 348 349 } catch (Exception e) { 350 351 // Prevent throwing unchecked exceptions at this point, 352 // see issue #20 353 throw new JOSEException(e.getMessage(), e); 354 } 355 356 if (verified) { 357 358 state = State.VERIFIED; 359 } 360 361 return verified; 362 } 363 364 365 /** 366 * Serialises this JWS object to its compact format consisting of 367 * Base64URL-encoded parts delimited by period ('.') characters. It 368 * must be in a {@link State#SIGNED signed} or 369 * {@link State#VERIFIED verified} state. 370 * 371 * <pre> 372 * [header-base64url].[payload-base64url].[signature-base64url] 373 * </pre> 374 * 375 * @return The serialised JWS object. 376 * 377 * @throws IllegalStateException If the JWS object is not in a 378 * {@link State#SIGNED signed} or 379 * {@link State#VERIFIED verified} state. 380 */ 381 @Override 382 public String serialize() { 383 return serialize(false); 384 } 385 386 387 /** 388 * Serialises this JWS object to its compact format consisting of 389 * Base64URL-encoded parts delimited by period ('.') characters. It 390 * must be in a {@link State#SIGNED signed} or 391 * {@link State#VERIFIED verified} state. 392 * 393 * @param detachedPayload {@code true} to return a serialised object 394 * with a detached payload compliant with RFC 395 * 7797, {@code false} for regular JWS 396 * serialisation. 397 * 398 * @return The serialised JOSE object. 399 * 400 * @throws IllegalStateException If the JOSE object is not in a state 401 * that permits serialisation. 402 */ 403 public String serialize(final boolean detachedPayload) { 404 ensureSignedOrVerifiedState(); 405 406 if (detachedPayload) { 407 return header.toBase64URL().toString() + '.' + '.' + signature.toString(); 408 } 409 410 return signingInputString + '.' + signature.toString(); 411 } 412 413 /** 414 * Parses a JWS object from the specified string in compact format. The 415 * parsed JWS object will be given a {@link State#SIGNED} state. 416 * 417 * @param s The JWS string to parse. Must not be {@code null}. 418 * 419 * @return The JWS object. 420 * 421 * @throws ParseException If the string couldn't be parsed to a JWS 422 * object. 423 */ 424 public static JWSObject parse(final String s) 425 throws ParseException { 426 427 Base64URL[] parts = JOSEObject.split(s); 428 429 if (parts.length != 3) { 430 431 throw new ParseException("Unexpected number of Base64URL parts, must be three", 0); 432 } 433 434 return new JWSObject(parts[0], parts[1], parts[2]); 435 } 436 437 438 /** 439 * Parses a JWS object from the specified string in compact format and 440 * a detached payload which can be optionally unencoded (RFC 7797). The 441 * parsed JWS object will be given a {@link State#SIGNED} state. 442 * 443 * @param s The JWS string to parse for a detached 444 * payload. Must not be {@code null}. 445 * @param detachedPayload The detached payload, optionally unencoded 446 * (RFC 7797). Must not be {@code null}. 447 * 448 * @return The JWS object. 449 * 450 * @throws ParseException If the string couldn't be parsed to a JWS 451 * object. 452 */ 453 public static JWSObject parse(final String s, final Payload detachedPayload) 454 throws ParseException { 455 456 Base64URL[] parts = JOSEObject.split(s); 457 458 if (parts.length != 3) { 459 throw new ParseException("Unexpected number of Base64URL parts, must be three", 0); 460 } 461 462 if (! parts[1].toString().isEmpty()) { 463 throw new ParseException("The payload Base64URL part must be empty", 0); 464 } 465 466 return new JWSObject(parts[0], detachedPayload, parts[2]); 467 } 468}