Java tutorial
/************************************************************************* * (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))); } }