Native cipher, with more smoke tests!
This commit is contained in:
parent
897a59254c
commit
94ee61cd35
3
native/compile-native.sh
Executable file
3
native/compile-native.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
gcc -shared -fPIC -O3 -Werror -I/usr/lib/jvm/default-java/include/ src/main/c/NativeCipherImpl.c -o src/main/resources/native-cipher.so -lcrypto
|
31
native/nb-configuration.xml
Normal file
31
native/nb-configuration.xml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project-shared-configuration>
|
||||||
|
<!--
|
||||||
|
This file contains additional configuration written by modules in the NetBeans IDE.
|
||||||
|
The configuration is intended to be shared among all the users of project and
|
||||||
|
therefore it is assumed to be part of version control checkout.
|
||||||
|
Without this configuration present, some functionality in the IDE may be limited or fail altogether.
|
||||||
|
-->
|
||||||
|
<properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
|
||||||
|
<!--
|
||||||
|
Properties that influence various parts of the IDE, especially code formatting and the like.
|
||||||
|
You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
|
||||||
|
That way multiple projects can share the same settings (useful for formatting rules for example).
|
||||||
|
Any value defined here will override the pom.xml file value but is only applicable to the current project.
|
||||||
|
-->
|
||||||
|
<org-netbeans-modules-editor-indent.CodeStyle.usedProfile>project</org-netbeans-modules-editor-indent.CodeStyle.usedProfile>
|
||||||
|
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>
|
||||||
|
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>
|
||||||
|
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>
|
||||||
|
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinMethodCallParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinMethodCallParens>
|
||||||
|
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSwitchParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSwitchParens>
|
||||||
|
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinCatchParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinCatchParens>
|
||||||
|
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinTryParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinTryParens>
|
||||||
|
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSynchronizedParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinSynchronizedParens>
|
||||||
|
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinArrayInitBrackets>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinArrayInitBrackets>
|
||||||
|
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinParens>
|
||||||
|
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinWhileParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinWhileParens>
|
||||||
|
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinIfParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinIfParens>
|
||||||
|
<org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinForParens>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceWithinForParens>
|
||||||
|
</properties>
|
||||||
|
</project-shared-configuration>
|
29
native/pom.xml
Normal file
29
native/pom.xml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>net.md-5</groupId>
|
||||||
|
<artifactId>bungeecord-parent</artifactId>
|
||||||
|
<version>1.7-SNAPSHOT</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>net.md-5</groupId>
|
||||||
|
<artifactId>bungeecord-native</artifactId>
|
||||||
|
<version>1.7-SNAPSHOT</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<name>BungeeCord-Native</name>
|
||||||
|
<description>Optional native code to speed up and enhance BungeeCord functionality.</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-transport</artifactId>
|
||||||
|
<version>${netty.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
56
native/src/main/c/NativeCipherImpl.c
Normal file
56
native/src/main/c/NativeCipherImpl.c
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
#include "net_md_5_bungee_NativeCipherImpl.h"
|
||||||
|
#include <openssl/aes.h>
|
||||||
|
#include <jni.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#define BYTE unsigned char
|
||||||
|
|
||||||
|
jlong Java_net_md_15_bungee_NativeCipherImpl_init
|
||||||
|
(JNIEnv* env, jobject obj, jbyteArray key)
|
||||||
|
{
|
||||||
|
AES_KEY *aes_key = malloc(sizeof(AES_KEY));
|
||||||
|
|
||||||
|
jboolean isKeyCopy;
|
||||||
|
BYTE *key_bytes = (*env)->GetByteArrayElements(env, key, &isKeyCopy);
|
||||||
|
int key_length = (*env)->GetArrayLength(env, key) * 8; // in bits
|
||||||
|
|
||||||
|
AES_set_encrypt_key(key_bytes, key_length, aes_key);
|
||||||
|
|
||||||
|
if (isKeyCopy) {
|
||||||
|
(*env)->ReleaseByteArrayElements(env, key, (jbyte*)key_bytes, JNI_ABORT);
|
||||||
|
}
|
||||||
|
return (long) aes_key;
|
||||||
|
}
|
||||||
|
void Java_net_md_15_bungee_NativeCipherImpl_free
|
||||||
|
(JNIEnv* env, jobject obj, jlong key)
|
||||||
|
{
|
||||||
|
free((AES_KEY*)key);
|
||||||
|
}
|
||||||
|
void Java_net_md_15_bungee_NativeCipherImpl_cipher
|
||||||
|
(JNIEnv* env, jobject obj, jboolean forEncryption, jlong key, jbyteArray iv, jlong in, jlong out, jint length)
|
||||||
|
{
|
||||||
|
AES_KEY *aes_key = (AES_KEY*)key;
|
||||||
|
|
||||||
|
size_t buffer_length = (size_t) length;
|
||||||
|
|
||||||
|
BYTE *input = (BYTE*) in;
|
||||||
|
BYTE *output = (BYTE*) out;
|
||||||
|
|
||||||
|
jboolean isCopy;
|
||||||
|
BYTE *iv_bytes = (*env)->GetByteArrayElements(env, iv, &isCopy);
|
||||||
|
|
||||||
|
AES_cfb8_encrypt(
|
||||||
|
input, // input buffer
|
||||||
|
output, // output buffer
|
||||||
|
buffer_length, // readable bytes
|
||||||
|
aes_key, // encryption key
|
||||||
|
iv_bytes, // IV
|
||||||
|
NULL, // not needed
|
||||||
|
forEncryption ? AES_ENCRYPT : AES_DECRYPT // encryption mode
|
||||||
|
);
|
||||||
|
|
||||||
|
// IV has changed, let's copy it back
|
||||||
|
if (isCopy) {
|
||||||
|
(*env)->ReleaseByteArrayElements(env, iv, (jbyte*)iv_bytes, 0);
|
||||||
|
}
|
||||||
|
}
|
37
native/src/main/c/net_md_5_bungee_NativeCipherImpl.h
Normal file
37
native/src/main/c/net_md_5_bungee_NativeCipherImpl.h
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/* DO NOT EDIT THIS FILE - it is machine generated */
|
||||||
|
#include <jni.h>
|
||||||
|
/* Header for class net_md_5_bungee_NativeCipherImpl */
|
||||||
|
|
||||||
|
#ifndef _Included_net_md_5_bungee_NativeCipherImpl
|
||||||
|
#define _Included_net_md_5_bungee_NativeCipherImpl
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
/*
|
||||||
|
* Class: net_md_5_bungee_NativeCipherImpl
|
||||||
|
* Method: init
|
||||||
|
* Signature: ([B)J
|
||||||
|
*/
|
||||||
|
JNIEXPORT jlong JNICALL Java_net_md_15_bungee_NativeCipherImpl_init
|
||||||
|
(JNIEnv *, jobject, jbyteArray);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Class: net_md_5_bungee_NativeCipherImpl
|
||||||
|
* Method: free
|
||||||
|
* Signature: (J)V
|
||||||
|
*/
|
||||||
|
JNIEXPORT void JNICALL Java_net_md_15_bungee_NativeCipherImpl_free
|
||||||
|
(JNIEnv *, jobject, jlong);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Class: net_md_5_bungee_NativeCipherImpl
|
||||||
|
* Method: cipher
|
||||||
|
* Signature: (ZJ[BJJI)V
|
||||||
|
*/
|
||||||
|
JNIEXPORT void JNICALL Java_net_md_15_bungee_NativeCipherImpl_cipher
|
||||||
|
(JNIEnv *, jobject, jboolean, jlong, jbyteArray, jlong, jlong, jint);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
21
native/src/main/java/net/md_5/bungee/BungeeCipher.java
Normal file
21
native/src/main/java/net/md_5/bungee/BungeeCipher.java
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package net.md_5.bungee;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to expose cipher methods from either native or fallback Java cipher.
|
||||||
|
*/
|
||||||
|
public interface BungeeCipher
|
||||||
|
{
|
||||||
|
|
||||||
|
void init(boolean forEncryption, SecretKey key) throws GeneralSecurityException;
|
||||||
|
|
||||||
|
void free();
|
||||||
|
|
||||||
|
void cipher(ByteBuf in, ByteBuf out) throws GeneralSecurityException;
|
||||||
|
|
||||||
|
ByteBuf cipher(ChannelHandlerContext ctx, ByteBuf in) throws GeneralSecurityException;
|
||||||
|
}
|
@ -1,24 +1,17 @@
|
|||||||
package net.md_5.bungee.netty;
|
package net.md_5.bungee;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.ShortBufferException;
|
import javax.crypto.ShortBufferException;
|
||||||
import lombok.AccessLevel;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
import lombok.NonNull;
|
import java.security.GeneralSecurityException;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
|
|
||||||
/**
|
public class FallbackCipher implements BungeeCipher
|
||||||
* Class to expose an
|
|
||||||
* {@link #cipher(io.netty.buffer.ByteBuf, io.netty.buffer.ByteBuf)} method to
|
|
||||||
* aid in the efficient passing of ByteBuffers through a cipher.
|
|
||||||
*/
|
|
||||||
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
|
|
||||||
public class CipherBase
|
|
||||||
{
|
{
|
||||||
|
|
||||||
@NonNull
|
private Cipher cipher;
|
||||||
private final Cipher cipher;
|
|
||||||
private ThreadLocal<byte[]> heapInLocal = new EmptyByteThreadLocal();
|
private ThreadLocal<byte[]> heapInLocal = new EmptyByteThreadLocal();
|
||||||
private ThreadLocal<byte[]> heapOutLocal = new EmptyByteThreadLocal();
|
private ThreadLocal<byte[]> heapOutLocal = new EmptyByteThreadLocal();
|
||||||
|
|
||||||
@ -32,6 +25,51 @@ public class CipherBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FallbackCipher() throws GeneralSecurityException
|
||||||
|
{
|
||||||
|
this.cipher = Cipher.getInstance( "AES/CFB8/NoPadding" );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(boolean forEncryption, SecretKey key) throws GeneralSecurityException
|
||||||
|
{
|
||||||
|
int mode = forEncryption ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE;
|
||||||
|
cipher.init( mode, key, new IvParameterSpec( key.getEncoded() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cipher(ByteBuf in, ByteBuf out) throws ShortBufferException
|
||||||
|
{
|
||||||
|
int readableBytes = in.readableBytes();
|
||||||
|
byte[] heapIn = bufToByte( in );
|
||||||
|
|
||||||
|
byte[] heapOut = heapOutLocal.get();
|
||||||
|
int outputSize = cipher.getOutputSize( readableBytes );
|
||||||
|
if ( heapOut.length < outputSize )
|
||||||
|
{
|
||||||
|
heapOut = new byte[ outputSize ];
|
||||||
|
heapOutLocal.set( heapOut );
|
||||||
|
}
|
||||||
|
out.writeBytes( heapOut, 0, cipher.update( heapIn, 0, readableBytes, heapOut ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf cipher(ChannelHandlerContext ctx, ByteBuf in) throws ShortBufferException
|
||||||
|
{
|
||||||
|
int readableBytes = in.readableBytes();
|
||||||
|
byte[] heapIn = bufToByte( in );
|
||||||
|
|
||||||
|
ByteBuf heapOut = ctx.alloc().heapBuffer( cipher.getOutputSize( readableBytes ) );
|
||||||
|
heapOut.writerIndex( cipher.update( heapIn, 0, readableBytes, heapOut.array(), heapOut.arrayOffset() ) );
|
||||||
|
|
||||||
|
return heapOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void free()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
private byte[] bufToByte(ByteBuf in)
|
private byte[] bufToByte(ByteBuf in)
|
||||||
{
|
{
|
||||||
byte[] heapIn = heapInLocal.get();
|
byte[] heapIn = heapInLocal.get();
|
||||||
@ -44,30 +82,4 @@ public class CipherBase
|
|||||||
in.readBytes( heapIn, 0, readableBytes );
|
in.readBytes( heapIn, 0, readableBytes );
|
||||||
return heapIn;
|
return heapIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ByteBuf cipher(ChannelHandlerContext ctx, ByteBuf in) throws ShortBufferException
|
|
||||||
{
|
|
||||||
int readableBytes = in.readableBytes();
|
|
||||||
byte[] heapIn = bufToByte( in );
|
|
||||||
|
|
||||||
ByteBuf heapOut = ctx.alloc().heapBuffer( cipher.getOutputSize( readableBytes ) );
|
|
||||||
heapOut.writerIndex( cipher.update( heapIn, 0, readableBytes, heapOut.array(), heapOut.arrayOffset() ) );
|
|
||||||
|
|
||||||
return heapOut;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void cipher(ByteBuf in, ByteBuf out) throws ShortBufferException
|
|
||||||
{
|
|
||||||
int readableBytes = in.readableBytes();
|
|
||||||
byte[] heapIn = bufToByte( in );
|
|
||||||
|
|
||||||
byte[] heapOut = heapOutLocal.get();
|
|
||||||
int outputSize = cipher.getOutputSize( readableBytes );
|
|
||||||
if ( heapOut.length < outputSize )
|
|
||||||
{
|
|
||||||
heapOut = new byte[ outputSize ];
|
|
||||||
heapOutLocal.set( heapOut );
|
|
||||||
}
|
|
||||||
out.writeBytes( heapOut, 0, cipher.update( heapIn, 0, readableBytes, heapOut ) );
|
|
||||||
}
|
|
||||||
}
|
}
|
113
native/src/main/java/net/md_5/bungee/NativeCipher.java
Normal file
113
native/src/main/java/net/md_5/bungee/NativeCipher.java
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
package net.md_5.bungee;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.io.ByteStreams;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import lombok.Getter;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
|
public class NativeCipher implements BungeeCipher
|
||||||
|
{
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final NativeCipherImpl nativeCipher = new NativeCipherImpl();
|
||||||
|
private boolean forEncryption;
|
||||||
|
private byte[] iv;
|
||||||
|
/*============================================================================*/
|
||||||
|
private static boolean loaded;
|
||||||
|
|
||||||
|
private long pointer;
|
||||||
|
|
||||||
|
public static boolean isSupported()
|
||||||
|
{
|
||||||
|
return "Linux".equals( System.getProperty( "os.name" ) ) && "amd64".equals( System.getProperty( "os.arch" ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean load()
|
||||||
|
{
|
||||||
|
if ( !loaded && isSupported() )
|
||||||
|
{
|
||||||
|
try ( InputStream lib = BungeeCipher.class.getClassLoader().getResourceAsStream( "native-cipher.so" ) )
|
||||||
|
{
|
||||||
|
// Else we will create and copy it to a temp file
|
||||||
|
File temp = File.createTempFile( "bungeecord-native-cipher", ".so" );
|
||||||
|
try ( OutputStream outputStream = new FileOutputStream( temp ) )
|
||||||
|
{
|
||||||
|
ByteStreams.copy( lib, outputStream );
|
||||||
|
System.load( temp.getPath() );
|
||||||
|
}
|
||||||
|
loaded = true;
|
||||||
|
} catch ( Throwable t )
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isLoaded()
|
||||||
|
{
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(boolean forEncryption, SecretKey key) throws GeneralSecurityException
|
||||||
|
{
|
||||||
|
if ( pointer != 0 )
|
||||||
|
{
|
||||||
|
nativeCipher.free( pointer );
|
||||||
|
}
|
||||||
|
this.forEncryption = forEncryption;
|
||||||
|
this.iv = key.getEncoded(); // initialize the IV
|
||||||
|
this.pointer = nativeCipher.init( key.getEncoded() );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void free()
|
||||||
|
{
|
||||||
|
if ( pointer != 0 )
|
||||||
|
{
|
||||||
|
nativeCipher.free( pointer );
|
||||||
|
pointer = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cipher(ByteBuf in, ByteBuf out) throws GeneralSecurityException
|
||||||
|
{
|
||||||
|
// Smoke tests
|
||||||
|
in.memoryAddress();
|
||||||
|
out.memoryAddress();
|
||||||
|
Preconditions.checkState( pointer != 0, "Invalid pointer to AES key!" );
|
||||||
|
Preconditions.checkState( iv != null, "Invalid IV!" );
|
||||||
|
|
||||||
|
// Store how many bytes we can cipher
|
||||||
|
int length = in.readableBytes();
|
||||||
|
// It is important to note that in AES CFB-8 mode, the number of read bytes, is the number of outputted bytes
|
||||||
|
out.ensureWritable( length );
|
||||||
|
|
||||||
|
// Cipher the bytes
|
||||||
|
nativeCipher.cipher( forEncryption, pointer, iv, in.memoryAddress() + in.readerIndex(), out.memoryAddress() + out.writerIndex(), length );
|
||||||
|
|
||||||
|
// Go to the end of the buffer, all bytes would of been read
|
||||||
|
in.readerIndex( in.writerIndex() );
|
||||||
|
// Add the number of ciphered bytes to our position
|
||||||
|
out.writerIndex( out.writerIndex() + length );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf cipher(ChannelHandlerContext ctx, ByteBuf in) throws GeneralSecurityException
|
||||||
|
{
|
||||||
|
int readableBytes = in.readableBytes();
|
||||||
|
ByteBuf heapOut = ctx.alloc().directBuffer( readableBytes ); // CFB8
|
||||||
|
cipher( in, heapOut );
|
||||||
|
|
||||||
|
return heapOut;
|
||||||
|
}
|
||||||
|
}
|
32
native/src/main/java/net/md_5/bungee/NativeCipherImpl.java
Normal file
32
native/src/main/java/net/md_5/bungee/NativeCipherImpl.java
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package net.md_5.bungee;
|
||||||
|
|
||||||
|
class NativeCipherImpl
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the key.
|
||||||
|
*
|
||||||
|
* @param key the key to for encryption
|
||||||
|
* @return the pointer to key
|
||||||
|
*/
|
||||||
|
native long init(byte[] key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees the key.
|
||||||
|
*
|
||||||
|
* @param key the pointer to key
|
||||||
|
*/
|
||||||
|
native void free(long key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will encrypt some data in AES-CFB8 using the specified key.
|
||||||
|
*
|
||||||
|
* @param forEncryption encryption / decryption mode
|
||||||
|
* @param key the pointer to key
|
||||||
|
* @param iv the iv to use
|
||||||
|
* @param in the starting memory address for reading data
|
||||||
|
* @param out the starting memory address for writing data
|
||||||
|
* @param length the length of data to read / write
|
||||||
|
*/
|
||||||
|
native void cipher(boolean forEncryption, long key, byte[] iv, long in, long out, int length);
|
||||||
|
}
|
BIN
native/src/main/resources/native-cipher.so
Executable file
BIN
native/src/main/resources/native-cipher.so
Executable file
Binary file not shown.
74
native/src/test/java/net/md_5/bungee/NativeCipherTest.java
Normal file
74
native/src/test/java/net/md_5/bungee/NativeCipherTest.java
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package net.md_5.bungee;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
public class NativeCipherTest
|
||||||
|
{
|
||||||
|
|
||||||
|
private final byte[] plainBytes = "This is a test".getBytes();
|
||||||
|
private final byte[] cipheredBytes = new byte[]
|
||||||
|
{
|
||||||
|
50, -7, 89, 1, -11, -32, -118, -48, -2, -72, 105, 97, -70, -81
|
||||||
|
};
|
||||||
|
private final SecretKey secret = new SecretKeySpec( new byte[ 16 ], "AES" );
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOpenSSL() throws Exception
|
||||||
|
{
|
||||||
|
if ( NativeCipher.isSupported() )
|
||||||
|
{
|
||||||
|
boolean loaded = NativeCipher.load();
|
||||||
|
Assert.assertTrue( "Native cipher failed to load!", loaded );
|
||||||
|
|
||||||
|
NativeCipher cipher = new NativeCipher();
|
||||||
|
System.out.println( "Testing OpenSSL cipher..." );
|
||||||
|
testACipher( cipher );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJDK() throws Exception
|
||||||
|
{
|
||||||
|
// Create JDK cipher
|
||||||
|
BungeeCipher cipher = new FallbackCipher();
|
||||||
|
|
||||||
|
System.out.println( "Testing Java cipher..." );
|
||||||
|
testACipher( cipher );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hackish test which can test both native and fallback ciphers using direct
|
||||||
|
* buffers.
|
||||||
|
*/
|
||||||
|
public void testACipher(BungeeCipher cipher) throws Exception
|
||||||
|
{
|
||||||
|
// Create input buf
|
||||||
|
ByteBuf nativePlain = Unpooled.directBuffer( plainBytes.length );
|
||||||
|
nativePlain.writeBytes( plainBytes );
|
||||||
|
// Create expected buf
|
||||||
|
ByteBuf nativeCiphered = Unpooled.directBuffer( cipheredBytes.length );
|
||||||
|
nativeCiphered.writeBytes( cipheredBytes );
|
||||||
|
// Create output buf
|
||||||
|
ByteBuf out = Unpooled.directBuffer( plainBytes.length );
|
||||||
|
|
||||||
|
// Encrypt
|
||||||
|
cipher.init( true, secret );
|
||||||
|
cipher.cipher( nativePlain, out );
|
||||||
|
Assert.assertEquals( nativeCiphered, out );
|
||||||
|
|
||||||
|
out.clear();
|
||||||
|
|
||||||
|
// Decrypt
|
||||||
|
cipher.init( false, secret );
|
||||||
|
cipher.cipher( nativeCiphered, out );
|
||||||
|
nativePlain.resetReaderIndex();
|
||||||
|
Assert.assertEquals( nativePlain, out );
|
||||||
|
|
||||||
|
System.out.println( "This cipher works correctly!" );
|
||||||
|
}
|
||||||
|
}
|
1
pom.xml
1
pom.xml
@ -44,6 +44,7 @@
|
|||||||
<module>protocol</module>
|
<module>protocol</module>
|
||||||
<module>proxy</module>
|
<module>proxy</module>
|
||||||
<module>query</module>
|
<module>query</module>
|
||||||
|
<module>native</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<scm>
|
<scm>
|
||||||
|
@ -47,6 +47,12 @@
|
|||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.md-5</groupId>
|
||||||
|
<artifactId>bungeecord-native</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.md-5</groupId>
|
<groupId>net.md-5</groupId>
|
||||||
<artifactId>bungeecord-protocol</artifactId>
|
<artifactId>bungeecord-protocol</artifactId>
|
||||||
|
@ -158,6 +158,14 @@ public class BungeeCord extends ProxyServer
|
|||||||
logger.info( "Unable to initialize fancy terminal. To fix this on Windows, install the correct Microsoft Visual C++ 2008 Runtime" );
|
logger.info( "Unable to initialize fancy terminal. To fix this on Windows, install the correct Microsoft Visual C++ 2008 Runtime" );
|
||||||
logger.info( "NOTE: This error is non crucial, and BungeeCord will still function correctly! Do not bug the author about it unless you are still unable to get it working" );
|
logger.info( "NOTE: This error is non crucial, and BungeeCord will still function correctly! Do not bug the author about it unless you are still unable to get it working" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( !NativeCipher.load() )
|
||||||
|
{
|
||||||
|
logger.warning( "NOTE: Failed to load native code. Falling back to Java cipher." );
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
logger.info( "Native code loaded." );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,7 +12,6 @@ import java.util.Arrays;
|
|||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import net.md_5.bungee.protocol.packet.EncryptionResponse;
|
import net.md_5.bungee.protocol.packet.EncryptionResponse;
|
||||||
@ -64,11 +63,19 @@ public class EncryptionUtil
|
|||||||
return new SecretKeySpec( cipher.doFinal( resp.getSharedSecret() ), "AES" );
|
return new SecretKeySpec( cipher.doFinal( resp.getSharedSecret() ), "AES" );
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Cipher getCipher(int opMode, Key shared) throws GeneralSecurityException
|
public static BungeeCipher getCipher(boolean forEncryption, SecretKey shared) throws GeneralSecurityException
|
||||||
{
|
{
|
||||||
Cipher cip = Cipher.getInstance( "AES/CFB8/NoPadding" );
|
BungeeCipher cipher;
|
||||||
cip.init( opMode, shared, new IvParameterSpec( shared.getEncoded() ) );
|
if ( NativeCipher.isLoaded() )
|
||||||
return cip;
|
{
|
||||||
|
cipher = new NativeCipher();
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
cipher = new FallbackCipher();
|
||||||
|
}
|
||||||
|
|
||||||
|
cipher.init( forEncryption, shared );
|
||||||
|
return cipher;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PublicKey getPubkey(EncryptionRequest request) throws GeneralSecurityException
|
public static PublicKey getPubkey(EncryptionRequest request) throws GeneralSecurityException
|
||||||
|
@ -8,14 +8,10 @@ import java.security.MessageDigest;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import net.md_5.bungee.BungeeCord;
|
import net.md_5.bungee.*;
|
||||||
import net.md_5.bungee.EncryptionUtil;
|
|
||||||
import net.md_5.bungee.UserConnection;
|
|
||||||
import net.md_5.bungee.Util;
|
|
||||||
import net.md_5.bungee.api.Callback;
|
import net.md_5.bungee.api.Callback;
|
||||||
import net.md_5.bungee.api.ChatColor;
|
import net.md_5.bungee.api.ChatColor;
|
||||||
import net.md_5.bungee.api.ProxyServer;
|
import net.md_5.bungee.api.ProxyServer;
|
||||||
@ -30,10 +26,10 @@ import net.md_5.bungee.api.event.ProxyPingEvent;
|
|||||||
import net.md_5.bungee.http.HttpClient;
|
import net.md_5.bungee.http.HttpClient;
|
||||||
import net.md_5.bungee.netty.HandlerBoss;
|
import net.md_5.bungee.netty.HandlerBoss;
|
||||||
import net.md_5.bungee.netty.ChannelWrapper;
|
import net.md_5.bungee.netty.ChannelWrapper;
|
||||||
import net.md_5.bungee.netty.CipherDecoder;
|
|
||||||
import net.md_5.bungee.netty.CipherEncoder;
|
|
||||||
import net.md_5.bungee.netty.PacketHandler;
|
import net.md_5.bungee.netty.PacketHandler;
|
||||||
import net.md_5.bungee.netty.PipelineUtils;
|
import net.md_5.bungee.netty.PipelineUtils;
|
||||||
|
import net.md_5.bungee.netty.cipher.CipherDecoder;
|
||||||
|
import net.md_5.bungee.netty.cipher.CipherEncoder;
|
||||||
import net.md_5.bungee.protocol.DefinedPacket;
|
import net.md_5.bungee.protocol.DefinedPacket;
|
||||||
import net.md_5.bungee.protocol.packet.Login;
|
import net.md_5.bungee.protocol.packet.Login;
|
||||||
import net.md_5.bungee.protocol.packet.Handshake;
|
import net.md_5.bungee.protocol.packet.Handshake;
|
||||||
@ -286,9 +282,9 @@ public class InitialHandler extends PacketHandler implements PendingConnection
|
|||||||
Preconditions.checkState( thisState == State.ENCRYPT, "Not expecting ENCRYPT" );
|
Preconditions.checkState( thisState == State.ENCRYPT, "Not expecting ENCRYPT" );
|
||||||
|
|
||||||
sharedKey = EncryptionUtil.getSecret( encryptResponse, request );
|
sharedKey = EncryptionUtil.getSecret( encryptResponse, request );
|
||||||
Cipher decrypt = EncryptionUtil.getCipher( Cipher.DECRYPT_MODE, sharedKey );
|
BungeeCipher decrypt = EncryptionUtil.getCipher( false, sharedKey );
|
||||||
ch.addBefore( PipelineUtils.FRAME_DECODER, PipelineUtils.DECRYPT_HANDLER, new CipherDecoder( decrypt ) );
|
ch.addBefore( PipelineUtils.FRAME_DECODER, PipelineUtils.DECRYPT_HANDLER, new CipherDecoder( decrypt ) );
|
||||||
Cipher encrypt = EncryptionUtil.getCipher( Cipher.ENCRYPT_MODE, sharedKey );
|
BungeeCipher encrypt = EncryptionUtil.getCipher( true, sharedKey );
|
||||||
ch.addBefore( PipelineUtils.FRAME_PREPENDER, PipelineUtils.ENCRYPT_HANDLER, new CipherEncoder( encrypt ) );
|
ch.addBefore( PipelineUtils.FRAME_PREPENDER, PipelineUtils.ENCRYPT_HANDLER, new CipherEncoder( encrypt ) );
|
||||||
|
|
||||||
String encName = URLEncoder.encode( InitialHandler.this.getName(), "UTF-8" );
|
String encName = URLEncoder.encode( InitialHandler.this.getName(), "UTF-8" );
|
||||||
|
@ -1,24 +1,27 @@
|
|||||||
package net.md_5.bungee.netty;
|
package net.md_5.bungee.netty.cipher;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import net.md_5.bungee.BungeeCipher;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.crypto.Cipher;
|
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class CipherDecoder extends MessageToMessageDecoder<ByteBuf>
|
public class CipherDecoder extends MessageToMessageDecoder<ByteBuf>
|
||||||
{
|
{
|
||||||
|
|
||||||
private final CipherBase cipher;
|
private final BungeeCipher cipher;
|
||||||
|
|
||||||
public CipherDecoder(Cipher cipher)
|
|
||||||
{
|
|
||||||
this.cipher = new CipherBase( cipher );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception
|
protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception
|
||||||
{
|
{
|
||||||
out.add( cipher.cipher( ctx, msg ) );
|
out.add( cipher.cipher( ctx, msg ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception
|
||||||
|
{
|
||||||
|
cipher.free();
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,23 +1,26 @@
|
|||||||
package net.md_5.bungee.netty;
|
package net.md_5.bungee.netty.cipher;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.MessageToByteEncoder;
|
import io.netty.handler.codec.MessageToByteEncoder;
|
||||||
import javax.crypto.Cipher;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import net.md_5.bungee.BungeeCipher;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class CipherEncoder extends MessageToByteEncoder<ByteBuf>
|
public class CipherEncoder extends MessageToByteEncoder<ByteBuf>
|
||||||
{
|
{
|
||||||
|
|
||||||
private final CipherBase cipher;
|
private final BungeeCipher cipher;
|
||||||
|
|
||||||
public CipherEncoder(Cipher cipher)
|
|
||||||
{
|
|
||||||
this.cipher = new CipherBase( cipher );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception
|
protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception
|
||||||
{
|
{
|
||||||
cipher.cipher( in, out );
|
cipher.cipher( in, out );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception
|
||||||
|
{
|
||||||
|
cipher.free();
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user