001package com.nimbusds.jose.crypto;
002
003
004import java.math.BigInteger;
005import java.util.HashSet;
006import java.util.Set;
007
008import net.jcip.annotations.ThreadSafe;
009
010import org.bouncycastle.asn1.x9.X9ECParameters;
011import org.bouncycastle.crypto.Digest;
012import org.bouncycastle.crypto.params.ECDomainParameters;
013import org.bouncycastle.crypto.params.ECPublicKeyParameters;
014import org.bouncycastle.math.ec.ECCurve;
015import org.bouncycastle.math.ec.ECPoint;
016
017import com.nimbusds.jose.JOSEException;
018import com.nimbusds.jose.JWSAlgorithm;
019import com.nimbusds.jose.JWSVerifier;
020import com.nimbusds.jose.JWSHeader;
021import com.nimbusds.jose.util.Base64URL;
022
023
024/**
025 * Elliptic Curve Digital Signature Algorithm (ECDSA) verifier of 
026 * {@link com.nimbusds.jose.JWSObject JWS objects}.
027 *
028 * <p>Supports the following JSON Web Algorithms (JWAs):
029 *
030 * <ul>
031 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#ES256}
032 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#ES384}
033 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#ES512}
034 * </ul>
035 *
036 * <p>Accepts all {@link com.nimbusds.jose.JWSHeader#getRegisteredParameterNames
037 * registered JWS header parameters}. Use {@link #setAcceptedAlgorithms} to
038 * restrict the acceptable JWS algorithms.
039 * 
040 * @author Axel Nennker
041 * @author Vladimir Dzhuvinov
042 * @version $version$ (2014-08-20)
043 */
044@ThreadSafe
045public class ECDSAVerifier extends ECDSAProvider implements JWSVerifier {
046
047
048        /**
049         * The accepted JWS algorithms.
050         */
051        private Set<JWSAlgorithm> acceptedAlgs =
052                new HashSet<>(supportedAlgorithms());
053
054
055        /**
056         * The critical header parameter checker.
057         */
058        private final CriticalHeaderParameterChecker critParamChecker =
059                new CriticalHeaderParameterChecker();
060
061
062        /**
063         * The 'x' EC coordinate.
064         */
065        private final BigInteger x;
066
067
068        /**
069         * The 'y' EC coordinate.
070         */
071        private final BigInteger y;
072
073
074
075        /**
076         * Creates a new Elliptic Curve Digital Signature Algorithm (ECDSA) 
077         * verifier.
078         *
079         * @param x The 'x' coordinate for the elliptic curve point. Must not 
080         *          be {@code null}.
081         * @param y The 'y' coordinate for the elliptic curve point. Must not 
082         *          be {@code null}.
083         */
084        public ECDSAVerifier(final BigInteger x, final BigInteger y) {
085
086                if (x == null) {
087
088                        throw new IllegalArgumentException("The \"x\" EC coordinate must not be null");
089                }
090
091                this.x = x;
092
093                if (y == null) {
094
095                        throw new IllegalArgumentException("The \"y\" EC coordinate must not be null");
096                }
097
098                this.y = y;
099        }
100
101
102        /**
103         * Gets the 'x' coordinate for the elliptic curve point.
104         *
105         * @return The 'x' coordinate.
106         */
107        public BigInteger getX() {
108
109                return x;
110        }
111
112
113        /**
114         * Gets the 'y' coordinate for the elliptic curve point.
115         *
116         * @return The 'y' coordinate.
117         */
118        public BigInteger getY() {
119
120                return y;
121        }
122
123
124        @Override
125        public Set<JWSAlgorithm> getAcceptedAlgorithms() {
126
127                return acceptedAlgs;
128        }
129
130
131        @Override
132        public void setAcceptedAlgorithms(final Set<JWSAlgorithm> acceptedAlgs) {
133
134                if (acceptedAlgs == null) {
135                        throw new IllegalArgumentException("The accepted JWS algorithms must not be null");
136                }
137
138                if (! supportedAlgorithms().containsAll(acceptedAlgs)) {
139                        throw new IllegalArgumentException("Unsupported JWS algorithm(s)");
140                }
141
142                this.acceptedAlgs = acceptedAlgs;
143        }
144
145
146        @Override
147        public Set<String> getIgnoredCriticalHeaderParameters() {
148
149                return critParamChecker.getIgnoredCriticalHeaders();
150        }
151
152
153        @Override
154        public void setIgnoredCriticalHeaderParameters(final Set<String> headers) {
155
156                critParamChecker.setIgnoredCriticalHeaders(headers);
157        }
158
159
160        @Override
161        public boolean verify(final JWSHeader header,
162                              final byte[] signedContent, 
163                              final Base64URL signature)
164                throws JOSEException {
165
166                if (! critParamChecker.headerPasses(header)) {
167                        return false;
168                }
169
170                ECDSAParameters initParams = getECDSAParameters(header.getAlgorithm());
171                X9ECParameters x9ECParameters = initParams.getX9ECParameters();
172                Digest digest = initParams.getDigest();
173
174                byte[] signatureBytes = signature.decode();
175
176                // Split signature into R and S parts
177                int rsByteArrayLength = ECDSAProvider.getSignatureByteArrayLength(header.getAlgorithm());
178
179                byte[] rBytes = new byte[rsByteArrayLength / 2];
180                byte[] sBytes = new byte[rsByteArrayLength / 2];
181
182                try {
183                        System.arraycopy(signatureBytes, 0, rBytes, 0, rBytes.length);
184                        System.arraycopy(signatureBytes, rBytes.length, sBytes, 0, sBytes.length);
185
186                } catch (Exception e) {
187
188                        throw new JOSEException("Invalid ECDSA signature format: " + e.getMessage(), e);
189                }
190
191                BigInteger r = new BigInteger(1, rBytes);
192                BigInteger s = new BigInteger(1, sBytes);
193
194
195                ECCurve curve = x9ECParameters.getCurve();
196                ECPoint q = curve.createPoint(x, y);
197
198                ECDomainParameters ecDomainParameters = new ECDomainParameters(
199                        curve, 
200                        x9ECParameters.getG(), 
201                        x9ECParameters.getN(), 
202                        x9ECParameters.getH(),
203                        x9ECParameters.getSeed());
204
205                ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(
206                        q, ecDomainParameters);
207
208                org.bouncycastle.crypto.signers.ECDSASigner verifier = 
209                        new org.bouncycastle.crypto.signers.ECDSASigner();
210
211                verifier.init(false, ecPublicKeyParameters);
212
213                digest.update(signedContent, 0, signedContent.length);
214                byte[] out = new byte[digest.getDigestSize()];
215                digest.doFinal(out, 0);
216
217                return verifier.verifySignature(out, r, s);
218        }
219}