|
| 1 | +package com.auth0.jwtdecodejava.algorithms; |
| 2 | + |
| 3 | +import com.auth0.jwtdecodejava.exceptions.SignatureVerificationException; |
| 4 | +import org.apache.commons.codec.binary.Base64; |
| 5 | + |
| 6 | +import java.security.InvalidKeyException; |
| 7 | +import java.security.NoSuchAlgorithmException; |
| 8 | +import java.security.PublicKey; |
| 9 | +import java.security.SignatureException; |
| 10 | + |
| 11 | +class ECDSAAlgorithm extends Algorithm { |
| 12 | + |
| 13 | + private final CryptoHelper crypto; |
| 14 | + private final int ecNumberSize; |
| 15 | + private final PublicKey publicKey; |
| 16 | + |
| 17 | + ECDSAAlgorithm(CryptoHelper crypto, String id, String algorithm, int ecNumberSize, PublicKey publicKey) { |
| 18 | + super(id, algorithm); |
| 19 | + if (publicKey == null) { |
| 20 | + throw new IllegalArgumentException("The PublicKey cannot be null"); |
| 21 | + } |
| 22 | + this.ecNumberSize = ecNumberSize; |
| 23 | + this.publicKey = publicKey; |
| 24 | + this.crypto = crypto; |
| 25 | + } |
| 26 | + |
| 27 | + ECDSAAlgorithm(String id, String algorithm, int ecNumberSize, PublicKey publicKey) { |
| 28 | + this(new CryptoHelper(), id, algorithm, ecNumberSize, publicKey); |
| 29 | + } |
| 30 | + |
| 31 | + PublicKey getPublicKey() { |
| 32 | + return publicKey; |
| 33 | + } |
| 34 | + |
| 35 | + @Override |
| 36 | + public void verify(String[] jwtParts) throws SignatureVerificationException { |
| 37 | + try { |
| 38 | + String content = String.format("%s.%s", jwtParts[0], jwtParts[1]); |
| 39 | + byte[] signature = Base64.decodeBase64(jwtParts[2]); |
| 40 | + if (!isDERSignature(signature)) { |
| 41 | + signature = JOSEToDER(signature); |
| 42 | + } |
| 43 | + boolean valid = crypto.verifySignatureFor(getDescription(), publicKey, content.getBytes(), signature); |
| 44 | + |
| 45 | + if (!valid) { |
| 46 | + throw new SignatureVerificationException(this); |
| 47 | + } |
| 48 | + } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) { |
| 49 | + throw new SignatureVerificationException(this, e); |
| 50 | + } |
| 51 | + } |
| 52 | + |
| 53 | + private boolean isDERSignature(byte[] signature) { |
| 54 | + // DER Structure: http://crypto.stackexchange.com/a/1797 |
| 55 | + // Should begin with 0x30 and have exactly the expected length |
| 56 | + return signature[0] == 0x30 && signature.length != ecNumberSize * 2; |
| 57 | + } |
| 58 | + |
| 59 | + private byte[] JOSEToDER(byte[] joseSignature) throws SignatureException { |
| 60 | + if (joseSignature.length != ecNumberSize * 2) { |
| 61 | + throw new SignatureException(String.format("The signature length was invalid. Expected %d bytes but received %d", ecNumberSize * 2, joseSignature.length)); |
| 62 | + } |
| 63 | + |
| 64 | + // Retrieve R and S number's length and padding. |
| 65 | + int rPadding = countPadding(joseSignature, 0, ecNumberSize); |
| 66 | + int sPadding = countPadding(joseSignature, ecNumberSize, joseSignature.length); |
| 67 | + int rLength = ecNumberSize - rPadding; |
| 68 | + int sLength = ecNumberSize - sPadding; |
| 69 | + |
| 70 | + int length = 2 + rLength + 2 + sLength; |
| 71 | + if (length > 255) { |
| 72 | + throw new SignatureException("Invalid ECDSA signature format"); |
| 73 | + } |
| 74 | + |
| 75 | + byte[] derSignature; |
| 76 | + int offset; |
| 77 | + if (length > 0x7f) { |
| 78 | + derSignature = new byte[3 + length]; |
| 79 | + derSignature[1] = (byte) 0x81; |
| 80 | + offset = 2; |
| 81 | + } else { |
| 82 | + derSignature = new byte[2 + length]; |
| 83 | + offset = 1; |
| 84 | + } |
| 85 | + |
| 86 | + // DER Structure: http://crypto.stackexchange.com/a/1797 |
| 87 | + // Header with length info |
| 88 | + derSignature[0] = (byte) 0x30; |
| 89 | + derSignature[offset++] = (byte) length; |
| 90 | + derSignature[offset++] = (byte) 0x02; |
| 91 | + derSignature[offset++] = (byte) rLength; |
| 92 | + |
| 93 | + // R number |
| 94 | + System.arraycopy(joseSignature, 0, derSignature, offset + (rLength - ecNumberSize), ecNumberSize); |
| 95 | + offset += rLength; |
| 96 | + |
| 97 | + // S number length |
| 98 | + derSignature[offset++] = (byte) 0x02; |
| 99 | + derSignature[offset++] = (byte) sLength; |
| 100 | + |
| 101 | + // S number |
| 102 | + System.arraycopy(joseSignature, ecNumberSize, derSignature, offset + (sLength - ecNumberSize), ecNumberSize); |
| 103 | + |
| 104 | + return derSignature; |
| 105 | + } |
| 106 | + |
| 107 | + private int countPadding(byte[] bytes, int fromIndex, int toIndex) { |
| 108 | + int padding = 0; |
| 109 | + while (fromIndex + padding < toIndex && bytes[fromIndex + padding] == 0) { |
| 110 | + padding++; |
| 111 | + } |
| 112 | + return bytes[fromIndex + padding] > 0x7f ? padding : padding - 1; |
| 113 | + } |
| 114 | +} |
0 commit comments