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 2014 ForgeRock AS. 015 */ 016 017package org.forgerock.openig.jwt; 018 019import static java.lang.String.*; 020import static org.forgerock.openig.jwt.JwtCookieSession.*; 021import static org.forgerock.openig.util.Json.*; 022 023import java.security.GeneralSecurityException; 024import java.security.Key; 025import java.security.KeyPair; 026import java.security.KeyPairGenerator; 027import java.security.KeyStore; 028import java.security.NoSuchAlgorithmException; 029import java.security.PrivateKey; 030import java.security.PublicKey; 031import java.security.SecureRandom; 032import java.security.cert.Certificate; 033 034import org.forgerock.json.fluent.JsonValue; 035import org.forgerock.openig.heap.GenericHeapObject; 036import org.forgerock.openig.heap.GenericHeaplet; 037import org.forgerock.openig.heap.HeapException; 038import org.forgerock.openig.http.Exchange; 039import org.forgerock.openig.http.Session; 040import org.forgerock.openig.http.SessionFactory; 041 042/** 043 * A JwtSessionFactory is responsible to configure and create a {@link JwtCookieSession}. 044 * 045 * <pre> 046 * { 047 * "name": "JwtSession", 048 * "type": "JwtSession", 049 * "config": { 050 * "keystore": "Ref To A KeyStore", 051 * "alias": "PrivateKey Alias", 052 * "password": "KeyStore/Key Password", 053 * "cookieName": "OpenIG" 054 * } 055 * } 056 * </pre> 057 * 058 * All the session configuration is optional: if you omit everything, the appropriate keys will be generated and the 059 * cookie name used will be {@link JwtCookieSession#OPENIG_JWT_SESSION}. 060 * 061 * <p> 062 * The {@literal keystore} attribute is an optional attribute that references a {@link KeyStore} heap object. It will 063 * be used to obtain the required encryption keys. If omitted, the {@literal alias} and {@literal password} 064 * attributes will also be ignored, and a temporary key pair will be generated. 065 * <p> 066 * The {@literal alias} string attribute specifies the name of the private key to obtain from the KeyStore. It is 067 * only required when a {@literal keystore} is specified. 068 * <p> 069 * The {@literal password} static expression attribute specifies the password to use when reading the 070 * private key from the KeyStore. It is only required when a {@literal keystore} is specified. 071 * <p> 072 * The {@literal cookieName} optional string attribute specifies the name of the cookie used to store the encrypted JWT. 073 * If not set, {@link JwtCookieSession#OPENIG_JWT_SESSION} is used. 074 * 075 * @since 3.1 076 */ 077public class JwtSessionFactory extends GenericHeapObject implements SessionFactory { 078 079 /** 080 * The pair of keys for JWT payload encryption/decryption. 081 */ 082 private final KeyPair keyPair; 083 084 /** 085 * The name of the cookie to be used to session's content transmission. 086 */ 087 private final String cookieName; 088 089 /** 090 * Builds a new JwtSessionFactory using the given KeyPair for session encryption, storing the opaque result in a 091 * cookie with the given name. 092 * 093 * @param keyPair 094 * Private and public keys used for ciphering/deciphering 095 * @param cookieName 096 * name of the cookie 097 */ 098 public JwtSessionFactory(final KeyPair keyPair, final String cookieName) { 099 this.keyPair = keyPair; 100 this.cookieName = cookieName; 101 } 102 103 @Override 104 public Session build(final Exchange exchange) { 105 return new JwtCookieSession(exchange, keyPair, cookieName, logger); 106 } 107 108 /** Creates and initializes a jwt-session in a heap environment. */ 109 public static class Heaplet extends GenericHeaplet { 110 111 /** RSA needs at least a 512 key length.*/ 112 private static final int KEY_SIZE = 1024; 113 114 @Override 115 public Object create() throws HeapException { 116 KeyPair keyPair = null; 117 JsonValue keystoreValue = config.get("keystore"); 118 if (!keystoreValue.isNull()) { 119 KeyStore keyStore = heap.resolve(keystoreValue, KeyStore.class); 120 121 String alias = config.get("alias").required().asString(); 122 String password = evaluate(config.get("password").required()); 123 124 try { 125 Key key = keyStore.getKey(alias, password.toCharArray()); 126 if (key instanceof PrivateKey) { 127 // Get certificate of private key 128 Certificate cert = keyStore.getCertificate(alias); 129 if (cert == null) { 130 throw new HeapException(format("Cannot get Certificate[alias:%s] from KeyStore[ref:%s]", 131 alias, 132 keystoreValue.asString())); 133 } 134 135 // Get public key 136 PublicKey publicKey = cert.getPublicKey(); 137 138 // Return a key pair 139 keyPair = new KeyPair(publicKey, (PrivateKey) key); 140 } else { 141 throw new HeapException(format("Either no Key[alias:%s] is available in KeyStore[ref:%s], " 142 + "or it is not a private key", 143 alias, 144 keystoreValue.asString())); 145 } 146 } catch (GeneralSecurityException e) { 147 throw new HeapException(format("Wrong password for Key[alias:%s] in KeyStore[ref:%s]", 148 alias, 149 keystoreValue.asString()), 150 e); 151 } 152 } else { 153 /* 154 * No KeyStore provided: generate a new KeyPair by ourself. In 155 * this case, 'alias' and 'password' attributes are ignored. JWT 156 * session cookies will not be portable between OpenIG instances 157 * config changes, and restarts. 158 */ 159 try { 160 KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); 161 generator.initialize(KEY_SIZE, new SecureRandom()); 162 keyPair = generator.generateKeyPair(); 163 } catch (NoSuchAlgorithmException e) { 164 throw new HeapException("Cannot build a random KeyPair", e); 165 } 166 167 logger.warning("JWT session support has been enabled but no encryption keys have " 168 + "been configured. A temporary key pair will be used but this means that " 169 + "OpenIG will not be able to decrypt any JWT session cookies after a " 170 + "configuration change, a server restart, nor will it be able to decrypt " 171 + "JWT session cookies encrypted by another OpenIG server."); 172 } 173 174 // Create the session factory with the given KeyPair and cookie name 175 return new JwtSessionFactory(keyPair, 176 config.get("cookieName").defaultTo(OPENIG_JWT_SESSION).asString()); 177 } 178 } 179}