3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-07 14:54:15 +00:00

Extract loading of X.509 trust stores to TrustStoreLoader.

This commit is contained in:
Andreas Schildbach 2014-03-30 11:53:25 +02:00
parent e7eec49671
commit 6f4315ed4d
3 changed files with 149 additions and 86 deletions

View File

@ -23,28 +23,20 @@ import com.google.bitcoin.script.ScriptBuilder;
import com.google.bitcoin.uri.BitcoinURI;
import com.google.bitcoin.utils.Threading;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.bitcoin.protocols.payments.Protos;
import org.spongycastle.asn1.ASN1String;
import org.spongycastle.asn1.x500.AttributeTypeAndValue;
import org.spongycastle.asn1.x500.RDN;
import org.spongycastle.asn1.x500.X500Name;
import org.spongycastle.asn1.x500.style.RFC4519Style;
import javax.annotation.Nullable;
import javax.security.auth.x500.X500Principal;
import java.io.*;
import java.math.BigInteger;
import java.net.*;
import java.security.*;
import java.security.cert.*;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
@ -80,7 +72,7 @@ import java.util.concurrent.Callable;
public class PaymentSession {
private static ListeningExecutorService executor = Threading.THREAD_POOL;
private NetworkParameters params;
private String trustStorePath;
private final TrustStoreLoader trustStoreLoader;
private Protos.PaymentRequest paymentRequest;
private Protos.PaymentDetails paymentDetails;
private BigInteger totalValue = BigInteger.ZERO;
@ -123,16 +115,15 @@ public class PaymentSession {
* If verifyPki is specified and the payment request object specifies a PKI method, then the system trust store will
* be used to verify the signature provided by the payment request. An exception is thrown by the future if the
* signature cannot be verified.
* If trustStorePath is not null, the trust store used for PKI verification will be loaded from the given location
* instead of using the system default trust store location.
* If trustStoreLoader is null, the system default trust store is used.
*/
public static ListenableFuture<PaymentSession> createFromBitcoinUri(final BitcoinURI uri, final boolean verifyPki, @Nullable final String trustStorePath)
public static ListenableFuture<PaymentSession> createFromBitcoinUri(final BitcoinURI uri, final boolean verifyPki, @Nullable final TrustStoreLoader trustStoreLoader)
throws PaymentRequestException {
String url = uri.getPaymentRequestUrl();
if (url == null)
throw new PaymentRequestException.InvalidPaymentRequestURL("No payment request URL (r= parameter) in BitcoinURI " + uri);
try {
return fetchPaymentRequest(new URI(url), verifyPki, trustStorePath);
return fetchPaymentRequest(new URI(url), verifyPki, trustStoreLoader);
} catch (URISyntaxException e) {
throw new PaymentRequestException.InvalidPaymentRequestURL(e);
}
@ -167,21 +158,20 @@ public class PaymentSession {
* If the payment request object specifies a PKI method, then the system trust store will
* be used to verify the signature provided by the payment request. An exception is thrown by the future if the
* signature cannot be verified.
* If trustStorePath is not null, the trust store used for PKI verification will be loaded from the given location
* instead of using the system default trust store location.
* If trustStoreLoader is null, the system default trust store is used.
*/
public static ListenableFuture<PaymentSession> createFromUrl(final String url, final boolean verifyPki, @Nullable final String trustStorePath)
public static ListenableFuture<PaymentSession> createFromUrl(final String url, final boolean verifyPki, @Nullable final TrustStoreLoader trustStoreLoader)
throws PaymentRequestException {
if (url == null)
throw new PaymentRequestException.InvalidPaymentRequestURL("null paymentRequestUrl");
try {
return fetchPaymentRequest(new URI(url), verifyPki, trustStorePath);
return fetchPaymentRequest(new URI(url), verifyPki, trustStoreLoader);
} catch(URISyntaxException e) {
throw new PaymentRequestException.InvalidPaymentRequestURL(e);
}
}
private static ListenableFuture<PaymentSession> fetchPaymentRequest(final URI uri, final boolean verifyPki, @Nullable final String trustStorePath) {
private static ListenableFuture<PaymentSession> fetchPaymentRequest(final URI uri, final boolean verifyPki, @Nullable final TrustStoreLoader trustStoreLoader) {
return executor.submit(new Callable<PaymentSession>() {
@Override
public PaymentSession call() throws Exception {
@ -189,7 +179,7 @@ public class PaymentSession {
connection.setRequestProperty("Accept", "application/bitcoin-paymentrequest");
connection.setUseCaches(false);
Protos.PaymentRequest paymentRequest = Protos.PaymentRequest.parseFrom(connection.getInputStream());
return new PaymentSession(paymentRequest, verifyPki, trustStorePath);
return new PaymentSession(paymentRequest, verifyPki, trustStoreLoader);
}
});
}
@ -199,8 +189,7 @@ public class PaymentSession {
* Verifies PKI by default.
*/
public PaymentSession(Protos.PaymentRequest request) throws PaymentRequestException {
parsePaymentRequest(request);
verifyPki();
this(request, true, null);
}
/**
@ -208,19 +197,16 @@ public class PaymentSession {
* If verifyPki is true, also validates the signature and throws an exception if it fails.
*/
public PaymentSession(Protos.PaymentRequest request, boolean verifyPki) throws PaymentRequestException {
parsePaymentRequest(request);
if (verifyPki)
verifyPki();
this(request, verifyPki, null);
}
/**
* Creates a PaymentSession from the provided {@link Protos.PaymentRequest}.
* If verifyPki is true, also validates the signature and throws an exception if it fails.
* If trustStorePath is not null, the trust store used for PKI verification will be loaded from the given location
* instead of using the system default trust store location.
* If trustStoreLoader is null, the system default trust store is used.
*/
public PaymentSession(Protos.PaymentRequest request, boolean verifyPki, @Nullable final String trustStorePath) throws PaymentRequestException {
this.trustStorePath = trustStorePath;
public PaymentSession(Protos.PaymentRequest request, boolean verifyPki, @Nullable final TrustStoreLoader trustStoreLoader) throws PaymentRequestException {
this.trustStoreLoader = trustStoreLoader != null ? trustStoreLoader : new TrustStoreLoader.DefaultTrustStoreLoader();
parsePaymentRequest(request);
if (verifyPki)
verifyPki();
@ -450,7 +436,7 @@ public class PaymentSession {
CertPath path = certificateFactory.generateCertPath(certs);
// Retrieves the most-trusted CAs from keystore.
PKIXParameters params = new PKIXParameters(createKeyStore(trustStorePath));
PKIXParameters params = new PKIXParameters(trustStoreLoader.getKeyStore());
// Revocation not supported in the current version.
params.setRevocationEnabled(false);
@ -508,63 +494,6 @@ public class PaymentSession {
}
}
private KeyStore createKeyStore(@Nullable String path)
throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
String keyStoreType = KeyStore.getDefaultType();
char[] defaultPassword = "changeit".toCharArray();
if (path != null) {
// If the user provided path, only try to load the keystore at that path.
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
FileInputStream is = new FileInputStream(path);
keyStore.load(is, defaultPassword);
return keyStore;
}
try {
// Check if we are on Android.
Class version = Class.forName("android.os.Build$VERSION");
// Build.VERSION_CODES.ICE_CREAM_SANDWICH is 14.
if (version.getDeclaredField("SDK_INT").getInt(version) >= 14) {
// After ICS, Android provided this nice method for loading the keystore,
// so we don't have to specify the location explicitly.
KeyStore keystore = KeyStore.getInstance("AndroidCAStore");
keystore.load(null, null);
return keystore;
} else {
keyStoreType = "BKS";
path = System.getProperty("java.home") + "/etc/security/cacerts.bks".replace('/', File.separatorChar);
}
} catch (ClassNotFoundException e) {
// NOP. android.os.Build is not present, so we are not on Android. Fall through.
} catch (NoSuchFieldException e) {
throw new RuntimeException(e); // Should never happen.
} catch (IllegalAccessException e) {
throw new RuntimeException(e); // Should never happen.
}
if (path == null) {
path = System.getProperty("javax.net.ssl.trustStore");
}
if (path == null) {
return loadFallbackStore(defaultPassword);
}
try {
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
FileInputStream is = new FileInputStream(path);
keyStore.load(is, defaultPassword);
return keyStore;
} catch (FileNotFoundException e) {
// If we failed to find a system trust store, load our own fallback trust store. This can fail on Android
// but we should never reach it there.
return loadFallbackStore(defaultPassword);
}
}
private KeyStore loadFallbackStore(char[] defaultPassword) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
KeyStore keyStore = KeyStore.getInstance("JKS");
InputStream is = getClass().getResourceAsStream("cacerts");
keyStore.load(is, defaultPassword);
return keyStore;
}
private void parsePaymentRequest(Protos.PaymentRequest request) throws PaymentRequestException {
try {
if (request == null)

View File

@ -0,0 +1,111 @@
/**
* Copyright 2014 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.bitcoin.protocols.payments;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import javax.annotation.Nonnull;
public interface TrustStoreLoader {
KeyStore getKeyStore() throws FileNotFoundException, KeyStoreException;
static final String DEFAULT_KEYSTORE_TYPE = KeyStore.getDefaultType();
static final String DEFAULT_KEYSTORE_PASSWORD = "changeit";
public class DefaultTrustStoreLoader implements TrustStoreLoader {
@Override
public KeyStore getKeyStore() throws FileNotFoundException, KeyStoreException {
String keystorePath = null;
String keystoreType = DEFAULT_KEYSTORE_TYPE;
try {
// Check if we are on Android.
Class<?> version = Class.forName("android.os.Build$VERSION");
// Build.VERSION_CODES.ICE_CREAM_SANDWICH is 14.
if (version.getDeclaredField("SDK_INT").getInt(version) >= 14) {
return loadIcsKeyStore();
} else {
keystoreType = "BKS";
keystorePath = System.getProperty("java.home")
+ "/etc/security/cacerts.bks".replace('/', File.separatorChar);
}
} catch (ClassNotFoundException e) {
// NOP. android.os.Build is not present, so we are not on Android. Fall through.
} catch (NoSuchFieldException e) {
throw new RuntimeException(e); // Should never happen.
} catch (IllegalAccessException e) {
throw new RuntimeException(e); // Should never happen.
}
if (keystorePath == null) {
keystorePath = System.getProperty("javax.net.ssl.trustStore");
}
if (keystorePath == null) {
return loadFallbackStore();
}
try {
return X509Utils.loadKeyStore(keystoreType, DEFAULT_KEYSTORE_PASSWORD,
new FileInputStream(keystorePath));
} catch (FileNotFoundException e) {
// If we failed to find a system trust store, load our own fallback trust store. This can fail on
// Android but we should never reach it there.
return loadFallbackStore();
}
}
private KeyStore loadIcsKeyStore() throws KeyStoreException {
try {
// After ICS, Android provided this nice method for loading the keystore,
// so we don't have to specify the location explicitly.
KeyStore keystore = KeyStore.getInstance("AndroidCAStore");
keystore.load(null, null);
return keystore;
} catch (IOException x) {
throw new KeyStoreException(x);
} catch (GeneralSecurityException x) {
throw new KeyStoreException(x);
}
}
private KeyStore loadFallbackStore() throws FileNotFoundException, KeyStoreException {
return X509Utils.loadKeyStore("JKS", DEFAULT_KEYSTORE_PASSWORD, getClass().getResourceAsStream("cacerts"));
}
}
public class FileTrustStoreLoader implements TrustStoreLoader {
private final File path;
public FileTrustStoreLoader(@Nonnull File path) throws FileNotFoundException {
if (!path.exists())
throw new FileNotFoundException(path.toString());
this.path = path;
}
@Override
public KeyStore getKeyStore() throws FileNotFoundException, KeyStoreException {
return X509Utils.loadKeyStore(DEFAULT_KEYSTORE_TYPE, DEFAULT_KEYSTORE_PASSWORD, new FileInputStream(path));
}
}
}

View File

@ -16,6 +16,11 @@
package com.google.bitcoin.protocols.payments;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.Collection;
@ -64,4 +69,22 @@ public class X509Utils {
return altName;
}
}
public static @Nonnull KeyStore loadKeyStore(@Nonnull String keystoreType, @Nullable String keystorePassword, @Nonnull InputStream is)
throws KeyStoreException {
try {
KeyStore keystore = KeyStore.getInstance(keystoreType);
keystore.load(is, keystorePassword != null ? keystorePassword.toCharArray() : null);
return keystore;
} catch (IOException x) {
throw new KeyStoreException(x);
} catch (GeneralSecurityException x) {
throw new KeyStoreException(x);
} finally {
try {
is.close();
} catch (IOException x) {
}
}
}
}