View Javadoc
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.math.BigInteger;
18  import java.util.Set;
19  
20  import org.htmlunit.corejs.javascript.Scriptable;
21  import org.htmlunit.corejs.javascript.ScriptableObject;
22  import org.htmlunit.corejs.javascript.VarScope;
23  import org.htmlunit.corejs.javascript.typedarrays.NativeArrayBuffer;
24  import org.htmlunit.corejs.javascript.typedarrays.NativeArrayBufferView;
25  import org.htmlunit.corejs.javascript.typedarrays.NativeUint8Array;
26  import org.htmlunit.javascript.JavaScriptEngine;
27  
28  /**
29   * Internal helper representing RSA hashed key algorithm parameters.
30   * Used by {@link SubtleCrypto} for RSA key operations.
31   *
32   * @author Lai Quang Duong
33   * @author Ronald Brill
34   */
35  final class RsaHashedKeyAlgorithm {
36  
37      static final Set<String> SUPPORTED_NAMES = Set.of("RSASSA-PKCS1-v1_5", "RSA-PSS", "RSA-OAEP");
38      static final Set<String> SUPPORTED_HASH_ALGORITHMS = Set.of("SHA-1", "SHA-256", "SHA-384", "SHA-512");
39  
40      private final String name_;
41      private final int modulusLength_;
42      private final byte[] publicExponent_;
43      private final String hash_;
44  
45      RsaHashedKeyAlgorithm(final String name, final int modulusLength,
46              final byte[] publicExponent, final String hash) {
47          if (!SUPPORTED_NAMES.contains(name)) {
48              throw new UnsupportedOperationException("RSA " + name);
49          }
50          name_ = name;
51  
52          if (modulusLength <= 0) {
53              throw new IllegalArgumentException("Data provided to an operation does not meet requirements");
54          }
55          modulusLength_ = modulusLength;
56  
57          if (publicExponent == null || publicExponent.length == 0) {
58              throw new IllegalArgumentException("Data provided to an operation does not meet requirements");
59          }
60          publicExponent_ = publicExponent.clone();
61  
62          if (!SUPPORTED_HASH_ALGORITHMS.contains(hash)) {
63              throw new UnsupportedOperationException("RSA hash " + hash);
64          }
65          hash_ = hash;
66      }
67  
68      /**
69       * Parse RSA hashed key algorithm parameters from a JS object.
70       *
71       * @param keyGenParams the JS algorithm parameters object
72       * @return the parsed RsaHashedKeyAlgorithm
73       */
74      static RsaHashedKeyAlgorithm from(final Scriptable keyGenParams) {
75          final Object nameProp = ScriptableObject.getProperty(keyGenParams, "name");
76          if (!(nameProp instanceof String name)) {
77              throw new IllegalArgumentException("An invalid or illegal string was specified");
78          }
79  
80          final Object modulusLengthProp = ScriptableObject.getProperty(keyGenParams, "modulusLength");
81          if (!(modulusLengthProp instanceof Number numModulusLength)) {
82              throw new IllegalArgumentException("An invalid or illegal string was specified");
83          }
84  
85          final Object publicExponentProp = ScriptableObject.getProperty(keyGenParams, "publicExponent");
86          final byte[] publicExponent = extractBytes(publicExponentProp);
87          if (publicExponent == null) {
88              throw new IllegalArgumentException("An invalid or illegal string was specified");
89          }
90  
91          final Object hashProp = ScriptableObject.getProperty(keyGenParams, "hash");
92          final String hash = SubtleCrypto.resolveAlgorithmName(hashProp);
93  
94          return new RsaHashedKeyAlgorithm(name, numModulusLength.intValue(), publicExponent, hash);
95      }
96  
97      private static byte[] extractBytes(final Object value) {
98          if (value instanceof NativeArrayBufferView view) {
99              final NativeArrayBuffer buf = view.getBuffer();
100             final byte[] result = new byte[view.getByteLength()];
101             System.arraycopy(buf.getBuffer(), view.getByteOffset(), result, 0, result.length);
102             return result;
103         }
104         if (value instanceof NativeArrayBuffer buf) {
105             return buf.getBuffer().clone();
106         }
107         return null;
108     }
109 
110     static boolean isSupported(final String name) {
111         return SUPPORTED_NAMES.contains(name);
112     }
113 
114     String getName() {
115         return name_;
116     }
117 
118     int getModulusLength() {
119         return modulusLength_;
120     }
121 
122     byte[] getPublicExponent() {
123         return publicExponent_.clone();
124     }
125 
126     /**
127      * @return the public exponent as a BigInteger
128      */
129     BigInteger getPublicExponentAsBigInteger() {
130         return new BigInteger(1, publicExponent_);
131     }
132 
133     String getHash() {
134         return hash_;
135     }
136 
137     /**
138      * @return the Java hash algorithm name (e.g. "SHA256" from "SHA-256")
139      */
140     String getJavaHash() {
141         return hash_.replace("-", "");
142     }
143 
144     /**
145      * Converts to a JS object matching the {@code RsaHashedKeyAlgorithm} dictionary:
146      * {@code {name: "RSASSA-PKCS1-v1_5", modulusLength: 2048, publicExponent: Uint8Array, hash: {name: "SHA-256"}}}
147      *
148      * @param scope the JS scope for prototype/parent setup
149      * @return the JS algorithm object
150      */
151     Scriptable toScriptableObject(final VarScope scope) {
152         final Scriptable hashObj = JavaScriptEngine.newObject(scope);
153         ScriptableObject.putProperty(hashObj, "name", getHash());
154 
155         final NativeUint8Array uint8Array = JavaScriptEngine.newUint8Array(scope, publicExponent_);
156 
157         final Scriptable algorithm = JavaScriptEngine.newObject(scope);
158         ScriptableObject.putProperty(algorithm, "name", getName());
159         ScriptableObject.putProperty(algorithm, "hash", hashObj);
160         ScriptableObject.putProperty(algorithm, "modulusLength", getModulusLength());
161         ScriptableObject.putProperty(algorithm, "publicExponent", uint8Array);
162         return algorithm;
163     }
164 }