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