Skip to content

Commit 115cf1e

Browse files
committed
Tighten up types for encryption metadata.
1 parent a68f11e commit 115cf1e

File tree

4 files changed

+111
-80
lines changed

4 files changed

+111
-80
lines changed

common/scala/src/main/resources/application.conf

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -481,13 +481,17 @@ whisk {
481481
# In general this setting should be left to its default disabled state
482482
retry-no-http-response-exception = false
483483
}
484-
# Support key sizes of: 0, 16, 32. Which correspond to, no encryption, AES128 and AES256. For AES256 your JVM must be
485-
# capable of that size key.
486484
# Enabling this will start to encrypt all default parameters for actions and packages. Be careful using this as
487485
# it will slowly migrate all the actions that have been 'updated' to use encrypted parameters but going back would
488486
# require a currently non-existing migration step.
489487
parameter-storage {
490-
key = ""
488+
# Base64 encoded 256 bit key
489+
#aes256 = ""
490+
# Base64 encoded 128 bit key
491+
#aes128 = ""
492+
# The current algorithm to use for parameter encryption, this can be changed but you have to leave all the keys
493+
# configured for any algorithm you used previously.
494+
#current = "aes128|aes256"
491495
}
492496
}
493497
#placeholder for test overrides so that tests can override defaults in application.conf (todo: move all defaults to reference.conf)

common/scala/src/main/scala/org/apache/openwhisk/core/entity/Parameter.scala

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ protected[core] class Parameters protected[entity] (private val params: Map[Para
9292
}
9393
val encrypt = p._2.encryption match {
9494
case (JsNull) => None
95-
case _ => Some("encryption" -> p._2.encryption.toJson)
95+
case _ => Some("encryption" -> p._2.encryption)
9696
}
9797
// Have do use this slightly strange construction to get the json object order identical.
9898
JsObject(ListMap() ++ encrypt ++ init ++ Map("key" -> p._1.name.toJson, "value" -> p._2.value.toJson))
@@ -105,7 +105,7 @@ protected[core] class Parameters protected[entity] (private val params: Map[Para
105105
if (p._2.encryption == JsNull)
106106
p._2.value.toJson
107107
else
108-
JsObject("value" -> p._2.value.toJson, "encryption" -> p._2.encryption.toJson, "init" -> p._2.init.toJson)
108+
JsObject("value" -> p._2.value.toJson, "encryption" -> p._2.encryption, "init" -> p._2.init.toJson)
109109
(p._1.name, newValue)
110110
}))
111111

@@ -148,7 +148,7 @@ protected[core] class Parameters protected[entity] (private val params: Map[Para
148148

149149
/**
150150
* A ParameterName is a parameter name for an action or trigger to bind to its environment.
151-
* It wraps a normalized string as a valueread type.
151+
* It wraps a normalized string as a value type.
152152
*
153153
* It is a value type (hence == is .equals, immutable and cannot be assigned null).
154154
* The constructor is private so that argument requirements are checked and normalized
@@ -175,10 +175,11 @@ protected[entity] class ParameterName protected[entity] (val name: String) exten
175175
*
176176
* @param v the value of the parameter, may be null
177177
* @param init if true, this parameter value is only offered to the action during initialization
178+
* @param encryptionDetails the name of the encrypter used to store the parameter.
178179
*/
179180
protected[entity] case class ParameterValue protected[entity] (private val v: JsValue,
180181
val init: Boolean,
181-
val e: JsValue = JsNull) {
182+
val encryptionDetails: Option[JsString] = None) {
182183

183184
/** @return JsValue if defined else JsNull. */
184185
protected[entity] def value = Option(v) getOrElse JsNull
@@ -187,7 +188,7 @@ protected[entity] case class ParameterValue protected[entity] (private val v: Js
187188
protected[entity] def isDefined = value != JsNull
188189

189190
/** @return JsValue if defined else JsNull. */
190-
protected[entity] def encryption = Option(e).getOrElse(JsNull)
191+
protected[entity] def encryption = encryptionDetails getOrElse JsNull
191192

192193
/**
193194
* The size of the ParameterValue entity as ByteSize.
@@ -217,7 +218,7 @@ protected[core] object Parameters extends ArgNormalizer[Parameters] {
217218
protected[core] def apply(p: String, v: String, init: Boolean = false): Parameters = {
218219
require(p != null && p.trim.nonEmpty, "key undefined")
219220
Parameters() + (new ParameterName(ArgNormalizer.trim(p)),
220-
ParameterValue(Option(v).map(_.trim.toJson).getOrElse(JsNull), init, JsNull))
221+
ParameterValue(Option(v).map(_.trim.toJson).getOrElse(JsNull), init, None))
221222
}
222223

223224
/**
@@ -233,7 +234,7 @@ protected[core] object Parameters extends ArgNormalizer[Parameters] {
233234
protected[core] def apply(p: String, v: JsValue, init: Boolean): Parameters = {
234235
require(p != null && p.trim.nonEmpty, "key undefined")
235236
Parameters() + (new ParameterName(ArgNormalizer.trim(p)),
236-
ParameterValue(Option(v).getOrElse(JsNull), init, JsNull))
237+
ParameterValue(Option(v).getOrElse(JsNull), init, None))
237238
}
238239

239240
/**
@@ -248,7 +249,7 @@ protected[core] object Parameters extends ArgNormalizer[Parameters] {
248249
protected[core] def apply(p: String, v: JsValue): Parameters = {
249250
require(p != null && p.trim.nonEmpty, "key undefined")
250251
Parameters() + (new ParameterName(ArgNormalizer.trim(p)),
251-
ParameterValue(Option(v).getOrElse(JsNull), false, JsNull))
252+
ParameterValue(Option(v).getOrElse(JsNull), false, None))
252253
}
253254

254255
def readMergedList(value: JsValue): Parameters =
@@ -261,11 +262,11 @@ protected[core] object Parameters extends ArgNormalizer[Parameters] {
261262
val paramVal: ParameterValue = tuple._2 match {
262263
case o: JsObject =>
263264
o.getFields("value", "init", "encryption") match {
264-
case Seq(v: JsValue, JsBoolean(i), e: JsValue) =>
265-
ParameterValue(v, i, e)
266-
case _ => ParameterValue(o, false, JsNull)
265+
case Seq(v: JsValue, JsBoolean(i), e: JsString) =>
266+
ParameterValue(v, i, Some(e))
267+
case _ => ParameterValue(o, false, None)
267268
}
268-
case v: JsValue => ParameterValue(v, false, JsNull)
269+
case v: JsValue => ParameterValue(v, false, None)
269270
}
270271
(key, paramVal)
271272
})
@@ -295,9 +296,13 @@ protected[core] object Parameters extends ArgNormalizer[Parameters] {
295296
val JsObject(o) = value
296297
o.foreach(i =>
297298
i._2.asJsObject.getFields("value", "init", "encryption") match {
299+
case Seq(v: JsValue, JsBoolean(init), e: JsValue) if e != JsNull =>
300+
val key = new ParameterName(i._1)
301+
val value = ParameterValue(v, init, Some(JsString(e.toString())))
302+
converted = converted + (key -> value)
298303
case Seq(v: JsValue, JsBoolean(init), e: JsValue) =>
299304
val key = new ParameterName(i._1)
300-
val value = ParameterValue(v, init, e)
305+
val value = ParameterValue(v, init, None)
301306
converted = converted + (key -> value)
302307
})
303308
if (converted.size == 0) {
@@ -325,17 +330,17 @@ protected[core] object Parameters extends ArgNormalizer[Parameters] {
325330
val key = new ParameterName(k)
326331
val value = ParameterValue(v, false)
327332
(key, value)
328-
case Seq(JsString(k), v: JsValue, JsBoolean(i), e: JsValue) =>
333+
case Seq(JsString(k), v: JsValue, JsBoolean(i), e: JsString) =>
329334
val key = new ParameterName(k)
330-
val value = ParameterValue(v, i, e)
335+
val value = ParameterValue(v, i, Some(e))
331336
(key, value)
332337
case Seq(JsString(k), v: JsValue, JsBoolean(i)) =>
333338
val key = new ParameterName(k)
334339
val value = ParameterValue(v, i)
335340
(key, value)
336-
case Seq(JsString(k), v: JsValue, e: JsValue) if (i.asJsObject.fields.contains("encryption")) =>
341+
case Seq(JsString(k), v: JsValue, e: JsString) if (i.asJsObject.fields.contains("encryption")) =>
337342
val key = new ParameterName(k)
338-
val value = ParameterValue(v, false, e)
343+
val value = ParameterValue(v, false, None)
339344
(key, value)
340345
}
341346
})

common/scala/src/main/scala/org/apache/openwhisk/core/entity/ParameterEncryption.scala

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,55 +29,62 @@ import spray.json.DefaultJsonProtocol._
2929
import spray.json.{JsNull, JsString}
3030
import pureconfig.generic.auto._
3131

32-
private trait encrypter {
33-
def encrypt(p: ParameterValue): ParameterValue
34-
def decrypt(p: ParameterValue): ParameterValue
35-
val name: String
36-
}
37-
38-
case class ParameterStorageConfig(key: String = "") {
39-
def getKeyBytes(): Array[Byte] = {
40-
if (key.length == 0) {
41-
Array[Byte]()
42-
} else {
43-
Base64.getDecoder().decode(key)
44-
}
45-
}
4632

47-
}
33+
case class ParameterStorageConfig(current:String = "", aes128: String = "", aes256:String = "")
4834

4935
object ParameterEncryption {
5036
private val storageConfigLoader = loadConfig[ParameterStorageConfig](ConfigKeys.parameterStorage)
5137
var storageConfig = storageConfigLoader.getOrElse(ParameterStorageConfig.apply())
52-
private def enc = storageConfig.getKeyBytes.length match {
53-
case 16 => new Aes128(storageConfig.getKeyBytes)
54-
case 32 => new Aes256(storageConfig.getKeyBytes)
55-
case 0 => new NoopCrypt
56-
case _ =>
57-
throw new IllegalArgumentException(
58-
s"Only 0, 16 and 32 characters support for key size but instead got ${storageConfig.getKeyBytes.length}")
59-
}
6038
def lock(params: Parameters): Parameters = {
39+
val configuredEncryptors = new encrypters(storageConfig)
6140
new Parameters(
6241
params.getMap
6342
.map(({
6443
case (paramName, paramValue) if paramValue.encryption == JsNull =>
65-
paramName -> enc.encrypt(paramValue)
44+
paramName -> configuredEncryptors.getCurrentEncrypter().encrypt(paramValue)
6645
case (paramName, paramValue) => paramName -> paramValue
6746
})))
6847
}
6948
def unlock(params: Parameters): Parameters = {
49+
val configuredEncryptors = new encrypters(storageConfig)
7050
new Parameters(
7151
params.getMap
7252
.map(({
7353
case (paramName, paramValue)
74-
if paramValue.encryption != JsNull && paramValue.encryption.convertTo[String] == enc.name =>
75-
paramName -> enc.decrypt(paramValue)
54+
if paramValue.encryption != JsNull && !configuredEncryptors.getEncrypter(paramValue.encryption.convertTo[String]).isEmpty =>
55+
paramName -> configuredEncryptors.getEncrypter(paramValue.encryption.convertTo[String]).get.decrypt(paramValue)
7656
case (paramName, paramValue) => paramName -> paramValue
7757
})))
7858
}
7959
}
8060

61+
private trait encrypter {
62+
def encrypt(p: ParameterValue): ParameterValue
63+
def decrypt(p: ParameterValue): ParameterValue
64+
val name: String
65+
}
66+
67+
private class encrypters(val storageConfig:ParameterStorageConfig) {
68+
private val availableEncrypters = Map("" -> new NoopCrypt()) ++
69+
(if (!storageConfig.aes256.isEmpty) Some(Aes256.name -> new Aes256(getKeyBytes(storageConfig.aes256))) else None ) ++
70+
(if (!storageConfig.aes128.isEmpty) Some(Aes128.name -> new Aes128(getKeyBytes(storageConfig.aes128))) else None )
71+
72+
protected[entity] def getCurrentEncrypter(): encrypter = {
73+
availableEncrypters.get(ParameterEncryption.storageConfig.current).get
74+
}
75+
protected[entity] def getEncrypter(name:String)= {
76+
availableEncrypters.get(name)
77+
}
78+
79+
def getKeyBytes(key:String): Array[Byte] = {
80+
if (key.length == 0) {
81+
Array[Byte]()
82+
} else {
83+
Base64.getDecoder().decode(key)
84+
}
85+
}
86+
}
87+
8188
private trait AesEncryption extends encrypter {
8289
val key: Array[Byte]
8390
val ivLen: Int
@@ -88,7 +95,6 @@ private trait AesEncryption extends encrypter {
8895
private val secureRandom = new SecureRandom()
8996

9097
def encrypt(value: ParameterValue): ParameterValue = {
91-
9298
val iv = new Array[Byte](ivLen)
9399
secureRandom.nextBytes(iv)
94100
val gcmSpec = new GCMParameterSpec(tLen, iv)
@@ -102,7 +108,7 @@ private trait AesEncryption extends encrypter {
102108
byteBuffer.put(iv)
103109
byteBuffer.put(cipherText)
104110
val cipherMessage = byteBuffer.array
105-
ParameterValue(JsString(Base64.getEncoder.encodeToString(cipherMessage)), value.init, JsString(name))
111+
ParameterValue(JsString(Base64.getEncoder.encodeToString(cipherMessage)), value.init, Some(JsString(name)))
106112
}
107113

108114
def decrypt(value: ParameterValue): ParameterValue = {
@@ -127,11 +133,17 @@ private trait AesEncryption extends encrypter {
127133

128134
}
129135

130-
private case class Aes128(val key: Array[Byte], val ivLen: Int = 12, val name: String = "aes128")
136+
private object Aes128 {
137+
val name:String = "aes128"
138+
}
139+
private case class Aes128(val key: Array[Byte], val ivLen: Int = 12, val name:String = Aes128.name)
131140
extends AesEncryption
132141
with encrypter
133142

134-
private case class Aes256(val key: Array[Byte], val ivLen: Int = 128, val name: String = "aes256")
143+
private object Aes256 {
144+
val name:String = "aes256"
145+
}
146+
private case class Aes256(val key: Array[Byte], val ivLen: Int = 128, val name:String = Aes256.name)
135147
extends AesEncryption
136148
with encrypter
137149

0 commit comments

Comments
 (0)