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