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.security.NoSuchAlgorithmException;
022import java.security.Provider;
023import java.security.Signature;
024import java.security.interfaces.ECKey;
025import java.security.spec.ECParameterSpec;
026
027import com.nimbusds.jose.JOSEException;
028import com.nimbusds.jose.JWSAlgorithm;
029import com.nimbusds.jose.jwk.Curve;
030
031
032/**
033 * Elliptic Curve Digital Signature Algorithm (ECDSA) functions and utilities.
034 *
035 * @author Vladimir Dzhuvinov
036 * @author Aleksei Doroganov
037 * @version 2022-04-21
038 */
039public class ECDSA {
040
041
042        /**
043         * Resolves the matching EC DSA algorithm for the specified EC key
044         * (public or private).
045         *
046         * @param ecKey The EC key. Must not be {@code null}.
047         *
048         * @return The matching EC DSA algorithm.
049         *
050         * @throws JOSEException If the elliptic curve of key is not supported.
051         */
052        public static JWSAlgorithm resolveAlgorithm(final ECKey ecKey)
053                throws JOSEException {
054
055                ECParameterSpec ecParameterSpec = ecKey.getParams();
056                return resolveAlgorithm(Curve.forECParameterSpec(ecParameterSpec));
057        }
058
059
060        /**
061         * Resolves the matching EC DSA algorithm for the specified elliptic
062         * curve.
063         *
064         * @param curve The elliptic curve. May be {@code null}.
065         *
066         * @return The matching EC DSA algorithm.
067         *
068         * @throws JOSEException If the elliptic curve of key is not supported.
069         */
070        public static JWSAlgorithm resolveAlgorithm(final Curve curve)
071                throws JOSEException {
072
073                if (curve == null) {
074                        throw new JOSEException("The EC key curve is not supported, must be P-256, P-384 or P-521");
075                } else if (Curve.P_256.equals(curve)) {
076                        return JWSAlgorithm.ES256;
077                } else if (Curve.SECP256K1.equals(curve)) {
078                        return JWSAlgorithm.ES256K;
079                } else if (Curve.P_384.equals(curve)) {
080                        return JWSAlgorithm.ES384;
081                } else if (Curve.P_521.equals(curve)) {
082                        return JWSAlgorithm.ES512;
083                } else {
084                        throw new JOSEException("Unexpected curve: " + curve);
085                }
086        }
087
088
089        /**
090         * Creates a new JCA signer / verifier for ECDSA.
091         *
092         * @param alg         The ECDSA JWS algorithm. Must not be
093         *                    {@code null}.
094         * @param jcaProvider The JCA provider, {@code null} if not specified.
095         *
096         * @return The JCA signer / verifier instance.
097         *
098         * @throws JOSEException If a JCA signer / verifier couldn't be
099         *                       created.
100         */
101        public static Signature getSignerAndVerifier(final JWSAlgorithm alg,
102                                                     final Provider jcaProvider)
103                throws JOSEException {
104
105                String jcaAlg;
106
107                if (alg.equals(JWSAlgorithm.ES256)) {
108                        jcaAlg = "SHA256withECDSA";
109                } else if (alg.equals(JWSAlgorithm.ES256K)) {
110                        jcaAlg = "SHA256withECDSA";
111                } else if (alg.equals(JWSAlgorithm.ES384)) {
112                        jcaAlg = "SHA384withECDSA";
113                } else if (alg.equals(JWSAlgorithm.ES512)) {
114                        jcaAlg = "SHA512withECDSA";
115                } else {
116                        throw new JOSEException(
117                                AlgorithmSupportMessage.unsupportedJWSAlgorithm(
118                                        alg,
119                                        ECDSAProvider.SUPPORTED_ALGORITHMS));
120                }
121
122                try {
123                        if (jcaProvider != null) {
124                                return Signature.getInstance(jcaAlg, jcaProvider);
125                        } else {
126                                return Signature.getInstance(jcaAlg);
127                        }
128                } catch (NoSuchAlgorithmException e) {
129                        throw new JOSEException("Unsupported ECDSA algorithm: " + e.getMessage(), e);
130                }
131        }
132
133
134        /**
135         * Returns the expected signature byte array length (R + S parts) for
136         * the specified ECDSA algorithm.
137         *
138         * @param alg The ECDSA algorithm. Must be supported and not
139         *            {@code null}.
140         *
141         * @return The expected byte array length for the signature.
142         *
143         * @throws JOSEException If the algorithm is not supported.
144         */
145        public static int getSignatureByteArrayLength(final JWSAlgorithm alg)
146                throws JOSEException {
147
148                if (alg.equals(JWSAlgorithm.ES256)) {
149
150                        return 64;
151
152                } else if (alg.equals(JWSAlgorithm.ES256K)) {
153
154                        return 64;
155
156                } else if (alg.equals(JWSAlgorithm.ES384)) {
157
158                        return 96;
159
160                } else if (alg.equals(JWSAlgorithm.ES512)) {
161
162                        return 132;
163
164                } else {
165
166                        throw new JOSEException(AlgorithmSupportMessage.unsupportedJWSAlgorithm(
167                                alg,
168                                ECDSAProvider.SUPPORTED_ALGORITHMS));
169                }
170        }
171
172
173        /**
174         * Transcodes the JCA ASN.1/DER-encoded signature into the concatenated
175         * R + S format expected by ECDSA JWS.
176         *
177         * @param derSignature The ASN1./DER-encoded. Must not be {@code null}.
178         * @param outputLength The expected length of the ECDSA JWS signature.
179         *
180         * @return The ECDSA JWS encoded signature.
181         *
182         * @throws JOSEException If the ASN.1/DER signature format is invalid.
183         */
184        public static byte[] transcodeSignatureToConcat(final byte[] derSignature, final int outputLength)
185                throws JOSEException {
186
187                if (derSignature.length < 8 || derSignature[0] != 48) {
188                        throw new JOSEException("Invalid ECDSA signature format");
189                }
190
191                int offset;
192                if (derSignature[1] > 0) {
193                        offset = 2;
194                } else if (derSignature[1] == (byte) 0x81) {
195                        offset = 3;
196                } else {
197                        throw new JOSEException("Invalid ECDSA signature format");
198                }
199
200                byte rLength = derSignature[offset + 1];
201
202                int i;
203                for (i = rLength; (i > 0) && (derSignature[(offset + 2 + rLength) - i] == 0); i--) {
204                        // do nothing
205                }
206
207                byte sLength = derSignature[offset + 2 + rLength + 1];
208
209                int j;
210                for (j = sLength; (j > 0) && (derSignature[(offset + 2 + rLength + 2 + sLength) - j] == 0); j--) {
211                        // do nothing
212                }
213
214                int rawLen = Math.max(i, j);
215                rawLen = Math.max(rawLen, outputLength / 2);
216
217                if ((derSignature[offset - 1] & 0xff) != derSignature.length - offset
218                        || (derSignature[offset - 1] & 0xff) != 2 + rLength + 2 + sLength
219                        || derSignature[offset] != 2
220                        || derSignature[offset + 2 + rLength] != 2) {
221                        throw new JOSEException("Invalid ECDSA signature format");
222                }
223
224                final byte[] concatSignature = new byte[2 * rawLen];
225
226                System.arraycopy(derSignature, (offset + 2 + rLength) - i, concatSignature, rawLen - i, i);
227                System.arraycopy(derSignature, (offset + 2 + rLength + 2 + sLength) - j, concatSignature, 2 * rawLen - j, j);
228
229                return concatSignature;
230        }
231
232
233
234        /**
235         * Transcodes the ECDSA JWS signature into ASN.1/DER format for use by
236         * the JCA verifier.
237         *
238         * @param jwsSignature The JWS signature, consisting of the
239         *                     concatenated R and S values. Must not be
240         *                     {@code null}.
241         *
242         * @return The ASN.1/DER encoded signature.
243         *
244         * @throws JOSEException If the ECDSA JWS signature format is invalid
245         *                       or conversion failed unexpectedly.
246         */
247        public static byte[] transcodeSignatureToDER(final byte[] jwsSignature)
248                throws JOSEException {
249
250                // Adapted from org.apache.xml.security.algorithms.implementations.SignatureECDSA
251                try {
252                        int rawLen = jwsSignature.length / 2;
253                        
254                        int i;
255                        
256                        for (i = rawLen; (i > 0) && (jwsSignature[rawLen - i] == 0); i--) {
257                                // do nothing
258                        }
259                        
260                        int j = i;
261                        
262                        if (jwsSignature[rawLen - i] < 0) {
263                                j += 1;
264                        }
265                        
266                        int k;
267                        
268                        for (k = rawLen; (k > 0) && (jwsSignature[2 * rawLen - k] == 0); k--) {
269                                // do nothing
270                        }
271                        
272                        int l = k;
273                        
274                        if (jwsSignature[2 * rawLen - k] < 0) {
275                                l += 1;
276                        }
277                        
278                        int len = 2 + j + 2 + l;
279                        
280                        if (len > 255) {
281                                throw new JOSEException("Invalid ECDSA signature format");
282                        }
283                        
284                        int offset;
285                        
286                        final byte[] derSignature;
287                        
288                        if (len < 128) {
289                                derSignature = new byte[2 + 2 + j + 2 + l];
290                                offset = 1;
291                        } else {
292                                derSignature = new byte[3 + 2 + j + 2 + l];
293                                derSignature[1] = (byte) 0x81;
294                                offset = 2;
295                        }
296                        
297                        derSignature[0] = 48;
298                        derSignature[offset++] = (byte) len;
299                        derSignature[offset++] = 2;
300                        derSignature[offset++] = (byte) j;
301                        
302                        System.arraycopy(jwsSignature, rawLen - i, derSignature, (offset + j) - i, i);
303                        
304                        offset += j;
305                        
306                        derSignature[offset++] = 2;
307                        derSignature[offset++] = (byte) l;
308                        
309                        System.arraycopy(jwsSignature, 2 * rawLen - k, derSignature, (offset + l) - k, k);
310                        
311                        return derSignature;
312                        
313                } catch (Exception e) {
314                        // Watch for unchecked exceptions
315                        
316                        if (e instanceof JOSEException) {
317                                throw e;
318                        }
319                        
320                        throw new JOSEException(e.getMessage(), e);
321                }
322        }
323        
324        
325        /**
326         * Returns {@code true} if the JWS signature R and S values are all
327         * zeroes.
328         *
329         * @param jwsSignature The JWS signature bytes. Must not be
330         *                     {@code null}.
331         *
332         * @return {@code true} if all bytes are zeroes, else {@code false}.
333         */
334        public static boolean concatSignatureAllZeroes(final byte[] jwsSignature) {
335                
336                for (final byte b : jwsSignature) {
337                        if (b != 0) {
338                                return false;
339                        }
340                }
341                return true;
342        }
343
344
345        /**
346         * Prevents public instantiation.
347         */
348        private ECDSA() {}
349}