mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-23 20:52:03 +00:00
GP-1769 improved Ghidra Server SSH key use and error handling. Replaced use of ganymed-ssh2 library with Bouncy Castle library use.
This commit is contained in:
parent
1a1d06b749
commit
8c209ce76e
@ -15,7 +15,7 @@
|
||||
*/
|
||||
package ghidra.server.security;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
@ -24,13 +24,25 @@ import javax.security.auth.callback.NameCallback;
|
||||
import javax.security.auth.login.FailedLoginException;
|
||||
import javax.security.auth.login.LoginException;
|
||||
|
||||
import ch.ethz.ssh2.signature.*;
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.digests.SHA1Digest;
|
||||
import org.bouncycastle.crypto.params.DSAKeyParameters;
|
||||
import org.bouncycastle.crypto.params.RSAKeyParameters;
|
||||
import org.bouncycastle.crypto.signers.*;
|
||||
import org.bouncycastle.util.Strings;
|
||||
|
||||
import ghidra.framework.remote.GhidraPrincipal;
|
||||
import ghidra.framework.remote.SSHSignatureCallback;
|
||||
import ghidra.framework.remote.security.SSHKeyManager;
|
||||
import ghidra.net.*;
|
||||
import ghidra.server.UserManager;
|
||||
|
||||
/**
|
||||
* <code>SSHAuthenticationModule</code> provides SHA1-RSA and SHA1-DSA signature-based authentication
|
||||
* support using SSH public/private keys where user public keys are made available to the server.
|
||||
* Module makes use of a {@link SSHSignatureCallback} object to convey the signature request to a
|
||||
* client.
|
||||
*/
|
||||
public class SSHAuthenticationModule {
|
||||
|
||||
private static final long MAX_TOKEN_TIME = 10000;
|
||||
@ -86,13 +98,49 @@ public class SSHAuthenticationModule {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read UInt32 from SSH-encoded buffer.
|
||||
* (modeled after org.bouncycastle.crypto.util.SSHBuffer.readU32())
|
||||
* @param in data input stream
|
||||
* @return integer value
|
||||
* @throws IOException if IO error occurs reading input stream or inadequate
|
||||
* bytes are available.
|
||||
*/
|
||||
private static int sshBufferReadUInt32(ByteArrayInputStream in) throws IOException {
|
||||
byte[] tmp = in.readNBytes(4);
|
||||
if (tmp.length != 4) {
|
||||
throw new IOException("insufficient data");
|
||||
}
|
||||
int value = (tmp[0] & 0xff) << 24;
|
||||
value |= (tmp[1] & 0xff) << 16;
|
||||
value |= (tmp[2] & 0xff) << 8;
|
||||
value |= (tmp[3] & 0xff);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read block of data from SSH-encoded buffer.
|
||||
* (modeled after org.bouncycastle.crypto.util.SSHBuffer.readBlock())
|
||||
* @param in data input stream
|
||||
* @return byte array
|
||||
* @throws IOException if IO error occurs reading input stream or inadequate
|
||||
* bytes are available.
|
||||
*/
|
||||
private static byte[] sshBufferReadBlock(ByteArrayInputStream in) throws IOException {
|
||||
int len = sshBufferReadUInt32(in);
|
||||
if (len <= 0 || len > in.available()) {
|
||||
throw new IOException("insufficient data");
|
||||
}
|
||||
return in.readNBytes(len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete the authentication process
|
||||
* @param userMgr Ghidra server user manager
|
||||
* @param subject unauthenticated user ID (must be used if name callback not provided/allowed)
|
||||
* @param callbacks authentication callbacks
|
||||
* @return authenticated user ID (may come from callbacks)
|
||||
* @throws LoginException
|
||||
* @throws LoginException if authentication failure occurs
|
||||
*/
|
||||
public String authenticate(UserManager userMgr, Subject subject, Callback[] callbacks)
|
||||
throws LoginException {
|
||||
@ -162,23 +210,41 @@ public class SSHAuthenticationModule {
|
||||
}
|
||||
|
||||
try {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(sigBytes);
|
||||
String keyAlgorithm = Strings.fromByteArray(sshBufferReadBlock(in));
|
||||
byte[] sig = sshBufferReadBlock(in);
|
||||
if (in.available() != 0) {
|
||||
throw new FailedLoginException("SSH Signature contained extra bytes");
|
||||
}
|
||||
|
||||
Object sshPublicKey = SSHKeyManager.getSSHPublicKey(sshPublicKeyFile);
|
||||
CipherParameters cipherParams = SSHKeyManager.getSSHPublicKey(sshPublicKeyFile);
|
||||
|
||||
if (sshPublicKey instanceof RSAPublicKey) {
|
||||
RSAPublicKey key = (RSAPublicKey) sshPublicKey;
|
||||
RSASignature rsaSignature = RSASHA1Verify.decodeSSHRSASignature(sigBytes);
|
||||
if (!RSASHA1Verify.verifySignature(token, rsaSignature, key)) {
|
||||
if (cipherParams instanceof RSAKeyParameters) {
|
||||
if (!"ssh-rsa".equals(keyAlgorithm)) {
|
||||
throw new FailedLoginException("Invalid SSH RSA Signature");
|
||||
}
|
||||
RSADigestSigner signer = new RSADigestSigner(new SHA1Digest());
|
||||
signer.init(false, cipherParams);
|
||||
signer.update(token, 0, token.length);
|
||||
if (!signer.verifySignature(sig)) {
|
||||
throw new FailedLoginException("Incorrect signature");
|
||||
}
|
||||
}
|
||||
else if (sshPublicKey instanceof DSAPublicKey) {
|
||||
DSAPublicKey key = (DSAPublicKey) sshPublicKey;
|
||||
DSASignature dsaSignature = DSASHA1Verify.decodeSSHDSASignature(sigBytes);
|
||||
if (!DSASHA1Verify.verifySignature(token, dsaSignature, key)) {
|
||||
else if (cipherParams instanceof DSAKeyParameters) {
|
||||
if (!"ssh-dss".equals(keyAlgorithm)) {
|
||||
throw new FailedLoginException("Invalid SSH DSA Signature");
|
||||
}
|
||||
DSADigestSigner signer = new DSADigestSigner(new DSASigner(), new SHA1Digest());
|
||||
signer.init(false, cipherParams);
|
||||
signer.update(token, 0, token.length);
|
||||
if (!signer.verifySignature(sig)) {
|
||||
throw new FailedLoginException("Incorrect signature");
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new FailedLoginException("Unsupported public key");
|
||||
}
|
||||
|
||||
}
|
||||
catch (LoginException e) {
|
||||
throw e;
|
||||
|
@ -1 +0,0 @@
|
||||
MODULE FILE LICENSE: lib/ganymed-ssh2-262.jar Christian Plattner
|
@ -26,7 +26,5 @@ dependencies {
|
||||
api project(':Generic')
|
||||
api project(':DB')
|
||||
api project(':Docking')
|
||||
api "ch.ethz.ganymed:ganymed-ssh2:262@jar"
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
##VERSION: 2.0
|
||||
##MODULE IP: Christian Plattner
|
||||
Module.manifest||GHIDRA||||END|
|
||||
src/main/java/ghidra/framework/client/package.html||GHIDRA||reviewed||END|
|
||||
src/main/java/ghidra/framework/store/db/package.html||GHIDRA||reviewed||END|
|
||||
|
@ -19,6 +19,7 @@ import java.awt.Component;
|
||||
import java.io.*;
|
||||
import java.net.Authenticator;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.security.InvalidKeyException;
|
||||
|
||||
import javax.security.auth.callback.*;
|
||||
|
||||
@ -86,29 +87,23 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
|
||||
ClientUtil.setClientAuthenticator(authenticator);
|
||||
|
||||
if (keystorePath != null) {
|
||||
File f = new File(keystorePath);
|
||||
if (!f.exists()) {
|
||||
File keyfile = new File(keystorePath);
|
||||
if (!keyfile.exists()) {
|
||||
// If keystorePath file not found - try accessing as SSH key resource stream
|
||||
// InputStream keyIn = ResourceManager.getResourceAsStream(keystorePath);
|
||||
InputStream keyIn = keystorePath.getClass().getResourceAsStream(keystorePath);
|
||||
if (keyIn != null) {
|
||||
try {
|
||||
sshPrivateKey = SSHKeyManager.getSSHPrivateKey(keyIn);
|
||||
Msg.info(HeadlessClientAuthenticator.class,
|
||||
"Loaded SSH key: " + keystorePath);
|
||||
return;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(HeadlessClientAuthenticator.class,
|
||||
"Failed to open keystore for SSH use: " + keystorePath, e);
|
||||
throw new IOException("Failed to parse keystore: " + keystorePath);
|
||||
}
|
||||
finally {
|
||||
try (InputStream keyIn =
|
||||
HeadlessClientAuthenticator.class.getResourceAsStream(keystorePath)) {
|
||||
if (keyIn != null) {
|
||||
try {
|
||||
keyIn.close();
|
||||
sshPrivateKey = SSHKeyManager.getSSHPrivateKey(keyIn);
|
||||
Msg.info(HeadlessClientAuthenticator.class,
|
||||
"Loaded SSH key: " + keystorePath);
|
||||
return;
|
||||
}
|
||||
catch (IOException e) {
|
||||
// ignore
|
||||
catch (Exception e) {
|
||||
Msg.error(HeadlessClientAuthenticator.class,
|
||||
"Failed to open keystore for SSH use: " + keystorePath, e);
|
||||
throw new IOException("Failed to parse keystore: " + keystorePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -116,24 +111,27 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
|
||||
throw new FileNotFoundException("Keystore not found: " + keystorePath);
|
||||
}
|
||||
|
||||
boolean success = false;
|
||||
try {
|
||||
sshPrivateKey = SSHKeyManager.getSSHPrivateKey(new File(keystorePath));
|
||||
sshPrivateKey = SSHKeyManager.getSSHPrivateKey(keyfile);
|
||||
success = true;
|
||||
Msg.info(HeadlessClientAuthenticator.class, "Loaded SSH key: " + keystorePath);
|
||||
}
|
||||
catch (IOException e) {
|
||||
try {
|
||||
// try keystore as PKI keystore if failed as SSH keystore
|
||||
ApplicationKeyManagerFactory.setKeyStore(keystorePath, false);
|
||||
Msg.info(HeadlessClientAuthenticator.class, "Loaded PKI key: " + keystorePath);
|
||||
}
|
||||
catch (IOException e1) {
|
||||
Msg.error(HeadlessClientAuthenticator.class,
|
||||
"Failed to open keystore for PKI use: " + keystorePath, e1);
|
||||
Msg.error(HeadlessClientAuthenticator.class,
|
||||
"Failed to open keystore for SSH use: " + keystorePath, e);
|
||||
throw new IOException("Failed to parse keystore: " + keystorePath);
|
||||
catch (InvalidKeyException e) { // keyfile is not a valid SSH provate key format
|
||||
// does not appear to be an SSH private key - try PKI keystore parse
|
||||
if (ApplicationKeyManagerFactory.setKeyStore(keystorePath, false)) {
|
||||
success = true;
|
||||
Msg.info(HeadlessClientAuthenticator.class,
|
||||
"Loaded PKI keystore: " + keystorePath);
|
||||
}
|
||||
}
|
||||
catch (IOException e) { // SSH key parse failure only
|
||||
Msg.error(HeadlessClientAuthenticator.class,
|
||||
"Failed to open keystore for SSH use: " + keystorePath, e);
|
||||
}
|
||||
if (!success) {
|
||||
throw new IOException("Failed to parse keystore: " + keystorePath);
|
||||
}
|
||||
}
|
||||
else {
|
||||
sshPrivateKey = null;
|
||||
|
@ -15,14 +15,17 @@
|
||||
*/
|
||||
package ghidra.framework.remote;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.security.SecureRandom;
|
||||
import java.io.*;
|
||||
|
||||
import javax.security.auth.callback.Callback;
|
||||
|
||||
import ch.ethz.ssh2.signature.*;
|
||||
import generic.random.SecureRandomFactory;
|
||||
import org.bouncycastle.crypto.CryptoException;
|
||||
import org.bouncycastle.crypto.DataLengthException;
|
||||
import org.bouncycastle.crypto.digests.SHA1Digest;
|
||||
import org.bouncycastle.crypto.params.DSAKeyParameters;
|
||||
import org.bouncycastle.crypto.params.RSAKeyParameters;
|
||||
import org.bouncycastle.crypto.signers.*;
|
||||
import org.bouncycastle.util.Strings;
|
||||
|
||||
/**
|
||||
* <code>SSHSignatureCallback</code> provides a Callback implementation used
|
||||
@ -45,6 +48,7 @@ public class SSHSignatureCallback implements Callback, Serializable {
|
||||
/**
|
||||
* Construct callback with a random token to be signed by the client.
|
||||
* @param token random bytes to be signed
|
||||
* @param serverSignature server signature of token (using server PKI)
|
||||
*/
|
||||
public SSHSignatureCallback(byte[] token, byte[] serverSignature) {
|
||||
this.token = token;
|
||||
@ -66,6 +70,7 @@ public class SSHSignatureCallback implements Callback, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the server signature of token (using server PKI)
|
||||
* @return the server's signature of the token bytes.
|
||||
*/
|
||||
public byte[] getServerSignature() {
|
||||
@ -80,28 +85,77 @@ public class SSHSignatureCallback implements Callback, Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign this challenge with the specified SSH private key.
|
||||
* @param sshPrivateKey RSAPrivateKey or DSAPrivateKey
|
||||
* @throws IOException if signature generation failed
|
||||
* @see RSAPrivateKey
|
||||
* @see DSAPrivateKey
|
||||
* Write UInt32 to an SSH-encoded buffer.
|
||||
* (modeled after org.bouncycastle.crypto.util.SSHBuilder.u32(int))
|
||||
* @param value integer value
|
||||
* @param out data output stream
|
||||
*/
|
||||
public void sign(Object sshPrivateKey) throws IOException {
|
||||
if (sshPrivateKey instanceof RSAPrivateKey) {
|
||||
RSAPrivateKey key = (RSAPrivateKey) sshPrivateKey;
|
||||
// TODO: verify correct key by using accepted public key fingerprint
|
||||
RSASignature rsaSignature = RSASHA1Verify.generateSignature(token, key);
|
||||
signature = RSASHA1Verify.encodeSSHRSASignature(rsaSignature);
|
||||
private static void sshBuilderWriteUInt32(int value, ByteArrayOutputStream out) {
|
||||
byte[] tmp = new byte[4];
|
||||
tmp[0] = (byte) ((value >>> 24) & 0xff);
|
||||
tmp[1] = (byte) ((value >>> 16) & 0xff);
|
||||
tmp[2] = (byte) ((value >>> 8) & 0xff);
|
||||
tmp[3] = (byte) (value & 0xff);
|
||||
out.writeBytes(tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write byte array to an SSH-encoded buffer.
|
||||
* (modeled after org.bouncycastle.crypto.util.SSHBuilder.writeBlock(byte[])
|
||||
* @param value byte array
|
||||
* @param out data output stream
|
||||
*/
|
||||
private static void sshBuilderWriteBlock(byte[] value, ByteArrayOutputStream out) {
|
||||
sshBuilderWriteUInt32(value.length, out);
|
||||
out.writeBytes(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write string to an SSH-encoded buffer.
|
||||
* (modeled after org.bouncycastle.crypto.util.SSHBuilder.writeString(String)
|
||||
* @param str string data
|
||||
* @param out data output stream
|
||||
*/
|
||||
private static void sshBuilderWriteString(String str, ByteArrayOutputStream out) {
|
||||
sshBuilderWriteBlock(Strings.toByteArray(str), out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign this challenge with the specified SSH private key.
|
||||
* @param privateKeyParameters SSH private key parameters
|
||||
* ({@link RSAKeyParameters} or {@link RSAKeyParameters})
|
||||
* @throws IOException if signature generation failed
|
||||
*/
|
||||
public void sign(Object privateKeyParameters) throws IOException {
|
||||
try {
|
||||
// NOTE: Signature is formatted consistent with legacy implementation
|
||||
// for backward compatibility
|
||||
if (privateKeyParameters instanceof RSAKeyParameters) {
|
||||
RSAKeyParameters cipherParams = (RSAKeyParameters) privateKeyParameters;
|
||||
RSADigestSigner signer = new RSADigestSigner(new SHA1Digest());
|
||||
signer.init(true, cipherParams);
|
||||
signer.update(token, 0, token.length);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
sshBuilderWriteString("ssh-rsa", out);
|
||||
sshBuilderWriteBlock(signer.generateSignature(), out);
|
||||
signature = out.toByteArray();
|
||||
}
|
||||
else if (privateKeyParameters instanceof DSAKeyParameters) {
|
||||
DSAKeyParameters cipherParams = (DSAKeyParameters) privateKeyParameters;
|
||||
DSADigestSigner signer = new DSADigestSigner(new DSASigner(), new SHA1Digest());
|
||||
signer.init(true, cipherParams);
|
||||
signer.update(token, 0, token.length);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
sshBuilderWriteString("ssh-dss", out);
|
||||
sshBuilderWriteBlock(signer.generateSignature(), out);
|
||||
signature = out.toByteArray();
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported SSH private key");
|
||||
}
|
||||
}
|
||||
else if (sshPrivateKey instanceof DSAPrivateKey) {
|
||||
DSAPrivateKey key = (DSAPrivateKey) sshPrivateKey;
|
||||
// TODO: verify correct key by using accepted public key fingerprint
|
||||
SecureRandom random = SecureRandomFactory.getSecureRandom();
|
||||
DSASignature dsaSignature = DSASHA1Verify.generateSignature(token, key, random);
|
||||
signature = DSASHA1Verify.encodeSSHDSASignature(dsaSignature);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported SSH private key");
|
||||
catch (DataLengthException | CryptoException e) {
|
||||
throw new IOException("Cannot generate SSH signature: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -16,26 +15,30 @@
|
||||
*/
|
||||
package ghidra.framework.remote.security;
|
||||
|
||||
import ghidra.security.KeyStorePasswordProvider;
|
||||
|
||||
import java.io.*;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Security;
|
||||
import java.util.Arrays;
|
||||
|
||||
import ch.ethz.ssh2.crypto.Base64;
|
||||
import ch.ethz.ssh2.crypto.PEMDecoder;
|
||||
import ch.ethz.ssh2.signature.*;
|
||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.params.DSAKeyParameters;
|
||||
import org.bouncycastle.crypto.params.RSAKeyParameters;
|
||||
import org.bouncycastle.crypto.util.OpenSSHPublicKeyUtil;
|
||||
import org.bouncycastle.crypto.util.PrivateKeyFactory;
|
||||
import org.bouncycastle.openssl.*;
|
||||
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
|
||||
import org.bouncycastle.util.encoders.Base64;
|
||||
|
||||
import ghidra.security.KeyStorePasswordProvider;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
public class SSHKeyManager {
|
||||
|
||||
// private static final String DEFAULT_KEYSTORE_PATH =
|
||||
// System.getProperty("user.home") + File.separator + ".ssh/id_rsa";
|
||||
//
|
||||
// /**
|
||||
// * Preference name for the SSH key file paths
|
||||
// */
|
||||
// private static final String SSH_KEYSTORE_PROPERTY = "ghidra.sshKeyFile";
|
||||
|
||||
// The public key file is derived by adding this extension to the key store filename
|
||||
//private static final String SSH_PUBLIC_KEY_EXT1 = ".pub";
|
||||
static {
|
||||
// For JcaPEMKeyConverter().setProvider("BC")
|
||||
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
|
||||
}
|
||||
|
||||
private static KeyStorePasswordProvider passwordProvider;
|
||||
|
||||
@ -45,148 +48,143 @@ public class SSHKeyManager {
|
||||
|
||||
/**
|
||||
* Set PKI protected keystore password provider
|
||||
* @param provider
|
||||
* @param provider key store password provider
|
||||
*/
|
||||
public static synchronized void setProtectedKeyStorePasswordProvider(
|
||||
KeyStorePasswordProvider provider) {
|
||||
passwordProvider = provider;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Return the SSH private key for the current user. The default ~/.ssh/id_rsa file
|
||||
// * will be used unless the System property <i>ghidra.sshKeyFile</i> has been set.
|
||||
// * If the corresponding key file is encrypted the currently installed password
|
||||
// * provider will be used to obtain the decrypt password.
|
||||
// * @return RSAPrivateKey or DSAPrivateKey
|
||||
// * @throws FileNotFoundException key file not found
|
||||
// * @throws IOException if key file not found or key parse failed
|
||||
// * @see RSAPrivateKey
|
||||
// * @see DSAPrivateKey
|
||||
// */
|
||||
// public static Object getUsersSSHPrivateKey() throws IOException {
|
||||
//
|
||||
// String privateKeyStorePath = System.getProperty(SSH_KEYSTORE_PROPERTY);
|
||||
// if (privateKeyStorePath == null) {
|
||||
// privateKeyStorePath = DEFAULT_KEYSTORE_PATH;
|
||||
// }
|
||||
//
|
||||
// return getSSHPrivateKey(new File(privateKeyStorePath));
|
||||
// }
|
||||
|
||||
/**
|
||||
* Return the SSH private key corresponding to the specified key file.
|
||||
* If the specified key file is encrypted the currently installed password
|
||||
* provider will be used to obtain the decrypt password.
|
||||
* @param sshPrivateKeyFile
|
||||
* @return RSAPrivateKey or DSAPrivateKey
|
||||
* @param sshPrivateKeyFile private ssh key file
|
||||
* @return private key cipher parameters ({@link RSAKeyParameters} or {@link DSAKeyParameters})
|
||||
* @throws FileNotFoundException key file not found
|
||||
* @throws IOException if key file not found or key parse failed
|
||||
* @see RSAPrivateKey
|
||||
* @see DSAPrivateKey
|
||||
* @throws InvalidKeyException if key is not an SSH private key (i.e., PEM format)
|
||||
*/
|
||||
public static Object getSSHPrivateKey(File sshPrivateKeyFile) throws IOException {
|
||||
public static CipherParameters getSSHPrivateKey(File sshPrivateKeyFile)
|
||||
throws InvalidKeyException, IOException {
|
||||
|
||||
if (!sshPrivateKeyFile.isFile()) {
|
||||
throw new FileNotFoundException("SSH private key file not found: " + sshPrivateKeyFile);
|
||||
}
|
||||
|
||||
InputStream keyIn = new FileInputStream(sshPrivateKeyFile);
|
||||
try {
|
||||
try (InputStream keyIn = new FileInputStream(sshPrivateKeyFile)) {
|
||||
return getSSHPrivateKey(keyIn, sshPrivateKeyFile.getAbsolutePath());
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
keyIn.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SSH private key corresponding to the specified key input stream.
|
||||
* If the specified key is encrypted the currently installed password
|
||||
* provider will be used to obtain the decrypt password.
|
||||
* @param sshPrivateKeyIn
|
||||
* @return RSAPrivateKey or DSAPrivateKey
|
||||
* @param sshPrivateKeyIn private ssh key resource input stream
|
||||
* @return private key cipher parameters ({@link RSAKeyParameters} or {@link DSAKeyParameters})
|
||||
* @throws FileNotFoundException key file not found
|
||||
* @throws IOException if key file not found or key parse failed
|
||||
* @see RSAPrivateKey
|
||||
* @see DSAPrivateKey
|
||||
* @throws InvalidKeyException if key is not an SSH private key (i.e., PEM format)
|
||||
*/
|
||||
public static Object getSSHPrivateKey(InputStream sshPrivateKeyIn) throws IOException {
|
||||
public static CipherParameters getSSHPrivateKey(InputStream sshPrivateKeyIn)
|
||||
throws InvalidKeyException, IOException {
|
||||
return getSSHPrivateKey(sshPrivateKeyIn, "Protected SSH Key");
|
||||
}
|
||||
|
||||
private static Object getSSHPrivateKey(InputStream sshPrivateKeyIn, String srcName)
|
||||
throws IOException {
|
||||
private static CipherParameters getSSHPrivateKey(InputStream sshPrivateKeyIn, String srcName)
|
||||
throws InvalidKeyException, IOException {
|
||||
|
||||
boolean isEncrypted = false;
|
||||
StringBuffer keyBuf = new StringBuffer();
|
||||
BufferedReader r = new BufferedReader(new InputStreamReader(sshPrivateKeyIn));
|
||||
String line;
|
||||
while ((line = r.readLine()) != null) {
|
||||
if (line.startsWith("Proc-Type:")) {
|
||||
isEncrypted = (line.indexOf("ENCRYPTED") > 0);
|
||||
try (BufferedReader r = new BufferedReader(new InputStreamReader(sshPrivateKeyIn))) {
|
||||
boolean checkKeyFormat = true;
|
||||
String line;
|
||||
while ((line = r.readLine()) != null) {
|
||||
if (checkKeyFormat) {
|
||||
if (!line.startsWith("-----BEGIN ") || line.indexOf(" KEY-----") < 0) {
|
||||
throw new InvalidKeyException("Invalid SSH Private Key");
|
||||
}
|
||||
if (!line.startsWith("-----BEGIN RSA PRIVATE KEY-----") &&
|
||||
!line.startsWith("-----BEGIN DSA PRIVATE KEY-----")) {
|
||||
Msg.error(SSHKeyManager.class,
|
||||
"Unsupported SSH Key Format (see svrREADME.html)");
|
||||
throw new IOException("Unsupported SSH Private Key");
|
||||
}
|
||||
checkKeyFormat = false;
|
||||
}
|
||||
if (keyBuf.length() != 0) {
|
||||
keyBuf.append('\n');
|
||||
}
|
||||
keyBuf.append(line);
|
||||
}
|
||||
keyBuf.append(line);
|
||||
keyBuf.append('\n');
|
||||
}
|
||||
r.close();
|
||||
|
||||
String password = null;
|
||||
if (isEncrypted) {
|
||||
char[] pwd = passwordProvider.getKeyStorePassword(srcName, false);
|
||||
if (pwd == null) {
|
||||
throw new IOException("Password required to open SSH private keystore");
|
||||
}
|
||||
// Don't like using String for password - but API doesn't give us a choice
|
||||
password = new String(pwd);
|
||||
}
|
||||
|
||||
return PEMDecoder.decode(keyBuf.toString().toCharArray(), password);
|
||||
char[] password = null;
|
||||
try (Reader r = new StringReader(keyBuf.toString())) {
|
||||
|
||||
PEMParser pemParser = new PEMParser(r);
|
||||
Object object = pemParser.readObject();
|
||||
|
||||
PrivateKeyInfo privateKeyInfo;
|
||||
if (object instanceof PEMEncryptedKeyPair) {
|
||||
|
||||
password = passwordProvider.getKeyStorePassword(srcName, false);
|
||||
if (password == null) {
|
||||
throw new IOException("Password required to open SSH private keystore");
|
||||
}
|
||||
|
||||
// Encrypted key - we will use provided password
|
||||
PEMEncryptedKeyPair ckp = (PEMEncryptedKeyPair) object;
|
||||
PEMDecryptorProvider decProv =
|
||||
new JcePEMDecryptorProviderBuilder().build(password);
|
||||
privateKeyInfo = ckp.decryptKeyPair(decProv).getPrivateKeyInfo();
|
||||
}
|
||||
else {
|
||||
// Unencrypted key - no password needed
|
||||
PEMKeyPair ukp = (PEMKeyPair) object;
|
||||
privateKeyInfo = ukp.getPrivateKeyInfo();
|
||||
}
|
||||
return PrivateKeyFactory.createKey(privateKeyInfo);
|
||||
}
|
||||
finally {
|
||||
if (password != null) {
|
||||
Arrays.fill(password, (char) 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to instantiate an SSH public key from the specified file
|
||||
* which contains a single public key.
|
||||
* @param sshPublicKeyFile
|
||||
* @return RSAPublicKey or DSAPublicKey
|
||||
* @param sshPublicKeyFile public ssh key file
|
||||
* @return public key cipher parameters {@link RSAKeyParameters} or {@link DSAKeyParameters}
|
||||
* @throws FileNotFoundException key file not found
|
||||
* @throws IOException if key file not found or key parse failed
|
||||
* @see RSAPublicKey
|
||||
* @see DSAPublicKey
|
||||
*/
|
||||
public static Object getSSHPublicKey(File sshPublicKeyFile) throws IOException {
|
||||
public static CipherParameters getSSHPublicKey(File sshPublicKeyFile) throws IOException {
|
||||
|
||||
BufferedReader r = new BufferedReader(new FileReader(sshPublicKeyFile));
|
||||
String keyLine = null;
|
||||
String line;
|
||||
while ((line = r.readLine()) != null) {
|
||||
if (!line.startsWith("ssh-")) {
|
||||
continue;
|
||||
try (BufferedReader r = new BufferedReader(new FileReader(sshPublicKeyFile))) {
|
||||
String line;
|
||||
while ((line = r.readLine()) != null) {
|
||||
if (!line.startsWith("ssh-")) {
|
||||
continue;
|
||||
}
|
||||
keyLine = line;
|
||||
break;
|
||||
}
|
||||
keyLine = line;
|
||||
break;
|
||||
}
|
||||
r.close();
|
||||
|
||||
if (keyLine != null) {
|
||||
String[] pieces = keyLine.split(" ");
|
||||
if (pieces.length >= 2) {
|
||||
byte[] pubkeyBytes = Base64.decode(pieces[1].toCharArray());
|
||||
if ("ssh-rsa".equals(pieces[0])) {
|
||||
return RSASHA1Verify.decodeSSHRSAPublicKey(pubkeyBytes);
|
||||
}
|
||||
else if ("ssh-dsa".equals(pieces[0])) {
|
||||
return DSASHA1Verify.decodeSSHDSAPublicKey(pubkeyBytes);
|
||||
}
|
||||
String[] part = keyLine.split("\\s+");
|
||||
if (part.length >= 2 && part[0].startsWith("ssh-")) {
|
||||
byte[] pubkeyBytes = Base64.decode(part[1]);
|
||||
return OpenSSHPublicKeyUtil.parsePublicKey(pubkeyBytes);
|
||||
}
|
||||
}
|
||||
|
||||
throw new IOException(
|
||||
"Invalid SSH public key file, valid ssh-rsa or ssh-dsa entry not found: " +
|
||||
"Invalid SSH public key file, supported SSH public key not found: " +
|
||||
sshPublicKeyFile);
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
##MODULE IP: Apache License 2.0
|
||||
##MODULE IP: Bouncy Castle License
|
||||
##MODULE IP: BSD
|
||||
##MODULE IP: Christian Plattner
|
||||
##MODULE IP: Crystal Clear Icons - LGPL 2.1
|
||||
##MODULE IP: FAMFAMFAM Icons - CC 2.5
|
||||
##MODULE IP: JDOM License
|
||||
|
@ -127,17 +127,16 @@ public class ApplicationKeyManagerFactory {
|
||||
* This change will take immediate effect for the current executing application,
|
||||
* however, it may still be superseded by a system property setting when running
|
||||
* the application in the future. See {@link #getKeyStore()}.
|
||||
* @param path keystore file path
|
||||
* @param path keystore file path or null to clear current key store and preference.
|
||||
* @param savePreference if true will be saved as user preference
|
||||
* @throws IOException if file or certificate error occurs
|
||||
* @return true if successful else false if error occured (see log).
|
||||
*/
|
||||
public static synchronized void setKeyStore(String path, boolean savePreference)
|
||||
throws IOException {
|
||||
public static synchronized boolean setKeyStore(String path, boolean savePreference) {
|
||||
|
||||
if (System.getProperty(KEYSTORE_PATH_PROPERTY) != null) {
|
||||
Msg.showError(ApplicationKeyManagerFactory.class, null, "Set KeyStore Failed",
|
||||
"KeyStore was set via system property and can not be changed");
|
||||
return;
|
||||
"PKI KeyStore was set via system property and can not be changed");
|
||||
return false;
|
||||
}
|
||||
|
||||
path = prunePath(path);
|
||||
@ -149,9 +148,11 @@ public class ApplicationKeyManagerFactory {
|
||||
Preferences.setProperty(KEYSTORE_PATH_PROPERTY, path);
|
||||
Preferences.store();
|
||||
}
|
||||
return keyInitialized;
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// ignore - keystore left unchanged
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -509,7 +510,9 @@ public class ApplicationKeyManagerFactory {
|
||||
* has been set, a self-signed certificate will be generated. If nothing has been set, the
|
||||
* wrappedKeyManager will remain null and false will be returned. If an error occurs it
|
||||
* will be logged and key managers will remain uninitialized.
|
||||
* @return true if key manager initialized successfully or was previously initialized.
|
||||
* @return true if key manager initialized successfully or was previously initialized, else
|
||||
* false if keystore path has not been set and default identity for self-signed certificate
|
||||
* has not be established (see {@link ApplicationKeyManagerFactory#setDefaultIdentity(X500Principal)}).
|
||||
* @throws CancelledException user cancelled keystore password entry request
|
||||
*/
|
||||
private synchronized boolean init() throws CancelledException {
|
||||
@ -527,7 +530,9 @@ public class ApplicationKeyManagerFactory {
|
||||
* wrappedKeyManager will remain null and false will be returned. If an error occurs it
|
||||
* will be logged and key managers will remain uninitialized.
|
||||
* @param newKeystorePath specifies the keystore to be opened or null for no keystore
|
||||
* @return true if key manager initialized successfully or was previously initialized
|
||||
* @return true if key manager initialized successfully or was previously initialized, else
|
||||
* false if new keystore path was not specified and default identity for self-signed certificate
|
||||
* has not be established (see {@link ApplicationKeyManagerFactory#setDefaultIdentity(X500Principal)}).
|
||||
* @throws CancelledException user cancelled keystore password entry request
|
||||
*/
|
||||
private synchronized boolean init(String newKeystorePath) throws CancelledException {
|
||||
@ -576,25 +581,25 @@ public class ApplicationKeyManagerFactory {
|
||||
isSelfSigned = false;
|
||||
|
||||
if (keyManagers.length == 0) {
|
||||
Msg.showError(this, null, "Keystore Failure",
|
||||
"Failed to create key manager: failed to process keystore (no keys processed)");
|
||||
Msg.showError(this, null, "PKI Keystore Failure",
|
||||
"Failed to create PKI key manager: failed to process keystore (no keys processed)");
|
||||
}
|
||||
else if (keyManagers.length == 1) {
|
||||
Msg.showError(this, null, "Keystore Failure",
|
||||
"Failed to create key manager: failed to process keystore (expected X.509)");
|
||||
Msg.showError(this, null, "PKI Keystore Failure",
|
||||
"Failed to create PKI key manager: failed to process keystore (expected X.509)");
|
||||
}
|
||||
else {
|
||||
// Unexpected condition
|
||||
Msg.showError(this, null, "Keystore Failure",
|
||||
"Failed to create key manager: unsupported keystore produced multiple KeyManagers");
|
||||
Msg.showError(this, null, "PKI Keystore Failure",
|
||||
"Failed to create PKI key manager: unsupported keystore produced multiple KeyManagers");
|
||||
}
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.showError(this, null, "Keystore Failure",
|
||||
"Failed to create key manager: " + e.getMessage(), e);
|
||||
Msg.showError(this, null, "PKI Keystore Failure",
|
||||
"Failed to create PKI key manager: " + e.getMessage(), e);
|
||||
}
|
||||
finally {
|
||||
if (keystoreData != null) {
|
||||
|
@ -16,7 +16,6 @@
|
||||
package ghidra.framework.main;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.DockingAction;
|
||||
@ -26,7 +25,6 @@ import docking.widgets.OptionDialog;
|
||||
import docking.widgets.filechooser.GhidraFileChooser;
|
||||
import ghidra.net.ApplicationKeyManagerFactory;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Helper class to manage the actions on the Edit menu.
|
||||
@ -121,14 +119,8 @@ class EditActionManager {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ApplicationKeyManagerFactory.setKeyStore(null, true);
|
||||
clearCertPathAction.setEnabled(false);
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.error(this,
|
||||
"Error occurred while clearing PKI certificate setting: " + e.getMessage());
|
||||
}
|
||||
ApplicationKeyManagerFactory.setKeyStore(null, true);
|
||||
clearCertPathAction.setEnabled(false);
|
||||
}
|
||||
|
||||
private void editCertPath() {
|
||||
@ -167,16 +159,9 @@ class EditActionManager {
|
||||
if (file == null) {
|
||||
return; // cancelled
|
||||
}
|
||||
try {
|
||||
ApplicationKeyManagerFactory.setKeyStore(file.getAbsolutePath(), true);
|
||||
clearCertPathAction.setEnabled(true);
|
||||
validInput = true;
|
||||
}
|
||||
catch (IOException e) {
|
||||
Msg.showError(this, tool.getToolFrame(), "Certificate Failure",
|
||||
"Failed to initialize key manager.\n" + e.getMessage(), e);
|
||||
file = null;
|
||||
}
|
||||
ApplicationKeyManagerFactory.setKeyStore(file.getAbsolutePath(), true);
|
||||
clearCertPathAction.setEnabled(true);
|
||||
validInput = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -320,6 +320,17 @@ eliminate SSH based authentication for the corresponding user. When creating th
|
||||
owner with full access and any SSH public keys readable by the process owner. Changes to the SSH
|
||||
public key files may be made without restarting the Ghidra Server.
|
||||
</P>
|
||||
<P>Each user may generate a suitable SSH key pair with the <typewriter>ssh-keygen</typewriter> command issued from a
|
||||
shell prompt. A PEM formatted RSA key-pair should be generated using the following command options:
|
||||
</P>
|
||||
<PRE>
|
||||
ssh-keygen -m pem -t rsa -b 2048
|
||||
</PRE>
|
||||
<P>NOTE: Ghidra Server authentication does not currently support the OPENSSH key format which may be the default
|
||||
<typewriter>ssh-keygen</typewriter> format (<typewriter>-m</typewriter> option) on some systems such as Ubuntu.
|
||||
In addition, other key types (<typewriter>-t</typewriter> option) such as <i>ecdsa</i> and <i>ed25519</i>
|
||||
are not currently supported.
|
||||
</P>
|
||||
|
||||
(<a href="#top">Back to Top</a>)
|
||||
<div style="border-top: 4px double; margin-top: 1em; padding-top: 1em;"> </div>
|
||||
|
@ -16,21 +16,22 @@
|
||||
package ghidra.server.remote;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.math.BigInteger;
|
||||
import java.security.*;
|
||||
import java.security.interfaces.*;
|
||||
import java.util.Base64;
|
||||
|
||||
import ch.ethz.ssh2.packets.TypesWriter;
|
||||
import org.bouncycastle.util.Strings;
|
||||
|
||||
public class SSHKeyUtil {
|
||||
|
||||
/**
|
||||
* Generate private/public SSH keys for test purposes using RSA algorithm.
|
||||
* Generate private/public SSH RSA keys for test purposes using RSA algorithm.
|
||||
* @return kay pair array suitable for writing to SSH private and public
|
||||
* key files ([0] corresponds to private key, [1] corresponds to public key)
|
||||
* @throws NoSuchAlgorithmException
|
||||
* key files ([0] corresponds to private key PEM file, [1] corresponds to public key file)
|
||||
* @throws NoSuchAlgorithmException failed to instantiate RSA key pair generator
|
||||
*/
|
||||
public static String[] generateSSHKeys() throws NoSuchAlgorithmException {
|
||||
public static String[] generateSSHRSAKeys() throws NoSuchAlgorithmException {
|
||||
|
||||
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
|
||||
generator.initialize(2048);
|
||||
@ -84,19 +85,64 @@ public class SSHKeyUtil {
|
||||
out.writeBytes(data);
|
||||
}
|
||||
|
||||
private static String getRSAPublicKey(KeyPair rsaKeyPair) {
|
||||
String keyAlgorithm = "ssh-rsa";
|
||||
RSAPublicKey rsaPublicKey = (RSAPublicKey) rsaKeyPair.getPublic();
|
||||
TypesWriter w = new TypesWriter();
|
||||
w.writeString(keyAlgorithm);
|
||||
w.writeMPInt(rsaPublicKey.getPublicExponent());
|
||||
w.writeMPInt(rsaPublicKey.getModulus());
|
||||
/**
|
||||
* Write UInt32 to an SSH-encoded buffer.
|
||||
* (modeled after org.bouncycastle.crypto.util.SSHBuilder.u32(int))
|
||||
* @param value integer value
|
||||
* @param out data output stream
|
||||
*/
|
||||
private static void sshBuilderWriteUInt32(int value, ByteArrayOutputStream out) {
|
||||
byte[] tmp = new byte[4];
|
||||
tmp[0] = (byte) ((value >>> 24) & 0xff);
|
||||
tmp[1] = (byte) ((value >>> 16) & 0xff);
|
||||
tmp[2] = (byte) ((value >>> 8) & 0xff);
|
||||
tmp[3] = (byte) (value & 0xff);
|
||||
out.writeBytes(tmp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write string to an SSH-encoded buffer.
|
||||
* (modeled after org.bouncycastle.crypto.util.SSHBuilder.writeString(String)
|
||||
* @param str string data
|
||||
* @param out data output stream
|
||||
*/
|
||||
private static void sshBuilderWriteString(String str, ByteArrayOutputStream out) {
|
||||
byte[] data = Strings.toByteArray(str);
|
||||
sshBuilderWriteUInt32(data.length, out);
|
||||
out.writeBytes(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate SSH RSA public key file content
|
||||
* @param rsaKeyPair SSH public/private key pair
|
||||
* @return SSH public key file content string
|
||||
*/
|
||||
private static String getRSAPublicKey(KeyPair rsaKeyPair) {
|
||||
|
||||
RSAPublicKey rsaPublicKey = (RSAPublicKey) rsaKeyPair.getPublic();
|
||||
String keyAlgorithm = "ssh-rsa";
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
sshBuilderWriteString(keyAlgorithm, out);
|
||||
BigInteger e = rsaPublicKey.getPublicExponent();
|
||||
byte[] data = e.toByteArray();
|
||||
sshBuilderWriteUInt32(data.length, out);
|
||||
out.writeBytes(data);
|
||||
BigInteger m = rsaPublicKey.getModulus();
|
||||
data = m.toByteArray();
|
||||
sshBuilderWriteUInt32(data.length, out);
|
||||
out.writeBytes(data);
|
||||
byte[] bytesOut = out.toByteArray();
|
||||
|
||||
byte[] bytesOut = w.getBytes();
|
||||
String publicKeyEncoded = new String(Base64.getEncoder().encodeToString(bytesOut));
|
||||
return keyAlgorithm + " " + publicKeyEncoded + " test\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate SSH RSA private key file content in PEM format
|
||||
* @param rsaKeyPair SSH public/private key pair
|
||||
* @return SSH private key file content string in PEM format
|
||||
*/
|
||||
private static String getRSAPrivateKey(KeyPair rsaKeyPair) {
|
||||
RSAPrivateKey privateKey = (RSAPrivateKey) rsaKeyPair.getPrivate();
|
||||
RSAPrivateCrtKey privateCrtKey = (RSAPrivateCrtKey) privateKey;
|
||||
|
@ -827,7 +827,7 @@ public class ServerTestUtil {
|
||||
System.arraycopy(users, 0, userArray, 1, users.length);
|
||||
createUsers(dirPath, userArray);
|
||||
|
||||
String keys[] = SSHKeyUtil.generateSSHKeys();
|
||||
String keys[] = SSHKeyUtil.generateSSHRSAKeys();
|
||||
addSSHKeys(dirPath, keys[0], "test.key", keys[1], "test.pub");
|
||||
|
||||
LocalFileSystem repoFilesystem = createRepository(dirPath, "Test", ADMIN_USER + "=ADMIN",
|
||||
|
@ -1,87 +0,0 @@
|
||||
Copyright (c) 2006 - 2010 Christian Plattner. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
a.) Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
b.) Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
c.) Neither the name of Christian Plattner nor the names of its contributors may
|
||||
be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
This software includes work that was released under the following license:
|
||||
|
||||
Copyright (c) 2005 - 2006 Swiss Federal Institute of Technology (ETH Zurich),
|
||||
Department of Computer Science (http://www.inf.ethz.ch),
|
||||
Christian Plattner. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
a.) Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
b.) Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
c.) Neither the name of ETH Zurich nor the names of its contributors may
|
||||
be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
The Java implementations of the AES, Blowfish and 3DES ciphers have been
|
||||
taken (and slightly modified) from the cryptography package released by
|
||||
"The Legion Of The Bouncy Castle".
|
||||
|
||||
Their license states the following:
|
||||
|
||||
Copyright (c) 2000 - 2004 The Legion Of The Bouncy Castle
|
||||
(http://www.bouncycastle.org)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
@ -3,7 +3,6 @@ Apache_License_2.0.txt||LICENSE||||END|
|
||||
Apache_License_2.0_with_LLVM_Exceptions.txt||LICENSE||||END|
|
||||
BSD.txt||LICENSE||||END|
|
||||
Bouncy_Castle_License.txt||LICENSE||||END|
|
||||
Christian_Plattner.txt||LICENSE||||END|
|
||||
Creative_Commons_Attribution_2.5.html||LICENSE||||END|
|
||||
Crystal_Clear_Icons_-_LGPL_2.1.txt||LICENSE||||END|
|
||||
FAMFAMFAM_Icons_-_CC_2.5.txt||LICENSE||||END|
|
||||
|
Loading…
Reference in New Issue
Block a user