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