001/*
002 * nimbus-jose-jwt
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.jose.crypto.impl;
019
020
021import java.math.BigInteger;
022import java.security.NoSuchAlgorithmException;
023import java.security.Provider;
024import java.security.Signature;
025import java.security.interfaces.ECKey;
026import java.security.spec.ECParameterSpec;
027import java.util.Set;
028
029import com.nimbusds.jose.JOSEException;
030import com.nimbusds.jose.JWSAlgorithm;
031import com.nimbusds.jose.jwk.Curve;
032import com.nimbusds.jose.jwk.ECParameterTable;
033import com.nimbusds.jose.util.ByteUtils;
034
035
036/**
037 * Elliptic Curve Digital Signature Algorithm (ECDSA) functions and utilities.
038 *
039 * @author Vladimir Dzhuvinov
040 * @author Aleksei Doroganov
041 * @version 2022-04-22
042 */
043public class ECDSA {
044
045
046        /**
047         * Resolves the matching EC DSA algorithm for the specified EC key
048         * (public or private).
049         *
050         * @param ecKey The EC key. Must not be {@code null}.
051         *
052         * @return The matching EC DSA algorithm.
053         *
054         * @throws JOSEException If the elliptic curve of key is not supported.
055         */
056        public static JWSAlgorithm resolveAlgorithm(final ECKey ecKey)
057                throws JOSEException {
058
059                ECParameterSpec ecParameterSpec = ecKey.getParams();
060                return resolveAlgorithm(Curve.forECParameterSpec(ecParameterSpec));
061        }
062
063
064        /**
065         * Resolves the matching EC DSA algorithm for the specified elliptic
066         * curve.
067         *
068         * @param curve The elliptic curve. May be {@code null}.
069         *
070         * @return The matching EC DSA algorithm.
071         *
072         * @throws JOSEException If the elliptic curve of key is not supported.
073         */
074        public static JWSAlgorithm resolveAlgorithm(final Curve curve)
075                throws JOSEException {
076
077                if (curve == null) {
078                        throw new JOSEException("The EC key curve is not supported, must be P-256, P-384 or P-521");
079                } else if (Curve.P_256.equals(curve)) {
080                        return JWSAlgorithm.ES256;
081                } else if (Curve.SECP256K1.equals(curve)) {
082                        return JWSAlgorithm.ES256K;
083                } else if (Curve.P_384.equals(curve)) {
084                        return JWSAlgorithm.ES384;
085                } else if (Curve.P_521.equals(curve)) {
086                        return JWSAlgorithm.ES512;
087                } else {
088                        throw new JOSEException("Unexpected curve: " + curve);
089                }
090        }
091
092
093        /**
094         * Creates a new JCA signer / verifier for ECDSA.
095         *
096         * @param alg         The ECDSA JWS algorithm. Must not be
097         *                    {@code null}.
098         * @param jcaProvider The JCA provider, {@code null} if not specified.
099         *
100         * @return The JCA signer / verifier instance.
101         *
102         * @throws JOSEException If a JCA signer / verifier couldn't be
103         *                       created.
104         */
105        public static Signature getSignerAndVerifier(final JWSAlgorithm alg,
106                                                     final Provider jcaProvider)
107                throws JOSEException {
108
109                String jcaAlg;
110
111                if (alg.equals(JWSAlgorithm.ES256)) {
112                        jcaAlg = "SHA256withECDSA";
113                } else if (alg.equals(JWSAlgorithm.ES256K)) {
114                        jcaAlg = "SHA256withECDSA";
115                } else if (alg.equals(JWSAlgorithm.ES384)) {
116                        jcaAlg = "SHA384withECDSA";
117                } else if (alg.equals(JWSAlgorithm.ES512)) {
118                        jcaAlg = "SHA512withECDSA";
119                } else {
120                        throw new JOSEException(
121                                AlgorithmSupportMessage.unsupportedJWSAlgorithm(
122                                        alg,
123                                        ECDSAProvider.SUPPORTED_ALGORITHMS));
124                }
125
126                try {
127                        if (jcaProvider != null) {
128                                return Signature.getInstance(jcaAlg, jcaProvider);
129                        } else {
130                                return Signature.getInstance(jcaAlg);
131                        }
132                } catch (NoSuchAlgorithmException e) {
133                        throw new JOSEException("Unsupported ECDSA algorithm: " + e.getMessage(), e);
134                }
135        }
136
137
138        /**
139         * Returns the expected signature byte array length (R + S parts) for
140         * the specified ECDSA algorithm.
141         *
142         * @param alg The ECDSA algorithm. Must be supported and not
143         *            {@code null}.
144         *
145         * @return The expected byte array length for the signature.
146         *
147         * @throws JOSEException If the algorithm is not supported.
148         */
149        public static int getSignatureByteArrayLength(final JWSAlgorithm alg)
150                throws JOSEException {
151
152                if (alg.equals(JWSAlgorithm.ES256)) {
153
154                        return 64;
155
156                } else if (alg.equals(JWSAlgorithm.ES256K)) {
157
158                        return 64;
159
160                } else if (alg.equals(JWSAlgorithm.ES384)) {
161
162                        return 96;
163
164                } else if (alg.equals(JWSAlgorithm.ES512)) {
165
166                        return 132;
167
168                } else {
169
170                        throw new JOSEException(AlgorithmSupportMessage.unsupportedJWSAlgorithm(
171                                alg,
172                                ECDSAProvider.SUPPORTED_ALGORITHMS));
173                }
174        }
175
176
177        /**
178         * Transcodes the JCA ASN.1/DER-encoded signature into the concatenated
179         * R + S format expected by ECDSA JWS.
180         *
181         * @param derSignature The ASN1./DER-encoded. Must not be {@code null}.
182         * @param outputLength The expected length of the ECDSA JWS signature.
183         *
184         * @return The ECDSA JWS encoded signature.
185         *
186         * @throws JOSEException If the ASN.1/DER signature format is invalid.
187         */
188        public static byte[] transcodeSignatureToConcat(final byte[] derSignature, final int outputLength)
189                throws JOSEException {
190
191                if (derSignature.length < 8 || derSignature[0] != 48) {
192                        throw new JOSEException("Invalid ECDSA signature format");
193                }
194
195                int offset;
196                if (derSignature[1] > 0) {
197                        offset = 2;
198                } else if (derSignature[1] == (byte) 0x81) {
199                        offset = 3;
200                } else {
201                        throw new JOSEException("Invalid ECDSA signature format");
202                }
203
204                byte rLength = derSignature[offset + 1];
205
206                int i;
207                for (i = rLength; (i > 0) && (derSignature[(offset + 2 + rLength) - i] == 0); i--) {
208                        // do nothing
209                }
210
211                byte sLength = derSignature[offset + 2 + rLength + 1];
212
213                int j;
214                for (j = sLength; (j > 0) && (derSignature[(offset + 2 + rLength + 2 + sLength) - j] == 0); j--) {
215                        // do nothing
216                }
217
218                int rawLen = Math.max(i, j);
219                rawLen = Math.max(rawLen, outputLength / 2);
220
221                if ((derSignature[offset - 1] & 0xff) != derSignature.length - offset
222                        || (derSignature[offset - 1] & 0xff) != 2 + rLength + 2 + sLength
223                        || derSignature[offset] != 2
224                        || derSignature[offset + 2 + rLength] != 2) {
225                        throw new JOSEException("Invalid ECDSA signature format");
226                }
227
228                final byte[] concatSignature = new byte[2 * rawLen];
229
230                System.arraycopy(derSignature, (offset + 2 + rLength) - i, concatSignature, rawLen - i, i);
231                System.arraycopy(derSignature, (offset + 2 + rLength + 2 + sLength) - j, concatSignature, 2 * rawLen - j, j);
232
233                return concatSignature;
234        }
235
236
237
238        /**
239         * Transcodes the ECDSA JWS signature into ASN.1/DER format for use by
240         * the JCA verifier.
241         *
242         * @param jwsSignature The JWS signature, consisting of the
243         *                     concatenated R and S values. Must not be
244         *                     {@code null}.
245         *
246         * @return The ASN.1/DER encoded signature.
247         *
248         * @throws JOSEException If the ECDSA JWS signature format is invalid
249         *                       or conversion failed unexpectedly.
250         */
251        public static byte[] transcodeSignatureToDER(final byte[] jwsSignature)
252                throws JOSEException {
253
254                // Adapted from org.apache.xml.security.algorithms.implementations.SignatureECDSA
255                try {
256                        int rawLen = jwsSignature.length / 2;
257                        
258                        int i;
259                        
260                        for (i = rawLen; (i > 0) && (jwsSignature[rawLen - i] == 0); i--) {
261                                // do nothing
262                        }
263                        
264                        int j = i;
265                        
266                        if (jwsSignature[rawLen - i] < 0) {
267                                j += 1;
268                        }
269                        
270                        int k;
271                        
272                        for (k = rawLen; (k > 0) && (jwsSignature[2 * rawLen - k] == 0); k--) {
273                                // do nothing
274                        }
275                        
276                        int l = k;
277                        
278                        if (jwsSignature[2 * rawLen - k] < 0) {
279                                l += 1;
280                        }
281                        
282                        int len = 2 + j + 2 + l;
283                        
284                        if (len > 255) {
285                                throw new JOSEException("Invalid ECDSA signature format");
286                        }
287                        
288                        int offset;
289                        
290                        final byte[] derSignature;
291                        
292                        if (len < 128) {
293                                derSignature = new byte[2 + 2 + j + 2 + l];
294                                offset = 1;
295                        } else {
296                                derSignature = new byte[3 + 2 + j + 2 + l];
297                                derSignature[1] = (byte) 0x81;
298                                offset = 2;
299                        }
300                        
301                        derSignature[0] = 48;
302                        derSignature[offset++] = (byte) len;
303                        derSignature[offset++] = 2;
304                        derSignature[offset++] = (byte) j;
305                        
306                        System.arraycopy(jwsSignature, rawLen - i, derSignature, (offset + j) - i, i);
307                        
308                        offset += j;
309                        
310                        derSignature[offset++] = 2;
311                        derSignature[offset++] = (byte) l;
312                        
313                        System.arraycopy(jwsSignature, 2 * rawLen - k, derSignature, (offset + l) - k, k);
314                        
315                        return derSignature;
316                        
317                } catch (Exception e) {
318                        // Watch for unchecked exceptions
319                        
320                        if (e instanceof JOSEException) {
321                                throw e;
322                        }
323                        
324                        throw new JOSEException(e.getMessage(), e);
325                }
326        }
327        
328        
329        /**
330         * Ensures the specified ECDSA signature is legal. Intended to prevent
331         * attacks on JCA implementations vulnerable to CVE-2022-21449 and
332         * similar bugs.
333         *
334         * @param jwsSignature The JWS signature. Must not be {@code null}.
335         * @param jwsAlg       The ECDSA JWS algorithm. Must not be
336         *                     {@code null}.
337         *
338         * @throws JOSEException If the signature is found to be illegal, or
339         *                       the JWS algorithm or curve are not supported.
340         */
341        public static void ensureLegalSignature(final byte[] jwsSignature,
342                                                final JWSAlgorithm jwsAlg)
343                throws JOSEException {
344                
345                if (ByteUtils.isZeroFilled(jwsSignature)) {
346                        // Quick check to make sure S and R are not both zero (CVE-2022-21449)
347                        throw new JOSEException("Blank signature");
348                }
349                
350                Set<Curve> matchingCurves = Curve.forJWSAlgorithm(jwsAlg);
351                if (matchingCurves == null || matchingCurves.size() > 1) {
352                        throw new JOSEException("Unsupported JWS algorithm: " + jwsAlg);
353                }
354                
355                Curve curve = matchingCurves.iterator().next();
356                
357                ECParameterSpec ecParameterSpec = ECParameterTable.get(curve);
358                
359                if (ecParameterSpec == null) {
360                        throw new JOSEException("Unsupported curve: " + curve);
361                }
362                
363                final int signatureLength = ECDSA.getSignatureByteArrayLength(jwsAlg);
364                
365                if (ECDSA.getSignatureByteArrayLength(jwsAlg) != jwsSignature.length) {
366                        // Quick format check, concatenation of R|S (may be padded
367                        // to match lengths) in ESxxx signatures has fixed length
368                        throw new JOSEException("Illegal signature length");
369                }
370                
371                // Split the signature bytes in the middle
372                final int valueLength = signatureLength / 2;
373                
374                // Extract R
375                final byte[] rBytes = ByteUtils.subArray(jwsSignature, 0, valueLength);
376                final BigInteger rValue = new BigInteger(1, rBytes);
377                
378                // Extract S
379                final byte[] sBytes = ByteUtils.subArray(jwsSignature, valueLength, valueLength);
380                final BigInteger sValue = new BigInteger(1, sBytes);
381                
382                // Trivial zero check
383                if (sValue.equals(BigInteger.ZERO) || rValue.equals(BigInteger.ZERO)) {
384                        throw new JOSEException("S and R must not be 0");
385                }
386                
387                final BigInteger N = ecParameterSpec.getOrder();
388                
389                // R and S must not be greater than the curve order N
390                if (N.compareTo(rValue) < 1 || N.compareTo(sValue) < 1) {
391                        throw new JOSEException("S and R must not exceed N");
392                }
393                
394                // Extra paranoid check
395                if (rValue.mod(N).equals(BigInteger.ZERO) || sValue.mod(N).equals(BigInteger.ZERO)) {
396                        throw new JOSEException("R or S mod N != 0 check failed");
397                }
398                
399                // Signature deemed legal, can proceed to DER transcoding and verification now
400        }
401
402
403        /**
404         * Prevents public instantiation.
405         */
406        private ECDSA() {}
407}