Skip to content

Commit 9ef9583

Browse files
Merge branch 'bugfix/OTA-5369/parsing-failure-when-import-keys' into '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
2 parents 87e8d57 + 23c706f commit 9ef9583

File tree

8 files changed

+134
-18
lines changed

8 files changed

+134
-18
lines changed

cli/src/main/scala/com/advancedtelematic/tuf/cli/Cli.scala

+1
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ object Cli extends App with VersionInfo {
187187
.toCommand(ImportPublicKey)
188188
.text("Imports a public key and stores it on a configurable location")
189189
.children(
190+
repoNameOpt(this),
190191
manyKeyNamesOpt(this).text("The path to the public key that you want to add."),
191192
opt[Path]("input")
192193
.abbr("i")

cli/src/main/scala/com/advancedtelematic/tuf/cli/CommandHandler.scala

+4-5
Original file line numberDiff line numberDiff line change
@@ -283,10 +283,9 @@ object CommandHandler {
283283
.toFuture
284284

285285
case ImportPublicKey =>
286-
CliKeyStorage.readPublicKey(config.inputPath.valueOrConfigError).flatMap { key =>
287-
config.keyNames.map { keyName =>
288-
userKeyStorage.writePublicKey(keyName, key)
289-
}.sequence_
290-
}
286+
CliKeyStorage
287+
.forRepo(tufRepo.repoPath)
288+
.importPublicKey(config.inputPath.valueOrConfigError, config.keyNames)
289+
.toFuture
291290
}
292291
}

cli/src/main/scala/com/advancedtelematic/tuf/cli/repo/CliKeyStorage.scala

+64-10
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
package com.advancedtelematic.tuf.cli.repo
22

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

11-
import scala.util.Try
12-
import com.advancedtelematic.libtuf.data.TufCodecs._
13-
import io.circe.syntax._
14-
import io.circe.jawn._
13+
import java.io.StringReader
14+
import java.nio.file.attribute.PosixFilePermission._
15+
import java.nio.file.attribute.{PosixFilePermission, PosixFilePermissions}
16+
import java.nio.file.{FileAlreadyExistsException, Files, Path}
17+
import scala.collection.JavaConverters._
18+
import scala.util.{Failure, Success, Try}
1519

1620

1721
object CliKeyStorage {
@@ -65,7 +69,7 @@ class CliKeyStorage private (root: Path) {
6569
for {
6670
_ <- ensureKeysDirCreated()
6771
_ <- writePublic(name, pub)
68-
_ = log.info(s"Saved public key to ${root.relativize(name.publicKeyPath)}}")
72+
_ = log.info(s"Saved public key to ${root.relativize(name.publicKeyPath)}")
6973
} yield ()
7074
}
7175

@@ -95,4 +99,54 @@ class CliKeyStorage private (root: Path) {
9599
pub <- readPublicKey(keyName)
96100
priv <- readPrivateKey(keyName)
97101
} yield (pub, priv)
102+
103+
def importPublicKey(pemPath: Path, keyNames: List[KeyName]): Try[Unit] = {
104+
import cats.instances.list._
105+
import cats.instances.try_._
106+
import cats.syntax.traverse._
107+
108+
for {
109+
key <- readPublicKeyPem(pemPath)
110+
_ <- keyNames.traverse(keyName => writePublicKey(keyName, key))
111+
} yield ()
112+
}
113+
114+
private def readPublicKeyPem(path: Path): Try[TufKey] = {
115+
val tryReadPemFile = Try {
116+
val source = scala.io.Source.fromFile(path.toFile)
117+
val pem = source.mkString
118+
source.close()
119+
pem
120+
}
121+
122+
val pemToPublicKeyInfo = (pem: String) => Try {
123+
val parser = new PEMParser(new StringReader(pem))
124+
parser.readObject() match {
125+
case key: SubjectPublicKeyInfo => key
126+
case keyPair: PEMKeyPair => keyPair.getPublicKeyInfo
127+
}
128+
}
129+
130+
val tryParseRSA = (pem: String) =>
131+
RsaKeyType.crypto.parsePublic(pem)
132+
133+
val tryParseEcPrime256 = (pem: String) =>
134+
pemToPublicKeyInfo(pem)
135+
.map(k => Utils.bytesToHex(k.getEncoded))
136+
.flatMap(EcPrime256KeyType.crypto.parsePublic)
137+
138+
val tryParseEd25519 = (pem: String) =>
139+
pemToPublicKeyInfo(pem)
140+
.map(k => Utils.bytesToHex(k.getPublicKeyData.getBytes))
141+
.flatMap(Ed25519KeyType.crypto.parsePublic)
142+
143+
val publicKeyTry = (pem: String) =>
144+
tryParseRSA(pem).orElse(tryParseEcPrime256(pem)).orElse(tryParseEd25519(pem))
145+
.transform(key => Success(key), _ => Failure(new Exception(s"Cannot parse public key from $path")))
146+
147+
for {
148+
pem <- tryReadPemFile
149+
publicKey <- publicKeyTry(pem)
150+
} yield publicKey
151+
}
98152
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-----BEGIN PUBLIC KEY-----
2+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwSXQGAldIT62ONHQALG1
3+
N8itakru/T+OvMuOsaDFWuE1uNigXlkULWxonv2ArMcPSM9pQWKFEstGnpmV3dAB
4+
N1Y5crExhAcXLvQqQm4J20BqZRwW2bldBZMofvxrlkBC+x2gkTyi3p1DIdC9nQd/
5+
8sE5thkU5OAHCuszI2axnJI6geh6riRG/QGJtdvCWc6tYBl78RWiK8m1n2w7olRd
6+
CUK4vpxGhoYSkwHe8Qmhx7HMp41XxTcrX7P1pa1p0vBgZqp6n4O8ixOrJJUWb4QV
7+
Iu4RVdyj/4bGUJdArsQvhRi0w+VjSoyZJ4ASE+4tV0NiknRHZNbWrnifVSzLUee3
8+
/wIDAQAQqwewqeqweqweqwweqweqwe
9+
-----END PUBLIC KEY-----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-----BEGIN PUBLIC KEY-----
2+
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEq5dZn4FXJrkVCFMRDmlRswBF8yMA
3+
/osWy2Vftnbckj+iiShMWav5WtUhtilMSLMJ7vc9XUao6vQJSVWi6HWurQ==
4+
-----END PUBLIC KEY-----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-----BEGIN PUBLIC KEY-----
2+
MCowBQYDK2VwAyEANA4Sy6xDvVpQOuwgjBcm7QLvkZbTmcsqLcYcsvpM1o0=
3+
-----END PUBLIC KEY-----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-----BEGIN PUBLIC KEY-----
2+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwSXQGAldIT62ONHQALG1
3+
N8itakru/T+OvMuOsaDFWuE1uNigXlkULWxonv2ArMcPSM9pQWKFEstGnpmV3dAB
4+
N1Y5crExhAcXLvQqQm4J20BqZRwW2bldBZMofvxrlkBC+x2gkTyi3p1DIdC9nQd/
5+
8sE5thkU5OAHCuszI2axnJI6geh6riRG/QGJtdvCWc6tYBl78RWiK8m1n2w7olRd
6+
CUK4vpxGhoYSkwHe8Qmhx7HMp41XxTcrX7P1pa1p0vBgZqp6n4O8ixOrJJUWb4QV
7+
Iu4RVdyj/4bGUJdArsQvhRi0w+VjSoyZJ4ASE+4tV0NiknRHZNbWrnifVSzLUee3
8+
/wIDAQAB
9+
-----END PUBLIC KEY-----

cli/src/test/scala/com/advancedtelematic/tuf/cli/CliKeyStorageSpec.scala

+40-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
package com.advancedtelematic.tuf.cli
22

3-
import java.nio.file.Files
3+
import com.advancedtelematic.libtuf.data.TufDataType.{EcPrime256KeyType, Ed25519KeyType, RsaKeyType}
4+
5+
import java.nio.file.{Files, Paths}
46
import java.nio.file.attribute.PosixFilePermission
57
import PosixFilePermission._
6-
78
import com.advancedtelematic.tuf.cli.DataType.KeyName
89
import com.advancedtelematic.tuf.cli.repo.CliKeyStorage
910
import com.advancedtelematic.tuf.cli.util.{CliSpec, KeyTypeSpecSupport}
11+
import org.scalatest.TryValues
1012

1113
import scala.collection.JavaConverters._
1214

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

1618
lazy val subject = CliKeyStorage.forRepo(tempDir)
@@ -40,4 +42,39 @@ class CliKeyStorageSpec extends CliSpec with KeyTypeSpecSupport {
4042
val perms = Files.getPosixFilePermissions(tempDir.resolve("keys"))
4143
perms.asScala shouldBe Set(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE)
4244
}
45+
46+
test("import RSA public key should work") {
47+
val rsaPublicKeyPem = Paths.get(this.getClass.getResource("/pem-files/valid-public-RSA.pem").toURI)
48+
val keyName = KeyName("test-rsa-key")
49+
50+
subject.readPublicKey(keyName).failure.exception.getMessage should include ("No such file or directory")
51+
subject.importPublicKey(rsaPublicKeyPem, List(keyName)).isSuccess shouldBe true
52+
subject.readPublicKey(keyName).success.value.keytype shouldBe RsaKeyType
53+
}
54+
55+
test("import Ed25519 public key should work") {
56+
val ed25519PublicKeyPem = Paths.get(this.getClass.getResource("/pem-files/valid-public-Ed25519.pem").toURI)
57+
val keyName = KeyName("test-Ed25519-key")
58+
59+
subject.readPublicKey(keyName).failure.exception.getMessage should include ("No such file or directory")
60+
subject.importPublicKey(ed25519PublicKeyPem, List(keyName)).isSuccess shouldBe true
61+
subject.readPublicKey(keyName).success.value.keytype shouldBe Ed25519KeyType
62+
}
63+
64+
test("import EcPrime256 public key should work") {
65+
val ecPrime256PublicKeyPem = Paths.get(this.getClass.getResource("/pem-files/valid-public-EcPrime256.pem").toURI)
66+
val keyName = KeyName("test-EcPrime256-key")
67+
68+
subject.readPublicKey(keyName).failure.exception.getMessage should include ("No such file or directory")
69+
subject.importPublicKey(ecPrime256PublicKeyPem, List(keyName)).isSuccess shouldBe true
70+
subject.readPublicKey(keyName).success.value.keytype shouldBe EcPrime256KeyType
71+
}
72+
73+
test("import invalid public key should fail") {
74+
val rsaPublicKeyPem = Paths.get(this.getClass.getResource("/pem-files/invalid-public-RSA.pem").toURI)
75+
val keyName = KeyName("test-invalid-rsa-key")
76+
77+
subject.readPublicKey(keyName).failure.exception.getMessage should include ("No such file or directory")
78+
subject.importPublicKey(rsaPublicKeyPem, List(keyName)).failure.exception.getMessage should include (s"Cannot parse public key from $rsaPublicKeyPem")
79+
}
4380
}

0 commit comments

Comments
 (0)