001package com.nimbusds.jose.crypto;
002
003
004import java.math.BigInteger;
005
006import net.jcip.annotations.ThreadSafe;
007
008import org.bouncycastle.asn1.x9.X9ECParameters;
009import org.bouncycastle.crypto.Digest;
010import org.bouncycastle.crypto.params.ECDomainParameters;
011import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
012
013import com.nimbusds.jose.JOSEException;
014import com.nimbusds.jose.JWSAlgorithm;
015import com.nimbusds.jose.JWSSigner;
016import com.nimbusds.jose.ReadOnlyJWSHeader;
017import com.nimbusds.jose.util.Base64URL;
018import com.nimbusds.jose.util.BigIntegerUtils;
019
020
021/**
022 * Elliptic Curve Digital Signature Algorithm (ECDSA) signer of 
023 * {@link com.nimbusds.jose.JWSObject JWS objects}. This class is thread-safe.
024 *
025 * <p>Supports the following JSON Web Algorithms (JWAs):
026 *
027 * <ul>
028 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#ES256}
029 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#ES384}
030 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#ES512}
031 * </ul>
032 * 
033 * @author Axel Nennker
034 * @author Vladimir Dzhuvinov
035 * @version $version$ (2013-03-21)
036 */
037@ThreadSafe
038public class ECDSASigner extends ECDSAProvider implements JWSSigner {
039
040
041        /**
042         * The private key.
043         */
044        private final BigInteger privateKey;
045
046
047        /**
048         * Creates a new Elliptic Curve Digital Signature Algorithm (ECDSA) 
049         * signer.
050         *
051         * @param privateKey The private key ('d' parameter). Must not be 
052         *                   {@code null}.
053         */
054        public ECDSASigner(final BigInteger privateKey) {
055
056                if (privateKey == null) {
057
058                        throw new IllegalArgumentException("The private key must not be null");
059                }
060
061                this.privateKey = privateKey;
062        }
063
064
065        /**
066         * Gets the private key ('d' parameter).
067         *
068         * @return The private key.
069         */
070        public BigInteger getPrivateKey() {
071
072                return privateKey;
073        }
074
075
076        /**
077         * Performs the actual ECDSA signing.
078         *
079         * @param ecPrivateKeyParameters The EC private key parameters. Must 
080         *                               not be {@code null}.
081         * @param bytes                  The byte array to sign. Must not be 
082         *                               {@code null}.
083         *
084         * @return The ECDSA signature parts R and S.
085         */
086        private static BigInteger[] doECDSA(final ECPrivateKeyParameters ecPrivateKeyParameters, 
087                                            final byte[] bytes) {
088
089                org.bouncycastle.crypto.signers.ECDSASigner signer = 
090                        new org.bouncycastle.crypto.signers.ECDSASigner();
091
092                signer.init(true, ecPrivateKeyParameters);
093                
094                return signer.generateSignature(bytes);
095        }
096
097
098        /**
099         * Converts the specified big integers to byte arrays and returns their
100         * array concatenation.
101         *
102         * @param r                 The R parameter. Must not be {@code null}.
103         * @param s                 The S parameter. Must not be {@code null}.
104         * @param rsByteArrayLength The expected concatenated array length.
105         *
106         * @return The resulting concatenated array.
107         */
108        private static byte[] formatSignature(final BigInteger r, 
109                                              final BigInteger s,
110                                              final int rsByteArrayLength) {
111
112                byte[] rBytes = BigIntegerUtils.toBytesUnsigned(r);
113                byte[] sBytes = BigIntegerUtils.toBytesUnsigned(s);
114
115                final int outLength = rBytes.length + sBytes.length;
116
117                byte[] rsBytes = new byte[rsByteArrayLength];
118
119                int i = 0;
120
121                // Copy R bytes to first array half, zero pad front
122                int offset = (rsByteArrayLength / 2) - rBytes.length;
123
124                i += offset;
125
126                for (byte rB: rBytes) {
127
128                        rsBytes[i++] = rB;
129                }
130
131                // Copy S bytes to second array half, zero pad front
132                i = rsByteArrayLength / 2;
133
134                offset = (rsByteArrayLength / 2) - sBytes.length;
135
136                i += offset;
137
138                for (byte sB: sBytes) {
139
140                        rsBytes[i++] = sB;
141                }
142
143                return rsBytes;
144        }
145
146
147        @Override
148        public Base64URL sign(final ReadOnlyJWSHeader header, final byte[] signableContent)
149                throws JOSEException {
150
151                ECDSAParameters initParams = getECDSAParameters(header.getAlgorithm());
152                X9ECParameters x9ECParameters = initParams.getX9ECParameters();
153                Digest digest = initParams.getDigest();
154
155                ECDomainParameters ecParameterSpec = new ECDomainParameters(
156                        x9ECParameters.getCurve(), 
157                        x9ECParameters.getG(), 
158                        x9ECParameters.getN(), 
159                        x9ECParameters.getH(), 
160                        x9ECParameters.getSeed());
161
162                ECPrivateKeyParameters ecPrivateKeyParameters = 
163                        new ECPrivateKeyParameters(privateKey, ecParameterSpec);
164
165                digest.update(signableContent, 0, signableContent.length);
166                byte[] out = new byte[digest.getDigestSize()];
167                digest.doFinal(out, 0);
168
169                BigInteger[] signatureParts = doECDSA(ecPrivateKeyParameters, out);
170
171                int rsByteArrayLength = ECDSAProvider.getSignatureByteArrayLength(header.getAlgorithm());
172
173                return Base64URL.encode(formatSignature(signatureParts[0], 
174                                                        signatureParts[1], 
175                                                        rsByteArrayLength));
176        }
177}