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