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}