All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.snowflake.client.core.SFTrustManager Maven / Gradle / Ivy

/*
 * Copyright (c) 2012-2019 Snowflake Computing Inc. All rights reserved.
 */

package net.snowflake.client.core;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import net.snowflake.client.log.SFLogger;
import net.snowflake.client.log.SFLoggerFactory;
import net.snowflake.client.util.DecorrelatedJitterBackoff;
import net.snowflake.client.util.SFPair;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.ssl.SSLInitializationException;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DLSequence;
import org.bouncycastle.asn1.ocsp.CertID;
import org.bouncycastle.asn1.ocsp.OCSPResponse;
import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.Certificate;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.Extensions;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.TBSCertificate;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.bouncycastle.cert.ocsp.CertificateID;
import org.bouncycastle.cert.ocsp.CertificateStatus;
import org.bouncycastle.cert.ocsp.OCSPException;
import org.bouncycastle.cert.ocsp.OCSPReq;
import org.bouncycastle.cert.ocsp.OCSPReqBuilder;
import org.bouncycastle.cert.ocsp.OCSPResp;
import org.bouncycastle.cert.ocsp.RevokedStatus;
import org.bouncycastle.cert.ocsp.SingleResp;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.DigestCalculator;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.SSLEngine;

import java.io.*;
import java.math.BigInteger;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.*;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.text.MessageFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;

import com.nimbusds.jose.*;
import com.nimbusds.jwt.*;
import org.bouncycastle.util.io.pem.PemReader;


/**
 * SFTrustManager is a composite of TrustManager of the default JVM
 * TrustManager and Snowflake OCSP revocation status checker. Use this
 * when initializing SSLContext object.
 *
 * 
 * {@code
 * TrustManager[] trustManagers = {new SFTrustManager()};
 * SSLContext sslContext = SSLContext.getInstance("TLS");
 * sslContext.init(null, trustManagers, null);
 * }
 * 
*/ class SFTrustManager extends X509ExtendedTrustManager { private static final SFLogger LOGGER = SFLoggerFactory.getLogger(SFTrustManager.class); private static final ASN1ObjectIdentifier OIDocsp = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.48.1").intern(); private static final ASN1ObjectIdentifier SHA1RSA = new ASN1ObjectIdentifier("1.2.840.113549.1.1.5").intern(); private static final ASN1ObjectIdentifier SHA256RSA = new ASN1ObjectIdentifier("1.2.840.113549.1.1.11").intern(); private static final ASN1ObjectIdentifier SHA384RSA = new ASN1ObjectIdentifier("1.2.840.113549.1.1.12").intern(); private static final ASN1ObjectIdentifier SHA512RSA = new ASN1ObjectIdentifier("1.2.840.113549.1.1.13").intern(); /** * Object mapper for JSON encoding and decoding */ private static final ObjectMapper OBJECT_MAPPER = ObjectMapperFactory.getObjectMapper(); /** * System property name to specify cache directory. */ public static final String CACHE_DIR_PROP = "net.snowflake.jdbc.ocspResponseCacheDir"; /** * Environment name to specify the cache directory. Used if system property not set. */ private static final String CACHE_DIR_ENV = "SF_OCSP_RESPONSE_CACHE_DIR"; /** * OCSP response cache entry expiration time (s) */ private static final long CACHE_EXPIRATION_IN_SECONDS = 86400L; /** * OCSP response cache lock file expiration time (s) */ private static final long CACHE_FILE_LOCK_EXPIRATION_IN_SECONDS = 60L; /** * Default OCSP Cache server host name */ public static final String DEFAULT_OCSP_CACHE_HOST = "http://ocsp.snowflakecomputing.com"; /** * Default OCSP Retry URL */ public static final String SF_DEFAULT_OCSP_RESPONSE_RETRY_URL = "http://ocsp.snowflakecomputing.com/retry"; /** * OCSP response cache file name. Should be identical to other driver's * cache file name. */ public static final String CACHE_FILE_NAME = "ocsp_response_cache.json"; /** * provider name */ private static final String BOUNCY_CASTLE_PROVIDER = "BC"; /** * OCSP response file cache directory */ private static final FileCacheManager fileCacheManager; /** * SSD Support management */ static SSDManager ssdManager = new SSDManager(); static { fileCacheManager = FileCacheManager .builder() .setCacheDirectorySystemProperty(CACHE_DIR_PROP) .setCacheDirectoryEnvironmentVariable(CACHE_DIR_ENV) .setBaseCacheFileName(CACHE_FILE_NAME) .setCacheExpirationInSeconds(CACHE_EXPIRATION_IN_SECONDS) .setCacheFileLockExpirationInSeconds(CACHE_FILE_LOCK_EXPIRATION_IN_SECONDS).build(); } /** * OCSP response cache server URL. */ private static String SF_OCSP_RESPONSE_CACHE_SERVER_URL = String.format( "%s/%s", DEFAULT_OCSP_CACHE_HOST, CACHE_FILE_NAME); /** * OCSP Response Cache server Retry URL pattern */ static String SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN; class OCSPCacheServer { String SF_OCSP_RESPONSE_CACHE_SERVER; String SF_OCSP_RESPONSE_RETRY_URL; boolean new_endpoint_enabled; void resetOCSPResponseCacheServer(String host) { String ocspCacheServerUrl; if (host.indexOf(".global.snowflakecomputing.com") > 0) { ocspCacheServerUrl = String.format( "https://ocspssd%s/%s", host.substring(host.indexOf('-')), "ocsp"); } else if (host.indexOf(".snowflakecomputing.com") > 0) { ocspCacheServerUrl = String.format( "https://ocspssd%s/%s", host.substring(host.indexOf('.')), "ocsp"); } else { ocspCacheServerUrl = "https://ocspssd.snowflakecomputing.com/ocsp"; } SF_OCSP_RESPONSE_CACHE_SERVER = String.format("%s/%s", ocspCacheServerUrl, "fetch"); SF_OCSP_RESPONSE_RETRY_URL = String.format("%s/%s", ocspCacheServerUrl, "retry"); } } OCSPCacheServer ocspCacheServer = new OCSPCacheServer(); /** * Tolerable validity date range ratio. */ private static final float TOLERABLE_VALIDITY_RANGE_RATIO = 0.01f; /** * Maximum clocktime skew (ms) */ private static final long MAX_CLOCK_SKEW_IN_MILLISECONDS = 900000L; /** * Minimum cache warm up time (ms) */ private static final long MIN_CACHE_WARMUP_TIME_IN_MILLISECONDS = 18000000L; /** * Maximum retry counter (times) */ private static final int MAX_RETRY_COUNTER = 10; /** * Initial sleeping time in retry (ms) */ private static final long INITIAL_SLEEPING_TIME_IN_MILLISECONDS = 1000L; /** * Maximum sleeping time in retry (ms) */ private static final long MAX_SLEEPING_TIME_IN_MILLISECONDS = 16000L; /** * Map from signature algorithm ASN1 object to the name. */ private static final Map SIGNATURE_OID_TO_STRING = new HashMap<>(); static { SIGNATURE_OID_TO_STRING.put(SHA1RSA, "SHA1withRSA"); SIGNATURE_OID_TO_STRING.put(SHA256RSA, "SHA256withRSA"); SIGNATURE_OID_TO_STRING.put(SHA384RSA, "SHA384withRSA"); SIGNATURE_OID_TO_STRING.put(SHA512RSA, "SHA512withRSA"); } /** * Map from OCSP response code to a string representation. */ private static final Map OCSP_RESPONSE_CODE_TO_STRING = new HashMap<>(); static { OCSP_RESPONSE_CODE_TO_STRING.put(OCSPResp.SUCCESSFUL, "successful"); OCSP_RESPONSE_CODE_TO_STRING.put(OCSPResp.MALFORMED_REQUEST, "malformedRequest"); OCSP_RESPONSE_CODE_TO_STRING.put(OCSPResp.INTERNAL_ERROR, "internalError"); OCSP_RESPONSE_CODE_TO_STRING.put(OCSPResp.TRY_LATER, "tryLater"); OCSP_RESPONSE_CODE_TO_STRING.put(OCSPResp.SIG_REQUIRED, "sigRequired"); OCSP_RESPONSE_CODE_TO_STRING.put(OCSPResp.UNAUTHORIZED, "unauthorized"); } static { // Add Bouncy Castle to the security provider. This is required to // verify the signature on OCSP response and attached certificates. if (Security.getProvider(BOUNCY_CASTLE_PROVIDER) == null) { Security.addProvider(new BouncyCastleProvider()); } } private static JcaX509CertificateConverter CONVERTER_X509 = new JcaX509CertificateConverter(); /** * RootCA cache */ private static Map ROOT_CA = new HashMap<>(); private final static Object ROOT_CA_LOCK = new Object(); /** * OCSP Response cache */ private final static Map> OCSP_RESPONSE_CACHE = new HashMap<>(); private final static Object OCSP_RESPONSE_CACHE_LOCK = new Object(); private static boolean WAS_CACHE_UPDATED = false; private static boolean WAS_CACHE_READ = false; /** * Date and timestamp format */ private final static SimpleDateFormat DATE_FORMAT_UTC = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); static { DATE_FORMAT_UTC.setTimeZone(TimeZone.getTimeZone("UTC")); } /** * The default JVM Trust manager. */ private final X509TrustManager trustManager; /** * The default JVM Extended Trust Manager */ private final X509ExtendedTrustManager exTrustManager; /** * Use OCSP response cache server? */ private final boolean useOcspResponseCacheServer; /** * Constructor with the cache file. If not specified, the default cachefile * is used. * * @param cacheFile cache file. * @param useOcspResponseCacheServer true if use OCSP response cache server is used. */ SFTrustManager(File cacheFile, boolean useOcspResponseCacheServer) { this.trustManager = getTrustManager( KeyManagerFactory.getDefaultAlgorithm()); this.exTrustManager = (X509ExtendedTrustManager) getTrustManager( KeyManagerFactory.getDefaultAlgorithm()); checkNewOCSPEndpointAvailability(); if (ssdManager.getSSDSupportStatus()) { readDirectives(); } synchronized (OCSP_RESPONSE_CACHE_LOCK) { if (cacheFile != null) { fileCacheManager.overrideCacheFile(cacheFile); } if (!WAS_CACHE_READ) { // read cache file once JsonNode res = fileCacheManager.readCacheFile(); readJsonStoreCache(res); WAS_CACHE_READ = true; } } this.useOcspResponseCacheServer = useOcspResponseCacheServer; } /** * Look for Out of Band Server Side Directives *

* These can be two types only: * 1. Key Update Directive - key_upd_ssd.ssd * 2. Host Specific OCSP Bypass Directive - host_spec_bypass_ssd.ssd */ void readDirectives() { KeyUpdSSD keyUpdDir = ssdManager.getKeyUpdateSSD(); HostSpecSSD hostSpecDir = ssdManager.getHostSpecBypassSSD(); if (keyUpdDir != null) { processKeyUpdateDirective(keyUpdDir.getIssuer(), keyUpdDir.getKeyUpdDirective()); } if (hostSpecDir != null) { ssdManager.addToSSDCache(hostSpecDir.getHostSpecDirective()); } } void checkNewOCSPEndpointAvailability() { String new_ocsp_ept = null; try { new_ocsp_ept = System.getenv("SF_OCSP_ACTIVATE_NEW_ENDPOINT"); if(new_ocsp_ept != null) { ocspCacheServer.new_endpoint_enabled = true; } else { ocspCacheServer.new_endpoint_enabled = false; } } catch (Throwable ex) { LOGGER.debug("Could not get environment variable to check for New OCSP Endpoint Availability"); new_ocsp_ept = System.getProperty("net.snowflake.jdbc.ocsp_activate_new_endpoint"); if (new_ocsp_ept != null) { ocspCacheServer.new_endpoint_enabled = true; } else { ocspCacheServer.new_endpoint_enabled = false; } } } /** * Reset OCSP Cache server URL * * @param ocspCacheServerUrl OCSP Cache server URL */ static void resetOCSPResponseCacherServerURL(String ocspCacheServerUrl) { if (ocspCacheServerUrl == null || SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN != null) { return; } SF_OCSP_RESPONSE_CACHE_SERVER_URL = ocspCacheServerUrl; if (!SF_OCSP_RESPONSE_CACHE_SERVER_URL.startsWith(DEFAULT_OCSP_CACHE_HOST)) { try { URL url = new URL(SF_OCSP_RESPONSE_CACHE_SERVER_URL); if (url.getPort() > 0) { SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN = String.format("%s://%s:%d/retry/%s", url.getProtocol(), url.getHost(), url.getPort(), "%s/%s"); } else { SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN = String.format("%s://%s/retry/%s", url.getProtocol(), url.getHost(), "%s/%s"); } } catch (IOException e) { throw new RuntimeException( String.format( "Failed to parse SF_OCSP_RESPONSE_CACHE_SERVER_URL: %s", SF_OCSP_RESPONSE_CACHE_SERVER_URL)); } } } /** * Get TrustManager for the algorithm. * This is mainly used to get the JVM default trust manager and * cache all of the root CA. * * @param algorithm algorithm. * @return TrustManager object. */ private X509TrustManager getTrustManager(String algorithm) { try { TrustManagerFactory factory = TrustManagerFactory.getInstance(algorithm); factory.init((KeyStore) null); X509TrustManager ret = null; for (TrustManager tm : factory.getTrustManagers()) { // Multiple TrustManager may be attached. We just need X509 Trust // Manager here. if (tm instanceof X509TrustManager) { ret = (X509TrustManager) tm; break; } } if (ret == null) { return null; } synchronized (ROOT_CA_LOCK) { // cache root CA certificates for later use. if (ROOT_CA.size() == 0) { for (X509Certificate cert : ret.getAcceptedIssuers()) { Certificate bcCert = Certificate.getInstance(cert.getEncoded()); ROOT_CA.put(bcCert.getSubject().hashCode(), bcCert); } } } return ret; } catch (NoSuchAlgorithmException | KeyStoreException | CertificateEncodingException ex) { throw new SSLInitializationException(ex.getMessage(), ex); } } @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { // default behavior trustManager.checkClientTrusted(chain, authType); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { trustManager.checkServerTrusted(chain, authType); } @Override public void checkClientTrusted(X509Certificate[] chain, String authType, java.net.Socket socket) throws CertificateException { // default behavior this.exTrustManager.checkClientTrusted(chain, authType, socket); } @Override public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) throws CertificateException { // default behavior exTrustManager.checkClientTrusted(chain, authType, sslEngine); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType, java.net.Socket socket) throws CertificateException { // default behavior exTrustManager.checkServerTrusted(chain, authType, socket); String host = socket.getInetAddress().getHostName(); this.validateRevocationStatus(chain, host); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) throws CertificateException { // default behavior exTrustManager.checkServerTrusted(chain, authType, sslEngine); this.validateRevocationStatus(chain, sslEngine.getPeerHost()); } @Override public X509Certificate[] getAcceptedIssuers() { return trustManager.getAcceptedIssuers(); } /** * Certificate Revocation checks * * @param chain chain of certificates attached. * @param peerHost Hostname of the server * @throws CertificateException if any certificate validation fails */ void validateRevocationStatus(X509Certificate[] chain, String peerHost) throws CertificateException { final List bcChain = convertToBouncyCastleCertificate(chain); final List> pairIssuerSubjectList = getPairIssuerSubject(bcChain); if (peerHost.startsWith("ocspssd")) { return; } if (ocspCacheServer.new_endpoint_enabled) { ocspCacheServer.resetOCSPResponseCacheServer(peerHost); } synchronized (OCSP_RESPONSE_CACHE_LOCK) { boolean isCached = isCached(pairIssuerSubjectList); if (this.useOcspResponseCacheServer && !isCached) { if (!ocspCacheServer.new_endpoint_enabled) { LOGGER.debug( "Downloading OCSP response cache from the server. URL: {}", SF_OCSP_RESPONSE_CACHE_SERVER_URL); } else { LOGGER.debug( "Downloading OCSP response cache from the server. URL: {}", ocspCacheServer.SF_OCSP_RESPONSE_CACHE_SERVER); } readOcspResponseCacheServer(); // if the cache is downloaded from the server, it should be written // to the file cache at all times. WAS_CACHE_UPDATED = true; } executeRevocationStatusChecks(pairIssuerSubjectList, peerHost); if (WAS_CACHE_UPDATED) { JsonNode input = encodeCacheToJSON(); fileCacheManager.writeCacheFile(input); WAS_CACHE_UPDATED = false; } } } /** * Executes the revocation status checks for all chained certificates * * @param pairIssuerSubjectList a list of pair of issuer and subject certificates. * @throws CertificateException raises if any error occurs. */ private void executeRevocationStatusChecks( List> pairIssuerSubjectList, String peerHost) throws CertificateException { long currentTimeSecond = new Date().getTime() / 1000L; try { for (SFPair pairIssuerSubject : pairIssuerSubjectList) { executeOneRevoctionStatusCheck(pairIssuerSubject, currentTimeSecond, peerHost); } } catch (IOException ex) { LOGGER.debug("Failed to decode CertID. Ignored."); } } /** * Convert cache key to base64 encoded * cert id * * @param ocsp_cache_key Cache key to encode */ private static String encodeCacheKey(OcspResponseCacheKey ocsp_cache_key) { try { DigestCalculator digest = new SHA1DigestCalculator(); AlgorithmIdentifier algo = digest.getAlgorithmIdentifier(); ASN1OctetString nameHash = ASN1OctetString.getInstance(ocsp_cache_key.nameHash); ASN1OctetString keyHash = ASN1OctetString.getInstance(ocsp_cache_key.keyHash); ASN1Integer snumber = new ASN1Integer(ocsp_cache_key.serialNumber); CertID cid = new CertID(algo, nameHash, keyHash, snumber); return Base64.encodeBase64String(cid.toASN1Primitive().getEncoded()); } catch (Exception ex) { LOGGER.debug("Failed to encode cache key to base64 encoded cert id"); } return null; } /** * Executes a single revocation status check * * @param pairIssuerSubject a pair of issuer and subject certificate * @param currentTimeSecond the current timestamp * @throws IOException raises if encoding fails. * @throws CertificateException if certificate exception is raised. */ private void executeOneRevoctionStatusCheck( SFPair pairIssuerSubject, long currentTimeSecond, String peerHost) throws IOException, CertificateException { OCSPReq req = createRequest(pairIssuerSubject); CertID cid = req.getRequestList()[0].getCertID().toASN1Primitive(); OcspResponseCacheKey keyOcspResponse = new OcspResponseCacheKey( cid.getIssuerNameHash().getEncoded(), cid.getIssuerKeyHash().getEncoded(), cid.getSerialNumber().getValue()); String hostSpecSSD = null; long sleepTime = INITIAL_SLEEPING_TIME_IN_MILLISECONDS; DecorrelatedJitterBackoff backoff = new DecorrelatedJitterBackoff( sleepTime, MAX_SLEEPING_TIME_IN_MILLISECONDS); CertificateException error = null; boolean success = false; for (int retry = 0; retry < MAX_RETRY_COUNTER; ++retry) { /** * Look for Host Specific SSD in SSD Cache */ if (ssdManager.getSSDSupportStatus()) { boolean retval = false; SFPair resp = OCSP_RESPONSE_CACHE.get(ssdManager.getWildCardCertId()); if ((hostSpecSSD = ssdManager.getSSDFromCache()) != null) { try { retval = this.processOCSPBypassSSD(hostSpecSSD, keyOcspResponse, peerHost); if (retval) { success = true; break; } else { /* remove invalid entry from SSD Cache */ ssdManager.clearSSDCache(); } } catch (Throwable ex) { LOGGER.info("Unable to process Host Specific OCSP Response. Removing" + "it from the SSD Cache"); ssdManager.clearSSDCache(); } } else if (resp.right != null) { /** * Process WildCard SSD if present */ if (this.processOCSPBypassSSD(resp.right, ssdManager.getWildCardCertId(), "*")) { success = true; break; } else { /** * Delete WildCard from cache */ LOGGER.info("Found invalid wildcard SSD in cache, removing."); OCSP_RESPONSE_CACHE.remove(ssdManager.getWildCardCertId()); } } } SFPair value0 = OCSP_RESPONSE_CACHE.get(keyOcspResponse); OCSPResp ocspResp; try { if (value0 == null) { LOGGER.debug("not hit cache."); ocspResp = fetchOcspResponse(pairIssuerSubject, req, encodeCacheKey(keyOcspResponse), peerHost); OCSP_RESPONSE_CACHE.put( keyOcspResponse, SFPair.of(currentTimeSecond, ocspResponseToB64(ocspResp))); WAS_CACHE_UPDATED = true; value0 = SFPair.of(currentTimeSecond, ocspResponseToB64(ocspResp)); } LOGGER.debug("validating. {}", CertificateIDToString(req.getRequestList()[0].getCertID())); try { validateRevocationStatusMain(pairIssuerSubject, value0.right); success = true; break; } catch (Throwable ex) { if (ssdManager.getSSDSupportStatus()) { /** * Failed processing OCSP response * Try processing cache value as SSD */ if (this.processOCSPBypassSSD(value0.right, keyOcspResponse, peerHost)) { success = true; break; } else { throw ex; } } else { throw ex; } } } catch (CertificateException ex) { if (OCSP_RESPONSE_CACHE.containsKey(keyOcspResponse)) { LOGGER.debug("deleting the invalid OCSP cache."); OCSP_RESPONSE_CACHE.remove(keyOcspResponse); WAS_CACHE_UPDATED = true; } error = ex; LOGGER.debug("Retrying {}/{} after sleeping {}(ms)", retry + 1, MAX_RETRY_COUNTER, sleepTime); try { Thread.sleep(sleepTime); sleepTime = backoff.nextSleepTime(sleepTime); } catch (InterruptedException ex0) { // nop } } } if (!success) { // still not success, raise an error. throw error; } } /** * Is OCSP Response cached? * * @param pairIssuerSubjectList a list of pair of issuer and subject certificates * @return true if all of OCSP response are cached else false */ private boolean isCached(List> pairIssuerSubjectList) { long currentTimeSecond = new Date().getTime() / 1000L; boolean isCached = true; try { for (SFPair pairIssuerSubject : pairIssuerSubjectList) { OCSPReq req = createRequest(pairIssuerSubject); CertificateID certificateId = req.getRequestList()[0].getCertID(); LOGGER.debug(CertificateIDToString(certificateId)); CertID cid = certificateId.toASN1Primitive(); OcspResponseCacheKey k = new OcspResponseCacheKey( cid.getIssuerNameHash().getEncoded(), cid.getIssuerKeyHash().getEncoded(), cid.getSerialNumber().getValue()); SFPair res = OCSP_RESPONSE_CACHE.get(k); if (res == null) { LOGGER.debug("Not all OCSP responses for the certificate is in the cache."); isCached = false; break; } else if (currentTimeSecond - CACHE_EXPIRATION_IN_SECONDS > res.left) { LOGGER.debug("Cache for CertID expired."); isCached = false; break; } else { try { validateRevocationStatusMain(pairIssuerSubject, res.right); } catch (CertificateException ex) { LOGGER.debug("Cache includes invalid OCSPResponse. " + "Will download the OCSP cache from Snowflake OCSP server"); isCached = false; } } } } catch (IOException ex) { LOGGER.debug("Failed to encode CertID."); } return isCached; } /** * CertificateID to string * * @param certificateID CertificateID * @return a string representation of CertificateID */ private static String CertificateIDToString(CertificateID certificateID) { return String.format("CertID. NameHash: %s, KeyHash: %s, Serial Number: %s", byteToHexString(certificateID.getIssuerNameHash()), byteToHexString(certificateID.getIssuerKeyHash()), MessageFormat.format("{0,number,#}", certificateID.getSerialNumber())); } /** * Decodes OCSP Response Cache key from JSON * * @param elem A JSON element * @return OcspResponseCacheKey object */ private static SFPair> decodeCacheFromJSON(Map.Entry elem) throws IOException { long currentTimeSecond = new Date().getTime() / 1000; byte[] certIdDer = Base64.decodeBase64(elem.getKey()); DLSequence rawCertId = (DLSequence) ASN1ObjectIdentifier.fromByteArray(certIdDer); ASN1Encodable[] rawCertIdArray = rawCertId.toArray(); byte[] issuerNameHashDer = ((DEROctetString) rawCertIdArray[1]).getEncoded(); byte[] issuerKeyHashDer = ((DEROctetString) rawCertIdArray[2]).getEncoded(); BigInteger serialNumber = ((ASN1Integer) rawCertIdArray[3]).getValue(); OcspResponseCacheKey k = new OcspResponseCacheKey( issuerNameHashDer, issuerKeyHashDer, serialNumber); JsonNode ocspRespBase64 = elem.getValue(); if (!ocspRespBase64.isArray() || ocspRespBase64.size() != 2) { LOGGER.debug("Invalid cache file format."); return null; } long producedAt = ocspRespBase64.get(0).asLong(); String ocspResp = ocspRespBase64.get(1).asText(); if (currentTimeSecond - CACHE_EXPIRATION_IN_SECONDS <= producedAt) { // add cache return SFPair.of(k, SFPair.of(producedAt, ocspResp)); } else { // delete cache return SFPair.of(k, SFPair.of(producedAt, (String) null)); } } /** * Encode OCSP Response Cache to JSON * * @return JSON object */ private static ObjectNode encodeCacheToJSON() { try { ObjectNode out = OBJECT_MAPPER.createObjectNode(); for (Map.Entry> elem : OCSP_RESPONSE_CACHE.entrySet()) { OcspResponseCacheKey key = elem.getKey(); SFPair value0 = elem.getValue(); long currentTimeSecond = value0.left; DigestCalculator digest = new SHA1DigestCalculator(); AlgorithmIdentifier algo = digest.getAlgorithmIdentifier(); ASN1OctetString nameHash = ASN1OctetString.getInstance(key.nameHash); ASN1OctetString keyHash = ASN1OctetString.getInstance(key.keyHash); ASN1Integer serialNumber = new ASN1Integer(key.serialNumber); CertID cid = new CertID(algo, nameHash, keyHash, serialNumber); ArrayNode vout = OBJECT_MAPPER.createArrayNode(); vout.add(currentTimeSecond); vout.add(value0.right); out.set( Base64.encodeBase64String(cid.toASN1Primitive().getEncoded()), vout); } return out; } catch (IOException ex) { LOGGER.debug("Failed to encode ASN1 object."); } return null; } /** * Reads the OCSP response cache from the server. *

* Must be synchronized by OCSP_RESPONSE_CACHE_LOCK. */ private void readOcspResponseCacheServer() { long sleepTime = INITIAL_SLEEPING_TIME_IN_MILLISECONDS; DecorrelatedJitterBackoff backoff = new DecorrelatedJitterBackoff( sleepTime, MAX_SLEEPING_TIME_IN_MILLISECONDS); Exception error = null; String ocspCacheServerInUse; if (ocspCacheServer.new_endpoint_enabled) { ocspCacheServerInUse = ocspCacheServer.SF_OCSP_RESPONSE_CACHE_SERVER; } else { ocspCacheServerInUse = SF_OCSP_RESPONSE_CACHE_SERVER_URL; } for (int retry = 0; retry < MAX_RETRY_COUNTER; ++retry) { try { HttpClient client = getHttpClient(); URI uri = new URI(ocspCacheServerInUse); HttpGet get = new HttpGet(uri); HttpResponse response = client.execute(get); if (response == null || response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { throw new IOException( String.format( "Failed to get the OCSP response from the OCSP " + "cache server: HTTP: %d", response != null ? response.getStatusLine().getStatusCode() : -1)); } ByteArrayOutputStream out = new ByteArrayOutputStream(); IOUtils.copy(response.getEntity().getContent(), out); JsonNode m = OBJECT_MAPPER.readTree(out.toByteArray()); readJsonStoreCache(m); LOGGER.debug("Successfully downloaded OCSP cache from the server."); return; } catch (IOException | URISyntaxException ex) { error = ex; LOGGER.debug("Retrying {}/{} after sleeping {}(ms)", retry + 1, MAX_RETRY_COUNTER, sleepTime); try { Thread.sleep(sleepTime); sleepTime = backoff.nextSleepTime(sleepTime); } catch (InterruptedException ex0) { // nop } } } LOGGER.debug( "Failed to read the OCSP response cache from the server. " + "Server: {}, Err: {}", ocspCacheServerInUse, error); } private static void readJsonStoreCache(JsonNode m) { if (m == null || !m.getNodeType().equals(JsonNodeType.OBJECT)) { LOGGER.debug("Invalid cache file format."); return; } try { for (Iterator> itr = m.fields(); itr.hasNext(); ) { SFPair> ky = decodeCacheFromJSON(itr.next()); if (ky != null && ky.right != null && ky.right.right != null) { // valid range. cache the result in memory OCSP_RESPONSE_CACHE.put(ky.left, ky.right); } else if (ky != null && OCSP_RESPONSE_CACHE.containsKey(ky.left)) { // delete it from the cache if no OCSP response is back. OCSP_RESPONSE_CACHE.remove(ky.left); WAS_CACHE_UPDATED = true; } } } catch (IOException ex) { LOGGER.debug("Failed to decode the cache file"); } } private static class OCSPPostReqData { private String ocsp_url; private String ocsp_req; private String cert_id_enc; private String hostname; OCSPPostReqData(String ocsp_url, String ocsp_req, String cert_id_enc, String hname) { this.ocsp_url = ocsp_url; this.ocsp_req = ocsp_req; this.cert_id_enc = cert_id_enc; this.hostname = hname; } } /** * Fetches OCSP response from OCSP server * * @param pairIssuerSubject a pair of issuer and subject certificates * @param req OCSP Request object * @return OCSP Response object * @throws CertificateEncodingException if any other error occurs */ private OCSPResp fetchOcspResponse( SFPair pairIssuerSubject, OCSPReq req, String cid_enc, String hname) throws CertificateEncodingException { try { byte[] ocspReqDer = req.getEncoded(); String ocspReqDerBase64 = Base64.encodeBase64String(ocspReqDer); Set ocspUrls = getOcspUrls(pairIssuerSubject.right); String ocspUrlStr = ocspUrls.iterator().next(); // first one URL url; if (!ocspCacheServer.new_endpoint_enabled) { if (SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN != null) { URL ocspUrl = new URL(ocspUrlStr); url = new URL(String.format( SF_OCSP_RESPONSE_CACHE_SERVER_RETRY_URL_PATTERN, ocspUrl.getHost(), ocspReqDerBase64)); } else { url = new URL(String.format("%s/%s", ocspUrlStr, ocspReqDerBase64)); } LOGGER.debug( "not hit cache. Fetching OCSP response from CA OCSP server. {}", url.toString()); } else { url = new URL(ocspCacheServer.SF_OCSP_RESPONSE_RETRY_URL); LOGGER.debug( "not hit cache. Fetching OCSP response from Snowflake OCSP Response Fetcher. {}", url.toString()); } long sleepTime = INITIAL_SLEEPING_TIME_IN_MILLISECONDS; DecorrelatedJitterBackoff backoff = new DecorrelatedJitterBackoff( sleepTime, MAX_SLEEPING_TIME_IN_MILLISECONDS); boolean success = false; HttpResponse response = null; for (int retry = 0; retry < MAX_RETRY_COUNTER; ++retry) { if (!ocspCacheServer.new_endpoint_enabled) { HttpClient client = getHttpClient(); HttpGet get = new HttpGet(url.toString()); response = client.execute(get); } else { HttpClient client = getHttpClient(); HttpPost post = new HttpPost(url.toString()); post.setHeader("Content-Type", "application/json"); SFTrustManager.OCSPPostReqData postReqData = new SFTrustManager.OCSPPostReqData(ocspUrlStr, ocspReqDerBase64, cid_enc, hname); String json_payload = OBJECT_MAPPER.writeValueAsString(postReqData); post.setEntity(new StringEntity(json_payload, "utf-8")); response = client.execute(post); } if (response != null && response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { success = true; LOGGER.debug( "Successfully downloaded OCSP response from CA server. " + "URL: {}", url.toString()); break; } LOGGER.debug("Retrying {}/{} after sleeping {}(ms)", retry + 1, MAX_RETRY_COUNTER, sleepTime); try { Thread.sleep(sleepTime); sleepTime = backoff.nextSleepTime(sleepTime); } catch (InterruptedException ex0) { // nop } } if (!success) { throw new CertificateEncodingException( String.format( "Failed to get OCSP response. StatusCode: %d, URL: %s", response == null ? null : response.getStatusLine().getStatusCode(), ocspUrlStr)); } ByteArrayOutputStream out = new ByteArrayOutputStream(); IOUtils.copy(response.getEntity().getContent(), out); OCSPResp ocspResp = new OCSPResp(out.toByteArray()); if (ocspResp.getStatus() != OCSPResp.SUCCESSFUL) { throw new CertificateEncodingException( String.format("Failed to get OCSP response. Status: %s", OCSP_RESPONSE_CODE_TO_STRING.get(ocspResp.getStatus()))); } return ocspResp; } catch (IOException ex) { throw new CertificateEncodingException("Failed to encode object.", ex); } } /** * Validates the certificate revocation status * * @param pairIssuerSubject a pair of issuer and subject certificates * @param ocspRespB64 Base64 encoded OCSP Response object * @throws CertificateException raises if any other error occurs */ private void validateRevocationStatusMain( SFPair pairIssuerSubject, String ocspRespB64) throws CertificateException { try { OCSPResp ocspResp = b64ToOCSPResp(ocspRespB64); Date currentTime = new Date(); BasicOCSPResp basicOcspResp = (BasicOCSPResp) (ocspResp.getResponseObject()); X509CertificateHolder[] attachedCerts = basicOcspResp.getCerts(); X509CertificateHolder signVerifyCert; if (attachedCerts.length > 0) { LOGGER.debug( "Certificate is attached for verification. " + "Verifying it by the issuer certificate."); signVerifyCert = attachedCerts[0]; if (currentTime.after(signVerifyCert.getNotAfter()) || currentTime.before(signVerifyCert.getNotBefore())) { throw new OCSPException(String.format("Cert attached to OCSP Response is invalid." + "Current time - %s" + "Certificate not before time - %s" + "Certificate not after time - %s", currentTime.toString(), signVerifyCert.getNotBefore().toString(), signVerifyCert.getNotAfter().toString())); } verifySignature( new X509CertificateHolder(pairIssuerSubject.left.getEncoded()), signVerifyCert.getSignature(), CONVERTER_X509.getCertificate(signVerifyCert).getTBSCertificate(), signVerifyCert.getSignatureAlgorithm()); LOGGER.debug( "Verifying OCSP signature by the attached certificate public key." ); } else { LOGGER.debug("Certificate is NOT attached for verification. " + "Verifying OCSP signature by the issuer public key."); signVerifyCert = new X509CertificateHolder( pairIssuerSubject.left.getEncoded()); } verifySignature( signVerifyCert, basicOcspResp.getSignature(), basicOcspResp.getTBSResponseData(), basicOcspResp.getSignatureAlgorithmID()); validateBasicOcspResponse(currentTime, basicOcspResp); } catch (IOException | OCSPException ex) { throw new CertificateEncodingException( "Failed to check revocation status.", ex); } } /** * Validates OCSP Basic OCSP response. * * @param currentTime the current timestamp. * @param basicOcspResp BasicOcspResponse data. * @throws CertificateEncodingException raises if any failure occurs. */ private void validateBasicOcspResponse( Date currentTime, BasicOCSPResp basicOcspResp) throws CertificateEncodingException { for (SingleResp singleResps : basicOcspResp.getResponses()) { Date thisUpdate = singleResps.getThisUpdate(); Date nextUpdate = singleResps.getNextUpdate(); LOGGER.debug("Current Time: {}, This Update: {}, Next Update: {}", currentTime, thisUpdate, nextUpdate); CertificateStatus certStatus = singleResps.getCertStatus(); if (certStatus != CertificateStatus.GOOD) { if (certStatus instanceof RevokedStatus) { RevokedStatus status = (RevokedStatus) certStatus; int reason; try { reason = status.getRevocationReason(); } catch (IllegalStateException ex) { reason = -1; } Date revocationTime = status.getRevocationTime(); throw new CertificateEncodingException( String.format( "The certificate has been revoked. Reason: %d, Time: %s", reason, DATE_FORMAT_UTC.format(revocationTime))); } else { // Unknown status throw new CertificateEncodingException( "Failed to validate the certificate for UNKNOWN reason."); } } if (!isValidityRange(currentTime, thisUpdate, nextUpdate)) { throw new CertificateEncodingException( String.format( "The validity is out of range: " + "Current Time: %s, This Update: %s, Next Update: %s", DATE_FORMAT_UTC.format(currentTime), DATE_FORMAT_UTC.format(thisUpdate), DATE_FORMAT_UTC.format(nextUpdate))); } } LOGGER.debug("OK. Verified the certificate revocation status."); } /** * Verifies the signature of the data * * @param cert a certificate for public key. * @param sig signature in a byte array. * @param data data in a byte array. * @param idf algorithm identifier object. * @throws CertificateException raises if the verification fails. */ private static void verifySignature( X509CertificateHolder cert, byte[] sig, byte[] data, AlgorithmIdentifier idf) throws CertificateException { try { String algorithm = SIGNATURE_OID_TO_STRING.get(idf.getAlgorithm()); if (algorithm == null) { throw new NoSuchAlgorithmException( String.format("Unsupported signature OID. OID: %s", idf)); } Signature signer = Signature.getInstance( algorithm, BouncyCastleProvider.PROVIDER_NAME); X509Certificate c = CONVERTER_X509.getCertificate(cert); signer.initVerify(c.getPublicKey()); signer.update(data); if (!signer.verify(sig)) { throw new CertificateEncodingException( String.format("Failed to verify the signature. Potentially the " + "data was not generated by by the cert, %s", cert.getSubject())); } } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException | SignatureException ex) { throw new CertificateEncodingException( "Failed to verify the signature.", ex); } } /** * Converts Byte array to hex string * * @param bytes a byte array * @return a string in hexadecimal code */ private static String byteToHexString(byte[] bytes) { final char[] hexArray = "0123456789ABCDEF".toCharArray(); char[] hexChars = new char[bytes.length * 2]; for (int j = 0; j < bytes.length; j++) { int v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars); } /** * Creates a OCSP Request * * @param pairIssuerSubject a pair of issuer and subject certificates * @return OCSPReq object */ private OCSPReq createRequest( SFPair pairIssuerSubject) { Certificate issuer = pairIssuerSubject.left; Certificate subject = pairIssuerSubject.right; OCSPReqBuilder gen = new OCSPReqBuilder(); try { DigestCalculator digest = new SHA1DigestCalculator(); X509CertificateHolder certHolder = new X509CertificateHolder(issuer.getEncoded()); CertificateID certId = new CertificateID( digest, certHolder, subject.getSerialNumber().getValue()); gen.addRequest(certId); return gen.build(); } catch (OCSPException | IOException ex) { throw new RuntimeException("Failed to build a OCSPReq."); } } /** * Converts X509Certificate to Bouncy Castle Certificate * * @param chain an array of X509Certificate * @return a list of Bouncy Castle Certificate */ private List convertToBouncyCastleCertificate( X509Certificate[] chain) { final List bcChain = new ArrayList<>(); for (X509Certificate cert : chain) { try { bcChain.add(Certificate.getInstance(cert.getEncoded())); } catch (CertificateEncodingException ex) { throw new RuntimeException("Failed to decode the certificate DER data"); } } return bcChain; } /** * Creates a pair of Issuer and Subject certificates * * @param bcChain a list of bouncy castle Certificate * @return a list of paif of Issuer and Subject certificates */ private List> getPairIssuerSubject( List bcChain) { List> pairIssuerSubject = new ArrayList<>(); for (int i = 0, len = bcChain.size(); i < len; ++i) { Certificate bcCert = bcChain.get(i); if (bcCert.getIssuer().equals(bcCert.getSubject())) { continue; // skipping ROOT CA } if (i < len - 1) { pairIssuerSubject.add(SFPair.of(bcChain.get(i + 1), bcChain.get(i))); } else { synchronized (ROOT_CA_LOCK) { // no root CA certificate is attached in the certificate chain, so // getting one from the root CA from JVM. Certificate issuer = ROOT_CA.get(bcCert.getIssuer().hashCode()); if (issuer == null) { throw new RuntimeException("Failed to find the root CA."); } pairIssuerSubject.add(SFPair.of(issuer, bcChain.get(i))); } } } return pairIssuerSubject; } /** * Gets OCSP URLs associated with the certificate. * * @param bcCert Bouncy Castle Certificate * @return a set of OCSP URLs */ private Set getOcspUrls(Certificate bcCert) { TBSCertificate bcTbsCert = bcCert.getTBSCertificate(); Extensions bcExts = bcTbsCert.getExtensions(); if (bcExts == null) { throw new RuntimeException("Failed to get Tbs Certificate."); } Set ocsp = new HashSet<>(); for (Enumeration en = bcExts.oids(); en.hasMoreElements(); ) { ASN1ObjectIdentifier oid = (ASN1ObjectIdentifier) en.nextElement(); Extension bcExt = bcExts.getExtension(oid); if (bcExt.getExtnId() == Extension.authorityInfoAccess) { // OCSP URLS are included in authorityInfoAccess DLSequence seq = (DLSequence) bcExt.getParsedValue(); for (ASN1Encodable asn : seq) { ASN1Encodable[] pairOfAsn = ((DLSequence) asn).toArray(); if (pairOfAsn.length == 2) { ASN1ObjectIdentifier key = (ASN1ObjectIdentifier) pairOfAsn[0]; if (key == OIDocsp) { // ensure OCSP and not CRL GeneralName gn = GeneralName.getInstance(pairOfAsn[1]); ocsp.add(gn.getName().toString()); } } } } } return ocsp; } /** * Gets HttpClient object * * @return HttpClient */ private static HttpClient getHttpClient() { // using the default HTTP client return HttpUtil.getHttpClient(); } private static long maxLong(long v1, long v2) { return v1 > v2 ? v1 : v2; } /** * Calculates the tolerable validity time beyond the next update. *

* Sometimes CA's OCSP response update is delayed beyond the clock skew * as the update is not populated to all OCSP servers for certain period. * * @param thisUpdate the last update * @param nextUpdate the next update * @return the tolerable validity beyond the next update. */ private static long calculateTolerableVadility(Date thisUpdate, Date nextUpdate) { return maxLong((long) ((float) (nextUpdate.getTime() - thisUpdate.getTime()) * TOLERABLE_VALIDITY_RANGE_RATIO), MIN_CACHE_WARMUP_TIME_IN_MILLISECONDS); } /** * Checks the validity * * @param currentTime the current time * @param thisUpdate the last update timestamp * @param nextUpdate the next update timestamp * @return true if valid or false */ private static boolean isValidityRange(Date currentTime, Date thisUpdate, Date nextUpdate) { long tolerableValidity = calculateTolerableVadility(thisUpdate, nextUpdate); return thisUpdate.getTime() - MAX_CLOCK_SKEW_IN_MILLISECONDS <= currentTime.getTime() && currentTime.getTime() <= nextUpdate.getTime() + tolerableValidity; } /** * SSD Processing Code */ private void processKeyUpdateDirective(String issuer, String ssd) { try { /** * Get unverified part of the JWT to extract issuer. * */ //PlainJWT jwt_unverified = PlainJWT.parse(ssd); SignedJWT jwt_signed = SignedJWT.parse(ssd); String jwt_issuer = (String) jwt_signed.getHeader().getCustomParam("ssd_iss"); String ssd_pubKey; if (!jwt_issuer.equals(issuer)) { LOGGER.debug("Issuer mismatch. Invalid SSD"); return; } if (jwt_issuer.equals("dep1")) { ssd_pubKey = ssdManager.getPubKey("dep1"); } else { ssd_pubKey = ssdManager.getPubKey("dep2"); } if (ssd_pubKey == null) { LOGGER.debug("Invalid SSD"); return; } String publicKeyContent = ssd_pubKey.replaceAll("\\n", "").replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", ""); KeyFactory kf = KeyFactory.getInstance("RSA"); X509EncodedKeySpec keySpecX509 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyContent)); RSAPublicKey rsaPubKey = (RSAPublicKey) kf.generatePublic(keySpecX509); /** * Verify signature of the JWT Token */ SignedJWT jwt_token_verified = SignedJWT.parse(ssd); JWSVerifier jwsVerifier = new RSASSAVerifier(rsaPubKey); try { if (jwt_token_verified.verify(jwsVerifier)) { /** * verify nbf time */ long cur_time = System.currentTimeMillis(); Date nbf = jwt_token_verified.getJWTClaimsSet().getNotBeforeTime(); //long nbf = jwt_token_verified.getJWTClaimsSet().getLongClaim("nbf"); //double nbf = jwt_token_verified.getJWTClaimsSet().getDoubleClaim("nbf"); if (cur_time < nbf.getTime()) { LOGGER.debug("The SSD token is not yet valid. Current time less than Not Before Time"); return; } float key_ver = Float.parseFloat(jwt_token_verified.getJWTClaimsSet().getStringClaim("keyVer")); if (key_ver <= ssdManager.getPubKeyVer(jwt_issuer)) { return; } ssdManager.updateKey(jwt_issuer, jwt_token_verified.getJWTClaimsSet().getStringClaim("pubKey"), key_ver); } } catch (Throwable ex) { LOGGER.debug("Failed to verify JWT Token"); throw ex; } } catch (Throwable ex) { LOGGER.debug("Failed to parse JWT Token, aborting"); } } private boolean processOCSPBypassSSD(String ocsp_ssd, OcspResponseCacheKey cid, String hostname) { try { /** * Get unverified part of the JWT to extract issuer. */ SignedJWT jwt_unverified = SignedJWT.parse(ocsp_ssd); String jwt_issuer = (String) jwt_unverified.getHeader().getCustomParam("ssd_iss"); String ssd_pubKey = null; if (jwt_issuer.equals("dep1")) { ssd_pubKey = ssdManager.getPubKey("dep1"); } else { ssd_pubKey = ssdManager.getPubKey("dep2"); } String publicKeyContent = ssd_pubKey.replaceAll("\\n", "").replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", ""); KeyFactory kf = KeyFactory.getInstance("RSA"); X509EncodedKeySpec keySpecX509 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyContent)); RSAPublicKey rsaPubKey = (RSAPublicKey) kf.generatePublic(keySpecX509); /** * Verify signature of the JWT Token * Verify time validity of the JWT Token (API does not do this) */ SignedJWT jwt_token_verified = SignedJWT.parse(ocsp_ssd); JWSVerifier jwsVerifier = new RSASSAVerifier(rsaPubKey); try { if (jwt_token_verified.verify(jwsVerifier)) { String sfc_endpoint = jwt_token_verified.getJWTClaimsSet().getStringClaim("sfcEndpoint"); String jwt_certid = jwt_token_verified.getJWTClaimsSet().getStringClaim("certId"); Date jwt_nbf = jwt_token_verified.getJWTClaimsSet().getNotBeforeTime(); Date jwt_exp = jwt_token_verified.getJWTClaimsSet().getExpirationTime(); long current_ts = System.currentTimeMillis(); if (current_ts < jwt_exp.getTime() && current_ts >= jwt_nbf.getTime()) { if (!sfc_endpoint.equals("*")) { /** * In case there are multiple hostnames * associated to the same account. The * code expects a space separated list * of all hostnames associated with this * account in sfcEndpoint field */ String [] splitString = sfc_endpoint.split("\\s+"); for (String s : splitString) { if (s.equals(hostname)) { return true; } } return false; } /** * No In Band token can have > 7 days validity */ if (jwt_exp.getTime() - jwt_nbf.getTime() > (7 * 24 * 60 * 60 * 1000)) { return false; } byte[] jwt_certid_dec = Base64.decodeBase64(jwt_certid); DLSequence jwt_rawCertId = (DLSequence) ASN1ObjectIdentifier.fromByteArray(jwt_certid_dec); ASN1Encodable[] jwt_rawCertIdArray = jwt_rawCertId.toArray(); byte[] issuerNameHashDer = ((DEROctetString) jwt_rawCertIdArray[1]).getEncoded(); byte[] issuerKeyHashDer = ((DEROctetString) jwt_rawCertIdArray[2]).getEncoded(); BigInteger serialNumber = ((ASN1Integer) jwt_rawCertIdArray[3]).getValue(); OcspResponseCacheKey k = new OcspResponseCacheKey( issuerNameHashDer, issuerKeyHashDer, serialNumber); if (k.equals(cid)) { LOGGER.debug("Found a Signed OCSP Bypass SSD for ceri id " + cid.toString()); return true; } LOGGER.debug("Found invalid OCSP bypass for cert id " + cid.toString()); return false; } } return false; } catch (Throwable ex) { LOGGER.debug("Failed to verify JWT Token"); throw ex; } } catch (Throwable ex) { LOGGER.debug("Failed to parse JWT Token, aborting"); return false; } } /** * OCSP Response Utils */ private String ocspResponseToB64(OCSPResp ocspResp) { if (ocspResp == null) { return null; } try { return Base64.encodeBase64String(ocspResp.getEncoded()); } catch (Throwable ex) { LOGGER.debug("Could not convert OCSP Response to Base64"); return null; } } private OCSPResp b64ToOCSPResp(String ocspRespB64) { try { return new OCSPResp(Base64.decodeBase64(ocspRespB64)); } catch (Throwable ex) { LOGGER.debug("Could not cover OCSP Response from Base64 to OCSPResp object"); return null; } } /** * OCSP response cache key object */ static class OcspResponseCacheKey { final byte[] nameHash; final byte[] keyHash; final BigInteger serialNumber; OcspResponseCacheKey(byte[] nameHash, byte[] keyHash, BigInteger serialNumber) { this.nameHash = nameHash; this.keyHash = keyHash; this.serialNumber = serialNumber; } public int hashCode() { int ret = Arrays.hashCode(this.nameHash) * 37; ret = ret * 10 + Arrays.hashCode(this.keyHash) * 37; ret = ret * 10 + this.serialNumber.hashCode(); return ret; } public boolean equals(Object obj) { if (!(obj instanceof OcspResponseCacheKey)) { return false; } OcspResponseCacheKey target = (OcspResponseCacheKey) obj; return Arrays.equals(this.nameHash, target.nameHash) && Arrays.equals(this.keyHash, target.keyHash) && this.serialNumber.equals(target.serialNumber); } public String toString() { return String.format( "OcspResponseCacheKey: NameHash: %s, KeyHash: %s, SerialNumber: %s", byteToHexString(nameHash), byteToHexString(keyHash), serialNumber.toString()); } } /** * SHA1 Digest Calculator used in OCSP Req. */ static class SHA1DigestCalculator implements DigestCalculator { private ByteArrayOutputStream bOut = new ByteArrayOutputStream(); public AlgorithmIdentifier getAlgorithmIdentifier() { return new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1); } public OutputStream getOutputStream() { return bOut; } public byte[] getDigest() { byte[] bytes = bOut.toByteArray(); bOut.reset(); Digest sha1 = new SHA1Digest(); sha1.update(bytes, 0, bytes.length); byte[] digest = new byte[sha1.getDigestSize()]; sha1.doFinal(digest, 0); return digest; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy