/************************************************************************* * (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. ************************************************************************/ package com.eucalyptus.tokens.oidc; import java.io.IOException; import java.util.Map; import java.util.function.Predicate; import javax.annotation.Nonnull; import com.eucalyptus.util.CollectionUtils; import com.eucalyptus.util.Json; import com.eucalyptus.util.Pair; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.base.MoreObjects; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import javaslang.collection.Seq; import javaslang.collection.Stream; import javaslang.control.Option; import javaslang.control.Try; /** * JSON Web Key (JWK) * https://tools.ietf.org/html/rfc7517 */ public class JsonWebKeySet { @Nonnull private final Map<Pair<String,Option<String>>,JsonWebKey> keysByTypeAndId; public static JsonWebKeySet parse( final String keysJson ) throws OidcParseException { try { final JsonNode keys = Json.parseObject( keysJson ); return new JsonWebKeySet( Try.sequence( Stream.ofAll( Json.objectList( keys, "keys" ) ).map( JsonWebKeySet::fromJson ) ).get( ) ); } catch ( final Try.NonFatalException e ) { throw new OidcParseException( "Invalid json web key set: " + e.getCause( ).getMessage( ), e.getCause( ) ); } catch ( final IOException e ) { throw new OidcParseException( "Invalid json web key set: " + e.getMessage( ), e ); } } public <KT extends JsonWebKey> Option<KT> findKey( final Option<String> kid, final Class<KT> keyType, final String use, final String keyOp ) { final Pair<String,Option<String>> key = JsonWebKey.key( keyType, kid ); final Predicate<JsonWebKey> usage = jsonWebKey -> jsonWebKey.getUse( ).getOrElse( use ).equals( use ) && jsonWebKey.getKeyOps( ).getOrElse( Lists.newArrayList( keyOp ) ).contains( keyOp ); final JsonWebKey jsonWebKey = keysByTypeAndId.get( key ); if ( keyType.isInstance( jsonWebKey ) ) { return Option.of( keyType.cast( jsonWebKey ) ).filter( usage ); } else if ( !kid.isDefined( ) && keysByTypeAndId.size( ) == 1 ) { return Stream.ofAll( keysByTypeAndId.values( ) ).find( keyType::isInstance ).map( keyType::cast ).filter( usage ); } return Option.none( ); } public String toString( ) { return MoreObjects.toStringHelper( JsonWebKeySet.class ) .add( "keys", keysByTypeAndId.values( ) ) .toString( ); } private JsonWebKeySet( final Seq<JsonWebKey> keys ) throws OidcParseException { keysByTypeAndId = CollectionUtils.reduce( keys, Try.success( Maps.<Pair<String,Option<String>>,JsonWebKey>newHashMap( ) ), ( tryMap, key ) -> tryMap.mapTry( map -> { final Pair<String,Option<String>> keyPair = key.key( ); if ( map.containsKey( keyPair ) ) { throw new OidcParseException( "Duplicate key identifier: " + key.getKid( ) ); } map.put( keyPair, key ); return map; } ) ) .getOrElseThrow( cause -> new OidcParseException( cause.getMessage( ) ) ); } private static Try<JsonWebKey> fromJson( final JsonNode keyObject ) { final Try<String> kty = Try.of( () -> Json.text( keyObject, "kty" ) ); if ( kty.isFailure( ) ) { return Try.failure( kty.getCause( ) ); } final Try<JsonNode> nodeTry = Try.success( keyObject ); switch ( kty.get( ) ) { case EcJsonWebKey.TYPE: return nodeTry.mapTry( EcJsonWebKey::fromJson ); case RsaJsonWebKey.TYPE: return nodeTry.mapTry( RsaJsonWebKey::fromJson ); default: return nodeTry.mapTry( UnsupportedJsonWebKey::fromJson ); } } }