Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f52f1237a2 | |||
| 4130ba681c | |||
| df9d2c59a4 | |||
| 7d4ebab8f8 |
@@ -9,17 +9,15 @@ plugins {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(hlaeja.fasterxml.jackson.module.kotlin)
|
implementation(hlaeja.fasterxml.jackson.module.kotlin)
|
||||||
implementation(hlaeja.jjwt.api)
|
|
||||||
implementation(hlaeja.kotlin.logging)
|
implementation(hlaeja.kotlin.logging)
|
||||||
implementation(hlaeja.kotlin.reflect)
|
implementation(hlaeja.kotlin.reflect)
|
||||||
implementation(hlaeja.kotlinx.coroutines)
|
implementation(hlaeja.kotlinx.coroutines)
|
||||||
implementation(hlaeja.library.hlaeja.common.messages)
|
implementation(hlaeja.library.hlaeja.common.messages)
|
||||||
|
implementation(hlaeja.library.hlaeja.jwt)
|
||||||
implementation(hlaeja.springboot.starter.actuator)
|
implementation(hlaeja.springboot.starter.actuator)
|
||||||
implementation(hlaeja.springboot.starter.r2dbc)
|
implementation(hlaeja.springboot.starter.r2dbc)
|
||||||
implementation(hlaeja.springboot.starter.webflux)
|
implementation(hlaeja.springboot.starter.webflux)
|
||||||
|
|
||||||
runtimeOnly(hlaeja.jjwt.impl)
|
|
||||||
runtimeOnly(hlaeja.jjwt.jackson)
|
|
||||||
runtimeOnly(hlaeja.postgresql)
|
runtimeOnly(hlaeja.postgresql)
|
||||||
runtimeOnly(hlaeja.postgresql.r2dbc)
|
runtimeOnly(hlaeja.postgresql.r2dbc)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
version=0.3.0
|
version=0.4.0
|
||||||
catalog=0.7.0
|
catalog=0.8.0
|
||||||
container.port.host=9010
|
container.port.host=9010
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package ltd.hlaeja.controller
|
package ltd.hlaeja.controller
|
||||||
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
import ltd.hlaeja.jwt.service.PrivateJwtService
|
||||||
import ltd.hlaeja.library.deviceRegistry.Device
|
import ltd.hlaeja.library.deviceRegistry.Device
|
||||||
import ltd.hlaeja.service.DeviceService
|
import ltd.hlaeja.service.DeviceService
|
||||||
import ltd.hlaeja.service.JwtService
|
|
||||||
import ltd.hlaeja.util.toDeviceResponse
|
import ltd.hlaeja.util.toDeviceResponse
|
||||||
import org.springframework.web.bind.annotation.GetMapping
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
import org.springframework.web.bind.annotation.PathVariable
|
import org.springframework.web.bind.annotation.PathVariable
|
||||||
@@ -14,18 +14,18 @@ import org.springframework.web.bind.annotation.RestController
|
|||||||
@RestController
|
@RestController
|
||||||
class DeviceController(
|
class DeviceController(
|
||||||
private val deviceService: DeviceService,
|
private val deviceService: DeviceService,
|
||||||
private val jwtService: JwtService,
|
private val privateJwtService: PrivateJwtService,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@PostMapping("/device")
|
@PostMapping("/device")
|
||||||
suspend fun addDevice(
|
suspend fun addDevice(
|
||||||
@RequestBody request: Device.Request,
|
@RequestBody request: Device.Request,
|
||||||
): Device.Response = deviceService.addDevice(request.type)
|
): Device.Response = deviceService.addDevice(request.type)
|
||||||
.toDeviceResponse(jwtService)
|
.toDeviceResponse(privateJwtService)
|
||||||
|
|
||||||
@GetMapping("/device-{device}")
|
@GetMapping("/device-{device}")
|
||||||
suspend fun getDevice(
|
suspend fun getDevice(
|
||||||
@PathVariable device: UUID,
|
@PathVariable device: UUID,
|
||||||
): Device.Response = deviceService.getDevice(device)
|
): Device.Response = deviceService.getDevice(device)
|
||||||
.toDeviceResponse(jwtService)
|
.toDeviceResponse(privateJwtService)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import java.time.ZonedDateTime
|
|||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import ltd.hlaeja.entity.DeviceEntity
|
import ltd.hlaeja.entity.DeviceEntity
|
||||||
import ltd.hlaeja.repository.DeviceRepository
|
import ltd.hlaeja.repository.DeviceRepository
|
||||||
|
import org.springframework.dao.DataIntegrityViolationException
|
||||||
|
import org.springframework.http.HttpStatus.BAD_REQUEST
|
||||||
import org.springframework.http.HttpStatus.NOT_FOUND
|
import org.springframework.http.HttpStatus.NOT_FOUND
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import org.springframework.web.server.ResponseStatusException
|
import org.springframework.web.server.ResponseStatusException
|
||||||
@@ -18,8 +20,13 @@ class DeviceService(
|
|||||||
|
|
||||||
suspend fun addDevice(
|
suspend fun addDevice(
|
||||||
type: UUID,
|
type: UUID,
|
||||||
): DeviceEntity = deviceRepository.save(DeviceEntity(null, ZonedDateTime.now(), type))
|
): DeviceEntity = try {
|
||||||
|
deviceRepository.save(DeviceEntity(null, ZonedDateTime.now(), type))
|
||||||
.also { log.debug { "Added device ${it.id}" } }
|
.also { log.debug { "Added device ${it.id}" } }
|
||||||
|
} catch (e: DataIntegrityViolationException) {
|
||||||
|
log.warn { e.localizedMessage }
|
||||||
|
throw ResponseStatusException(BAD_REQUEST)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getDevice(device: UUID): DeviceEntity = deviceRepository.findById(device)
|
suspend fun getDevice(device: UUID): DeviceEntity = deviceRepository.findById(device)
|
||||||
?.also { log.debug { "Get device ${it.id}" } }
|
?.also { log.debug { "Get device ${it.id}" } }
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
package ltd.hlaeja.service
|
|
||||||
|
|
||||||
import io.jsonwebtoken.Jwts
|
|
||||||
import java.security.interfaces.RSAPrivateKey
|
|
||||||
import java.util.UUID
|
|
||||||
import ltd.hlaeja.property.JwtProperty
|
|
||||||
import ltd.hlaeja.util.PrivateKeyProvider
|
|
||||||
import org.springframework.stereotype.Service
|
|
||||||
|
|
||||||
@Service
|
|
||||||
class JwtService(
|
|
||||||
jwtProperty: JwtProperty,
|
|
||||||
) {
|
|
||||||
|
|
||||||
private var privateKey: RSAPrivateKey = PrivateKeyProvider.load(jwtProperty.privateKey)
|
|
||||||
|
|
||||||
suspend fun makeIdentity(device: UUID): String {
|
|
||||||
return Jwts.builder()
|
|
||||||
.claims()
|
|
||||||
.add("device", device)
|
|
||||||
.and()
|
|
||||||
.signWith(privateKey)
|
|
||||||
.compact()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,11 +4,11 @@ import java.time.ZonedDateTime
|
|||||||
import ltd.hlaeja.entity.DeviceEntity
|
import ltd.hlaeja.entity.DeviceEntity
|
||||||
import ltd.hlaeja.entity.NodeEntity
|
import ltd.hlaeja.entity.NodeEntity
|
||||||
import ltd.hlaeja.entity.TypeEntity
|
import ltd.hlaeja.entity.TypeEntity
|
||||||
|
import ltd.hlaeja.jwt.service.PrivateJwtService
|
||||||
import ltd.hlaeja.library.deviceRegistry.Device
|
import ltd.hlaeja.library.deviceRegistry.Device
|
||||||
import ltd.hlaeja.library.deviceRegistry.Identity
|
import ltd.hlaeja.library.deviceRegistry.Identity
|
||||||
import ltd.hlaeja.library.deviceRegistry.Node
|
import ltd.hlaeja.library.deviceRegistry.Node
|
||||||
import ltd.hlaeja.library.deviceRegistry.Type
|
import ltd.hlaeja.library.deviceRegistry.Type
|
||||||
import ltd.hlaeja.service.JwtService
|
|
||||||
import org.springframework.http.HttpStatus.EXPECTATION_FAILED
|
import org.springframework.http.HttpStatus.EXPECTATION_FAILED
|
||||||
import org.springframework.web.server.ResponseStatusException
|
import org.springframework.web.server.ResponseStatusException
|
||||||
|
|
||||||
@@ -40,10 +40,10 @@ fun NodeEntity.toIdentityResponse(): Identity.Response = Identity.Response(
|
|||||||
device,
|
device,
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun DeviceEntity.toDeviceResponse(
|
fun DeviceEntity.toDeviceResponse(
|
||||||
jwtService: JwtService,
|
jwtService: PrivateJwtService,
|
||||||
): Device.Response = Device.Response(
|
): Device.Response = Device.Response(
|
||||||
id ?: throw ResponseStatusException(EXPECTATION_FAILED),
|
id ?: throw ResponseStatusException(EXPECTATION_FAILED),
|
||||||
type,
|
type,
|
||||||
jwtService.makeIdentity(id),
|
jwtService.sign("device" to id),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
package ltd.hlaeja.util
|
|
||||||
|
|
||||||
import java.security.KeyFactory
|
|
||||||
import java.security.interfaces.RSAPrivateKey
|
|
||||||
import java.security.spec.PKCS8EncodedKeySpec
|
|
||||||
import java.util.Base64.getDecoder
|
|
||||||
import ltd.hlaeja.exception.KeyProviderException
|
|
||||||
|
|
||||||
object PrivateKeyProvider {
|
|
||||||
|
|
||||||
fun load(
|
|
||||||
pemFile: String,
|
|
||||||
): RSAPrivateKey = readPrivatePemFile(pemFile)
|
|
||||||
.let(::makePrivateKey)
|
|
||||||
|
|
||||||
private fun makePrivateKey(
|
|
||||||
privateKeyBytes: ByteArray,
|
|
||||||
): RSAPrivateKey = KeyFactory.getInstance("RSA")
|
|
||||||
.generatePrivate(PKCS8EncodedKeySpec(privateKeyBytes)) as RSAPrivateKey
|
|
||||||
|
|
||||||
private fun readPrivatePemFile(
|
|
||||||
privateKey: String,
|
|
||||||
): ByteArray = javaClass.classLoader
|
|
||||||
.getResource(privateKey)
|
|
||||||
?.readText()
|
|
||||||
?.let(::getPrivateKeyByteArray)
|
|
||||||
?: throw KeyProviderException("Could not load private key")
|
|
||||||
|
|
||||||
private fun getPrivateKeyByteArray(
|
|
||||||
keyText: String,
|
|
||||||
): ByteArray = keyText.replace(Regex("[\r\n]+"), "")
|
|
||||||
.removePrefix("-----BEGIN PRIVATE KEY-----")
|
|
||||||
.removeSuffix("-----END PRIVATE KEY-----")
|
|
||||||
.let { getDecoder().decode(it) }
|
|
||||||
}
|
|
||||||
@@ -19,11 +19,6 @@
|
|||||||
"name": "spring.application.build.os.version",
|
"name": "spring.application.build.os.version",
|
||||||
"type": "java.lang.String",
|
"type": "java.lang.String",
|
||||||
"description": "Application build os version."
|
"description": "Application build os version."
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "jwt.private-key",
|
|
||||||
"type": "java.lang.String",
|
|
||||||
"description": "Jwt private key file."
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import java.time.ZonedDateTime
|
|||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import ltd.hlaeja.entity.DeviceEntity
|
import ltd.hlaeja.entity.DeviceEntity
|
||||||
|
import ltd.hlaeja.jwt.service.PrivateJwtService
|
||||||
import ltd.hlaeja.library.deviceRegistry.Device
|
import ltd.hlaeja.library.deviceRegistry.Device
|
||||||
import ltd.hlaeja.service.DeviceService
|
import ltd.hlaeja.service.DeviceService
|
||||||
import ltd.hlaeja.service.JwtService
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Nested
|
import org.junit.jupiter.api.Nested
|
||||||
@@ -28,13 +28,13 @@ class DeviceControllerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val deviceService: DeviceService = mockk()
|
val deviceService: DeviceService = mockk()
|
||||||
val jwtService: JwtService = mockk()
|
val privateJwtService: PrivateJwtService = mockk()
|
||||||
|
|
||||||
lateinit var controller: DeviceController
|
lateinit var controller: DeviceController
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
controller = DeviceController(deviceService, jwtService)
|
controller = DeviceController(deviceService, privateJwtService)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
@@ -45,14 +45,14 @@ class DeviceControllerTest {
|
|||||||
// given
|
// given
|
||||||
val request = Device.Request(uuid)
|
val request = Device.Request(uuid)
|
||||||
coEvery { deviceService.addDevice(any()) } returns DeviceEntity(uuid, timestamp, uuid)
|
coEvery { deviceService.addDevice(any()) } returns DeviceEntity(uuid, timestamp, uuid)
|
||||||
coEvery { jwtService.makeIdentity(any()) } returns PAYLOAD
|
coEvery { privateJwtService.sign(any()) } returns PAYLOAD
|
||||||
|
|
||||||
// when
|
// when
|
||||||
val response = controller.addDevice(request)
|
val response = controller.addDevice(request)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
coVerify(exactly = 1) { deviceService.addDevice(any()) }
|
coVerify(exactly = 1) { deviceService.addDevice(any()) }
|
||||||
coVerify(exactly = 1) { jwtService.makeIdentity(any()) }
|
coVerify(exactly = 1) { privateJwtService.sign(any()) }
|
||||||
|
|
||||||
assertThat(response.identity).isEqualTo(PAYLOAD)
|
assertThat(response.identity).isEqualTo(PAYLOAD)
|
||||||
}
|
}
|
||||||
@@ -80,14 +80,14 @@ class DeviceControllerTest {
|
|||||||
fun `get device - success`() = runTest {
|
fun `get device - success`() = runTest {
|
||||||
// given
|
// given
|
||||||
coEvery { deviceService.getDevice(any()) } returns DeviceEntity(uuid, timestamp, uuid)
|
coEvery { deviceService.getDevice(any()) } returns DeviceEntity(uuid, timestamp, uuid)
|
||||||
coEvery { jwtService.makeIdentity(any()) } returns PAYLOAD
|
coEvery { privateJwtService.sign(any()) } returns PAYLOAD
|
||||||
|
|
||||||
// when
|
// when
|
||||||
val response = controller.getDevice(uuid)
|
val response = controller.getDevice(uuid)
|
||||||
|
|
||||||
// then
|
// then
|
||||||
coVerify(exactly = 1) { deviceService.getDevice(any()) }
|
coVerify(exactly = 1) { deviceService.getDevice(any()) }
|
||||||
coVerify(exactly = 1) { jwtService.makeIdentity(any()) }
|
coVerify(exactly = 1) { privateJwtService.sign(any()) }
|
||||||
|
|
||||||
assertThat(response.identity).isEqualTo(PAYLOAD)
|
assertThat(response.identity).isEqualTo(PAYLOAD)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
package ltd.hlaeja.service
|
|
||||||
|
|
||||||
import java.util.UUID
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import ltd.hlaeja.property.JwtProperty
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
|
||||||
import org.junit.jupiter.api.BeforeEach
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
|
|
||||||
class JwtServiceTest {
|
|
||||||
|
|
||||||
val property: JwtProperty = JwtProperty("cert/valid-private-key.pem")
|
|
||||||
lateinit var service: JwtService
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
fun setUp() {
|
|
||||||
service = JwtService(property)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `should generate a JWT successfully with a valid private key`() = runTest {
|
|
||||||
// given
|
|
||||||
val deviceId = UUID.fromString("00000000-0000-0000-0000-000000000000")
|
|
||||||
|
|
||||||
// when
|
|
||||||
val jwt = service.makeIdentity(deviceId)
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertThat(jwt).contains("eyJkZXZpY2UiOiIwMDAwMDAwMC0wMDAwLTAwMDAtMDAwMC0wMDAwMDAwMDAwMDAifQ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package ltd.hlaeja.util
|
|
||||||
|
|
||||||
import java.security.interfaces.RSAPrivateKey
|
|
||||||
import ltd.hlaeja.exception.KeyProviderException
|
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.assertThrows
|
|
||||||
|
|
||||||
class PrivateKeyProviderTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `load private key - success`() {
|
|
||||||
// given
|
|
||||||
val pemFilePath = "cert/valid-private-key.pem"
|
|
||||||
|
|
||||||
// when
|
|
||||||
val privateKey: RSAPrivateKey = PrivateKeyProvider.load(pemFilePath)
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertThat(privateKey).isNotNull
|
|
||||||
assertThat(privateKey.algorithm).isEqualTo("RSA")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `load private key - file does not exist`() {
|
|
||||||
// given
|
|
||||||
val nonExistentPemFilePath = "cert/non-existent.pem"
|
|
||||||
|
|
||||||
// when exception
|
|
||||||
val exception = assertThrows<KeyProviderException> {
|
|
||||||
PrivateKeyProvider.load(nonExistentPemFilePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertThat(exception.message).isEqualTo("Could not load private key")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `load private key - file is invalid`() {
|
|
||||||
// given
|
|
||||||
val invalidPemFilePath = "cert/invalid-private-key.pem"
|
|
||||||
|
|
||||||
// when exception
|
|
||||||
val exception = assertThrows<IllegalArgumentException> {
|
|
||||||
PrivateKeyProvider.load(invalidPemFilePath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// then
|
|
||||||
assertThat(exception.message).contains("Input byte array has wrong 4-byte ending unit")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
VEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBK
|
|
||||||
VU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMg
|
|
||||||
SVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBU
|
|
||||||
SElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpV
|
|
||||||
TksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJ
|
|
||||||
UyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRI
|
|
||||||
SVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVO
|
|
||||||
SyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElT
|
|
||||||
IEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJ
|
|
||||||
UyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5L
|
|
||||||
IFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMg
|
|
||||||
SlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElT
|
|
||||||
IElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksg
|
|
||||||
VEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBK
|
|
||||||
VU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMg
|
|
||||||
SVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBU
|
|
||||||
SElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpV
|
|
||||||
TksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJ
|
|
||||||
UyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRI
|
|
||||||
SVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVO
|
|
||||||
SyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElT
|
|
||||||
IEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJ
|
|
||||||
UyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5L
|
|
||||||
IFRISVMgSVMgSlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMg
|
|
||||||
SlVOSyBUSElTIElTIEpVTksgVEhJUyBJUyBKVU5LIFRISVMgSVMgSlVOSyBUSElT
|
|
||||||
IElTIEpVTksg==
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
Reference in New Issue
Block a user