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