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.io.Serializable; 022import java.text.ParseException; 023 024import net.minidev.json.JSONObject; 025 026import com.nimbusds.jose.util.Base64URL; 027import com.nimbusds.jose.util.JSONObjectUtils; 028 029 030/** 031 * The base abstract class for unsecured (plain / {@code alg=none}), JSON Web 032 * Signature (JWS) secured and JSON Web Encryption (JWE) secured objects. 033 * 034 * @author Vladimir Dzhuvinov 035 * @version 2015-06-10 036 */ 037public abstract class JOSEObject implements Serializable { 038 039 040 private static final long serialVersionUID = 1L; 041 042 043 /** 044 * The MIME type of JOSE objects serialised to a compact form: 045 * {@code application/jose; charset=UTF-8} 046 */ 047 public static final String MIME_TYPE_COMPACT = "application/jose; charset=UTF-8"; 048 049 050 /** 051 * The MIME type of JOSE objects serialised to a JSON object form: 052 * {@code application/jose+json; charset=UTF-8} 053 */ 054 public static final String MIME_TYPE_JS = "application/jose+json; charset=UTF-8"; 055 056 057 /** 058 * The payload (message), {@code null} if not specified. 059 */ 060 private Payload payload; 061 062 063 /** 064 * The original parsed Base64URL parts, {@code null} if the JOSE object 065 * was created from scratch. The individual parts may be empty or 066 * {@code null} to indicate a missing part. 067 */ 068 private Base64URL[] parsedParts; 069 070 071 /** 072 * Creates a new JOSE object. The payload and the original parsed 073 * Base64URL parts are not defined. 074 */ 075 protected JOSEObject() { 076 077 payload = null; 078 parsedParts = null; 079 } 080 081 082 /** 083 * Creates a new JOSE object with the specified payload. 084 * 085 * @param payload The payload, {@code null} if not available (e.g for 086 * an encrypted JWE object). 087 */ 088 protected JOSEObject(final Payload payload) { 089 090 this.payload = payload; 091 } 092 093 094 /** 095 * Returns the header of this JOSE object. 096 * 097 * @return The header. 098 */ 099 public abstract Header getHeader(); 100 101 102 /** 103 * Sets the payload of this JOSE object. 104 * 105 * @param payload The payload, {@code null} if not available (e.g. for 106 * an encrypted JWE object). 107 */ 108 protected void setPayload(final Payload payload) { 109 110 this.payload = payload; 111 } 112 113 114 /** 115 * Returns the payload of this JOSE object. 116 * 117 * @return The payload, {@code null} if not available (for an encrypted 118 * JWE object that hasn't been decrypted). 119 */ 120 public Payload getPayload() { 121 122 return payload; 123 } 124 125 126 /** 127 * Sets the original parsed Base64URL parts used to create this JOSE 128 * object. 129 * 130 * @param parts The original Base64URL parts used to creates this JOSE 131 * object, {@code null} if the object was created from 132 * scratch. The individual parts may be empty or 133 * {@code null} to indicate a missing part. 134 */ 135 protected void setParsedParts(final Base64URL... parts) { 136 137 parsedParts = parts; 138 } 139 140 141 /** 142 * Returns the original parsed Base64URL parts used to create this JOSE 143 * object. 144 * 145 * @return The original Base64URL parts used to creates this JOSE 146 * object, {@code null} if the object was created from scratch. 147 * The individual parts may be empty or {@code null} to 148 * indicate a missing part. 149 */ 150 public Base64URL[] getParsedParts() { 151 152 return parsedParts; 153 } 154 155 156 /** 157 * Returns the original parsed string used to create this JOSE object. 158 * 159 * @see #getParsedParts 160 * 161 * @return The parsed string used to create this JOSE object, 162 * {@code null} if the object was creates from scratch. 163 */ 164 public String getParsedString() { 165 166 if (parsedParts == null) { 167 return null; 168 } 169 170 StringBuilder sb = new StringBuilder(); 171 172 for (Base64URL part: parsedParts) { 173 174 if (sb.length() > 0) { 175 sb.append('.'); 176 } 177 178 if (part != null) { 179 sb.append(part.toString()); 180 } 181 } 182 183 return sb.toString(); 184 } 185 186 187 /** 188 * Serialises this JOSE object to its compact format consisting of 189 * Base64URL-encoded parts delimited by period ('.') characters. 190 * 191 * @return The serialised JOSE object. 192 * 193 * @throws IllegalStateException If the JOSE object is not in a state 194 * that permits serialisation. 195 */ 196 public abstract String serialize(); 197 198 199 /** 200 * Splits a compact serialised JOSE object into its Base64URL-encoded 201 * parts. 202 * 203 * @param s The compact serialised JOSE object to split. Must not be 204 * {@code null}. 205 * 206 * @return The JOSE Base64URL-encoded parts (three for unsecured and 207 * JWS objects, five for JWE objects). 208 * 209 * @throws ParseException If the specified string couldn't be split 210 * into three or five Base64URL-encoded parts. 211 */ 212 public static Base64URL[] split(final String s) 213 throws ParseException { 214 215 // We must have 2 (JWS) or 4 dots (JWE) 216 217 // String.split() cannot handle empty parts 218 final int dot1 = s.indexOf("."); 219 220 if (dot1 == -1) { 221 throw new ParseException("Invalid serialized unsecured/JWS/JWE object: Missing part delimiters", 0); 222 } 223 224 final int dot2 = s.indexOf(".", dot1 + 1); 225 226 if (dot2 == -1) { 227 throw new ParseException("Invalid serialized unsecured/JWS/JWE object: Missing second delimiter", 0); 228 } 229 230 // Third dot for JWE only 231 final int dot3 = s.indexOf(".", dot2 + 1); 232 233 if (dot3 == -1) { 234 235 // Two dots only? -> We have a JWS 236 Base64URL[] parts = new Base64URL[3]; 237 parts[0] = new Base64URL(s.substring(0, dot1)); 238 parts[1] = new Base64URL(s.substring(dot1 + 1, dot2)); 239 parts[2] = new Base64URL(s.substring(dot2 + 1)); 240 return parts; 241 } 242 243 // Fourth final dot for JWE 244 final int dot4 = s.indexOf(".", dot3 + 1); 245 246 if (dot4 == -1) { 247 throw new ParseException("Invalid serialized JWE object: Missing fourth delimiter", 0); 248 } 249 250 if (dot4 != -1 && s.indexOf(".", dot4 + 1) != -1) { 251 throw new ParseException("Invalid serialized unsecured/JWS/JWE object: Too many part delimiters", 0); 252 } 253 254 // Four dots -> five parts 255 Base64URL[] parts = new Base64URL[5]; 256 parts[0] = new Base64URL(s.substring(0, dot1)); 257 parts[1] = new Base64URL(s.substring(dot1 + 1, dot2)); 258 parts[2] = new Base64URL(s.substring(dot2 + 1, dot3)); 259 parts[3] = new Base64URL(s.substring(dot3 + 1, dot4)); 260 parts[4] = new Base64URL(s.substring(dot4 + 1)); 261 return parts; 262 } 263 264 265 /** 266 * Parses a JOSE object from the specified string in compact format. 267 * 268 * @param s The string to parse. Must not be {@code null}. 269 * 270 * @return The corresponding {@link PlainObject}, {@link JWSObject} or 271 * {@link JWEObject} instance. 272 * 273 * @throws ParseException If the string couldn't be parsed to a valid 274 * unsecured, JWS or JWE object. 275 */ 276 public static JOSEObject parse(final String s) 277 throws ParseException { 278 279 Base64URL[] parts = split(s); 280 281 JSONObject jsonObject; 282 283 try { 284 jsonObject = JSONObjectUtils.parse(parts[0].decodeToString()); 285 286 } catch (ParseException e) { 287 288 throw new ParseException("Invalid unsecured/JWS/JWE header: " + e.getMessage(), 0); 289 } 290 291 Algorithm alg = Header.parseAlgorithm(jsonObject); 292 293 if (alg.equals(Algorithm.NONE)) { 294 return PlainObject.parse(s); 295 } else if (alg instanceof JWSAlgorithm) { 296 return JWSObject.parse(s); 297 } else if (alg instanceof JWEAlgorithm) { 298 return JWEObject.parse(s); 299 } else { 300 throw new AssertionError("Unexpected algorithm type: " + alg); 301 } 302 } 303}