Skip to content

Commit 47d58c0

Browse files
duonglaiquangrbri
authored andcommitted
SubtleCrypto: implement generateKey()
1 parent 2e79577 commit 47d58c0

6 files changed

Lines changed: 791 additions & 9 deletions

File tree

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright (c) 2002-2026 Gargoyle Software Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* https://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
package org.htmlunit.javascript.host.crypto;
16+
17+
import java.util.Set;
18+
19+
import org.htmlunit.corejs.javascript.NativeObject;
20+
import org.htmlunit.corejs.javascript.ScriptRuntime;
21+
import org.htmlunit.corejs.javascript.Scriptable;
22+
import org.htmlunit.corejs.javascript.ScriptableObject;
23+
import org.htmlunit.corejs.javascript.TopLevel;
24+
25+
/**
26+
* Internal helper representing AES key algorithm parameters.
27+
* Used by {@link SubtleCrypto} for AES key operations.
28+
*
29+
* @author Lai Quang Duong
30+
*/
31+
final class AesKeyAlgorithm {
32+
33+
static final Set<String> SUPPORTED_NAMES = Set.of("AES-CBC", "AES-CTR", "AES-GCM", "AES-KW");
34+
static final Set<Integer> SUPPORTED_LENGTHS = Set.of(128, 192, 256);
35+
36+
private final String name_;
37+
private final int length_;
38+
39+
AesKeyAlgorithm(final String name, final int length) {
40+
if (!SUPPORTED_NAMES.contains(name)) {
41+
throw new UnsupportedOperationException("AES " + name);
42+
}
43+
name_ = name;
44+
45+
if (!SUPPORTED_LENGTHS.contains(length)) {
46+
throw new IllegalArgumentException("Data provided to an operation does not meet requirements");
47+
}
48+
length_ = length;
49+
}
50+
51+
/**
52+
* Parse AES key algorithm parameters from a JS object.
53+
*
54+
* @param keyGenParams the JS algorithm parameters object
55+
* @return the parsed AesKeyAlgorithm
56+
*/
57+
static AesKeyAlgorithm from(final Scriptable keyGenParams) {
58+
final Object nameProp = ScriptableObject.getProperty(keyGenParams, "name");
59+
if (!(nameProp instanceof String name)) {
60+
throw new IllegalArgumentException("An invalid or illegal string was specified");
61+
}
62+
63+
final Object lengthProp = ScriptableObject.getProperty(keyGenParams, "length");
64+
if (!(lengthProp instanceof Number numLength)) {
65+
throw new IllegalArgumentException("An invalid or illegal string was specified");
66+
}
67+
68+
return new AesKeyAlgorithm(name, numLength.intValue());
69+
}
70+
71+
static boolean isSupported(final String name) {
72+
return SUPPORTED_NAMES.contains(name);
73+
}
74+
75+
String getName() {
76+
return name_;
77+
}
78+
79+
int getLength() {
80+
return length_;
81+
}
82+
83+
/**
84+
* Converts to a JS object matching the {@code AesKeyAlgorithm} dictionary:
85+
* {@code {name: "AES-GCM", length: 256}}
86+
*
87+
* @param scope the JS scope for prototype/parent setup
88+
* @return the JS algorithm object
89+
*/
90+
Scriptable toScriptableObject(final Scriptable scope) {
91+
final NativeObject algorithm = new NativeObject();
92+
ScriptRuntime.setBuiltinProtoAndParent(algorithm, scope, TopLevel.Builtins.Object);
93+
ScriptableObject.putProperty(algorithm, "name", getName());
94+
ScriptableObject.putProperty(algorithm, "length", getLength());
95+
return algorithm;
96+
}
97+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright (c) 2002-2026 Gargoyle Software Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* https://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
package org.htmlunit.javascript.host.crypto;
16+
17+
import java.util.Map;
18+
import java.util.Set;
19+
20+
import org.htmlunit.corejs.javascript.NativeObject;
21+
import org.htmlunit.corejs.javascript.ScriptRuntime;
22+
import org.htmlunit.corejs.javascript.Scriptable;
23+
import org.htmlunit.corejs.javascript.ScriptableObject;
24+
import org.htmlunit.corejs.javascript.TopLevel;
25+
26+
/**
27+
* Internal helper representing EC key algorithm parameters.
28+
* Used by {@link SubtleCrypto} for ECDSA and ECDH key operations.
29+
*
30+
* @author Lai Quang Duong
31+
*/
32+
final class EcKeyAlgorithm {
33+
34+
static final Set<String> SUPPORTED_NAMES = Set.of("ECDSA", "ECDH");
35+
36+
private static final Map<String, String> CURVE_TO_JCA = Map.of(
37+
"P-256", "secp256r1",
38+
"P-384", "secp384r1",
39+
"P-521", "secp521r1"
40+
);
41+
42+
private final String name_;
43+
private final String namedCurve_;
44+
45+
EcKeyAlgorithm(final String name, final String namedCurve) {
46+
if (!SUPPORTED_NAMES.contains(name)) {
47+
throw new UnsupportedOperationException("EC " + name);
48+
}
49+
name_ = name;
50+
51+
if (!CURVE_TO_JCA.containsKey(namedCurve)) {
52+
throw new UnsupportedOperationException("EC curve " + namedCurve);
53+
}
54+
namedCurve_ = namedCurve;
55+
}
56+
57+
/**
58+
* Parse EC key algorithm parameters from a JS object.
59+
*
60+
* @param keyGenParams the JS algorithm parameters object
61+
* @return the parsed EcKeyAlgorithm
62+
*/
63+
static EcKeyAlgorithm from(final Scriptable keyGenParams) {
64+
final Object nameProp = ScriptableObject.getProperty(keyGenParams, "name");
65+
if (!(nameProp instanceof String name)) {
66+
throw new IllegalArgumentException("An invalid or illegal string was specified");
67+
}
68+
69+
final Object curveProp = ScriptableObject.getProperty(keyGenParams, "namedCurve");
70+
if (!(curveProp instanceof String namedCurve)) {
71+
throw new IllegalArgumentException("An invalid or illegal string was specified");
72+
}
73+
74+
return new EcKeyAlgorithm(name, namedCurve);
75+
}
76+
77+
static boolean isSupported(final String name) {
78+
return SUPPORTED_NAMES.contains(name);
79+
}
80+
81+
String getName() {
82+
return name_;
83+
}
84+
85+
String getNamedCurve() {
86+
return namedCurve_;
87+
}
88+
89+
/**
90+
* @return the JCA curve name (e.g. "secp256r1" for "P-256")
91+
*/
92+
String getJavaCurveName() {
93+
return CURVE_TO_JCA.get(namedCurve_);
94+
}
95+
96+
/**
97+
* Converts to a JS object matching the {@code EcKeyAlgorithm} dictionary:
98+
* {@code {name: "ECDSA", namedCurve: "P-256"}}
99+
*
100+
* @param scope the JS scope for prototype/parent setup
101+
* @return the JS algorithm object
102+
*/
103+
Scriptable toScriptableObject(final Scriptable scope) {
104+
final NativeObject algorithm = new NativeObject();
105+
ScriptRuntime.setBuiltinProtoAndParent(algorithm, scope, TopLevel.Builtins.Object);
106+
ScriptableObject.putProperty(algorithm, "name", getName());
107+
ScriptableObject.putProperty(algorithm, "namedCurve", getNamedCurve());
108+
return algorithm;
109+
}
110+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Copyright (c) 2002-2026 Gargoyle Software Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* https://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
package org.htmlunit.javascript.host.crypto;
16+
17+
import java.util.Set;
18+
19+
import org.htmlunit.corejs.javascript.NativeObject;
20+
import org.htmlunit.corejs.javascript.ScriptRuntime;
21+
import org.htmlunit.corejs.javascript.Scriptable;
22+
import org.htmlunit.corejs.javascript.ScriptableObject;
23+
import org.htmlunit.corejs.javascript.TopLevel;
24+
25+
/**
26+
* Internal helper representing HMAC key algorithm parameters.
27+
* Used by {@link SubtleCrypto} for HMAC key operations.
28+
*
29+
* @author Lai Quang Duong
30+
*/
31+
final class HmacKeyAlgorithm {
32+
33+
static final Set<String> SUPPORTED_HASH_ALGORITHMS = Set.of("SHA-1", "SHA-256", "SHA-384", "SHA-512");
34+
35+
private final String hash_;
36+
private final int length_;
37+
38+
HmacKeyAlgorithm(final String hash, final int length) {
39+
if (!SUPPORTED_HASH_ALGORITHMS.contains(hash)) {
40+
throw new UnsupportedOperationException("HMAC " + hash);
41+
}
42+
hash_ = hash;
43+
44+
if (length <= 0) {
45+
throw new IllegalArgumentException("Data provided to an operation does not meet requirements");
46+
}
47+
length_ = length;
48+
}
49+
50+
/**
51+
* Parse HMAC key algorithm parameters from a JS object.
52+
*
53+
* @param keyGenParams the JS algorithm parameters object
54+
* @return the parsed HmacKeyAlgorithm
55+
*/
56+
static HmacKeyAlgorithm from(final Scriptable keyGenParams) {
57+
return from(keyGenParams, null);
58+
}
59+
60+
/**
61+
* Parse HMAC key algorithm parameters from a JS object, with an optional fallback length.
62+
*
63+
* @param keyGenParams the JS algorithm parameters object
64+
* @param fallbackLength optional length to use when not specified in params;
65+
* if null, defaults to the hash block size
66+
* @return the parsed HmacKeyAlgorithm
67+
*/
68+
static HmacKeyAlgorithm from(final Scriptable keyGenParams, final Integer fallbackLength) {
69+
final Object hashProp = ScriptableObject.getProperty(keyGenParams, "hash");
70+
final String hash = SubtleCrypto.resolveAlgorithmName(hashProp);
71+
72+
final int length;
73+
final Object lengthProp = ScriptableObject.getProperty(keyGenParams, "length");
74+
if (lengthProp == Scriptable.NOT_FOUND) {
75+
if (fallbackLength != null) {
76+
length = fallbackLength;
77+
}
78+
else {
79+
// default to the block size of the hash algorithm
80+
length = switch (hash) {
81+
case "SHA-1", "SHA-256" -> 512;
82+
case "SHA-384", "SHA-512" -> 1024;
83+
default -> throw new UnsupportedOperationException("HMAC " + hash);
84+
};
85+
}
86+
}
87+
else {
88+
if (!(lengthProp instanceof Number numLength)) {
89+
throw new IllegalArgumentException("An invalid or illegal string was specified");
90+
}
91+
length = numLength.intValue();
92+
}
93+
94+
return new HmacKeyAlgorithm(hash, length);
95+
}
96+
97+
String getHash() {
98+
return hash_;
99+
}
100+
101+
int getLength() {
102+
return length_;
103+
}
104+
105+
/**
106+
* @return the Java algorithm name for {@link javax.crypto.Mac} (e.g. "HmacSHA256")
107+
*/
108+
String getJavaName() {
109+
return "Hmac" + hash_.replace("-", "");
110+
}
111+
112+
/**
113+
* Converts to a JS object matching the {@code HmacKeyAlgorithm} dictionary:
114+
* {@code {name: "HMAC", hash: {name: "SHA-256"}, length: N}}
115+
*
116+
* @param scope the JS scope for prototype/parent setup
117+
* @return the JS algorithm object
118+
*/
119+
Scriptable toScriptableObject(final Scriptable scope) {
120+
final NativeObject hashObj = new NativeObject();
121+
ScriptRuntime.setBuiltinProtoAndParent(hashObj, scope, TopLevel.Builtins.Object);
122+
ScriptableObject.putProperty(hashObj, "name", getHash());
123+
124+
final NativeObject algorithm = new NativeObject();
125+
ScriptRuntime.setBuiltinProtoAndParent(algorithm, scope, TopLevel.Builtins.Object);
126+
ScriptableObject.putProperty(algorithm, "name", "HMAC");
127+
ScriptableObject.putProperty(algorithm, "hash", hashObj);
128+
ScriptableObject.putProperty(algorithm, "length", getLength());
129+
return algorithm;
130+
}
131+
}

0 commit comments

Comments
 (0)