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