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.ECPublicKeyParameters;
012import org.bouncycastle.math.ec.ECCurve;
013import org.bouncycastle.math.ec.ECPoint;
014
015import com.nimbusds.jose.DefaultJWSHeaderFilter;
016import com.nimbusds.jose.JOSEException;
017import com.nimbusds.jose.JWSHeaderFilter;
018import com.nimbusds.jose.JWSVerifier;
019import com.nimbusds.jose.ReadOnlyJWSHeader;
020import com.nimbusds.jose.util.Base64URL;
021
022
023/**
024 * Elliptic Curve Digital Signature Algorithm (ECDSA) verifier of 
025 * {@link com.nimbusds.jose.JWSObject JWS objects}.
026 *
027 * <p>Supports the following JSON Web Algorithms (JWAs):
028 *
029 * <ul>
030 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#ES256}
031 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#ES384}
032 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#ES512}
033 * </ul>
034 *
035 * <p>Accepts all {@link com.nimbusds.jose.JWSHeader#getRegisteredParameterNames
036 * registered JWS header parameters}. Modify the {@link #getJWSHeaderFilter
037 * header filter} properties to restrict the acceptable JWS algorithms and
038 * header parameters, or to allow custom JWS header parameters.
039 * 
040 * @author Axel Nennker
041 * @author Vladimir Dzhuvinov
042 * @version $version$ (2013-10-07)
043 */
044@ThreadSafe
045public class ECDSAVerifier extends ECDSAProvider implements JWSVerifier {
046
047
048        /**
049         * The JWS header filter.
050         */
051        private final DefaultJWSHeaderFilter headerFilter;
052
053
054        /**
055         * The 'x' EC coordinate.
056         */
057        private final BigInteger x;
058
059
060        /**
061         * The 'y' EC coordinate.
062         */
063        private final BigInteger y;
064
065
066
067        /**
068         * Creates a new Elliptic Curve Digital Signature Algorithm (ECDSA) 
069         * verifier.
070         *
071         * @param x The 'x' coordinate for the elliptic curve point. Must not 
072         *          be {@code null}.
073         * @param y The 'y' coordinate for the elliptic curve point. Must not 
074         *          be {@code null}.
075         */
076        public ECDSAVerifier(final BigInteger x, final BigInteger y) {
077
078                if (x == null) {
079
080                        throw new IllegalArgumentException("The \"x\" EC coordinate must not be null");
081                }
082
083                this.x = x;
084
085                if (y == null) {
086
087                        throw new IllegalArgumentException("The \"y\" EC coordinate must not be null");
088                }
089
090                this.y = y;
091
092                headerFilter = new DefaultJWSHeaderFilter(supportedAlgorithms());
093        }
094
095
096        /**
097         * Gets the 'x' coordinate for the elliptic curve point.
098         *
099         * @return The 'x' coordinate.
100         */
101        public BigInteger getX() {
102
103                return x;
104        }
105
106
107        /**
108         * Gets the 'y' coordinate for the elliptic curve point.
109         *
110         * @return The 'y' coordinate.
111         */
112        public BigInteger getY() {
113
114                return y;
115        }
116
117
118        @Override
119        public JWSHeaderFilter getJWSHeaderFilter() {
120
121                return headerFilter;
122        }
123
124
125        @Override
126        public boolean verify(final ReadOnlyJWSHeader header, 
127                              final byte[] signedContent, 
128                              final Base64URL signature)
129                throws JOSEException {
130
131                ECDSAParameters initParams = getECDSAParameters(header.getAlgorithm());
132                X9ECParameters x9ECParameters = initParams.getX9ECParameters();
133                Digest digest = initParams.getDigest();
134
135                byte[] signatureBytes = signature.decode();
136
137                // Split signature into R and S parts
138                int rsByteArrayLength = ECDSAProvider.getSignatureByteArrayLength(header.getAlgorithm());
139
140                byte[] rBytes = new byte[rsByteArrayLength / 2];
141                byte[] sBytes = new byte[rsByteArrayLength / 2];
142
143                try {
144                        System.arraycopy(signatureBytes, 0, rBytes, 0, rBytes.length);
145                        System.arraycopy(signatureBytes, rBytes.length, sBytes, 0, sBytes.length);
146
147                } catch (Exception e) {
148
149                        throw new JOSEException("Invalid ECDSA signature format: " + e.getMessage(), e);
150                }
151
152                BigInteger r = new BigInteger(1, rBytes);
153                BigInteger s = new BigInteger(1, sBytes);
154
155
156                ECCurve curve = x9ECParameters.getCurve();
157                ECPoint qB = curve.createPoint(x, y, false);
158                ECPoint q = new ECPoint.Fp(curve, qB.getX(), qB.getY());
159
160                ECDomainParameters ecDomainParameters = new ECDomainParameters(
161                        curve, 
162                        x9ECParameters.getG(), 
163                        x9ECParameters.getN(), 
164                        x9ECParameters.getH(),
165                        x9ECParameters.getSeed());
166
167                ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(
168                        q, ecDomainParameters);
169
170                org.bouncycastle.crypto.signers.ECDSASigner verifier = 
171                        new org.bouncycastle.crypto.signers.ECDSASigner();
172
173                verifier.init(false, ecPublicKeyParameters);
174
175                digest.update(signedContent, 0, signedContent.length);
176                byte[] out = new byte[digest.getDigestSize()];
177                digest.doFinal(out, 0);
178
179                return verifier.verifySignature(out, r, s);
180        }
181}