@agiledigital/pingone-aic-script-tester 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE-APACHE +201 -0
- package/LICENSE-MIT +21 -0
- package/README.md +274 -0
- package/dist/bin/fetch-jar.d.ts +2 -0
- package/dist/bin/fetch-jar.js +13 -0
- package/dist/bin/pull-profile.d.ts +2 -0
- package/dist/bin/pull-profile.js +40 -0
- package/dist/bin/show-log.d.ts +2 -0
- package/dist/bin/show-log.js +31 -0
- package/dist/classes/.complete +12 -0
- package/dist/classes/AmClassShutter.class +0 -0
- package/dist/classes/AmContextFactory$ObservedContext.class +0 -0
- package/dist/classes/AmContextFactory.class +0 -0
- package/dist/classes/AmScopes.class +0 -0
- package/dist/classes/HostOps.class +0 -0
- package/dist/classes/JsValues.class +0 -0
- package/dist/classes/Json$Parser.class +0 -0
- package/dist/classes/Json.class +0 -0
- package/dist/classes/LanguageVersions.class +0 -0
- package/dist/classes/Runner.class +0 -0
- package/dist/classes/ScriptContextScope.class +0 -0
- package/dist/src/aic/callbacks.d.ts +35 -0
- package/dist/src/aic/callbacks.js +130 -0
- package/dist/src/aic/conform.d.ts +90 -0
- package/dist/src/aic/conform.js +193 -0
- package/dist/src/aic/constants.d.ts +13 -0
- package/dist/src/aic/constants.js +13 -0
- package/dist/src/aic/diff.d.ts +25 -0
- package/dist/src/aic/diff.js +201 -0
- package/dist/src/aic/emit-journey.d.ts +64 -0
- package/dist/src/aic/emit-journey.js +254 -0
- package/dist/src/aic/emit-result.d.ts +10 -0
- package/dist/src/aic/emit-result.js +39 -0
- package/dist/src/aic/emit-session.d.ts +26 -0
- package/dist/src/aic/emit-session.js +132 -0
- package/dist/src/aic/emit-subject.d.ts +40 -0
- package/dist/src/aic/emit-subject.js +210 -0
- package/dist/src/aic/file-lease.d.ts +35 -0
- package/dist/src/aic/file-lease.js +543 -0
- package/dist/src/aic/http.d.ts +21 -0
- package/dist/src/aic/http.js +127 -0
- package/dist/src/aic/idm.d.ts +10 -0
- package/dist/src/aic/idm.js +112 -0
- package/dist/src/aic/index.d.ts +35 -0
- package/dist/src/aic/index.js +21 -0
- package/dist/src/aic/js.d.ts +8 -0
- package/dist/src/aic/js.js +11 -0
- package/dist/src/aic/lease-identity.d.ts +38 -0
- package/dist/src/aic/lease-identity.js +72 -0
- package/dist/src/aic/lease-lock.d.ts +45 -0
- package/dist/src/aic/lease-lock.js +173 -0
- package/dist/src/aic/managed.d.ts +28 -0
- package/dist/src/aic/managed.js +199 -0
- package/dist/src/aic/provider.d.ts +156 -0
- package/dist/src/aic/provider.js +416 -0
- package/dist/src/aic/record.d.ts +28 -0
- package/dist/src/aic/record.js +183 -0
- package/dist/src/aic/resource-snapshot.d.ts +12 -0
- package/dist/src/aic/resource-snapshot.js +116 -0
- package/dist/src/aic/run.d.ts +102 -0
- package/dist/src/aic/run.js +450 -0
- package/dist/src/aic/tenant.d.ts +107 -0
- package/dist/src/aic/tenant.js +307 -0
- package/dist/src/aic/trace.d.ts +25 -0
- package/dist/src/aic/trace.js +56 -0
- package/dist/src/aic/txid.d.ts +11 -0
- package/dist/src/aic/txid.js +25 -0
- package/dist/src/aic/unsupported.d.ts +15 -0
- package/dist/src/aic/unsupported.js +60 -0
- package/dist/src/bindings/allowlist.d.ts +42 -0
- package/dist/src/bindings/allowlist.js +62 -0
- package/dist/src/bindings/harvest.d.ts +7 -0
- package/dist/src/bindings/harvest.js +137 -0
- package/dist/src/bindings/index.d.ts +4 -0
- package/dist/src/bindings/index.js +3 -0
- package/dist/src/bindings/preamble.d.ts +23 -0
- package/dist/src/bindings/preamble.js +36 -0
- package/dist/src/bindings/run.d.ts +33 -0
- package/dist/src/bindings/run.js +55 -0
- package/dist/src/case/equal.d.ts +2 -0
- package/dist/src/case/equal.js +40 -0
- package/dist/src/case/index.d.ts +5 -0
- package/dist/src/case/index.js +4 -0
- package/dist/src/case/portable.d.ts +10 -0
- package/dist/src/case/portable.js +28 -0
- package/dist/src/case/session.d.ts +34 -0
- package/dist/src/case/session.js +66 -0
- package/dist/src/case/state.d.ts +4 -0
- package/dist/src/case/state.js +47 -0
- package/dist/src/case/types.d.ts +266 -0
- package/dist/src/case/types.js +106 -0
- package/dist/src/case/util.d.ts +7 -0
- package/dist/src/case/util.js +115 -0
- package/dist/src/case/validate.d.ts +8 -0
- package/dist/src/case/validate.js +463 -0
- package/dist/src/case/verdict.d.ts +6 -0
- package/dist/src/case/verdict.js +810 -0
- package/dist/src/diagnostics.d.ts +7 -0
- package/dist/src/diagnostics.js +5 -0
- package/dist/src/emit-dts.d.ts +2 -0
- package/dist/src/emit-dts.js +82 -0
- package/dist/src/emit-js.d.ts +3 -0
- package/dist/src/emit-js.js +149 -0
- package/dist/src/harness/failures.d.ts +26 -0
- package/dist/src/harness/failures.js +113 -0
- package/dist/src/harness/idm.d.ts +12 -0
- package/dist/src/harness/idm.js +44 -0
- package/dist/src/harness/index.d.ts +11 -0
- package/dist/src/harness/index.js +6 -0
- package/dist/src/harness/lease.d.ts +136 -0
- package/dist/src/harness/lease.js +286 -0
- package/dist/src/harness/residue.d.ts +23 -0
- package/dist/src/harness/residue.js +73 -0
- package/dist/src/harness/show-log.d.ts +43 -0
- package/dist/src/harness/show-log.js +253 -0
- package/dist/src/harness/spec.d.ts +67 -0
- package/dist/src/harness/spec.js +189 -0
- package/dist/src/harness/step.d.ts +80 -0
- package/dist/src/harness/step.js +97 -0
- package/dist/src/harness/types.d.ts +100 -0
- package/dist/src/harness/types.js +1 -0
- package/dist/src/harness/vitest.d.ts +52 -0
- package/dist/src/harness/vitest.js +155 -0
- package/dist/src/idents.d.ts +4 -0
- package/dist/src/idents.js +42 -0
- package/dist/src/jvm.d.ts +124 -0
- package/dist/src/jvm.js +432 -0
- package/dist/src/load.d.ts +2 -0
- package/dist/src/load.js +104 -0
- package/dist/src/paths.d.ts +36 -0
- package/dist/src/paths.js +50 -0
- package/dist/src/profile/index.d.ts +5 -0
- package/dist/src/profile/index.js +3 -0
- package/dist/src/profile/normalise.d.ts +16 -0
- package/dist/src/profile/normalise.js +106 -0
- package/dist/src/profile/pull.d.ts +22 -0
- package/dist/src/profile/pull.js +36 -0
- package/dist/src/profile/seed.d.ts +11 -0
- package/dist/src/profile/seed.js +28 -0
- package/dist/src/profile/store.d.ts +13 -0
- package/dist/src/profile/store.js +60 -0
- package/dist/src/profile/types.d.ts +45 -0
- package/dist/src/profile/types.js +1 -0
- package/dist/src/profile/validate.d.ts +38 -0
- package/dist/src/profile/validate.js +129 -0
- package/dist/src/project.d.ts +37 -0
- package/dist/src/project.js +93 -0
- package/dist/src/protocol.d.ts +62 -0
- package/dist/src/protocol.js +68 -0
- package/dist/src/provider-module.d.ts +11 -0
- package/dist/src/provider-module.js +46 -0
- package/dist/src/runner.d.ts +39 -0
- package/dist/src/runner.js +337 -0
- package/dist/src/schema.d.ts +48 -0
- package/dist/src/schema.js +93 -0
- package/generated/scripted-decision-mocks.cjs +722 -0
- package/generated/scripted-decision-mocks.d.ts +626 -0
- package/generated/scripted-decision-next.json +3640 -0
- package/java/AmClassShutter.java +98 -0
- package/java/AmContextFactory.java +69 -0
- package/java/AmScopes.java +49 -0
- package/java/HostOps.java +180 -0
- package/java/JsValues.java +39 -0
- package/java/Json.java +441 -0
- package/java/LanguageVersions.java +55 -0
- package/java/Runner.java +289 -0
- package/java/ScriptContextScope.java +147 -0
- package/package.json +88 -0
- package/src/bindings/rhino/runtime.cjs +3021 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import java.util.ArrayList;
|
|
2
|
+
import java.util.List;
|
|
3
|
+
import org.mozilla.javascript.ClassShutter;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Replica of the AM script sandbox's per-context Java class allow-list.
|
|
7
|
+
*
|
|
8
|
+
* <p>AM runs Rhino behind a class shutter that decides, per class and per
|
|
9
|
+
* script context, whether a Java class is visible at all. The local runner had
|
|
10
|
+
* none, so every Java name resolved and every iterator worked — which made the
|
|
11
|
+
* harness strictly MORE permissive than the tenant. A script using
|
|
12
|
+
* {@code new java.util.HashMap()} or {@code list.iterator()} passed locally and
|
|
13
|
+
* is refused by AIC.
|
|
14
|
+
*
|
|
15
|
+
* <p>The policy is not invented here. It is the {@code allowLists} array of the
|
|
16
|
+
* context's binding descriptor (for a scripted decision node,
|
|
17
|
+
* {@code docs/api/bindings/scripted-decision-next.json} — 51 entries), passed in
|
|
18
|
+
* per job so a second context can supply its own list.
|
|
19
|
+
*
|
|
20
|
+
* <p>Rhino gives both of AM's observed failure shapes from this one hook, at no
|
|
21
|
+
* extra effort — see {@code docs/api/12-script-bindings-matrix.md}:
|
|
22
|
+
*
|
|
23
|
+
* <ul>
|
|
24
|
+
* <li>a name the script writes itself never resolves past the package object,
|
|
25
|
+
* so {@code new java.util.HashMap()} fails as
|
|
26
|
+
* {@code TypeError: [JavaPackage java.util.HashMap] is not a function};
|
|
27
|
+
* <li>an instance handed to the script by a call or binding throws
|
|
28
|
+
* {@code InternalError: Access to Java class "…" is prohibited.}
|
|
29
|
+
* </ul>
|
|
30
|
+
*
|
|
31
|
+
* <p>Patterns are exact names, or a trailing {@code *} meaning prefix. That one
|
|
32
|
+
* rule covers every form the descriptor uses: {@code java.util.Collections$*},
|
|
33
|
+
* {@code com.sun.proxy.$*} and
|
|
34
|
+
* {@code org.forgerock.openam.core.rest.authn.callbackhandlers.*}.
|
|
35
|
+
*/
|
|
36
|
+
final class AmClassShutter implements ClassShutter {
|
|
37
|
+
|
|
38
|
+
private final String[] exact;
|
|
39
|
+
private final String[] prefixes;
|
|
40
|
+
|
|
41
|
+
private AmClassShutter(String[] exact, String[] prefixes) {
|
|
42
|
+
this.exact = exact;
|
|
43
|
+
this.prefixes = prefixes;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Build from the descriptor's `allowLists` entries. */
|
|
47
|
+
static AmClassShutter of(List<String> patterns) {
|
|
48
|
+
List<String> exactNames = new ArrayList<String>();
|
|
49
|
+
List<String> prefixNames = new ArrayList<String>();
|
|
50
|
+
for (String pattern : patterns) {
|
|
51
|
+
if (pattern == null || pattern.isEmpty()) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (pattern.endsWith("*")) {
|
|
55
|
+
prefixNames.add(pattern.substring(0, pattern.length() - 1));
|
|
56
|
+
} else {
|
|
57
|
+
exactNames.add(pattern);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return new AmClassShutter(
|
|
61
|
+
exactNames.toArray(new String[0]), prefixNames.toArray(new String[0]));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Rhino's own runtime classes, which the shutter must never hide.
|
|
66
|
+
*
|
|
67
|
+
* <p>The shutter is consulted for engine internals as well as for classes a
|
|
68
|
+
* script names. Hiding them does not enforce policy, it breaks the language:
|
|
69
|
+
* a template literal produces an {@code org.mozilla.javascript.ConsString},
|
|
70
|
+
* so a shutter built from the allow-list alone fails
|
|
71
|
+
* {@code `hi ${who}`} with {@code Access to Java class
|
|
72
|
+
* "org.mozilla.javascript.ConsString" is prohibited} — and template literals
|
|
73
|
+
* are used by 181 of the 384 scripts in the production corpus and run fine on
|
|
74
|
+
* AIC. AM is itself running Rhino, so its shutter cannot be hiding these
|
|
75
|
+
* either.
|
|
76
|
+
*
|
|
77
|
+
* <p>This is a requirement of the engine, not an inference about AM's policy.
|
|
78
|
+
*/
|
|
79
|
+
private static final String ENGINE_PACKAGE = "org.mozilla.javascript.";
|
|
80
|
+
|
|
81
|
+
@Override
|
|
82
|
+
public boolean visibleToScripts(String fullClassName) {
|
|
83
|
+
if (fullClassName.startsWith(ENGINE_PACKAGE)) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
for (String name : exact) {
|
|
87
|
+
if (name.equals(fullClassName)) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
for (String prefix : prefixes) {
|
|
92
|
+
if (fullClassName.startsWith(prefix)) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import org.mozilla.javascript.Context;
|
|
2
|
+
import org.mozilla.javascript.ContextFactory;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Replica of AM 8.1.1 {@code ObservedContextFactory} + its inner {@code
|
|
6
|
+
* ObservedJavaScriptContext}, plus the {@code
|
|
7
|
+
* RhinoScriptEngineFactory.getContext} knobs that constructor does not set.
|
|
8
|
+
*
|
|
9
|
+
* <p>Measured:
|
|
10
|
+
*
|
|
11
|
+
* <ul>
|
|
12
|
+
* <li>{@code makeContext} constructs {@code ObservedJavaScriptContext}
|
|
13
|
+
* <li>that constructor: {@code setOptimizationLevel(-1)}, {@code
|
|
14
|
+
* setInstructionObserverThreshold(1000)}, and {@code
|
|
15
|
+
* setLanguageVersion(200)} only when {@code
|
|
16
|
+
* org.forgerock.am.scripting.disableES6} is false. Language version is
|
|
17
|
+
* a per-job choice here (AIC's measured JS is {@code VERSION_DEFAULT}).
|
|
18
|
+
* <li>{@code observeInstructionCount}: if configured timeout (AM stores it
|
|
19
|
+
* in <em>seconds</em>) has elapsed since {@code startTime}, throw {@code
|
|
20
|
+
* new Error("Interrupt.")}
|
|
21
|
+
* <li>{@code hasFeature(21)} is true ({@code FEATURE_ENABLE_JAVA_MAP_ACCESS})
|
|
22
|
+
* <li>{@code getContext} also {@code setMaximumInterpreterStackDepth} (default
|
|
23
|
+
* 10000). We set that on every context.
|
|
24
|
+
* </ul>
|
|
25
|
+
*
|
|
26
|
+
* <p>Timeout is per-job milliseconds rather than AM's process-wide seconds, so
|
|
27
|
+
* one runaway script cannot take the runner down. {@code 0} means no timeout,
|
|
28
|
+
* matching AM's {@code ScriptEngineConfiguration.NO_TIMEOUT}.
|
|
29
|
+
*/
|
|
30
|
+
final class AmContextFactory extends ContextFactory {
|
|
31
|
+
|
|
32
|
+
@Override
|
|
33
|
+
protected boolean hasFeature(Context cx, int featureIndex) {
|
|
34
|
+
if (featureIndex == Context.FEATURE_ENABLE_JAVA_MAP_ACCESS) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
return super.hasFeature(cx, featureIndex);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@Override
|
|
41
|
+
protected Context makeContext() {
|
|
42
|
+
return new ObservedContext(this);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@Override
|
|
46
|
+
protected void observeInstructionCount(Context cx, int instructionCount) {
|
|
47
|
+
ObservedContext observed = (ObservedContext) cx;
|
|
48
|
+
long timeoutMs = observed.timeoutMs;
|
|
49
|
+
if (timeoutMs <= 0) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (System.currentTimeMillis() - observed.startTime > timeoutMs) {
|
|
53
|
+
throw new Error("Interrupt.");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static final class ObservedContext extends Context {
|
|
58
|
+
final long startTime;
|
|
59
|
+
long timeoutMs;
|
|
60
|
+
|
|
61
|
+
ObservedContext(ContextFactory factory) {
|
|
62
|
+
super(factory);
|
|
63
|
+
this.startTime = System.currentTimeMillis();
|
|
64
|
+
setOptimizationLevel(-1);
|
|
65
|
+
setInstructionObserverThreshold(1000);
|
|
66
|
+
setMaximumInterpreterStackDepth(10000);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import javax.script.ScriptContext;
|
|
2
|
+
import javax.script.SimpleScriptContext;
|
|
3
|
+
import org.mozilla.javascript.Context;
|
|
4
|
+
import org.mozilla.javascript.Scriptable;
|
|
5
|
+
import org.mozilla.javascript.ScriptableObject;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* AM 8.1.1 {@code RhinoScriptEngine.makeScriptable} / {@code getScope},
|
|
9
|
+
* measured with {@code javap -p -c}.
|
|
10
|
+
*
|
|
11
|
+
* <pre>
|
|
12
|
+
* ScriptContextScope scope = new ScriptContextScope(scriptContext);
|
|
13
|
+
* ScriptableObject std = cx.initStandardObjects();
|
|
14
|
+
* scope.setPrototype(std);
|
|
15
|
+
* scope.put("context", scope, scriptContext);
|
|
16
|
+
* return scope;
|
|
17
|
+
* </pre>
|
|
18
|
+
*
|
|
19
|
+
* <p>The standard-objects object is the <em>prototype</em>, not the parent
|
|
20
|
+
* (parent stays {@code null}). {@code getScope} additionally installs CommonJS
|
|
21
|
+
* {@code require} when {@code LIBRARY_SCRIPT} is on and {@code libraryBindings}
|
|
22
|
+
* is present; that path is not modelled here (decision-node eval).
|
|
23
|
+
*
|
|
24
|
+
* <p>AM calls {@code initStandardObjects()} on every eval — it does not share a
|
|
25
|
+
* sealed standard-objects parent. We do the same, so {@code Object.prototype}
|
|
26
|
+
* mutations cannot leak from job N to job N+1.
|
|
27
|
+
*/
|
|
28
|
+
final class AmScopes {
|
|
29
|
+
|
|
30
|
+
static final String FILENAME_KEY = "javax.script.filename";
|
|
31
|
+
|
|
32
|
+
private AmScopes() {}
|
|
33
|
+
|
|
34
|
+
static Scriptable makeScriptable(Context cx, ScriptContext scriptContext) {
|
|
35
|
+
ScriptContextScope scope = new ScriptContextScope(scriptContext);
|
|
36
|
+
ScriptableObject std = cx.initStandardObjects();
|
|
37
|
+
scope.setPrototype(std);
|
|
38
|
+
scope.put("context", scope, scriptContext);
|
|
39
|
+
return scope;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static SimpleScriptContext newEngineContext(String sourceName) {
|
|
43
|
+
SimpleScriptContext scriptContext = new SimpleScriptContext();
|
|
44
|
+
if (sourceName != null) {
|
|
45
|
+
scriptContext.setAttribute(FILENAME_KEY, sourceName, ScriptContext.ENGINE_SCOPE);
|
|
46
|
+
}
|
|
47
|
+
return scriptContext;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import java.nio.charset.StandardCharsets;
|
|
2
|
+
import java.security.KeyFactory;
|
|
3
|
+
import java.security.KeyPair;
|
|
4
|
+
import java.security.KeyPairGenerator;
|
|
5
|
+
import java.security.MessageDigest;
|
|
6
|
+
import java.security.SecureRandom;
|
|
7
|
+
import java.security.Signature;
|
|
8
|
+
import java.security.spec.ECGenParameterSpec;
|
|
9
|
+
import java.security.spec.PKCS8EncodedKeySpec;
|
|
10
|
+
import java.security.spec.X509EncodedKeySpec;
|
|
11
|
+
import java.util.Base64;
|
|
12
|
+
import java.util.UUID;
|
|
13
|
+
import javax.crypto.Cipher;
|
|
14
|
+
import javax.crypto.Mac;
|
|
15
|
+
import javax.crypto.SecretKeyFactory;
|
|
16
|
+
import javax.crypto.spec.PBEKeySpec;
|
|
17
|
+
import javax.crypto.spec.SecretKeySpec;
|
|
18
|
+
import org.mozilla.javascript.BaseFunction;
|
|
19
|
+
import org.mozilla.javascript.Context;
|
|
20
|
+
import org.mozilla.javascript.JavaScriptException;
|
|
21
|
+
import org.mozilla.javascript.Scriptable;
|
|
22
|
+
import org.mozilla.javascript.ScriptableObject;
|
|
23
|
+
import org.mozilla.javascript.Wrapper;
|
|
24
|
+
|
|
25
|
+
/** Narrow Java bridge captured by trusted mocks, then removed before user code runs. */
|
|
26
|
+
public final class HostOps extends BaseFunction {
|
|
27
|
+
private static final SecureRandom RANDOM = new SecureRandom();
|
|
28
|
+
static final java.util.List<String> DIGESTS =
|
|
29
|
+
java.util.Arrays.asList("SHA-256", "SHA-384", "SHA-1", "SHA-512");
|
|
30
|
+
|
|
31
|
+
// A real Java byte[], as AIC returns: the same NativeJavaArray a script gets
|
|
32
|
+
// from java.lang.String#getBytes, which the shutter already allows.
|
|
33
|
+
private static Object wrapBytes(Context cx, Scriptable scope, byte[] bytes) {
|
|
34
|
+
return cx.getWrapFactory().wrap(cx, scope, bytes, byte[].class);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private static byte[] toBytes(Object value) {
|
|
38
|
+
return (byte[]) Context.jsToJava(value, byte[].class);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@Override
|
|
42
|
+
public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
|
|
43
|
+
try {
|
|
44
|
+
return dispatch(cx, scope, args);
|
|
45
|
+
} catch (RuntimeException e) {
|
|
46
|
+
throw e;
|
|
47
|
+
} catch (Exception e) {
|
|
48
|
+
throw Context.reportRuntimeError(e.toString());
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private Object dispatch(Context cx, Scriptable scope, Object[] args) throws Exception {
|
|
53
|
+
String op = (String) Context.jsToJava(args[0], String.class);
|
|
54
|
+
switch (op) {
|
|
55
|
+
case "base64Encode": {
|
|
56
|
+
// AIC's encode(byte[]) overload refuses a JS array (measured), where
|
|
57
|
+
// most of the crypto methods convert one.
|
|
58
|
+
Object raw = args[1] instanceof Wrapper ? ((Wrapper) args[1]).unwrap() : args[1];
|
|
59
|
+
if (!(raw instanceof byte[])) {
|
|
60
|
+
throw Context.reportRuntimeError("Cannot convert " + raw.getClass().getName() + "@"
|
|
61
|
+
+ Integer.toHexString(System.identityHashCode(raw)) + " to byte[]");
|
|
62
|
+
}
|
|
63
|
+
byte[] b = (byte[]) raw;
|
|
64
|
+
boolean url = Context.toBoolean(args[2]);
|
|
65
|
+
return url ? Base64.getUrlEncoder().withoutPadding().encodeToString(b)
|
|
66
|
+
: Base64.getEncoder().encodeToString(b);
|
|
67
|
+
}
|
|
68
|
+
case "base64Decode": {
|
|
69
|
+
String s = (String) Context.jsToJava(args[1], String.class);
|
|
70
|
+
try {
|
|
71
|
+
return wrapBytes(cx, scope, Context.toBoolean(args[2])
|
|
72
|
+
? Base64.getUrlDecoder().decode(s) : Base64.getDecoder().decode(s));
|
|
73
|
+
} catch (IllegalArgumentException e) {
|
|
74
|
+
// Thrown as a bare JS value so the mock can rethrow it without a
|
|
75
|
+
// location suffix, as AIC reports it.
|
|
76
|
+
throw new JavaScriptException(e.getMessage(), null, 0);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
case "utf8": return wrapBytes(cx, scope,
|
|
80
|
+
((String) Context.jsToJava(args[1], String.class)).getBytes(StandardCharsets.UTF_8));
|
|
81
|
+
case "fromUtf8": return new String(toBytes(args[1]), StandardCharsets.UTF_8);
|
|
82
|
+
case "uuid": return UUID.randomUUID().toString();
|
|
83
|
+
case "randomInt32": return Integer.valueOf(RANDOM.nextInt());
|
|
84
|
+
case "digest": {
|
|
85
|
+
String alg = (String) Context.jsToJava(args[1], String.class);
|
|
86
|
+
// AIC's list (measured); anything else is refused before it gets here.
|
|
87
|
+
if (!DIGESTS.contains(alg)) {
|
|
88
|
+
throw Context.reportRuntimeError("digest " + alg);
|
|
89
|
+
}
|
|
90
|
+
return wrapBytes(cx, scope, MessageDigest.getInstance(alg).digest(toBytes(args[2])));
|
|
91
|
+
}
|
|
92
|
+
case "hmac": {
|
|
93
|
+
String alg = (String) Context.jsToJava(args[1], String.class);
|
|
94
|
+
if (!DIGESTS.contains(alg)) {
|
|
95
|
+
throw Context.reportRuntimeError("hmac " + alg);
|
|
96
|
+
}
|
|
97
|
+
Mac mac = Mac.getInstance("Hmac" + alg.replace("-", ""));
|
|
98
|
+
mac.init(new SecretKeySpec(toBytes(args[2]), mac.getAlgorithm()));
|
|
99
|
+
return wrapBytes(cx, scope, mac.doFinal(toBytes(args[3])));
|
|
100
|
+
}
|
|
101
|
+
case "aes": {
|
|
102
|
+
Cipher c = Cipher.getInstance("AES/ECB/PKCS5Padding");
|
|
103
|
+
c.init(Context.toBoolean(args[1]) ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE,
|
|
104
|
+
new SecretKeySpec(toBytes(args[2]), "AES"));
|
|
105
|
+
return wrapBytes(cx, scope, c.doFinal(toBytes(args[3])));
|
|
106
|
+
}
|
|
107
|
+
case "pbkdf2": {
|
|
108
|
+
byte[] password = toBytes(args[1]);
|
|
109
|
+
// AIC casts the salt rather than converting it, so a JS array fails
|
|
110
|
+
// (measured); its message also names the webapp class loader.
|
|
111
|
+
Object rawSalt = args[2] instanceof Wrapper ? ((Wrapper) args[2]).unwrap() : args[2];
|
|
112
|
+
if (!(rawSalt instanceof byte[])) {
|
|
113
|
+
throw new JavaScriptException("class " + rawSalt.getClass().getName() + " cannot be cast to class [B", null, 0);
|
|
114
|
+
}
|
|
115
|
+
byte[] salt = (byte[]) rawSalt;
|
|
116
|
+
int iterations = ((Number) Context.jsToJava(args[3], Number.class)).intValue();
|
|
117
|
+
int bits = ((Number) Context.jsToJava(args[4], Number.class)).intValue();
|
|
118
|
+
String hash = (String) Context.jsToJava(args[5], String.class);
|
|
119
|
+
// Reachable through __parent__, so no wider than utils accepts.
|
|
120
|
+
if (!DIGESTS.contains(hash)) {
|
|
121
|
+
throw Context.reportRuntimeError("pbkdf2 " + hash);
|
|
122
|
+
}
|
|
123
|
+
PBEKeySpec spec = new PBEKeySpec(new String(password, StandardCharsets.UTF_8).toCharArray(), salt, iterations, bits);
|
|
124
|
+
return wrapBytes(cx, scope, SecretKeyFactory.getInstance("PBKDF2WithHmac" + hash.replace("-", ""))
|
|
125
|
+
.generateSecret(spec).getEncoded());
|
|
126
|
+
}
|
|
127
|
+
case "generateSecret": {
|
|
128
|
+
// AIC's generated AES and HMAC keys are both 32 bytes (measured).
|
|
129
|
+
byte[] bytes = new byte[32];
|
|
130
|
+
RANDOM.nextBytes(bytes);
|
|
131
|
+
return wrapBytes(cx, scope, bytes);
|
|
132
|
+
}
|
|
133
|
+
case "generateKeyPair": {
|
|
134
|
+
String alg = (String) Context.jsToJava(args[1], String.class);
|
|
135
|
+
// Reachable through __parent__, so no wider than utils accepts.
|
|
136
|
+
if (!"RSA".equals(alg) && !"ECDSA".equals(alg)) {
|
|
137
|
+
throw Context.reportRuntimeError("generateKeyPair " + alg);
|
|
138
|
+
}
|
|
139
|
+
KeyPairGenerator gen = KeyPairGenerator.getInstance("ECDSA".equals(alg) ? "EC" : alg);
|
|
140
|
+
if ("RSA".equals(alg)) gen.initialize(2048);
|
|
141
|
+
if ("ECDSA".equals(alg)) gen.initialize(new ECGenParameterSpec("secp256r1"));
|
|
142
|
+
KeyPair pair = gen.generateKeyPair();
|
|
143
|
+
Scriptable result = cx.newObject(scope);
|
|
144
|
+
ScriptableObject.putProperty(result, "privateKey", wrapBytes(cx, scope, pair.getPrivate().getEncoded()));
|
|
145
|
+
ScriptableObject.putProperty(result, "publicKey", wrapBytes(cx, scope, pair.getPublic().getEncoded()));
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
case "rsa": {
|
|
149
|
+
boolean encrypt = Context.toBoolean(args[1]);
|
|
150
|
+
byte[] key = toBytes(args[2]);
|
|
151
|
+
byte[] data = toBytes(args[3]);
|
|
152
|
+
// RSA transformation and encoded key formats were not independently measured; use JDK defaults.
|
|
153
|
+
KeyFactory factory = KeyFactory.getInstance("RSA");
|
|
154
|
+
Cipher cipher = Cipher.getInstance("RSA");
|
|
155
|
+
cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE,
|
|
156
|
+
encrypt ? factory.generatePublic(new X509EncodedKeySpec(key)) : factory.generatePrivate(new PKCS8EncodedKeySpec(key)));
|
|
157
|
+
return wrapBytes(cx, scope, cipher.doFinal(data));
|
|
158
|
+
}
|
|
159
|
+
case "ecdsaSign": {
|
|
160
|
+
byte[] key = toBytes(args[1]);
|
|
161
|
+
Signature sig = Signature.getInstance("SHA256withECDSAinP1363Format");
|
|
162
|
+
sig.initSign(KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(key)), RANDOM);
|
|
163
|
+
sig.update(toBytes(args[2])); return wrapBytes(cx, scope, sig.sign());
|
|
164
|
+
}
|
|
165
|
+
case "ecdsaVerify": {
|
|
166
|
+
byte[] key = toBytes(args[1]);
|
|
167
|
+
Signature sig = Signature.getInstance("SHA256withECDSAinP1363Format");
|
|
168
|
+
sig.initVerify(KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(key)));
|
|
169
|
+
sig.update(toBytes(args[2]));
|
|
170
|
+
// AIC answers false for a DER signature rather than throwing (measured).
|
|
171
|
+
try {
|
|
172
|
+
return Boolean.valueOf(sig.verify(toBytes(args[3])));
|
|
173
|
+
} catch (java.security.SignatureException e) {
|
|
174
|
+
return Boolean.FALSE;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
default: throw Context.reportRuntimeError("unknown host operation: " + op);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import org.mozilla.javascript.Context;
|
|
2
|
+
import org.mozilla.javascript.Scriptable;
|
|
3
|
+
import org.mozilla.javascript.Undefined;
|
|
4
|
+
|
|
5
|
+
/** Classify a Rhino completion / binding value for JSON emission. */
|
|
6
|
+
final class JsValues {
|
|
7
|
+
|
|
8
|
+
final boolean defined;
|
|
9
|
+
final String kind;
|
|
10
|
+
final Object json;
|
|
11
|
+
|
|
12
|
+
private JsValues(boolean defined, String kind, Object json) {
|
|
13
|
+
this.defined = defined;
|
|
14
|
+
this.kind = kind;
|
|
15
|
+
this.json = json;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static JsValues of(Object raw) {
|
|
19
|
+
if (raw == Scriptable.NOT_FOUND) {
|
|
20
|
+
return new JsValues(false, "missing", null);
|
|
21
|
+
}
|
|
22
|
+
if (raw == null) {
|
|
23
|
+
return new JsValues(true, "null", null);
|
|
24
|
+
}
|
|
25
|
+
if (Undefined.isUndefined(raw)) {
|
|
26
|
+
return new JsValues(true, "undefined", null);
|
|
27
|
+
}
|
|
28
|
+
if (raw instanceof Boolean) {
|
|
29
|
+
return new JsValues(true, "boolean", raw);
|
|
30
|
+
}
|
|
31
|
+
if (raw instanceof Number) {
|
|
32
|
+
return new JsValues(true, "number", raw);
|
|
33
|
+
}
|
|
34
|
+
if (raw instanceof String) {
|
|
35
|
+
return new JsValues(true, "string", raw);
|
|
36
|
+
}
|
|
37
|
+
return new JsValues(true, "string", Context.toString(raw));
|
|
38
|
+
}
|
|
39
|
+
}
|