001/* 002 * nimbus-jose-jwt 003 * 004 * Copyright 2012-2025, Connect2id Ltd. 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; 019 020 021import com.nimbusds.jose.*; 022import com.nimbusds.jose.crypto.impl.AlgorithmSupportMessage; 023import com.nimbusds.jose.crypto.impl.ECDSA; 024import com.nimbusds.jose.crypto.impl.ECDSAProvider; 025import com.nimbusds.jose.crypto.opts.UserAuthenticationRequired; 026import com.nimbusds.jose.jwk.Curve; 027import com.nimbusds.jose.jwk.ECKey; 028import com.nimbusds.jose.util.Base64URL; 029import net.jcip.annotations.ThreadSafe; 030 031import java.security.InvalidKeyException; 032import java.security.PrivateKey; 033import java.security.Signature; 034import java.security.SignatureException; 035import java.security.interfaces.ECPrivateKey; 036import java.util.Collections; 037import java.util.Set; 038 039 040/** 041 * Elliptic Curve Digital Signature Algorithm (ECDSA) signer of 042 * {@link com.nimbusds.jose.JWSObject JWS objects}. Expects a private EC key 043 * (with a P-256, P-384, P-521 or secp256k1 curve). 044 * 045 * <p>See RFC 7518 046 * <a href="https://tools.ietf.org/html/rfc7518#section-3.4">section 3.4</a> 047 * for more information. 048 * 049 * <p>This class is thread-safe. 050 * 051 * <p>Supports the following algorithms: 052 * 053 * <ul> 054 * <li>{@link com.nimbusds.jose.JWSAlgorithm#ES256} 055 * <li>{@link com.nimbusds.jose.JWSAlgorithm#ES256K} 056 * <li>{@link com.nimbusds.jose.JWSAlgorithm#ES384} 057 * <li>{@link com.nimbusds.jose.JWSAlgorithm#ES512} 058 * </ul> 059 * 060 * <p>Supports the following {@link JWSSignerOption options}: 061 * 062 * <ul> 063 * <li>{@link UserAuthenticationRequired} -- to prompt the user to 064 * authenticate in order to complete the signing operation. Android 065 * applications can use this option to trigger a biometric prompt that 066 * is required to unlock a private key created with 067 * {@code setUserAuthenticationRequired(true)}. 068 * </ul> 069 * 070 * @author Axel Nennker 071 * @author Vladimir Dzhuvinov 072 * @version 2025-07-19 073 */ 074@ThreadSafe 075public class ECDSASigner extends ECDSAProvider implements JWSSigner { 076 077 078 /** 079 * The private EC key. Represented by generic private key interface to 080 * support key stores that prevent exposure of the private key 081 * parameters via the {@link java.security.interfaces.ECPrivateKey} 082 * API. 083 * 084 * See https://bitbucket.org/connect2id/nimbus-jose-jwt/issues/169 085 */ 086 private final PrivateKey privateKey; 087 088 089 /** 090 * The configured options, empty set if none. 091 */ 092 private final Set<JWSSignerOption> opts; 093 094 095 /** 096 * Creates a new Elliptic Curve Digital Signature Algorithm (ECDSA) 097 * signer. 098 * 099 * @param privateKey The private EC key. Must not be {@code null}. 100 * 101 * @throws JOSEException If the elliptic curve of key is not supported. 102 */ 103 public ECDSASigner(final ECPrivateKey privateKey) 104 throws JOSEException { 105 106 this(privateKey, Collections.<JWSSignerOption>emptySet()); 107 } 108 109 110 /** 111 * Creates a new Elliptic Curve Digital Signature Algorithm (ECDSA) 112 * signer. 113 * 114 * @param privateKey The private EC key. Must not be {@code null}. 115 * @param opts The signing options, empty or {@code null} if 116 * none. 117 * 118 * @throws JOSEException If the elliptic curve of key is not supported. 119 */ 120 public ECDSASigner(final ECPrivateKey privateKey, final Set<JWSSignerOption> opts) 121 throws JOSEException { 122 123 super(ECDSA.resolveAlgorithm(privateKey)); 124 125 this.privateKey = privateKey; 126 this.opts = opts != null ? opts : Collections.<JWSSignerOption>emptySet(); 127 } 128 129 130 /** 131 * Creates a new Elliptic Curve Digital Signature Algorithm (ECDSA) 132 * signer. This constructor is intended for a private EC key located 133 * in a PKCS#11 store that doesn't expose the private key parameters 134 * (such as a smart card or HSM). 135 * 136 * @param privateKey The private EC key. Its algorithm must be "EC". 137 * Must not be {@code null}. 138 * @param curve The elliptic curve for the key. Must not be 139 * {@code null}. 140 * 141 * @throws JOSEException If the elliptic curve of key is not supported. 142 */ 143 public ECDSASigner(final PrivateKey privateKey, final Curve curve) 144 throws JOSEException { 145 this(privateKey, curve, Collections.<JWSSignerOption>emptySet()); 146 } 147 148 149 /** 150 * Creates a new Elliptic Curve Digital Signature Algorithm (ECDSA) 151 * signer. This constructor is intended for a private EC key located 152 * in a PKCS#11 store that doesn't expose the private key parameters 153 * (such as a smart card or HSM). 154 * 155 * @param privateKey The private EC key. Its algorithm must be "EC". 156 * Must not be {@code null}. 157 * @param curve The elliptic curve for the key. Must not be 158 * {@code null}. 159 * @param opts The signing options, empty or {@code null} if 160 * none. 161 * 162 * @throws JOSEException If the elliptic curve of key is not supported. 163 */ 164 public ECDSASigner(final PrivateKey privateKey, final Curve curve, final Set<JWSSignerOption> opts) 165 throws JOSEException { 166 167 super(ECDSA.resolveAlgorithm(curve)); 168 169 if (! "EC".equalsIgnoreCase(privateKey.getAlgorithm())) { 170 throw new IllegalArgumentException("The private key algorithm must be EC"); 171 } 172 173 this.privateKey = privateKey; 174 this.opts = opts != null ? opts : Collections.<JWSSignerOption>emptySet(); 175 } 176 177 178 /** 179 * Creates a new Elliptic Curve Digital Signature Algorithm (ECDSA) 180 * signer. 181 * 182 * @param ecJWK The EC JSON Web Key (JWK). Must contain a private part. 183 * Must not be {@code null}. 184 * 185 * @throws JOSEException If the EC JWK doesn't contain a private part, 186 * its extraction failed, or the elliptic curve 187 * is not supported. 188 */ 189 public ECDSASigner(final ECKey ecJWK) 190 throws JOSEException { 191 this(ecJWK, null); 192 } 193 194 195 /** 196 * Creates a new Elliptic Curve Digital Signature Algorithm (ECDSA) 197 * signer. 198 * 199 * @param ecJWK The EC JSON Web Key (JWK). Must contain a private part. 200 * Must not be {@code null}. 201 * @param opts The signing options, empty or {@code null} if 202 * none. 203 * 204 * @throws JOSEException If the EC JWK doesn't contain a private part, 205 * its extraction failed, or the elliptic curve 206 * is not supported. 207 */ 208 public ECDSASigner(final ECKey ecJWK, final Set<JWSSignerOption> opts) 209 throws JOSEException { 210 211 super(ECDSA.resolveAlgorithm(ecJWK.getCurve())); 212 213 if (! ecJWK.isPrivate()) { 214 throw new JOSEException("The EC JWK doesn't contain a private part"); 215 } 216 217 privateKey = ecJWK.toPrivateKey(); 218 this.opts = opts != null ? opts : Collections.<JWSSignerOption>emptySet(); 219 } 220 221 222 /** 223 * Gets the private EC key. 224 * 225 * @return The private EC key. Casting to 226 * {@link java.security.interfaces.ECPrivateKey} may not be 227 * possible if the key is located in a PKCS#11 store that 228 * doesn't expose the private key parameters. 229 */ 230 public PrivateKey getPrivateKey() { 231 232 return privateKey; 233 } 234 235 236 @Override 237 public Base64URL sign(final JWSHeader header, final byte[] signingInput) 238 throws JOSEException { 239 240 final JWSAlgorithm alg = header.getAlgorithm(); 241 242 if (! supportedJWSAlgorithms().contains(alg)) { 243 throw new JOSEException(AlgorithmSupportMessage.unsupportedJWSAlgorithm(alg, supportedJWSAlgorithms())); 244 } 245 246 // DER-encoded signature, according to JCA spec 247 final byte[] jcaSignature; 248 try { 249 final Signature dsa = ECDSA.getSignerAndVerifier(alg, getJCAContext().getProvider()); 250 dsa.initSign(privateKey, getJCAContext().getSecureRandom()); 251 252 if (opts.contains(UserAuthenticationRequired.getInstance())) { 253 254 throw new ActionRequiredForJWSCompletionException( 255 "Authenticate user to complete signing", 256 UserAuthenticationRequired.getInstance(), 257 new CompletableJWSObjectSigning() { 258 @Override 259 public Signature getInitializedSignature() { 260 return dsa; 261 } 262 263 @Override 264 public Base64URL complete() throws JOSEException { 265 266 try { 267 dsa.update(signingInput); 268 final byte[] jcaSignature = dsa.sign(); 269 final int rsByteArrayLength = ECDSA.getSignatureByteArrayLength(header.getAlgorithm()); 270 final byte[] jwsSignature = ECDSA.transcodeSignatureToConcat(jcaSignature, rsByteArrayLength); 271 return Base64URL.encode(jwsSignature); 272 } catch (SignatureException e) { 273 throw new JOSEException(e.getMessage(), e); 274 } 275 } 276 } 277 ); 278 } 279 dsa.update(signingInput); 280 jcaSignature = dsa.sign(); 281 282 } catch (InvalidKeyException | SignatureException e) { 283 284 throw new JOSEException(e.getMessage(), e); 285 } 286 287 final int rsByteArrayLength = ECDSA.getSignatureByteArrayLength(header.getAlgorithm()); 288 final byte[] jwsSignature = ECDSA.transcodeSignatureToConcat(jcaSignature, rsByteArrayLength); 289 return Base64URL.encode(jwsSignature); 290 } 291}