com.eucalyptus.tokens.oidc.JsonWebSignatureVerifier.java Source code

Java tutorial

Introduction

Here is the source code for com.eucalyptus.tokens.oidc.JsonWebSignatureVerifier.java

Source

/*************************************************************************
 * (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.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.Signature;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.AlgorithmParameterSpec;
import java.util.List;
import java.util.function.Predicate;
import javax.annotation.Nonnull;
import com.eucalyptus.util.Pair;
import com.google.common.io.BaseEncoding;
import javaslang.collection.Stream;
import javaslang.control.Option;

/**
 *
 */
public class JsonWebSignatureVerifier {

    public interface KeyResolver {
        <K extends JsonWebKey> Option<K> resolve(Option<String> kid, Class<K> keyType);
    }

    public static boolean isValid(@Nonnull final String jsonHeaderB64, @Nonnull final String jsonBodyB64,
            @Nonnull final String signatureB64, @Nonnull final KeyResolver keyResolver,
            @Nonnull final Predicate<String> signatureAlgorithmPredicate)
            throws GeneralSecurityException, OidcParseException {
        // decode / syntax validation
        final Pair<JoseHeader, byte[]> decoded = decode(jsonHeaderB64, jsonBodyB64, signatureB64);
        final JoseHeader header = decoded.getLeft();
        final byte[] signature = decoded.getRight();

        // resolve and validate signing algorithm / key
        final Pair<JsonWebSignatureAlgorithm, JsonWebKey> resolved = resolve(header, keyResolver,
                signatureAlgorithmPredicate);
        final JsonWebSignatureAlgorithm algorithm = resolved.getLeft();
        final JsonWebKey key = resolved.getRight();

        // verify
        final byte[] bytesToSign = (jsonHeaderB64 + "." + jsonBodyB64).getBytes(StandardCharsets.UTF_8);
        final String sigAlgorithm = algorithm.getJcaSignatureAlgorithm();
        final Option<String> sigProvider = algorithm.getJcaSignatureProvider();
        final Signature jcaSignature = sigProvider.isDefined()
                ? Signature.getInstance(sigAlgorithm, sigProvider.get())
                : Signature.getInstance(sigAlgorithm);
        final Option<AlgorithmParameterSpec> sigAlgorithmParameterSpec = algorithm
                .getJcaSignatureAlgorithmParameterSpec();
        if (sigAlgorithmParameterSpec.isDefined()) {
            jcaSignature.setParameter(sigAlgorithmParameterSpec.get());
        }
        jcaSignature.initVerify(algorithm.publicKey(key));
        jcaSignature.update(bytesToSign);
        return jcaSignature.verify(algorithm.signature(signature));
    }

    private static Pair<JoseHeader, byte[]> decode(@Nonnull final String jsonHeaderB64,
            @Nonnull final String jsonBodyB64, @Nonnull final String signatureB64)
            throws GeneralSecurityException, OidcParseException {
        final String jsonHeader;
        final byte[] signature;
        try {
            final BaseEncoding b64Url = BaseEncoding.base64Url(); // allow padding?
            jsonHeader = new String(b64Url.decode(jsonHeaderB64), StandardCharsets.UTF_8);
            b64Url.decode(jsonBodyB64); // ensures valid b64url encoding
            signature = b64Url.decode(signatureB64);
        } catch (final IllegalArgumentException e) {
            throw new GeneralSecurityException("Unable to decode", e);
        }
        final JoseHeader header = JoseHeader.parse(jsonHeader);
        if (header.getCrit().map(List::size).getOrElse(0) > 0) {
            throw new GeneralSecurityException("Unsupported critical extension " + header.getCrit());
        }
        return Pair.pair(header, signature);
    }

    private static Pair<JsonWebSignatureAlgorithm, JsonWebKey> resolve(@Nonnull final JoseHeader header,
            @Nonnull final KeyResolver keyResolver, @Nonnull final Predicate<String> signatureAlgorithmPredicate)
            throws GeneralSecurityException {
        final JsonWebSignatureAlgorithm algorithm = JsonWebSignatureAlgorithm.lookup(header.getAlg())
                .filter(alg -> signatureAlgorithmPredicate.test(alg.name()))
                .getOrElseThrow(() -> new GeneralSecurityException("Unsupported algorithm: " + header.getAlg()));
        final JsonWebKey key = keyResolver.resolve(header.getKid(), algorithm.keyType())
                .getOrElseThrow(() -> new GeneralSecurityException("Signing key not found"));
        final Option<String> certB64 = key.getX5c().flatMap(list -> Stream.ofAll(list).headOption());
        if (certB64.isDefined() && !certB64.toTry().mapTry(JsonWebSignatureVerifier::certificateFromB64Der)
                .mapTry(cert -> algorithm.matches(key, cert))
                .getOrElseThrow(ex -> new GeneralSecurityException("Certificate decode error", ex))) {
            throw new GeneralSecurityException("Certificate does not match public key material");
        }
        return Pair.pair(algorithm, key);
    }

    private static X509Certificate certificateFromB64Der(@Nonnull final String b64)
            throws GeneralSecurityException {
        return (X509Certificate) CertificateFactory.getInstance("X.509")
                .generateCertificate(new ByteArrayInputStream(BaseEncoding.base64().decode(b64)));
    }
}