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.Scriptable;
20 import org.htmlunit.corejs.javascript.ScriptableObject;
21 import org.htmlunit.corejs.javascript.VarScope;
22 import org.htmlunit.javascript.JavaScriptEngine;
23
24 /**
25 * Internal helper representing HMAC key algorithm parameters.
26 * Used by {@link SubtleCrypto} for HMAC key operations.
27 *
28 * @author Lai Quang Duong
29 * @author Ronald Brill
30 */
31 final class HmacKeyAlgorithm implements KeyAlgorithm {
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 length_ = length;
44 }
45
46 /**
47 * Parses HMAC key algorithm parameters from a JS object.
48 *
49 * @param keyGenParams the JS algorithm parameters object
50 * @return the parsed HmacKeyAlgorithm
51 */
52 static HmacKeyAlgorithm from(final Scriptable keyGenParams) {
53 return from(keyGenParams, null);
54 }
55
56 /**
57 * Parses HMAC key algorithm parameters from a JS object, with an optional fallback length.
58 *
59 * @param keyGenParams the JS algorithm parameters object
60 * @param fallbackLength optional length to use when not specified in params;
61 * if null, defaults to the hash block size
62 * @return the parsed HmacKeyAlgorithm
63 */
64 static HmacKeyAlgorithm from(final Scriptable keyGenParams, final Integer fallbackLength) {
65 final String hash = SubtleCrypto.resolveAlgorithmName(SubtleCrypto.getRequiredMember(keyGenParams, "hash"));
66
67 final int length;
68 final Object lengthProp = ScriptableObject.getProperty(keyGenParams, "length");
69 if (lengthProp == Scriptable.NOT_FOUND || JavaScriptEngine.isUndefined(lengthProp)) {
70 if (fallbackLength != null) {
71 length = fallbackLength;
72 }
73 else {
74 // default to the block size of the hash algorithm
75 length = switch (hash) {
76 case "SHA-1", "SHA-256" -> 512;
77 case "SHA-384", "SHA-512" -> 1024;
78 default -> throw new UnsupportedOperationException("HMAC " + hash);
79 };
80 }
81 }
82 else {
83 length = (int) SubtleCrypto.getRequiredNumber(keyGenParams, "length");
84 }
85
86 return new HmacKeyAlgorithm(hash, length);
87 }
88
89 @Override
90 public String getName() {
91 return "HMAC";
92 }
93
94 String getHash() {
95 return hash_;
96 }
97
98 int getLength() {
99 return length_;
100 }
101
102 /**
103 * Returns the Java algorithm name for {@link javax.crypto.Mac} (e.g. "HmacSHA256").
104 *
105 * @return the Java algorithm name for {@link javax.crypto.Mac} (e.g. "HmacSHA256")
106 */
107 String getJavaName() {
108 return "Hmac" + hash_.replace("-", "");
109 }
110
111 /**
112 * Converts to a JS object matching the {@code HmacKeyAlgorithm} dictionary:
113 * {@code {name: "HMAC", hash: {name: "SHA-256"}, length: N}}.
114 *
115 * @param scope the JS scope for prototype/parent setup
116 * @return the JS algorithm object
117 */
118 @Override
119 public Scriptable toScriptableObject(final VarScope scope) {
120 final Scriptable hashObj = JavaScriptEngine.newObject(scope);
121 ScriptableObject.putProperty(hashObj, "name", getHash());
122
123 final Scriptable algorithm = JavaScriptEngine.newObject(scope);
124 ScriptableObject.putProperty(algorithm, "name", getName());
125 ScriptableObject.putProperty(algorithm, "hash", hashObj);
126 ScriptableObject.putProperty(algorithm, "length", getLength());
127 return algorithm;
128 }
129 }