001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2013-2015 ForgeRock AS. 015 */ 016 017package org.forgerock.json.jose.common; 018 019import org.forgerock.json.JsonValue; 020import org.forgerock.json.jose.exceptions.InvalidJwtException; 021import org.forgerock.json.jose.exceptions.JwtReconstructionException; 022import org.forgerock.json.jose.jwe.EncryptedJwt; 023import org.forgerock.json.jose.jwe.JweHeader; 024import org.forgerock.json.jose.jws.JwsHeader; 025import org.forgerock.json.jose.jws.SignedEncryptedJwt; 026import org.forgerock.json.jose.jws.SignedJwt; 027import org.forgerock.json.jose.jwt.Jwt; 028import org.forgerock.json.jose.jwt.JwtClaimsSet; 029import org.forgerock.json.jose.jwt.JwtType; 030import org.forgerock.json.jose.utils.Utils; 031import org.forgerock.util.encode.Base64url; 032 033import java.util.HashMap; 034import java.util.Map; 035 036/** 037 * A service that provides a method for reconstruct a JWT string back into its relevant JWT object, 038 * (SignedJwt, EncryptedJwt, SignedEncryptedJwt). 039 * 040 * @since 2.0.0 041 */ 042public class JwtReconstruction { 043 044 private static final int JWS_NUM_PARTS = 3; 045 private static final int JWE_NUM_PARTS = 5; 046 047 /** 048 * Reconstructs the given JWT string into a JWT object of the specified type. 049 * 050 * @param jwtString The JWT string. 051 * @param jwtClass The JWT class to reconstruct the JWT string to. 052 * @param <T> The type of JWT the JWT string represents. 053 * @return The reconstructed JWT object. 054 * @throws InvalidJwtException If the jwt does not consist of the correct number of parts. 055 * @throws JwtReconstructionException If the jwt does not consist of the correct number of parts. 056 */ 057 public <T extends Jwt> T reconstructJwt(String jwtString, Class<T> jwtClass) { 058 059 Jwt jwt; 060 061 //split into parts 062 String[] jwtParts = jwtString.split("\\.", -1); 063 if (jwtParts.length != 3 && jwtParts.length != 5) { 064 throw new InvalidJwtException("not right number of dots, " + jwtParts.length); 065 } 066 067 //first part always header 068 //turn into json value 069 JsonValue headerJson = new JsonValue(Utils.parseJson(Utils.base64urlDecode(jwtParts[0]))); 070 JwtType jwtType = JwtType.JWT; 071 if (headerJson.isDefined("typ")) { 072 jwtType = JwtType.valueOf(headerJson.get("typ").asString().toUpperCase()); 073 } 074 075 if (headerJson.isDefined("enc")) { 076 //is encrypted jwt 077 verifyNumberOfParts(jwtParts, JWE_NUM_PARTS); 078 jwt = reconstructEncryptedJwt(jwtParts); 079 } else if (JwtType.JWE.equals(jwtType)) { 080 verifyNumberOfParts(jwtParts, JWS_NUM_PARTS); 081 jwt = reconstructSignedEncryptedJwt(jwtParts); 082 } else if (headerJson.isDefined("alg")) { 083 //is signed jwt 084 verifyNumberOfParts(jwtParts, JWS_NUM_PARTS); 085 jwt = reconstructSignedJwt(jwtParts); 086 } else { 087 //plaintext jwt 088 verifyNumberOfParts(jwtParts, JWS_NUM_PARTS); 089 if (!jwtParts[2].isEmpty()) { 090 throw new InvalidJwtException("Third part of Plaintext JWT not empty."); 091 } 092 jwt = reconstructSignedJwt(jwtParts); 093 } 094 095 return jwtClass.cast(jwt); 096 } 097 098 /** 099 * Verifies that the JWT parts are the required length for the JWT type being reconstructed. 100 * 101 * @param jwtParts The JWT parts. 102 * @param required The required number of parts. 103 * @throws JwtReconstructionException If the jwt does not consist of the correct number of parts. 104 */ 105 private void verifyNumberOfParts(String[] jwtParts, int required) { 106 if (jwtParts.length != required) { 107 throw new JwtReconstructionException("Not the correct number of JWT parts. Expecting, " + required 108 + ", actually, " + jwtParts.length); 109 } 110 } 111 112 /** 113 * Reconstructs a Signed JWT from the given JWT string parts. 114 * <p> 115 * As a plaintext JWT is a JWS with an empty signature, this method should be used to reconstruct plaintext JWTs 116 * as well as signed JWTs. 117 * 118 * @param jwtParts The three base64url UTF-8 encoded string parts of a plaintext or signed JWT. 119 * @return A SignedJwt object. 120 */ 121 private SignedJwt reconstructSignedJwt(String[] jwtParts) { 122 123 String encodedHeader = jwtParts[0]; 124 String encodedClaimsSet = jwtParts[1]; 125 String encodedSignature = jwtParts[2]; 126 127 128 String header = Utils.base64urlDecode(encodedHeader); 129 String claimsSetString = Utils.base64urlDecode(encodedClaimsSet); 130 byte[] signature = Base64url.decode(encodedSignature); 131 132 JwsHeader jwsHeader = new JwsHeader(Utils.parseJson(header)); 133 134 JwtClaimsSet claimsSet = new JwtClaimsSet(Utils.parseJson(claimsSetString)); 135 136 return new SignedJwt(jwsHeader, claimsSet, (encodedHeader + "." + encodedClaimsSet).getBytes(Utils.CHARSET), 137 signature); 138 } 139 140 /** 141 * Reconstructs an encrypted JWT from the given JWT string parts. 142 * 143 * @param jwtParts The five base64url UTF-8 encoded string parts of an encrypted JWT. 144 * @return An EncryptedJwt object. 145 */ 146 private EncryptedJwt reconstructEncryptedJwt(String[] jwtParts) { 147 148 String encodedHeader = jwtParts[0]; 149 String encodedEncryptedKey = jwtParts[1]; 150 String encodedInitialisationVector = jwtParts[2]; 151 String encodedCiphertext = jwtParts[3]; 152 String encodedAuthenticationTag = jwtParts[4]; 153 154 155 String header = Utils.base64urlDecode(encodedHeader); 156 byte[] encryptedContentEncryptionKey = Base64url.decode(encodedEncryptedKey); 157 byte[] initialisationVector = Base64url.decode(encodedInitialisationVector); 158 byte[] ciphertext = Base64url.decode(encodedCiphertext); 159 byte[] authenticationTag = Base64url.decode(encodedAuthenticationTag); 160 161 162 JweHeader jweHeader = new JweHeader(Utils.parseJson(header)); 163 164 165 return new EncryptedJwt(jweHeader, encodedHeader, encryptedContentEncryptionKey, initialisationVector, 166 ciphertext, authenticationTag); 167 } 168 169 /** 170 * Reconstructs a signed and encrypted JWT from the given JWT string parts. 171 * <p> 172 * First reconstructs the nested encrypted JWT from within the signed JWT and then reconstructs the signed JWT using 173 * the reconstructed nested EncryptedJwt. 174 * 175 * @param jwtParts The three base64url UTF-8 encoded string parts of a signed JWT. 176 * @return A SignedEncryptedJwt object. 177 */ 178 private SignedEncryptedJwt reconstructSignedEncryptedJwt(String[] jwtParts) { 179 180 String encodedHeader = jwtParts[0]; 181 String encodedPayload = jwtParts[1]; 182 String encodedSignature = jwtParts[2]; 183 184 185 String header = Utils.base64urlDecode(encodedHeader); 186 String payloadString = Utils.base64urlDecode(encodedPayload); 187 byte[] signature = Base64url.decode(encodedSignature); 188 189 //split into parts 190 String[] encryptedJwtParts = payloadString.split("\\.", -1); 191 verifyNumberOfParts(encryptedJwtParts, JWE_NUM_PARTS); 192 EncryptedJwt encryptedJwt = reconstructEncryptedJwt(encryptedJwtParts); 193 194 Map<String, Object> combinedHeader = new HashMap<>(encryptedJwt.getHeader().getParameters()); 195 combinedHeader.putAll(Utils.parseJson(header)); 196 197 JwsHeader jwsHeader = new JwsHeader(combinedHeader); 198 199 return new SignedEncryptedJwt(jwsHeader, encryptedJwt, 200 (encodedHeader + "." + encodedPayload).getBytes(Utils.CHARSET), signature); 201 } 202}