Browse Source

Secio implementation (#10)

pull/12/head
Anton Nashatyrev 2 years ago
committed by Raúl Kripalani
parent
commit
5815124ac7
  1. 38
      build.gradle.kts
  2. 200
      gradlew.bat
  3. 4
      src/main/kotlin/io/libp2p/core/Host.kt
  4. 20
      src/main/kotlin/io/libp2p/core/Libp2pException.kt
  5. 12
      src/main/kotlin/io/libp2p/core/Peer.kt
  6. 4
      src/main/kotlin/io/libp2p/core/PeerConnection.kt
  7. 15
      src/main/kotlin/io/libp2p/core/PeerId.kt
  8. 1
      src/main/kotlin/io/libp2p/core/PeerStreams.kt
  9. 90
      src/main/kotlin/io/libp2p/core/Stream.kt
  10. 9
      src/main/kotlin/io/libp2p/core/crypto/Hash.kt
  11. 261
      src/main/kotlin/io/libp2p/core/crypto/Key.kt
  12. 41
      src/main/kotlin/io/libp2p/core/crypto/Libp2pCrypto.kt
  13. 2
      src/main/kotlin/io/libp2p/core/crypto/PublicKey.kt
  14. 177
      src/main/kotlin/io/libp2p/core/crypto/keys/Ecdsa.kt
  15. 82
      src/main/kotlin/io/libp2p/core/crypto/keys/Ed25519.kt
  16. 160
      src/main/kotlin/io/libp2p/core/crypto/keys/Rsa.kt
  17. 149
      src/main/kotlin/io/libp2p/core/crypto/keys/Secp256k1.kt
  18. 8
      src/main/kotlin/io/libp2p/core/multiformats/Multiaddr.kt
  19. 2
      src/main/kotlin/io/libp2p/core/multiformats/Protocol.kt
  20. 124
      src/main/kotlin/io/libp2p/core/protocol/Negotiator.kt
  21. 4
      src/main/kotlin/io/libp2p/core/protocol/ProtocolMatcher.kt
  22. 29
      src/main/kotlin/io/libp2p/core/protocol/ProtocolSelect.kt
  23. 9
      src/main/kotlin/io/libp2p/core/protocol/SecureChannel.kt
  24. 48
      src/main/kotlin/io/libp2p/core/security/secio/SecIoCodec.kt
  25. 78
      src/main/kotlin/io/libp2p/core/security/secio/SecIoSecureChannel.kt
  26. 17
      src/main/kotlin/io/libp2p/core/security/secio/SecioError.kt
  27. 206
      src/main/kotlin/io/libp2p/core/security/secio/SecioHandshake.kt
  28. 16
      src/main/kotlin/io/libp2p/core/types/BufferExtensions.kt
  29. 21
      src/main/kotlin/io/libp2p/core/types/ByteArrayExtensions.kt
  30. 12
      src/main/kotlin/io/libp2p/core/util/NettyExt.kt
  31. 20
      src/main/proto/crypto.proto
  32. 29
      src/main/proto/identify.proto
  33. 44
      src/main/proto/relay.proto
  34. 76
      src/main/proto/rpc.proto
  35. 16
      src/main/proto/spipe.proto
  36. 51
      src/test/kotlin/io/libp2p/core/HostTest.kt
  37. 20
      src/test/kotlin/io/libp2p/core/crypto/KeyTest.kt
  38. 59
      src/test/kotlin/io/libp2p/core/security/secio/NetworkTest.kt
  39. 99
      src/test/kotlin/io/libp2p/core/security/secio/SecIoSecureChannelTest.kt
  40. 59
      src/test/kotlin/io/libp2p/core/security/secio/SecioHandshakeTest.kt
  41. 49
      src/test/kotlin/io/libp2p/core/security/secio/TestHandler.kt

38
build.gradle.kts

@ -1,3 +1,6 @@
import com.google.protobuf.gradle.proto
import com.google.protobuf.gradle.protobuf
import com.google.protobuf.gradle.protoc
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
group = "io.libp2p"
@ -5,8 +8,11 @@ version = "0.0.1-SNAPSHOT"
description = "a minimal implementation of libp2p for the jvm"
plugins {
java
idea
kotlin("jvm") version "1.3.31"
id("org.jmailen.kotlinter") version "1.26.0"
id("com.google.protobuf") version "0.8.7"
`maven-publish`
}
@ -16,13 +22,20 @@ repositories {
maven("https://jitpack.io")
}
val log4j2Version = "2.11.2"
dependencies {
implementation(kotlin("stdlib-jdk8"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0-M1")
compile("io.netty:netty-all:4.1.36.Final")
compile("com.google.guava:guava:27.1-jre")
compile("org.bouncycastle:bcprov-jdk15on:1.61")
compile("org.bouncycastle:bcpkix-jdk15on:1.61")
compile("com.github.multiformats:java-multiaddr:v1.3.1")
compile("com.google.protobuf:protobuf-java:3.6.1")
compile("com.google.protobuf:protobuf-java:3.6.1")
compile("org.apache.logging.log4j:log4j-api:${log4j2Version}")
compile("org.apache.logging.log4j:log4j-core:${log4j2Version}")
testCompile("org.junit.jupiter:junit-jupiter-api:5.4.2")
@ -31,6 +44,31 @@ dependencies {
}
sourceSets {
main {
proto {
srcDir("src/main/proto")
}
}
}
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:3.0.0"
}
tasks.get("clean").doFirst({ delete(generatedFilesBaseDir) })
idea {
module {
sourceDirs.add(file("${generatedFilesBaseDir}/main/java"))
}
}
}
tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "1.8"
}

200
gradlew.bat

@ -1,100 +1,100 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem http://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem http://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

4
src/main/kotlin/io/libp2p/core/Host.kt

@ -1,7 +1,7 @@
package io.libp2p.core
import io.libp2p.core.protocol.ProtocolMatcher
import io.libp2p.core.security.SecureChannel
import io.libp2p.core.protocol.SecureChannel
/**
* The Host is the libp2p entrypoint.
@ -18,7 +18,7 @@ class Host private constructor (var id: PeerId?, var secureChannels: Map<Protoco
}
class Builder {
private var id : PeerId? = null
private var id: PeerId? = null
private var secureChannels = mutableMapOf<ProtocolMatcher, SecureChannel>()
/**

20
src/main/kotlin/io/libp2p/core/Libp2pException.kt

@ -0,0 +1,20 @@
/*
* Copyright 2019 BLK Technologies Limited (web3labs.com).
*
* 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 io.libp2p.core
class Libp2pException : RuntimeException {
constructor(message: String, ex: Exception?) : super(message, ex) {}
constructor(message: String) : super(message) {}
constructor(ex: Exception) : super(ex) {}
}

12
src/main/kotlin/io/libp2p/core/Peer.kt

@ -1,7 +1,6 @@
package io.libp2p.core
import io.libp2p.core.multiformats.Multiaddr
import io.netty.channel.ChannelFuture
import java.util.concurrent.Future
class Peer(val id: PeerId) {
@ -12,16 +11,16 @@ class Peer(val id: PeerId) {
fun streams(): PeerStreams = PeerStreams()
fun connect() : Future<PeerConnection> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
fun connect(): Future<PeerConnection> {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
fun disconnect() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
fun connection() : PeerConnection? {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
fun connection(): PeerConnection? {
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
enum class Status {
@ -29,4 +28,3 @@ class Peer(val id: PeerId) {
CONNECTED
}
}

4
src/main/kotlin/io/libp2p/core/PeerConnection.kt

@ -1,5 +1,3 @@
package io.libp2p.core
class PeerConnection {
}
class PeerConnection

15
src/main/kotlin/io/libp2p/core/PeerId.kt

@ -1,12 +1,19 @@
package io.libp2p.core
import io.libp2p.core.crypto.PubKey
inline class PeerId(val b: ByteArray) {
object companion {
companion object {
@JvmStatic
fun fromBase58(str: String): PeerId? {
return null
fun fromBase58(str: String): PeerId {
return PeerId(ByteArray(32))
}
@JvmStatic
fun fromPubKey(pubKey: PubKey): PeerId {
return PeerId(ByteArray(32))
}
}
}
}

1
src/main/kotlin/io/libp2p/core/PeerStreams.kt

@ -5,5 +5,4 @@ import io.netty.channel.ChannelFuture
class PeerStreams {
fun create(s: String): ChannelFuture = TODO()
}

90
src/main/kotlin/io/libp2p/core/Stream.kt

@ -1,13 +1,11 @@
package io.libp2p.core
import io.netty.buffer.ByteBufAllocator
import io.netty.channel.AbstractChannel
import io.netty.channel.Channel
import io.netty.channel.ChannelConfig
import io.netty.channel.ChannelFuture
import io.netty.channel.ChannelId
import io.netty.channel.ChannelMetadata
import io.netty.channel.ChannelOutboundBuffer
import io.netty.channel.ChannelPipeline
import io.netty.channel.ChannelProgressivePromise
import io.netty.channel.ChannelPromise
@ -16,61 +14,61 @@ import io.netty.util.Attribute
import io.netty.util.AttributeKey
import java.net.SocketAddress
class Stream: Channel {
class Stream : Channel {
override fun writeAndFlush(msg: Any?, promise: ChannelPromise?): ChannelFuture {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun writeAndFlush(msg: Any?): ChannelFuture {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun isActive(): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun alloc(): ByteBufAllocator {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun id(): ChannelId {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun newPromise(): ChannelPromise {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun write(msg: Any?): ChannelFuture {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun write(msg: Any?, promise: ChannelPromise?): ChannelFuture {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun closeFuture(): ChannelFuture {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun <T : Any?> hasAttr(key: AttributeKey<T>?): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun flush(): Channel {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun connect(remoteAddress: SocketAddress?): ChannelFuture {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun connect(remoteAddress: SocketAddress?, localAddress: SocketAddress?): ChannelFuture {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun connect(remoteAddress: SocketAddress?, promise: ChannelPromise?): ChannelFuture {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun connect(
@ -78,118 +76,118 @@ class Stream: Channel {
localAddress: SocketAddress?,
promise: ChannelPromise?
): ChannelFuture {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun newFailedFuture(cause: Throwable?): ChannelFuture {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun remoteAddress(): SocketAddress {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun config(): ChannelConfig {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun newSucceededFuture(): ChannelFuture {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun isOpen(): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun bytesBeforeUnwritable(): Long {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun bytesBeforeWritable(): Long {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun pipeline(): ChannelPipeline {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun close(): ChannelFuture {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun close(promise: ChannelPromise?): ChannelFuture {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun read(): Channel {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun voidPromise(): ChannelPromise {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun parent(): Channel {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun <T : Any?> attr(key: AttributeKey<T>?): Attribute<T> {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun deregister(): ChannelFuture {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun deregister(promise: ChannelPromise?): ChannelFuture {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun compareTo(other: Channel?): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun disconnect(): ChannelFuture {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun disconnect(promise: ChannelPromise?): ChannelFuture {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun unsafe(): Channel.Unsafe {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun newProgressivePromise(): ChannelProgressivePromise {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun isWritable(): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun metadata(): ChannelMetadata {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun localAddress(): SocketAddress {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun bind(localAddress: SocketAddress?): ChannelFuture {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun bind(localAddress: SocketAddress?, promise: ChannelPromise?): ChannelFuture {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun isRegistered(): Boolean {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
override fun eventLoop(): EventLoop {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
TODO("not implemented") // To change body of created functions use File | Settings | File Templates.
}
}

9
src/main/kotlin/io/libp2p/core/crypto/Hash.kt

@ -0,0 +1,9 @@
package io.libp2p.core.crypto
import org.bouncycastle.jcajce.provider.digest.SHA1
import org.bouncycastle.jcajce.provider.digest.SHA256
import org.bouncycastle.jcajce.provider.digest.SHA512
fun sha1(data: ByteArray) : ByteArray = SHA1.Digest().digest(data)
fun sha256(data: ByteArray): ByteArray = SHA256.Digest().digest(data)
fun sha512(data: ByteArray): ByteArray = SHA512.Digest().digest(data)

261
src/main/kotlin/io/libp2p/core/crypto/Key.kt

@ -0,0 +1,261 @@
/*
* Copyright 2019 BLK Technologies Limited (web3labs.com).
*
* 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 io.libp2p.core.crypto
import com.google.protobuf.ByteString
import crypto.pb.Crypto
import io.libp2p.core.crypto.keys.generateEcdsaKeyPair
import io.libp2p.core.crypto.keys.generateEd25519KeyPair
import io.libp2p.core.crypto.keys.generateRsaKeyPair
import io.libp2p.core.crypto.keys.generateSecp256k1KeyPair
import io.libp2p.core.crypto.keys.unmarshalEcdsaPrivateKey
import io.libp2p.core.crypto.keys.unmarshalEcdsaPublicKey
import io.libp2p.core.crypto.keys.unmarshalEd25519PrivateKey
import io.libp2p.core.crypto.keys.unmarshalEd25519PublicKey
import io.libp2p.core.crypto.keys.unmarshalRsaPrivateKey
import io.libp2p.core.crypto.keys.unmarshalRsaPublicKey
import io.libp2p.core.crypto.keys.unmarshalSecp256k1PrivateKey
import io.libp2p.core.crypto.keys.unmarshalSecp256k1PublicKey
import io.libp2p.core.types.toHex
import org.bouncycastle.crypto.digests.SHA256Digest
import org.bouncycastle.crypto.digests.SHA512Digest
import org.bouncycastle.crypto.macs.HMac
import org.bouncycastle.crypto.params.KeyParameter
import crypto.pb.Crypto.PrivateKey as PbPrivateKey
import crypto.pb.Crypto.PublicKey as PbPublicKey
enum class KEY_TYPE {
/**
* RSA is an enum for the supported RSA key type
*/
RSA,
/**
* Ed25519 is an enum for the supported Ed25519 key type
*/
ED25519,
/**
* Secp256k1 is an enum for the supported Secp256k1 key type
*/
SECP256K1,
/**
* ECDSA is an enum for the supported ECDSA key type
*/
ECDSA
}
interface Key {
val keyType: crypto.pb.Crypto.KeyType
/**
* Bytes returns a serialized, storeable representation of this key.
*/
fun bytes(): ByteArray
fun raw(): ByteArray
}
/**
* PrivKey represents a private key that can be used to generate a public key,
* sign data, and decrypt data that was encrypted with a public key.
*/
abstract class PrivKey(override val keyType: Crypto.KeyType) : Key {
/**
* Cryptographically sign the given bytes.
*/
abstract fun sign(data: ByteArray): ByteArray
/**
* Return a public key paired with this private key.
*/
abstract fun publicKey(): PubKey
override fun bytes(): ByteArray = marshalPrivateKey(this)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
return bytes().contentEquals((other as PrivKey).bytes())
}
}
/**
* PubKey is a public key.
*/
abstract class PubKey(override val keyType: Crypto.KeyType) : Key {
/**
* Verify that 'sig' is the signed hash of 'data'.
*/
abstract fun verify(data: ByteArray, signature: ByteArray): Boolean
override fun bytes(): ByteArray = marshalPublicKey(this)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
return bytes().contentEquals((other as PubKey).bytes())
}
}
class BadKeyTypeException : Exception("Invalid or unsupported key type")
/**
* Generates a new key pair of the provided type.
* @param type the type key to be generated.
* @param bits the number of bits desired for the key (only applicable for RSA).
*/
fun generateKeyPair(type: KEY_TYPE, bits: Int = 0): Pair<PrivKey, PubKey> {
return when (type) {
KEY_TYPE.RSA -> generateRsaKeyPair(bits)
KEY_TYPE.ED25519 -> generateEd25519KeyPair()
KEY_TYPE.SECP256K1 -> generateSecp256k1KeyPair()
KEY_TYPE.ECDSA -> generateEcdsaKeyPair()
}
}
/**
* Converts the protobuf serialized public key into its representative object.
* @param data the byte array of the protobuf public key.
* @return the equivalent public key.
*/
fun unmarshalPublicKey(data: ByteArray): PubKey {
val pmes = PbPublicKey.parseFrom(data)
val pmesd = pmes.data.toByteArray()
return when (pmes.type) {
Crypto.KeyType.RSA -> unmarshalRsaPublicKey(pmesd)
Crypto.KeyType.Ed25519 -> unmarshalEd25519PublicKey(pmesd)
Crypto.KeyType.Secp256k1 -> unmarshalSecp256k1PublicKey(pmesd)
Crypto.KeyType.ECDSA -> unmarshalEcdsaPublicKey(pmesd)
else -> throw BadKeyTypeException()
}
}
/**
* Converts a public key object into a protobuf serialized public key.
* @param pubKey the public key instance.
* @return the protobuf bytes.
*/
fun marshalPublicKey(pubKey: PubKey): ByteArray =
PbPublicKey.newBuilder()
.setType(pubKey.keyType)
.setData(ByteString.copyFrom(pubKey.raw()))
.build()
.toByteArray()
/**
* Converts a protobuf serialized private key into its representative object.
* @param data the byte array of hte protobuf private key.
* @return the equivalent private key.
*/
fun unmarshalPrivateKey(data: ByteArray): PrivKey {
val pmes = PbPrivateKey.parseFrom(data)
val pmesd = pmes.data.toByteArray()
return when (pmes.type) {
Crypto.KeyType.RSA -> unmarshalRsaPrivateKey(pmesd)
Crypto.KeyType.Ed25519 -> unmarshalEd25519PrivateKey(pmesd)
Crypto.KeyType.Secp256k1 -> unmarshalSecp256k1PrivateKey(pmesd)
Crypto.KeyType.ECDSA -> unmarshalEcdsaPrivateKey(pmesd)
else -> throw BadKeyTypeException()
}
}
/**
* Converts a public key object into a protobuf serialized private key.
* @param privKey the private key.
* @return the protobuf bytes.
*/
fun marshalPrivateKey(privKey: PrivKey): ByteArray =
PbPrivateKey.newBuilder()
.setType(privKey.keyType)
.setData(ByteString.copyFrom(privKey.raw()))
.build()
.toByteArray()
data class StretchedKey(val iv: ByteArray, val cipherKey: ByteArray, val macKey: ByteArray) {
override fun toString(): String =
"StretchedKey[iv=" + iv.toHex() + ", cipherKey=" + cipherKey.toHex() + ", macKey=" + macKey.toHex() + "]"
}
fun stretchKeys(cipherType: String, hashType: String, secret: ByteArray): Pair<StretchedKey, StretchedKey> {
val ivSize = 16
val cipherKeySize = when (cipherType) {
"AES-128" -> 16
"AES-256" -> 32
else -> throw IllegalArgumentException("Unsupported cipher: $cipherType")
}
val hmacKeySize = 20
val seed = "key expansion".toByteArray()
val result = ByteArray(2 * (ivSize + cipherKeySize + hmacKeySize))
val hmac = when(hashType) {
"SHA256" -> HMac(SHA256Digest())
"SHA512" -> HMac(SHA512Digest())
else -> throw IllegalArgumentException("Unsupported hash function: $hashType")
}
hmac.init(KeyParameter(secret))
hmac.update(seed, 0, seed.size)
val a = ByteArray(hmac.macSize)
hmac.doFinal(a, 0)
var j = 0
while (j < result.size) {
hmac.reset()
hmac.update(a, 0, a.size)
hmac.update(seed, 0, seed.size)
val b = ByteArray(hmac.macSize)
hmac.doFinal(b, 0)
var todo = b.size
if (j + todo > result.size) {
todo = result.size - j
}
b.copyInto(result, j, 0, todo)
j += todo
hmac.reset()
hmac.update(a, 0, a.size)
hmac.doFinal(a, 0)
}
val half = result.size / 2
val r1 = result.sliceArray(0 until half)
val r2 = result.sliceArray(half until result.size)
return Pair(
StretchedKey(
r1.sliceArray(0 until ivSize),
r1.sliceArray(ivSize until ivSize + cipherKeySize),
r1.sliceArray(ivSize + cipherKeySize until r1.size)
),
StretchedKey(
r2.sliceArray(0 until ivSize),
r2.sliceArray(ivSize until ivSize + cipherKeySize),
r2.sliceArray(ivSize + cipherKeySize until r2.size)
)
)
}

41
src/main/kotlin/io/libp2p/core/crypto/Libp2pCrypto.kt

@ -0,0 +1,41 @@
/*
* Copyright 2019 BLK Technologies Limited (web3labs.com).
*
* 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 io.libp2p.core.crypto
/**
* ErrRsaKeyTooSmall is returned when trying to generate or parse an RSA key
* that's smaller than 512 bits. Keys need to be larger enough to sign a 256bit
* hash so this is a reasonable absolute minimum.
*/
const val ErrRsaKeyTooSmall = "rsa keys must be >= 512 bits to be useful"
const val RSA_ALGORITHM = "RSA"
const val SHA_ALGORITHM = "SHA-256"
const val ECDSA_ALGORITHM = "ECDSA"
const val ED25519_ALGORITHM = "ED25519"
const val SECP_256K1_ALGORITHM = "secp256k1"
const val P256_CURVE = "P-256"
const val SHA_256_WITH_RSA = "SHA256withRSA"
const val SHA_256_WITH_ECDSA = "SHA256withECDSA"
const val KEY_PKCS8 = "PKCS#8"
object Libp2pCrypto {
val provider = org.bouncycastle.jce.provider.BouncyCastleProvider()
}

2
src/main/kotlin/io/libp2p/core/crypto/PublicKey.kt

@ -1,2 +0,0 @@
package io.libp2p.core.crypto

177
src/main/kotlin/io/libp2p/core/crypto/keys/Ecdsa.kt

@ -0,0 +1,177 @@
/*
* Copyright 2019 BLK Technologies Limited (web3labs.com).
*
* 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 io.libp2p.core.crypto.keys
import crypto.pb.Crypto
import io.libp2p.core.Libp2pException
import io.libp2p.core.crypto.ECDSA_ALGORITHM
import io.libp2p.core.crypto.KEY_PKCS8
import io.libp2p.core.crypto.Libp2pCrypto
import io.libp2p.core.crypto.P256_CURVE
import io.libp2p.core.crypto.PrivKey
import io.libp2p.core.crypto.PubKey
import io.libp2p.core.crypto.SHA_256_WITH_ECDSA
import io.libp2p.core.types.sliceTrailing
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey
import org.bouncycastle.jce.ECNamedCurveTable
import org.bouncycastle.jce.ECPointUtil
import org.bouncycastle.jce.provider.BouncyCastleProvider
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec
import org.bouncycastle.jce.spec.ECNamedCurveSpec
import org.bouncycastle.jce.spec.ECPublicKeySpec
import java.security.KeyFactory
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.SecureRandom
import java.security.Signature
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
import java.security.PrivateKey as JavaPrivateKey
import java.security.interfaces.ECPrivateKey as JavaECPrivateKey
import java.security.interfaces.ECPublicKey as JavaECPublicKey
private val CURVE: ECNamedCurveParameterSpec = ECNamedCurveTable.getParameterSpec(P256_CURVE)
/**
* @param priv the private key backing this instance.
*/
class EcdsaPrivateKey(val priv: JavaECPrivateKey) : PrivKey(Crypto.KeyType.ECDSA) {
init {
// Set up private key.
if (priv.format != KEY_PKCS8) {
throw Libp2pException("Private key must be of '$KEY_PKCS8' format")
}
}
override fun raw(): ByteArray = priv.encoded
/**
* Sign the given bytes and returns the signature of the input data.
* @param data the bytes to be signed.
* @return the signature as a byte array.
*/
override fun sign(data: ByteArray): ByteArray =
with(Signature.getInstance(SHA_256_WITH_ECDSA, Libp2pCrypto.provider)) {
// Signature is made up of r and s numbers.
initSign(priv)
update(data)
sign()
}
override fun publicKey(): EcdsaPublicKey {
val pubSpec: ECPublicKeySpec = (priv as BCECPrivateKey).run {
val q = parameters.g.multiply((this as org.bouncycastle.jce.interfaces.ECPrivateKey).d)
ECPublicKeySpec(q, parameters)
}
return with(KeyFactory.getInstance(ECDSA_ALGORITHM, Libp2pCrypto.provider)) {
EcdsaPublicKey(generatePublic(pubSpec) as JavaECPublicKey)
}
}
override fun hashCode(): Int = priv.hashCode()
}
/**
* @param pub the public key backing this instance.
*/
class EcdsaPublicKey(val pub: JavaECPublicKey) : PubKey(Crypto.KeyType.ECDSA) {
override fun raw(): ByteArray = pub.encoded
fun toUncompressedBytes(): ByteArray =
byteArrayOf(0x04) + pub.w.affineX.toByteArray().sliceTrailing(32) + pub.w.affineY.toByteArray().sliceTrailing(32)
override fun verify(data: ByteArray, signature: ByteArray): Boolean =
with(Signature.getInstance(SHA_256_WITH_ECDSA, Libp2pCrypto.provider)) {
initVerify(pub)
update(data)
verify(signature)
}
override fun hashCode(): Int = pub.hashCode()
}
/**
* Generates a new ECDSA private and public key with a specified curve.
* @param curve the curve spec.
* @return a pair of private and public keys.
*/
private fun generateECDSAKeyPairWithCurve(curve: ECNamedCurveParameterSpec): Pair<EcdsaPrivateKey, EcdsaPublicKey> {
val keypair: KeyPair = with(KeyPairGenerator.getInstance(ECDSA_ALGORITHM, Libp2pCrypto.provider)) {
initialize(curve, SecureRandom())
genKeyPair()
}
return Pair(EcdsaPrivateKey(keypair.private as JavaECPrivateKey), EcdsaPublicKey(keypair.public as JavaECPublicKey))
}
/**
* Generates a new ECDSA private and public key pair.
* @return a pair of private and public keys.
*/
fun generateEcdsaKeyPair(): Pair<PrivKey, PubKey> {
// http://www.bouncycastle.org/wiki/display/JA1/Supported+Curves+%28ECDSA+and+ECGOST%29
// and
// http://www.bouncycastle.org/wiki/pages/viewpage.action?pageId=362269
return generateECDSAKeyPairWithCurve(CURVE)
}
fun generateEcdsaKeyPair(curve: String): Pair<EcdsaPrivateKey, EcdsaPublicKey> {
// http://www.bouncycastle.org/wiki/display/JA1/Supported+Curves+%28ECDSA+and+ECGOST%29
// and
// http://www.bouncycastle.org/wiki/pages/viewpage.action?pageId=362269
return generateECDSAKeyPairWithCurve(ECNamedCurveTable.getParameterSpec(curve))
}
/**
* Generates a new ecdsa private and public key from an input private key.
* @param priv the private key.
* @return a pair of private and public keys.
*/
fun ecdsaKeyPairFromKey(priv: EcdsaPrivateKey): Pair<PrivKey, PubKey> = Pair(priv, priv.publicKey())
/**
* Unmarshals the given key bytes into an ECDSA private key instance.
* @param keyBytes the key bytes.
* @return a private key.
*/
fun unmarshalEcdsaPrivateKey(keyBytes: ByteArray): PrivKey = EcdsaPrivateKey(
KeyFactory.getInstance(ECDSA_ALGORITHM, Libp2pCrypto.provider).generatePrivate(
PKCS8EncodedKeySpec(keyBytes)
) as JavaECPrivateKey
)
/**
* Unmarshals the given key bytes into an ECDSA public key instance.
* @param keyBytes the key bytes.
* @return a public key.
*/
fun unmarshalEcdsaPublicKey(keyBytes: ByteArray): EcdsaPublicKey =
with(KeyFactory.getInstance(ECDSA_ALGORITHM, Libp2pCrypto.provider)) {
EcdsaPublicKey(generatePublic(X509EncodedKeySpec(keyBytes)) as JavaECPublicKey)
}
fun decodeEcdsaPublicKeyUncompressed(ecCurve: String, keyBytes: ByteArray): EcdsaPublicKey {
val spec = ECNamedCurveTable.getParameterSpec(ecCurve)
val kf = KeyFactory.getInstance("ECDSA", BouncyCastleProvider())
val params = ECNamedCurveSpec(ecCurve, spec.getCurve(), spec.getG(), spec.getN())
val point = ECPointUtil.decodePoint(params.getCurve(), keyBytes)
val pubKeySpec = java.security.spec.ECPublicKeySpec(point, params)
val publicKey = kf.generatePublic(pubKeySpec)
publicKey as JavaECPublicKey
return EcdsaPublicKey(publicKey)
}

82
src/main/kotlin/io/libp2p/core/crypto/keys/Ed25519.kt

@ -0,0 +1,82 @@
/*
* Copyright 2019 BLK Technologies Limited (web3labs.com).
*
* 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 io.libp2p.core.crypto.keys
import crypto.pb.Crypto
import io.libp2p.core.crypto.PrivKey
import io.libp2p.core.crypto.PubKey
import org.bouncycastle.crypto.generators.Ed25519KeyPairGenerator
import org.bouncycastle.crypto.params.Ed25519KeyGenerationParameters
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters
import org.bouncycastle.crypto.signers.Ed25519Signer
import java.security.SecureRandom
/**
* @param priv the private key backing this instance.
*/
class Ed25519PrivateKey(private val priv: Ed25519PrivateKeyParameters) : PrivKey(Crypto.KeyType.Ed25519) {
override fun raw(): ByteArray = priv.encoded
override fun sign(data: ByteArray): ByteArray = with(Ed25519Signer()) {
init(true, priv)
update(data, 0, data.size)
generateSignature()
}
override fun publicKey(): PubKey = Ed25519PublicKey(priv.generatePublicKey())
override fun hashCode(): Int = priv.hashCode()
}
/**
* @param pub the public key backing this instance.
*/
class Ed25519PublicKey(private val pub: Ed25519PublicKeyParameters) : PubKey(Crypto.KeyType.Ed25519) {
override fun raw(): ByteArray = pub.encoded
override fun verify(data: ByteArray, signature: ByteArray): Boolean = with(Ed25519Signer()) {
init(false, pub)
update(data, 0, data.size)
verifySignature(signature)
}
override fun hashCode(): Int = pub.hashCode()
}
/**
* @return a newly-generated ED25519 private and public key pair.
*/
fun generateEd25519KeyPair(): Pair<PrivKey, PubKey> = with(Ed25519KeyPairGenerator()) {
init(Ed25519KeyGenerationParameters(SecureRandom()))
val keypair = generateKeyPair()
val privateKey = keypair.private as Ed25519PrivateKeyParameters
Pair(Ed25519PrivateKey(privateKey), Ed25519PublicKey(keypair.public as Ed25519PublicKeyParameters))
}
/**
* Unmarshals the given key bytes into an ED25519 private key instance.
* @param keyBytes the key bytes.
* @return a private key.
*/
fun unmarshalEd25519PrivateKey(keyBytes: ByteArray): PrivKey =
Ed25519PrivateKey(Ed25519PrivateKeyParameters(keyBytes, 0))
/**
* Unmarshals the given key bytes into an ED25519 public key instance.
* @param keyBytes the key bytes.
* @return a public key.
*/
fun unmarshalEd25519PublicKey(keyBytes: ByteArray): PubKey = Ed25519PublicKey(Ed25519PublicKeyParameters(keyBytes, 0))

160
src/main/kotlin/io/libp2p/core/crypto/keys/Rsa.kt

@ -0,0 +1,160 @@
/*
* Copyright 2019 BLK Technologies Limited (web3labs.com).
*
* 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 io.libp2p.core.crypto.keys
import crypto.pb.Crypto
import io.libp2p.core.Libp2pException
import io.libp2p.core.crypto.ErrRsaKeyTooSmall
import io.libp2p.core.crypto.KEY_PKCS8
import io.libp2p.core.crypto.Libp2pCrypto
import io.libp2p.core.crypto.PrivKey
import io.libp2p.core.crypto.PubKey
import io.libp2p.core.crypto.RSA_ALGORITHM
import io.libp2p.core.crypto.SHA_256_WITH_RSA
import org.bouncycastle.asn1.ASN1Primitive
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.asn1.pkcs.RSAPrivateKey
import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters
import org.bouncycastle.crypto.util.PrivateKeyInfoFactory
import java.security.KeyFactory
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.Signature
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.RSAPublicKeySpec
import java.security.spec.X509EncodedKeySpec
import java.security.PrivateKey as JavaPrivateKey
import java.security.PublicKey as JavaPublicKey
/**
* @param sk the private key backing this instance.
* @param pk the public key backing this instance.
*/
class RsaPrivateKey(private val sk: JavaPrivateKey, private val pk: JavaPublicKey) : PrivKey(Crypto.KeyType.RSA) {
private val rsaPublicKey = RsaPublicKey(pk)
private val pkcs1PrivateKeyBytes: ByteArray
init {
// Set up private key.
val isKeyOfFormat: Boolean = sk.format?.equals(KEY_PKCS8) ?: false
if (!isKeyOfFormat) {
throw Libp2pException("Private key must be of '$KEY_PKCS8' format")
}
val bcPrivateKeyInfo = PrivateKeyInfo.getInstance(sk.encoded)
pkcs1PrivateKeyBytes = bcPrivateKeyInfo.parsePrivateKey().toASN1Primitive().encoded
}
override fun raw(): ByteArray = pkcs1PrivateKeyBytes
override fun sign(data: ByteArray): ByteArray =
with(Signature.getInstance(SHA_256_WITH_RSA, Libp2pCrypto.provider)) {
initSign(sk)
update(data)
sign()
}
override fun publicKey(): PubKey = rsaPublicKey
override fun hashCode(): Int = pk.hashCode()
}
/**
* @param k the public key backing this instance.
*/
class RsaPublicKey(private val k: JavaPublicKey) : PubKey(Crypto.KeyType.RSA) {
override fun raw(): ByteArray = k.encoded
override fun verify(data: ByteArray, signature: ByteArray): Boolean =
with(Signature.getInstance(SHA_256_WITH_RSA, Libp2pCrypto.provider)) {
initVerify(k)
update(data)
verify(signature)
}
override fun hashCode(): Int = k.hashCode()
}
/**
* Generates a new rsa private and public key.
* @param bits the number of bits required in the key.
* @return a pair of the private and public keys.
*/
fun generateRsaKeyPair(bits: Int): Pair<PrivKey, PubKey> {
if (bits < 512) {
throw Libp2pException(ErrRsaKeyTooSmall)
}
val kp: KeyPair = with(
KeyPairGenerator.getInstance(
RSA_ALGORITHM,
Libp2pCrypto.provider
)
) {
initialize(bits)
genKeyPair()
}
return Pair(
RsaPrivateKey(kp.private, kp.public),
RsaPublicKey(kp.public)
)
}
/**
* Unmarshals the given key bytes into an RSA public key instance.
* @param keyBytes the key bytes.
* @return a private key.
*/
fun unmarshalRsaPublicKey(keyBytes: ByteArray): PubKey =
RsaPublicKey(
KeyFactory.getInstance(
RSA_ALGORITHM,
Libp2pCrypto.provider
).generatePublic(X509EncodedKeySpec(keyBytes))
)
/**
* Unmarshals the given key bytes (in PKCS1 format) into an RSA PKCS8 private key instance.
* @param keyBytes the key bytes.
* @return a private key instance.
*/
fun unmarshalRsaPrivateKey(keyBytes: ByteArray): PrivKey {
// Input is ASN1 DER encoded PKCS1 private key bytes.
val rsaPrivateKey = RSAPrivateKey.getInstance(ASN1Primitive.fromByteArray(keyBytes))
val privateKeyParameters = RSAPrivateCrtKeyParameters(
rsaPrivateKey.modulus,
rsaPrivateKey.publicExponent,
rsaPrivateKey.privateExponent,
rsaPrivateKey.prime1,
rsaPrivateKey.prime2,
rsaPrivateKey.exponent1,
rsaPrivateKey.exponent2,
rsaPrivateKey.coefficient
)
// Now convert to a PKSC#8 key.
val privateKeyInfo = PrivateKeyInfoFactory.createPrivateKeyInfo(privateKeyParameters)
val algorithmId = privateKeyInfo.privateKeyAlgorithm.algorithm.id
val spec = PKCS8EncodedKeySpec(privateKeyInfo.encoded)
val sk = KeyFactory.getInstance(algorithmId, Libp2pCrypto.provider).generatePrivate(spec)
// We can extract the public key from the modulus and exponent of the private key. Woot!
val publicKeySpec = RSAPublicKeySpec(privateKeyParameters.modulus, privateKeyParameters.publicExponent)
val keyFactory = KeyFactory.getInstance(RSA_ALGORITHM)
val pk = keyFactory.generatePublic(publicKeySpec)
return RsaPrivateKey(sk, pk)
}

149
src/main/kotlin/io/libp2p/core/crypto/keys/Secp256k1.kt

@ -0,0 +1,149 @@
/*
* Copyright 2019 BLK Technologies Limited (web3labs.com).
*
* 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 io.libp2p.core.crypto.keys
import crypto.pb.Crypto
import io.libp2p.core.Libp2pException
import io.libp2p.core.crypto.PrivKey
import io.libp2p.core.crypto.PubKey
import io.libp2p.core.crypto.SECP_256K1_ALGORITHM
import org.bouncycastle.asn1.ASN1InputStream
import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.ASN1Primitive
import org.bouncycastle.asn1.ASN1Sequence
import org.bouncycastle.asn1.DERSequenceGenerator
import org.bouncycastle.asn1.sec.SECNamedCurves
import org.bouncycastle.crypto.ec.CustomNamedCurves
import org.bouncycastle.crypto.generators.ECKeyPairGenerator
import org.bouncycastle.crypto.params.ECDomainParameters
import org.bouncycastle.crypto.params.ECKeyGenerationParameters
import org.bouncycastle.crypto.params.ECPrivateKeyParameters
import org.bouncycastle.crypto.params.ECPublicKeyParameters
import org.bouncycastle.crypto.params.ParametersWithRandom
import org.bouncycastle.crypto.signers.ECDSASigner
import org.bouncycastle.math.ec.FixedPointCombMultiplier
import org.bouncycastle.math.ec.FixedPointUtil
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.math.BigInteger
import java.security.SecureRandom
// The parameters of the secp256k1 curve that Bitcoin uses.
private val CURVE_PARAMS = CustomNamedCurves.getByName(SECP_256K1_ALGORITHM)
private val CURVE: ECDomainParameters = CURVE_PARAMS.let {
FixedPointUtil.precompute(CURVE_PARAMS.g)
ECDomainParameters(CURVE_PARAMS.curve, CURVE_PARAMS.g, CURVE_PARAMS.n, CURVE_PARAMS.h)
}
/**
* @param privateKey the private key backing this instance.
*/
class Secp256k1PrivateKey(private val privateKey: ECPrivateKeyParameters) : PrivKey(Crypto.KeyType.Secp256k1) {
private val priv = privateKey.d
override fun raw(): ByteArray = priv.toByteArray()
override fun sign(data: ByteArray): ByteArray {
val (r, s) = with(ECDSASigner()) {
init(true, ParametersWithRandom(privateKey, SecureRandom()))
generateSignature(data).let {
Pair(it[0], it[1])
}
}
return with(ByteArrayOutputStream()) {
DERSequenceGenerator(this).run {
addObject(ASN1Integer(r))
addObject(ASN1Integer(s))
close()
toByteArray()
}
}
}
override fun publicKey(): PubKey {
val privKey = if (priv.bitLength() > CURVE.n.bitLength()) priv.mod(CURVE.n) else priv
val publicPoint = FixedPointCombMultiplier().multiply(CURVE.g, privKey)
return Secp256k1PublicKey(ECPublicKeyParameters(publicPoint, CURVE))
}
override fun hashCode(): Int = priv.hashCode()
}
/**
* @param pub the public key backing this instance.
*/
class Secp256k1PublicKey(private val pub: ECPublicKeyParameters) : PubKey(Crypto.KeyType.Secp256k1) {
override fun raw(): ByteArray = pub.q.getEncoded(true)
override fun verify(data: ByteArray, signature: ByteArray): Boolean {
val signer = ECDSASigner().also {
it.init(false, pub)
}
val asn1: ASN1Primitive =
ByteArrayInputStream(signature)
.use { inStream ->
ASN1InputStream(inStream)
.use { asnInputStream ->
asnInputStream.readObject()
}
}
val asn1Encodables = (asn1 as ASN1Sequence).toArray().also {
if (it.size != 2) {
throw Libp2pException("Invalid signature: expected 2 values for 'r' and 's' but got ${it.size}")
}
}
val r = (asn1Encodables[0].toASN1Primitive() as ASN1Integer).value
val s = (asn1Encodables[1].toASN1Primitive() as ASN1Integer).value
return signer.verifySignature(data, r.abs(), s.abs())
}
override fun hashCode(): Int = pub.hashCode()
}
/**
* Generates a new SECP256K1 private and public key.
* @return a pair of the private and public keys.
*/
fun generateSecp256k1KeyPair(): Pair<PrivKey, PubKey> = with(ECKeyPairGenerator()) {
val domain = SECNamedCurves.getByName(SECP_256K1_ALGORITHM).let {
ECDomainParameters(it.curve, it.g, it.n, it.h)
}
init(ECKeyGenerationParameters(domain, SecureRandom()))
val keypair = generateKeyPair()
val privateKey = keypair.private as ECPrivateKeyParameters
return Pair(Secp256k1PrivateKey(privateKey), Secp256k1PublicKey(keypair.public as ECPublicKeyParameters))
}
/**
* Unmarshals the given key bytes into a SECP256K1 private key instance.
* @param keyBytes the key bytes.
* @return a private key instance.
*/
fun unmarshalSecp256k1PrivateKey(data: ByteArray): PrivKey =
Secp256k1PrivateKey(ECPrivateKeyParameters(BigInteger(1, data), CURVE))
/**
* Unmarshals the given key bytes into a SECP256K1 public key instance.
* @param keyBytes the key bytes.
* @return a public key instance.
*/
fun unmarshalSecp256k1PublicKey(data: ByteArray): PubKey =
Secp256k1PublicKey(ECPublicKeyParameters(CURVE.curve.decodePoint(data), CURVE))

8
src/main/kotlin/io/libp2p/core/multiformats/Multiaddr.kt

@ -16,7 +16,7 @@ data class Multiaddr(val components: List<Pair<Protocol, ByteArray>>) {
fun getStringComponents(): List<Pair