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 com.nimbusds.jose.util.Base64URL; 024import com.nimbusds.jose.util.StandardCharset; 025import net.jcip.annotations.ThreadSafe; 026 027 028/** 029 * JSON Web Signature (JWS) secured object. This class is thread-safe. 030 * 031 * @author Vladimir Dzhuvinov 032 * @version 2016-07-26 033 */ 034@ThreadSafe 035public class JWSObject extends JOSEObject { 036 037 038 private static final long serialVersionUID = 1L; 039 040 041 /** 042 * Enumeration of the states of a JSON Web Signature (JWS) object. 043 */ 044 public enum State { 045 046 047 /** 048 * The JWS object is created but not signed yet. 049 */ 050 UNSIGNED, 051 052 053 /** 054 * The JWS object is signed but its signature is not verified. 055 */ 056 SIGNED, 057 058 059 /** 060 * The JWS object is signed and its signature was successfully verified. 061 */ 062 VERIFIED 063 } 064 065 066 /** 067 * The header. 068 */ 069 private final JWSHeader header; 070 071 072 /** 073 * The signing input for this JWS object. 074 * 075 * <p>Format: 076 * 077 * <pre> 078 * [header-base64url].[payload-base64url] 079 * </pre> 080 */ 081 private final String signingInputString; 082 083 084 /** 085 * The signature, {@code null} if not signed. 086 */ 087 private Base64URL signature; 088 089 090 /** 091 * The JWS object state. 092 */ 093 private State state; 094 095 096 /** 097 * Creates a new to-be-signed JSON Web Signature (JWS) object with the 098 * specified header and payload. The initial state will be 099 * {@link State#UNSIGNED unsigned}. 100 * 101 * @param header The JWS header. Must not be {@code null}. 102 * @param payload The payload. Must not be {@code null}. 103 */ 104 public JWSObject(final JWSHeader header, final Payload payload) { 105 106 if (header == null) { 107 108 throw new IllegalArgumentException("The JWS header must not be null"); 109 } 110 111 this.header = header; 112 113 if (payload == null) { 114 115 throw new IllegalArgumentException("The payload must not be null"); 116 } 117 118 setPayload(payload); 119 120 signingInputString = composeSigningInput(header.toBase64URL(), payload.toBase64URL()); 121 122 signature = null; 123 124 state = State.UNSIGNED; 125 } 126 127 128 /** 129 * Creates a new signed JSON Web Signature (JWS) object with the 130 * specified serialised parts. The state will be 131 * {@link State#SIGNED signed}. 132 * 133 * @param firstPart The first part, corresponding to the JWS header. 134 * Must not be {@code null}. 135 * @param secondPart The second part, corresponding to the payload. Must 136 * not be {@code null}. 137 * @param thirdPart The third part, corresponding to the signature. 138 * Must not be {@code null}. 139 * 140 * @throws ParseException If parsing of the serialised parts failed. 141 */ 142 public JWSObject(final Base64URL firstPart, final Base64URL secondPart, final Base64URL thirdPart) 143 throws ParseException { 144 145 if (firstPart == null) { 146 147 throw new IllegalArgumentException("The first part must not be null"); 148 } 149 150 try { 151 this.header = JWSHeader.parse(firstPart); 152 153 } catch (ParseException e) { 154 155 throw new ParseException("Invalid JWS header: " + e.getMessage(), 0); 156 } 157 158 if (secondPart == null) { 159 160 throw new IllegalArgumentException("The second part must not be null"); 161 } 162 163 setPayload(new Payload(secondPart)); 164 165 signingInputString = composeSigningInput(firstPart, secondPart); 166 167 if (thirdPart == null) { 168 throw new IllegalArgumentException("The third part must not be null"); 169 } 170 171 signature = thirdPart; 172 173 state = State.SIGNED; // but signature not verified yet! 174 175 setParsedParts(firstPart, secondPart, thirdPart); 176 } 177 178 179 @Override 180 public JWSHeader getHeader() { 181 182 return header; 183 } 184 185 186 /** 187 * Composes the signing input for the specified JWS object parts. 188 * 189 * <p>Format: 190 * 191 * <pre> 192 * [header-base64url].[payload-base64url] 193 * </pre> 194 * 195 * @param firstPart The first part, corresponding to the JWS header. 196 * Must not be {@code null}. 197 * @param secondPart The second part, corresponding to the payload. 198 * Must not be {@code null}. 199 * 200 * @return The signing input string. 201 */ 202 private static String composeSigningInput(final Base64URL firstPart, final Base64URL secondPart) { 203 204 return firstPart.toString() + '.' + secondPart.toString(); 205 } 206 207 208 /** 209 * Returns the signing input for this JWS object. 210 * 211 * <p>Format: 212 * 213 * <pre> 214 * [header-base64url].[payload-base64url] 215 * </pre> 216 * 217 * @return The signing input, to be passed to a JWS signer or verifier. 218 */ 219 public byte[] getSigningInput() { 220 221 return signingInputString.getBytes(StandardCharset.UTF_8); 222 } 223 224 225 /** 226 * Returns the signature of this JWS object. 227 * 228 * @return The signature, {@code null} if the JWS object is not signed 229 * yet. 230 */ 231 public Base64URL getSignature() { 232 233 return signature; 234 } 235 236 237 /** 238 * Returns the state of this JWS object. 239 * 240 * @return The state. 241 */ 242 public State getState() { 243 244 return state; 245 } 246 247 248 /** 249 * Ensures the current state is {@link State#UNSIGNED unsigned}. 250 * 251 * @throws IllegalStateException If the current state is not unsigned. 252 */ 253 private void ensureUnsignedState() { 254 255 if (state != State.UNSIGNED) { 256 257 throw new IllegalStateException("The JWS object must be in an unsigned state"); 258 } 259 } 260 261 262 /** 263 * Ensures the current state is {@link State#SIGNED signed} or 264 * {@link State#VERIFIED verified}. 265 * 266 * @throws IllegalStateException If the current state is not signed or 267 * verified. 268 */ 269 private void ensureSignedOrVerifiedState() { 270 271 if (state != State.SIGNED && state != State.VERIFIED) { 272 273 throw new IllegalStateException("The JWS object must be in a signed or verified state"); 274 } 275 } 276 277 278 /** 279 * Ensures the specified JWS signer supports the algorithm of this JWS 280 * object. 281 * 282 * @throws JOSEException If the JWS algorithm is not supported. 283 */ 284 private void ensureJWSSignerSupport(final JWSSigner signer) 285 throws JOSEException { 286 287 if (! signer.supportedJWSAlgorithms().contains(getHeader().getAlgorithm())) { 288 289 throw new JOSEException("The \"" + getHeader().getAlgorithm() + 290 "\" algorithm is not allowed or supported by the JWS signer: Supported algorithms: " + signer.supportedJWSAlgorithms()); 291 } 292 } 293 294 295 /** 296 * Signs this JWS object with the specified signer. The JWS object must 297 * be in a {@link State#UNSIGNED unsigned} state. 298 * 299 * @param signer The JWS signer. Must not be {@code null}. 300 * 301 * @throws IllegalStateException If the JWS object is not in an 302 * {@link State#UNSIGNED unsigned state}. 303 * @throws JOSEException If the JWS object couldn't be signed. 304 */ 305 public synchronized void sign(final JWSSigner signer) 306 throws JOSEException { 307 308 ensureUnsignedState(); 309 310 ensureJWSSignerSupport(signer); 311 312 try { 313 signature = signer.sign(getHeader(), getSigningInput()); 314 315 } catch (JOSEException e) { 316 317 throw e; 318 319 } catch (Exception e) { 320 321 // Prevent throwing unchecked exceptions at this point, 322 // see issue #20 323 throw new JOSEException(e.getMessage(), e); 324 } 325 326 state = State.SIGNED; 327 } 328 329 330 /** 331 * Checks the signature of this JWS object with the specified verifier. 332 * The JWS object must be in a {@link State#SIGNED signed} state. 333 * 334 * @param verifier The JWS verifier. Must not be {@code null}. 335 * 336 * @return {@code true} if the signature was successfully verified, 337 * else {@code false}. 338 * 339 * @throws IllegalStateException If the JWS object is not in a 340 * {@link State#SIGNED signed} or 341 * {@link State#VERIFIED verified state}. 342 * @throws JOSEException If the JWS object couldn't be 343 * verified. 344 */ 345 public synchronized boolean verify(final JWSVerifier verifier) 346 throws JOSEException { 347 348 ensureSignedOrVerifiedState(); 349 350 boolean verified; 351 352 try { 353 verified = verifier.verify(getHeader(), getSigningInput(), getSignature()); 354 355 } catch (JOSEException e) { 356 357 throw e; 358 359 } catch (Exception e) { 360 361 // Prevent throwing unchecked exceptions at this point, 362 // see issue #20 363 throw new JOSEException(e.getMessage(), e); 364 } 365 366 if (verified) { 367 368 state = State.VERIFIED; 369 } 370 371 return verified; 372 } 373 374 375 /** 376 * Serialises this JWS object to its compact format consisting of 377 * Base64URL-encoded parts delimited by period ('.') characters. It 378 * must be in a {@link State#SIGNED signed} or 379 * {@link State#VERIFIED verified} state. 380 * 381 * <pre> 382 * [header-base64url].[payload-base64url].[signature-base64url] 383 * </pre> 384 * 385 * @return The serialised JWS object. 386 * 387 * @throws IllegalStateException If the JWS object is not in a 388 * {@link State#SIGNED signed} or 389 * {@link State#VERIFIED verified} state. 390 */ 391 @Override 392 public String serialize() { 393 394 ensureSignedOrVerifiedState(); 395 396 return signingInputString + '.' + signature.toString(); 397 } 398 399 400 /** 401 * Parses a JWS object from the specified string in compact format. The 402 * parsed JWS object will be given a {@link State#SIGNED} state. 403 * 404 * @param s The string to parse. Must not be {@code null}. 405 * 406 * @return The JWS object. 407 * 408 * @throws ParseException If the string couldn't be parsed to a valid 409 * JWS object. 410 */ 411 public static JWSObject parse(final String s) 412 throws ParseException { 413 414 Base64URL[] parts = JOSEObject.split(s); 415 416 if (parts.length != 3) { 417 418 throw new ParseException("Unexpected number of Base64URL parts, must be three", 0); 419 } 420 421 return new JWSObject(parts[0], parts[1], parts[2]); 422 } 423}