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.Signature;
023import java.security.SignatureException;
024import java.security.interfaces.ECPublicKey;
025import java.util.Set;
026
027import net.jcip.annotations.ThreadSafe;
028
029import com.nimbusds.jose.*;
030import com.nimbusds.jose.crypto.impl.AlgorithmSupportMessage;
031import com.nimbusds.jose.crypto.impl.CriticalHeaderParamsDeferral;
032import com.nimbusds.jose.crypto.impl.ECDSA;
033import com.nimbusds.jose.crypto.impl.ECDSAProvider;
034import com.nimbusds.jose.crypto.utils.ECChecks;
035import com.nimbusds.jose.jwk.Curve;
036import com.nimbusds.jose.jwk.ECKey;
037import com.nimbusds.jose.util.Base64URL;
038
039
040/**
041 * Elliptic Curve Digital Signature Algorithm (ECDSA) verifier of 
042 * {@link com.nimbusds.jose.JWSObject JWS objects}. Expects a public EC key
043 * (with a P-256, P-384 or P-521 curve).
044 *
045 * <p>See RFC 7518
046 * <a href="https://tools.ietf.org/html/rfc7518#section-3.4">section 3.4</a>
047 * for more information.
048 *
049 * <p>This class is thread-safe.
050 *
051 * <p>Supports the following algorithms:
052 *
053 * <ul>
054 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#ES256}
055 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#ES384}
056 *     <li>{@link com.nimbusds.jose.JWSAlgorithm#ES512}
057 * </ul>
058 * 
059 * @author Axel Nennker
060 * @author Vladimir Dzhuvinov
061 * @version 2017-04-13
062 */
063@ThreadSafe
064public class ECDSAVerifier extends ECDSAProvider implements JWSVerifier, CriticalHeaderParamsAware {
065
066
067        /**
068         * The critical header policy.
069         */
070        private final CriticalHeaderParamsDeferral critPolicy = new CriticalHeaderParamsDeferral();
071
072
073        /**
074         * The public EC key.
075         */
076        private final ECPublicKey publicKey;
077
078
079        /**
080         * Creates a new Elliptic Curve Digital Signature Algorithm (ECDSA) 
081         * verifier.
082         *
083         * @param publicKey The public EC key. Must not be {@code null}.
084         *
085         * @throws JOSEException If the elliptic curve of key is not supported.
086         */
087        public ECDSAVerifier(final ECPublicKey publicKey)
088                throws JOSEException {
089
090                this(publicKey, null);
091        }
092        
093
094        /**
095         * Creates a new Elliptic Curve Digital Signature Algorithm (ECDSA)
096         * verifier.
097         *
098         * @param ecJWK The EC JSON Web Key (JWK). Must not be {@code null}.
099         *
100         * @throws JOSEException If the elliptic curve of key is not supported.
101         */
102        public ECDSAVerifier(final ECKey ecJWK)
103                throws JOSEException {
104
105                this(ecJWK.toECPublicKey());
106        }
107
108
109        /**
110         * Creates a new Elliptic Curve Digital Signature Algorithm (ECDSA)
111         * verifier.
112         *
113         * @param publicKey      The public EC key. Must not be {@code null}.
114         * @param defCritHeaders The names of the critical header parameters
115         *                       that are deferred to the application for
116         *                       processing, empty set or {@code null} if none.
117         *
118         * @throws JOSEException If the elliptic curve of key is not supported.
119         */
120        public ECDSAVerifier(final ECPublicKey publicKey, final Set<String> defCritHeaders)
121                throws JOSEException {
122
123                super(ECDSA.resolveAlgorithm(publicKey));
124
125                this.publicKey = publicKey;
126                
127                if (! ECChecks.isPointOnCurve(
128                        publicKey,
129                        Curve.forJWSAlgorithm(supportedECDSAAlgorithm()).iterator().next().toECParameterSpec())) {
130                        throw new JOSEException("Curve / public key parameters mismatch");
131                }
132
133                critPolicy.setDeferredCriticalHeaderParams(defCritHeaders);
134        }
135
136
137        /**
138         * Returns the public EC key.
139         *
140         * @return The public EC key.
141         */
142        public ECPublicKey getPublicKey() {
143
144                return publicKey;
145        }
146
147
148        @Override
149        public Set<String> getProcessedCriticalHeaderParams() {
150
151                return critPolicy.getProcessedCriticalHeaderParams();
152        }
153
154
155        @Override
156        public Set<String> getDeferredCriticalHeaderParams() {
157
158                return critPolicy.getProcessedCriticalHeaderParams();
159        }
160
161
162        @Override
163        public boolean verify(final JWSHeader header,
164                              final byte[] signedContent, 
165                              final Base64URL signature)
166                throws JOSEException {
167
168                final JWSAlgorithm alg = header.getAlgorithm();
169
170                if (! supportedJWSAlgorithms().contains(alg)) {
171                        throw new JOSEException(AlgorithmSupportMessage.unsupportedJWSAlgorithm(alg, supportedJWSAlgorithms()));
172                }
173
174                if (! critPolicy.headerPasses(header)) {
175                        return false;
176                }
177
178                final byte[] jwsSignature = signature.decode();
179
180                final byte[] derSignature;
181                
182                if (ECDSA.getSignatureByteArrayLength(header.getAlgorithm()) != jwsSignature.length) {
183                        // Quick format check, concatenation of R+S (may be padded
184                        // to match lengths) in ESxxx signatures has fixed length
185                        return false;
186                }
187
188                try {
189                        derSignature = ECDSA.transcodeSignatureToDER(jwsSignature);
190                } catch (JOSEException e) {
191                        // Invalid signature format
192                        return false;
193                }
194
195                Signature sig = ECDSA.getSignerAndVerifier(alg, getJCAContext().getProvider());
196
197                try {
198                        sig.initVerify(publicKey);
199                        sig.update(signedContent);
200                        return sig.verify(derSignature);
201
202                } catch (InvalidKeyException e) {
203                        throw new JOSEException("Invalid EC public key: " + e.getMessage(), e);
204                } catch (SignatureException e) {
205                        return false;
206                }
207        }
208}