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;
026
027import net.jcip.annotations.ThreadSafe;
028
029import com.nimbusds.jose.JOSEException;
030import com.nimbusds.jose.JWSAlgorithm;
031import com.nimbusds.jose.JWSHeader;
032import com.nimbusds.jose.JWSSigner;
033import com.nimbusds.jose.jwk.ECKey;
034import com.nimbusds.jose.util.Base64URL;
035
036
037/**
038 * Elliptic Curve Digital Signature Algorithm (ECDSA) signer of 
039 * {@link com.nimbusds.jose.JWSObject JWS objects}. Expects a private EC key
040 * (with a P-256, P-384 or P-521 curve).
041 *
042 * <p>See RFC 7518
043 * <a href="https://tools.ietf.org/html/rfc7518#section-3.4">section 3.4</a>
044 * for more information.
045 *
046 * <p>This class is thread-safe.
047 *
048 * <p>Supports the following algorithms:
049 *
050 * <ul>
051 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#ES256}
052 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#ES384}
053 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#ES512}
054 * </ul>
055 * 
056 * @author Axel Nennker
057 * @author Vladimir Dzhuvinov
058 * @version 2016-11-30
059 */
060@ThreadSafe
061public class ECDSASigner extends ECDSAProvider implements JWSSigner {
062        
063        
064        /**
065         * The private EC key. Represented by generic private key interface to
066         * support key stores that prevent exposure of the private key
067         * parameters via the {@link java.security.interfaces.RSAPrivateKey}
068         * API.
069         *
070         * See https://bitbucket.org/connect2id/nimbus-jose-jwt/issues/169
071         */
072        private final PrivateKey privateKey;
073
074
075        /**
076         * Creates a new Elliptic Curve Digital Signature Algorithm (ECDSA) 
077         * signer.
078         *
079         * @param privateKey The private EC key. Must not be {@code null}.
080         *
081         * @throws JOSEException If the elliptic curve of key is not supported.
082         */
083        public ECDSASigner(final ECPrivateKey privateKey)
084                throws JOSEException {
085
086                super(ECDSA.resolveAlgorithm(privateKey));
087
088                this.privateKey = privateKey;
089        }
090
091
092        /**
093         * Creates a new Elliptic Curve Digital Signature Algorithm (ECDSA)
094         * signer. This constructor is intended for a private EC key located
095         * in a PKCS#11 store that doesn't expose the private key parameters
096         * (such as a smart card or HSM).
097         *
098         * @param privateKey The private EC key. Its algorithm must be "EC".
099         *                   Must not be {@code null}.
100         * @param curve      The elliptic curve for the key. Must not be
101         *                   {@code null}.
102         *
103         * @throws JOSEException If the elliptic curve of key is not supported.
104         */
105        public ECDSASigner(final PrivateKey privateKey, final ECKey.Curve curve)
106                throws JOSEException {
107
108                super(ECDSA.resolveAlgorithm(curve));
109                
110                if (! "EC".equalsIgnoreCase(privateKey.getAlgorithm())) {
111                        throw new IllegalArgumentException("The private key algorithm must be EC");
112                }
113
114                this.privateKey = privateKey;
115        }
116
117
118        /**
119         * Creates a new Elliptic Curve Digital Signature Algorithm (ECDSA)
120         * signer.
121         *
122         * @param ecJWK The EC JSON Web Key (JWK). Must contain a private part.
123         *              Must not be {@code null}.
124         *
125         * @throws JOSEException If the EC JWK doesn't contain a private part,
126         *                       its extraction failed, or the elliptic curve
127         *                       is not supported.
128         */
129        public ECDSASigner(final ECKey ecJWK)
130                throws JOSEException {
131
132                super(ECDSA.resolveAlgorithm(ecJWK.getCurve()));
133
134                if (! ecJWK.isPrivate()) {
135                        throw new JOSEException("The EC JWK doesn't contain a private part");
136                }
137
138                privateKey = ecJWK.toPrivateKey();
139        }
140        
141        
142        /**
143         * Gets the private EC key.
144         *
145         * @return The private EC key. Casting to
146         *         {@link java.security.interfaces.ECPrivateKey} may not be
147         *         possible if the key is located in a PKCS#11 store that
148         *         doesn't expose the private key parameters.
149         */
150        public PrivateKey getPrivateKey() {
151                
152                return privateKey;
153        }
154
155
156        @Override
157        public Base64URL sign(final JWSHeader header, final byte[] signingInput)
158                throws JOSEException {
159
160                final JWSAlgorithm alg = header.getAlgorithm();
161
162                if (! supportedJWSAlgorithms().contains(alg)) {
163                        throw new JOSEException(AlgorithmSupportMessage.unsupportedJWSAlgorithm(alg, supportedJWSAlgorithms()));
164                }
165
166                // DER-encoded signature, according to JCA spec
167                // (sequence of two integers - R + S)
168                final byte[] jcaSignature;
169
170                try {
171                        Signature dsa = ECDSA.getSignerAndVerifier(alg, getJCAContext().getProvider());
172                        dsa.initSign(privateKey, getJCAContext().getSecureRandom());
173                        dsa.update(signingInput);
174                        jcaSignature = dsa.sign();
175
176                } catch (InvalidKeyException | SignatureException e) {
177
178                        throw new JOSEException(e.getMessage(), e);
179                }
180
181                final int rsByteArrayLength = ECDSA.getSignatureByteArrayLength(header.getAlgorithm());
182                final byte[] jwsSignature = ECDSA.transcodeSignatureToConcat(jcaSignature, rsByteArrayLength);
183                return Base64URL.encode(jwsSignature);
184        }
185}