mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-12 18:25:51 +00:00
Extract loading of X.509 trust stores to TrustStoreLoader.
This commit is contained in:
parent
e7eec49671
commit
6f4315ed4d
@ -23,28 +23,20 @@ import com.google.bitcoin.script.ScriptBuilder;
|
|||||||
import com.google.bitcoin.uri.BitcoinURI;
|
import com.google.bitcoin.uri.BitcoinURI;
|
||||||
import com.google.bitcoin.utils.Threading;
|
import com.google.bitcoin.utils.Threading;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.base.Joiner;
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
import org.bitcoin.protocols.payments.Protos;
|
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.annotation.Nullable;
|
||||||
import javax.security.auth.x500.X500Principal;
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
import java.security.*;
|
import java.security.*;
|
||||||
import java.security.cert.*;
|
import java.security.cert.*;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
@ -80,7 +72,7 @@ import java.util.concurrent.Callable;
|
|||||||
public class PaymentSession {
|
public class PaymentSession {
|
||||||
private static ListeningExecutorService executor = Threading.THREAD_POOL;
|
private static ListeningExecutorService executor = Threading.THREAD_POOL;
|
||||||
private NetworkParameters params;
|
private NetworkParameters params;
|
||||||
private String trustStorePath;
|
private final TrustStoreLoader trustStoreLoader;
|
||||||
private Protos.PaymentRequest paymentRequest;
|
private Protos.PaymentRequest paymentRequest;
|
||||||
private Protos.PaymentDetails paymentDetails;
|
private Protos.PaymentDetails paymentDetails;
|
||||||
private BigInteger totalValue = BigInteger.ZERO;
|
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
|
* 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
|
* be used to verify the signature provided by the payment request. An exception is thrown by the future if the
|
||||||
* signature cannot be verified.
|
* signature cannot be verified.
|
||||||
* If trustStorePath is not null, the trust store used for PKI verification will be loaded from the given location
|
* If trustStoreLoader is null, the system default trust store is used.
|
||||||
* instead of using the system default trust store location.
|
|
||||||
*/
|
*/
|
||||||
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 {
|
throws PaymentRequestException {
|
||||||
String url = uri.getPaymentRequestUrl();
|
String url = uri.getPaymentRequestUrl();
|
||||||
if (url == null)
|
if (url == null)
|
||||||
throw new PaymentRequestException.InvalidPaymentRequestURL("No payment request URL (r= parameter) in BitcoinURI " + uri);
|
throw new PaymentRequestException.InvalidPaymentRequestURL("No payment request URL (r= parameter) in BitcoinURI " + uri);
|
||||||
try {
|
try {
|
||||||
return fetchPaymentRequest(new URI(url), verifyPki, trustStorePath);
|
return fetchPaymentRequest(new URI(url), verifyPki, trustStoreLoader);
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
throw new PaymentRequestException.InvalidPaymentRequestURL(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
|
* 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
|
* be used to verify the signature provided by the payment request. An exception is thrown by the future if the
|
||||||
* signature cannot be verified.
|
* signature cannot be verified.
|
||||||
* If trustStorePath is not null, the trust store used for PKI verification will be loaded from the given location
|
* If trustStoreLoader is null, the system default trust store is used.
|
||||||
* instead of using the system default trust store location.
|
|
||||||
*/
|
*/
|
||||||
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 {
|
throws PaymentRequestException {
|
||||||
if (url == null)
|
if (url == null)
|
||||||
throw new PaymentRequestException.InvalidPaymentRequestURL("null paymentRequestUrl");
|
throw new PaymentRequestException.InvalidPaymentRequestURL("null paymentRequestUrl");
|
||||||
try {
|
try {
|
||||||
return fetchPaymentRequest(new URI(url), verifyPki, trustStorePath);
|
return fetchPaymentRequest(new URI(url), verifyPki, trustStoreLoader);
|
||||||
} catch(URISyntaxException e) {
|
} catch(URISyntaxException e) {
|
||||||
throw new PaymentRequestException.InvalidPaymentRequestURL(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>() {
|
return executor.submit(new Callable<PaymentSession>() {
|
||||||
@Override
|
@Override
|
||||||
public PaymentSession call() throws Exception {
|
public PaymentSession call() throws Exception {
|
||||||
@ -189,7 +179,7 @@ public class PaymentSession {
|
|||||||
connection.setRequestProperty("Accept", "application/bitcoin-paymentrequest");
|
connection.setRequestProperty("Accept", "application/bitcoin-paymentrequest");
|
||||||
connection.setUseCaches(false);
|
connection.setUseCaches(false);
|
||||||
Protos.PaymentRequest paymentRequest = Protos.PaymentRequest.parseFrom(connection.getInputStream());
|
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.
|
* Verifies PKI by default.
|
||||||
*/
|
*/
|
||||||
public PaymentSession(Protos.PaymentRequest request) throws PaymentRequestException {
|
public PaymentSession(Protos.PaymentRequest request) throws PaymentRequestException {
|
||||||
parsePaymentRequest(request);
|
this(request, true, null);
|
||||||
verifyPki();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -208,19 +197,16 @@ public class PaymentSession {
|
|||||||
* If verifyPki is true, also validates the signature and throws an exception if it fails.
|
* If verifyPki is true, also validates the signature and throws an exception if it fails.
|
||||||
*/
|
*/
|
||||||
public PaymentSession(Protos.PaymentRequest request, boolean verifyPki) throws PaymentRequestException {
|
public PaymentSession(Protos.PaymentRequest request, boolean verifyPki) throws PaymentRequestException {
|
||||||
parsePaymentRequest(request);
|
this(request, verifyPki, null);
|
||||||
if (verifyPki)
|
|
||||||
verifyPki();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a PaymentSession from the provided {@link Protos.PaymentRequest}.
|
* 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 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
|
* If trustStoreLoader is null, the system default trust store is used.
|
||||||
* instead of using the system default trust store location.
|
|
||||||
*/
|
*/
|
||||||
public PaymentSession(Protos.PaymentRequest request, boolean verifyPki, @Nullable final String trustStorePath) throws PaymentRequestException {
|
public PaymentSession(Protos.PaymentRequest request, boolean verifyPki, @Nullable final TrustStoreLoader trustStoreLoader) throws PaymentRequestException {
|
||||||
this.trustStorePath = trustStorePath;
|
this.trustStoreLoader = trustStoreLoader != null ? trustStoreLoader : new TrustStoreLoader.DefaultTrustStoreLoader();
|
||||||
parsePaymentRequest(request);
|
parsePaymentRequest(request);
|
||||||
if (verifyPki)
|
if (verifyPki)
|
||||||
verifyPki();
|
verifyPki();
|
||||||
@ -450,7 +436,7 @@ public class PaymentSession {
|
|||||||
CertPath path = certificateFactory.generateCertPath(certs);
|
CertPath path = certificateFactory.generateCertPath(certs);
|
||||||
|
|
||||||
// Retrieves the most-trusted CAs from keystore.
|
// 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.
|
// Revocation not supported in the current version.
|
||||||
params.setRevocationEnabled(false);
|
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 {
|
private void parsePaymentRequest(Protos.PaymentRequest request) throws PaymentRequestException {
|
||||||
try {
|
try {
|
||||||
if (request == null)
|
if (request == null)
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,11 @@
|
|||||||
|
|
||||||
package com.google.bitcoin.protocols.payments;
|
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.CertificateParsingException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@ -64,4 +69,22 @@ public class X509Utils {
|
|||||||
return altName;
|
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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user