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