
com.jsuereth.pgp.hkp.client.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bleep-plugin-pgp_2.13 Show documentation
Show all versions of bleep-plugin-pgp_2.13 Show documentation
A bleeping fast scala build tool!
package bleep
package plugin.pgp
package hkp
import java.io.InputStream
import java.nio.ByteBuffer
import scala.concurrent.Future
/** Represents a client connected to a PGP public key server. */
trait Client {
/** Retreives a PGP Public Key from the server. */
def getKey(id: Long): Future[PublicKeyRing]
/** Pushes a public key to a key server. */
def pushKey(key: PublicKey, logger: String => Unit): Unit
/** Pushes a public key to a key server. */
def pushKeyRing(key: PublicKeyRing, logger: String => Unit): Unit
/** Searches for a term on the keyserver and returns all the results. */
def search(term: String): Future[Vector[LookupKeyResult]]
}
// case class KeyIndexResult(id: String, identity: String, date: java.util.Date)
private[hkp] class GigahorseClient(serverUrl: String) extends Client {
import gigahorse.*
import support.okhttp.Gigahorse
import scala.concurrent.ExecutionContext.Implicits.*
val http = Gigahorse.http(Gigahorse.config)
def asInputStream: FullResponse => InputStream =
(r: FullResponse) => new ByteBufferBackedInputStream(r.bodyAsByteBuffer)
/** Attempts to pull a public key from the HKP server.
* @return
* Some(key) if successful, None otherwise.
*/
def getKey(id: Long): Future[PublicKeyRing] =
for {
ring <- http.run(initiateRequest(GetKey(id)), asInputStream andThen PublicKeyRing.load)
_ <- findId(ring, id)
} yield ring
// we have to look for ids matching the string, since IDs tend to be sent with lower 32 bits.
def findId(ring: PublicKeyRing, id: Long): Future[PublicKey] =
(ring.publicKeys find { k =>
idToString(k.keyID) contains idToString(id)
}) match {
case Some(x) => Future.successful(x)
case _ => Future.failed(new RuntimeException(s"Key $id was not found."))
}
/** Pushes a key to the given public key server. */
def pushKey(key: PublicKey, logger: String => Unit): Unit =
http
.run(
initiateFormPost(AddKey(key)),
Gigahorse.asString.andThen((c: String) => logger("received: " + c))
)
.discard()
/** Pushes a key to the given public key server. */
def pushKeyRing(key: PublicKeyRing, logger: String => Unit): Unit =
http
.run(
initiateFormPost(AddKey(key)),
Gigahorse.asString.andThen((c: String) => logger("received: " + c))
)
.discard()
/** Searches for a term on the keyserver and returns all the results. */
def search(term: String): Future[Vector[LookupKeyResult]] =
http
.run(
initiateRequest(Find(term)),
f = Gigahorse.asString.andThen((s: String) => Client.LookupParser.parse(s))
)
.recover { case _ =>
Vector()
}
// TODO - Allow search and parse format: http://keyserver.ubuntu.com:11371/pks/lookup?op=index&search=suereth
/*
info:1:2
pub:E29DF322:1:2048:1315150989::
uid:Josh Suereth :1315150989::
pub:EF5DDCCC:17:1024:1137516901::
uid:Terry Suereth (CE2008) :1137516901::
Note: Type bits/keyID Date
*/
private[this] def initiateRequest(cmd: HkpCommand): Request =
Gigahorse.url(serverUrl + cmd.url).addQueryString(cmd.vars.toList: _*)
private[this] def initiateFormPost(cmd: HkpCommand): Request =
Gigahorse
.url(serverUrl + cmd.url)
.post(cmd.vars map { case (k, v) =>
k -> List(v)
})
override def toString = "HkpServer(%s)" format serverUrl
}
private class ByteBufferBackedInputStream(buffer: ByteBuffer) extends InputStream {
override def read: Int =
if (!buffer.hasRemaining) -1
else buffer.get & 0xff
override def read(bytes: Array[Byte], offset: Int, len: Int): Int =
if (!buffer.hasRemaining) -1
else {
val len1 = Math.min(len, buffer.remaining)
buffer.get(bytes, offset, len1)
len1
}
}
case class LookupKeyResult(id: String, time: java.util.Date, user: Seq[String])
object Client {
import util.matching.Regex
private val HkpWithPort = new Regex("hkp://(.+)(:[0-9]+)")
private val Hkp = new Regex("hkp://(.+)/?")
/** Creates a new HKP client that can push/pull keys from a public server. */
def apply(url: String): Client = url match {
case Hkp(server) => new GigahorseClient("http://%s:11371" format server)
case HkpWithPort(server, port) => new GigahorseClient("http://%s:%s" format (server, port))
case _ => new GigahorseClient(url)
}
object LookupParser extends scala.util.parsing.combinator.RegexParsers {
def s: Parser[String] = ":"
def eol: Parser[String] = "[\r\n]+".r
def pub: Parser[String] = "pub"
def uid: Parser[String] = "uid"
def info: Parser[String] = "info"
def name: Parser[String] = guard(not(info | uid | pub)) ~> ("[^\\:\n\r]*".r)
def line: Parser[Seq[String]] = (pub <~ s) ~ rep1sep(name, s) ^^ { case x ~ xs =>
x +: xs
}
def userId: Parser[String] = ("uid" ~ s) ~> rep1sep(name, s) ^^ {
case Seq(name, date @ _, _*) => name
case other => sys.error(s"Unexpected $other")
}
def keyHeader: Parser[(String, java.util.Date)] = line ^? { case Seq("pub", name, _, _, date, _*) =>
(name, new java.util.Date(date.toLong * 1000L))
}
def key: Parser[LookupKeyResult] = keyHeader ~ rep1(userId) ^^ { case (id, ts) ~ users =>
LookupKeyResult(id, ts, users)
}
def infoheader = "info" ~ s ~ rep1sep(name, s)
def queryresponse: Parser[Seq[LookupKeyResult]] = infoheader ~> rep(key)
def parse(input: String): Vector[LookupKeyResult] = {
println("Parsing lookup value: " + input)
parseAll(queryresponse, input) match {
case Success(data, _) => data.toVector
case _ => sys.error("Issues")
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy