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}