Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 125 additions & 19 deletions src/main/scala/li/cil/oc/server/component/DataCard.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ package li.cil.oc.server.component

import java.security._
import java.security.interfaces.ECPublicKey
import java.security.interfaces.ECPrivateKey
import java.security.interfaces.EdECPrivateKey
import java.security.interfaces.EdECPublicKey
import java.security.interfaces.XECPrivateKey
import java.security.interfaces.XECPublicKey
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
import java.util
Expand Down Expand Up @@ -225,17 +230,33 @@ object DataCard {

// ----------------------------------------------------------------------- //

@Callback(direct = true, limit = 1, doc = """function([bitLen:number]):userdata, userdata -- Generates key pair. Returns: public, private keys. Allowed key lengths: 256, 384 bits.""")
@Callback(direct = true, limit = 1, doc = """function([bitLen:number[, keyType:string]]):userdata, userdata -- Generates a key pair. keyType is "ec" (default), "ed25519", or "x25519". bitLen only applies to "ec" keys (256 or 384, default 384). Returns: public, private keys.""")
def generateKeyPair(context: Context, args: Arguments): Array[AnyRef] = {
checkCost(Settings.get.dataCardAsymmetric)

val bitLen = args.optInteger(0, 384)
if (bitLen != 256 && bitLen != 384)
throw new IllegalArgumentException("invalid key length, must be 256 or 384")
val keyType = args.optString(1, "ec")

val kpg = KeyPairGenerator.getInstance("EC")
kpg.initialize(bitLen, SecureRandomInstance.get)
val kp = kpg.generateKeyPair()
val kp = keyType match {
case "ec" =>
val bitLen = args.optInteger(0, 384)
if (bitLen != 256 && bitLen != 384)
throw new IllegalArgumentException("invalid key length, must be 256 or 384")
val kpg = KeyPairGenerator.getInstance("EC")
kpg.initialize(bitLen, SecureRandomInstance.get)
kpg.generateKeyPair()

case "ed25519" =>
val kpg = KeyPairGenerator.getInstance("Ed25519")
kpg.initialize(255, SecureRandomInstance.get)
kpg.generateKeyPair()

case "x25519" =>
val kpg = KeyPairGenerator.getInstance("X25519")
kpg.initialize(255, SecureRandomInstance.get)
kpg.generateKeyPair()

case _ => throw new IllegalArgumentException("invalid key type, must be ec, ed25519, or x25519")
}

result(new ECUserdata(kp.getPublic), new ECUserdata(kp.getPrivate))
}
Expand All @@ -251,19 +272,31 @@ object DataCard {
@Callback(direct = true, limit = 1, doc = """function(priv:userdata, pub:userdata):string -- Generates a shared key. ecdh(a.priv, b.pub) == ecdh(b.priv, a.pub)""")
def ecdh(context: Context, args: Arguments): Array[AnyRef] = {
checkCost(Settings.get.dataCardAsymmetric)
val privKey = checkUserdata(args, 0, isPublic = Option(false)).value
val pubKey = checkUserdata(args, 1, isPublic = Option(true)).value
val privKey = checkECUserdata(args, 0, isPublic = Option(false)).value
val pubKey = checkECUserdata(args, 1, isPublic = Option(true)).value

val ka = KeyAgreement.getInstance("ECDH")
ka.init(privKey)
ka.doPhase(pubKey, true)
result(ka.generateSecret)
}

@Callback(direct = true, limit = 1, doc = """function(priv:userdata, pub:userdata):string -- Generates a shared key using X25519. x25519(a.priv, b.pub) == x25519(b.priv, a.pub)""")
def x25519(context: Context, args: Arguments): Array[AnyRef] = {
checkCost(Settings.get.dataCardAsymmetric)
val privKey = checkX25519Userdata(args, 0, isPublic = Option(false)).value
val pubKey = checkX25519Userdata(args, 1, isPublic = Option(true)).value

val ka = KeyAgreement.getInstance("X25519")
ka.init(privKey)
ka.doPhase(pubKey, true)
result(ka.generateSecret)
}

@Callback(direct = true, limit = 1, doc = """function(data:string, key:userdata[, sig:string]):string or boolean -- Signs or verifies data.""")
def ecdsa(context: Context, args: Arguments): Array[AnyRef] = {
val data = asymmetricCost(context, args)
val key = checkUserdata(args, 1)
val key = checkECUserdata(args, 1)
val sig = args.optByteArray(2, null)

val sign = Signature.getInstance("SHA256withECDSA")
Expand All @@ -290,9 +323,39 @@ object DataCard {
}
}

@Callback(direct = true, limit = 1, doc = """function(data:string, key:userdata[, sig:string]):string or boolean -- Signs or verifies data using Ed25519.""")
def ed25519(context: Context, args: Arguments): Array[AnyRef] = {
val data = asymmetricCost(context, args)
val key = checkEd25519Userdata(args, 1)
val sig = args.optByteArray(2, null)

val sign = Signature.getInstance("Ed25519")
if (sig != null) {
// Verify mode
key.value match {
case public: PublicKey =>
sign.initVerify(public)
sign.update(data)
result(sign.verify(sig))
case _ => throw new IllegalArgumentException("public key expected")
}
}
else {
// Sign mode
key.value match {
case k: PrivateKey =>
sign.initSign(k)
sign.update(data)
result(sign.sign())
case _ =>
throw new IllegalArgumentException("private key expected")
}
}
}

// ----------------------------------------------------------------------- //

private def checkUserdata(args: Arguments, i: Int, isPublic: Option[Boolean] = None) =
private def checkUserdata(args: Arguments, i: Int, isPublic: Option[Boolean] = None) = {
args.checkAny(i) match {
case value: ECUserdata =>
if (isPublic.fold(true)(_ == value.isPublic)) value
Expand All @@ -303,15 +366,48 @@ object DataCard {
case value => throw new IllegalArgumentException(
s"bad argument #${i + 1} (userdata expected, got ${value.getClass.getName})")
}
}

private def checkECUserdata(args: Arguments, i: Int, isPublic: Option[Boolean] = None) = {
val value = checkUserdata(args, i, isPublic)
value.value match {
case _: ECPublicKey | _: ECPrivateKey => value
case _ => throw new IllegalArgumentException(s"bad argument #${i + 1} (ec key expected)")
}
}

private def checkX25519Userdata(args: Arguments, i: Int, isPublic: Option[Boolean] = None) = {
val value = checkUserdata(args, i, isPublic)
value.value match {
case _: XECPublicKey | _: XECPrivateKey => value
case _ => throw new IllegalArgumentException(s"bad argument #${i + 1} (x25519 key expected)")
}
}

private def checkEd25519Userdata(args: Arguments, i: Int, isPublic: Option[Boolean] = None) = {
val value = checkUserdata(args, i, isPublic)
value.value match {
case _: EdECPublicKey | _: EdECPrivateKey => value
case _ => throw new IllegalArgumentException(s"bad argument #${i + 1} (ed25519 key expected)")
}
}
}

class ECUserdata(var value: Key) extends prefab.AbstractValue {
// Empty constructor for deserialization.
def this() = this(null)

def isPublic = value.isInstanceOf[ECPublicKey]
def isPublic = value.isInstanceOf[PublicKey]

def keyType = if (isPublic) ECUserdata.PublicTypeName else ECUserdata.PrivateTypeName
def keyType: String = value match {
case _: ECPublicKey => ECUserdata.PublicTypeName
case _: ECPrivateKey => ECUserdata.PrivateTypeName
case _: EdECPublicKey => ECUserdata.Ed25519PublicTypeName
case _: EdECPrivateKey => ECUserdata.Ed25519PrivateTypeName
case _: XECPublicKey => ECUserdata.X25519PublicTypeName
case _: XECPrivateKey => ECUserdata.X25519PrivateTypeName
case _ => throw new IllegalStateException(s"unsupported key type: ${value.getClass.getName}")
}

// ----------------------------------------------------------------------- //

Expand All @@ -338,15 +434,25 @@ object DataCard {
}
}


object ECUserdata {
final val PrivateTypeName = "ec-private"
final val PublicTypeName = "ec-public"

def deserializeKey(typeName: String, data: Array[Byte]): Key = {
if (typeName == PrivateTypeName) KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(data))
else if (typeName == PublicTypeName) KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(data))
else throw new IllegalArgumentException("invalid key type, must be ec-public or ec-private")
final val Ed25519PrivateTypeName = "ed25519-private"
final val Ed25519PublicTypeName = "ed25519-public"
final val X25519PrivateTypeName = "x25519-private"
final val X25519PublicTypeName = "x25519-public"

def deserializeKey(typeName: String, data: Array[Byte]): Key = typeName match {
case PrivateTypeName => KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(data))
case PublicTypeName => KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(data))
case Ed25519PrivateTypeName => KeyFactory.getInstance("Ed25519").generatePrivate(new PKCS8EncodedKeySpec(data))
case Ed25519PublicTypeName => KeyFactory.getInstance("Ed25519").generatePublic(new X509EncodedKeySpec(data))
case X25519PrivateTypeName => KeyFactory.getInstance("X25519").generatePrivate(new PKCS8EncodedKeySpec(data))
case X25519PublicTypeName => KeyFactory.getInstance("X25519").generatePublic(new X509EncodedKeySpec(data))
case _ => throw new IllegalArgumentException(
"invalid key type, must be one of ec-public, ec-private, ed25519-public, ed25519-private, x25519-public, x25519-private")
}
}

}
}
Loading