Skip to content

Commit

Permalink
Merge branch 'bugfix/OTA-5369/parsing-failure-when-import-keys' into …
Browse files Browse the repository at this point in the history
…'master'

OTA-5369 Fix import public keys from pem file

Closes OTA-5369

See merge request olp/edge/ota/connect/back-end/ota-tuf!310
  • Loading branch information
viktormolitskyihere committed Apr 27, 2021
2 parents 87e8d57 + 23c706f commit 9ef9583
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 18 deletions.
1 change: 1 addition & 0 deletions cli/src/main/scala/com/advancedtelematic/tuf/cli/Cli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ object Cli extends App with VersionInfo {
.toCommand(ImportPublicKey)
.text("Imports a public key and stores it on a configurable location")
.children(
repoNameOpt(this),
manyKeyNamesOpt(this).text("The path to the public key that you want to add."),
opt[Path]("input")
.abbr("i")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,10 +283,9 @@ object CommandHandler {
.toFuture

case ImportPublicKey =>
CliKeyStorage.readPublicKey(config.inputPath.valueOrConfigError).flatMap { key =>
config.keyNames.map { keyName =>
userKeyStorage.writePublicKey(keyName, key)
}.sequence_
}
CliKeyStorage
.forRepo(tufRepo.repoPath)
.importPublicKey(config.inputPath.valueOrConfigError, config.keyNames)
.toFuture
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
package com.advancedtelematic.tuf.cli.repo

import java.nio.file.attribute.{PosixFilePermission, PosixFilePermissions}
import java.nio.file.{FileAlreadyExistsException, Files, Path}
import PosixFilePermission._
import scala.collection.JavaConverters._
import com.advancedtelematic.libtuf.data.TufDataType.{KeyType, TufKey, TufKeyPair, TufPrivateKey}
import com.advancedtelematic.libtuf.data.TufCodecs._
import com.advancedtelematic.libtuf.data.TufDataType.{EcPrime256KeyType, Ed25519KeyType, KeyType, RsaKeyType, TufKey, TufKeyPair, TufPrivateKey}
import com.advancedtelematic.tuf.cli.DataType.KeyName
import io.circe.jawn._
import io.circe.syntax._
import net.i2p.crypto.eddsa.Utils
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.openssl.{PEMKeyPair, PEMParser}
import org.slf4j.LoggerFactory

import scala.util.Try
import com.advancedtelematic.libtuf.data.TufCodecs._
import io.circe.syntax._
import io.circe.jawn._
import java.io.StringReader
import java.nio.file.attribute.PosixFilePermission._
import java.nio.file.attribute.{PosixFilePermission, PosixFilePermissions}
import java.nio.file.{FileAlreadyExistsException, Files, Path}
import scala.collection.JavaConverters._
import scala.util.{Failure, Success, Try}


object CliKeyStorage {
Expand Down Expand Up @@ -65,7 +69,7 @@ class CliKeyStorage private (root: Path) {
for {
_ <- ensureKeysDirCreated()
_ <- writePublic(name, pub)
_ = log.info(s"Saved public key to ${root.relativize(name.publicKeyPath)}}")
_ = log.info(s"Saved public key to ${root.relativize(name.publicKeyPath)}")
} yield ()
}

Expand Down Expand Up @@ -95,4 +99,54 @@ class CliKeyStorage private (root: Path) {
pub <- readPublicKey(keyName)
priv <- readPrivateKey(keyName)
} yield (pub, priv)

def importPublicKey(pemPath: Path, keyNames: List[KeyName]): Try[Unit] = {
import cats.instances.list._
import cats.instances.try_._
import cats.syntax.traverse._

for {
key <- readPublicKeyPem(pemPath)
_ <- keyNames.traverse(keyName => writePublicKey(keyName, key))
} yield ()
}

private def readPublicKeyPem(path: Path): Try[TufKey] = {
val tryReadPemFile = Try {
val source = scala.io.Source.fromFile(path.toFile)
val pem = source.mkString
source.close()
pem
}

val pemToPublicKeyInfo = (pem: String) => Try {
val parser = new PEMParser(new StringReader(pem))
parser.readObject() match {
case key: SubjectPublicKeyInfo => key
case keyPair: PEMKeyPair => keyPair.getPublicKeyInfo
}
}

val tryParseRSA = (pem: String) =>
RsaKeyType.crypto.parsePublic(pem)

val tryParseEcPrime256 = (pem: String) =>
pemToPublicKeyInfo(pem)
.map(k => Utils.bytesToHex(k.getEncoded))
.flatMap(EcPrime256KeyType.crypto.parsePublic)

val tryParseEd25519 = (pem: String) =>
pemToPublicKeyInfo(pem)
.map(k => Utils.bytesToHex(k.getPublicKeyData.getBytes))
.flatMap(Ed25519KeyType.crypto.parsePublic)

val publicKeyTry = (pem: String) =>
tryParseRSA(pem).orElse(tryParseEcPrime256(pem)).orElse(tryParseEd25519(pem))
.transform(key => Success(key), _ => Failure(new Exception(s"Cannot parse public key from $path")))

for {
pem <- tryReadPemFile
publicKey <- publicKeyTry(pem)
} yield publicKey
}
}
9 changes: 9 additions & 0 deletions cli/src/test/resources/pem-files/invalid-public-RSA.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwSXQGAldIT62ONHQALG1
N8itakru/T+OvMuOsaDFWuE1uNigXlkULWxonv2ArMcPSM9pQWKFEstGnpmV3dAB
N1Y5crExhAcXLvQqQm4J20BqZRwW2bldBZMofvxrlkBC+x2gkTyi3p1DIdC9nQd/
8sE5thkU5OAHCuszI2axnJI6geh6riRG/QGJtdvCWc6tYBl78RWiK8m1n2w7olRd
CUK4vpxGhoYSkwHe8Qmhx7HMp41XxTcrX7P1pa1p0vBgZqp6n4O8ixOrJJUWb4QV
Iu4RVdyj/4bGUJdArsQvhRi0w+VjSoyZJ4ASE+4tV0NiknRHZNbWrnifVSzLUee3
/wIDAQAQqwewqeqweqweqwweqweqwe
-----END PUBLIC KEY-----
4 changes: 4 additions & 0 deletions cli/src/test/resources/pem-files/valid-public-EcPrime256.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEq5dZn4FXJrkVCFMRDmlRswBF8yMA
/osWy2Vftnbckj+iiShMWav5WtUhtilMSLMJ7vc9XUao6vQJSVWi6HWurQ==
-----END PUBLIC KEY-----
3 changes: 3 additions & 0 deletions cli/src/test/resources/pem-files/valid-public-Ed25519.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEANA4Sy6xDvVpQOuwgjBcm7QLvkZbTmcsqLcYcsvpM1o0=
-----END PUBLIC KEY-----
9 changes: 9 additions & 0 deletions cli/src/test/resources/pem-files/valid-public-RSA.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwSXQGAldIT62ONHQALG1
N8itakru/T+OvMuOsaDFWuE1uNigXlkULWxonv2ArMcPSM9pQWKFEstGnpmV3dAB
N1Y5crExhAcXLvQqQm4J20BqZRwW2bldBZMofvxrlkBC+x2gkTyi3p1DIdC9nQd/
8sE5thkU5OAHCuszI2axnJI6geh6riRG/QGJtdvCWc6tYBl78RWiK8m1n2w7olRd
CUK4vpxGhoYSkwHe8Qmhx7HMp41XxTcrX7P1pa1p0vBgZqp6n4O8ixOrJJUWb4QV
Iu4RVdyj/4bGUJdArsQvhRi0w+VjSoyZJ4ASE+4tV0NiknRHZNbWrnifVSzLUee3
/wIDAQAB
-----END PUBLIC KEY-----
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package com.advancedtelematic.tuf.cli

import java.nio.file.Files
import com.advancedtelematic.libtuf.data.TufDataType.{EcPrime256KeyType, Ed25519KeyType, RsaKeyType}

import java.nio.file.{Files, Paths}
import java.nio.file.attribute.PosixFilePermission
import PosixFilePermission._

import com.advancedtelematic.tuf.cli.DataType.KeyName
import com.advancedtelematic.tuf.cli.repo.CliKeyStorage
import com.advancedtelematic.tuf.cli.util.{CliSpec, KeyTypeSpecSupport}
import org.scalatest.TryValues

import scala.collection.JavaConverters._

class CliKeyStorageSpec extends CliSpec with KeyTypeSpecSupport {
class CliKeyStorageSpec extends CliSpec with KeyTypeSpecSupport with TryValues {
val tempDir = Files.createTempDirectory("tuf-keys")

lazy val subject = CliKeyStorage.forRepo(tempDir)
Expand Down Expand Up @@ -40,4 +42,39 @@ class CliKeyStorageSpec extends CliSpec with KeyTypeSpecSupport {
val perms = Files.getPosixFilePermissions(tempDir.resolve("keys"))
perms.asScala shouldBe Set(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE)
}

test("import RSA public key should work") {
val rsaPublicKeyPem = Paths.get(this.getClass.getResource("/pem-files/valid-public-RSA.pem").toURI)
val keyName = KeyName("test-rsa-key")

subject.readPublicKey(keyName).failure.exception.getMessage should include ("No such file or directory")
subject.importPublicKey(rsaPublicKeyPem, List(keyName)).isSuccess shouldBe true
subject.readPublicKey(keyName).success.value.keytype shouldBe RsaKeyType
}

test("import Ed25519 public key should work") {
val ed25519PublicKeyPem = Paths.get(this.getClass.getResource("/pem-files/valid-public-Ed25519.pem").toURI)
val keyName = KeyName("test-Ed25519-key")

subject.readPublicKey(keyName).failure.exception.getMessage should include ("No such file or directory")
subject.importPublicKey(ed25519PublicKeyPem, List(keyName)).isSuccess shouldBe true
subject.readPublicKey(keyName).success.value.keytype shouldBe Ed25519KeyType
}

test("import EcPrime256 public key should work") {
val ecPrime256PublicKeyPem = Paths.get(this.getClass.getResource("/pem-files/valid-public-EcPrime256.pem").toURI)
val keyName = KeyName("test-EcPrime256-key")

subject.readPublicKey(keyName).failure.exception.getMessage should include ("No such file or directory")
subject.importPublicKey(ecPrime256PublicKeyPem, List(keyName)).isSuccess shouldBe true
subject.readPublicKey(keyName).success.value.keytype shouldBe EcPrime256KeyType
}

test("import invalid public key should fail") {
val rsaPublicKeyPem = Paths.get(this.getClass.getResource("/pem-files/invalid-public-RSA.pem").toURI)
val keyName = KeyName("test-invalid-rsa-key")

subject.readPublicKey(keyName).failure.exception.getMessage should include ("No such file or directory")
subject.importPublicKey(rsaPublicKeyPem, List(keyName)).failure.exception.getMessage should include (s"Cannot parse public key from $rsaPublicKeyPem")
}
}

0 comments on commit 9ef9583

Please sign in to comment.