Improper Verification of Cryptographic Signature

Description

Improper Verification of Cryptographic Signature occurs when software does not verify, or incorrectly verifies, the cryptographic signature for data. This allows attackers to modify signed data, bypass authentication mechanisms, or impersonate trusted entities. Common scenarios include failing to verify JWT signatures, accepting "none" algorithms, not validating certificate chains, or using weak signature verification logic that can be bypassed.

Risk

Failure to properly verify signatures undermines the entire trust model of cryptographic systems. Attackers can forge authentication tokens (like JWTs with "none" algorithm), tamper with signed software updates, modify digitally signed documents, or perform man-in-the-middle attacks on TLS connections. The 2017 JWT "none" algorithm vulnerabilities affected numerous authentication libraries. Improper certificate validation has led to countless SSL/TLS bypass attacks.

Solution

Always verify cryptographic signatures before trusting signed data. For JWTs, explicitly specify allowed algorithms and reject "none". Validate the entire certificate chain for TLS/SSL. Use well-tested cryptographic libraries rather than implementing verification logic manually. Implement proper error handling that fails closed on verification errors. For software updates, verify signatures before installation. Use strong signature algorithms (RSA-2048+, ECDSA with P-256+).

Common Consequences

ImpactDetails
IntegrityScope: Data Tampering

Unsigned or improperly verified data can be modified by attackers without detection.
AuthenticationScope: Authentication Bypass

Forged signatures allow attackers to impersonate legitimate users or systems.
Access ControlScope: Privilege Escalation

Tampered tokens or certificates can grant unauthorized access levels.

Example Code + Solution Code

Vulnerable Code

# VULNERABLE: JWT verification without algorithm check
import jwt

def verify_token_vulnerable(token):
    # Attacker can change algorithm to "none" and forge tokens!
    payload = jwt.decode(token, options={"verify_signature": False})
    return payload

# VULNERABLE: Accepting any algorithm
def verify_token_any_algo(token, secret):
    # Attacker can switch from RS256 to HS256 and sign with public key
    payload = jwt.decode(token, secret)  # No algorithm restriction!
    return payload

# VULNERABLE: Not verifying signature at all
def parse_jwt_no_verify(token):
    import base64
    import json

    parts = token.split('.')
    payload = json.loads(base64.b64decode(parts[1] + '=='))
    # Signature (parts[2]) is completely ignored!
    return payload
// VULNERABLE: JWT without algorithm verification
import io.jsonwebtoken.Jwts;

public class VulnerableJwtVerifier {

    public Claims verifyToken(String token, String secret) {
        // No algorithm restriction - vulnerable to algorithm confusion
        return Jwts.parser()
            .setSigningKey(secret)
            .parseClaimsJws(token)
            .getBody();
    }
}

// VULNERABLE: Certificate validation disabled
import javax.net.ssl.*;
import java.security.cert.X509Certificate;

public class InsecureSSL {

    public static void disableCertificateValidation() {
        TrustManager[] trustAllCerts = new TrustManager[] {
            new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() { return null; }
                public void checkClientTrusted(X509Certificate[] certs, String authType) { }
                public void checkServerTrusted(X509Certificate[] certs, String authType) { }
            }
        };

        SSLContext sc = SSLContext.getInstance("SSL");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
    }
}
// VULNERABLE: JWT verification with algorithm confusion
const jwt = require('jsonwebtoken');

function verifyToken(token, secret) {
    // No algorithm specified - attacker can use "none"
    return jwt.verify(token, secret);
}

// VULNERABLE: Ignoring signature verification
function parseJwtUnsafe(token) {
    const parts = token.split('.');
    const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
    // Signature not verified!
    return payload;
}

// VULNERABLE: Accepting any algorithm
function verifyAnyAlgorithm(token, key) {
    const decoded = jwt.decode(token, { complete: true });
    // Using algorithm from token header - attacker controlled!
    return jwt.verify(token, key, { algorithms: [decoded.header.alg] });
}

Fixed Code

# SAFE: JWT verification with explicit algorithm
import jwt
from jwt.exceptions import InvalidTokenError

ALLOWED_ALGORITHMS = ['RS256']  # Explicitly allow only RS256

def verify_token_safe(token, public_key):
    try:
        payload = jwt.decode(
            token,
            public_key,
            algorithms=ALLOWED_ALGORITHMS,  # Explicit algorithm whitelist
            options={
                "verify_signature": True,
                "verify_exp": True,
                "verify_iat": True,
                "verify_nbf": True,
                "require": ["exp", "iat", "sub"]
            }
        )
        return payload
    except InvalidTokenError as e:
        raise ValueError(f"Invalid token: {e}")

# SAFE: Complete JWT validation class
class SecureJwtValidator:
    def __init__(self, public_key, issuer, audience):
        self.public_key = public_key
        self.issuer = issuer
        self.audience = audience

    def validate(self, token):
        try:
            payload = jwt.decode(
                token,
                self.public_key,
                algorithms=['RS256'],  # Only asymmetric
                issuer=self.issuer,
                audience=self.audience,
                options={
                    "verify_signature": True,
                    "verify_exp": True,
                    "verify_iat": True,
                    "verify_iss": True,
                    "verify_aud": True
                }
            )
            return payload
        except jwt.ExpiredSignatureError:
            raise ValueError("Token has expired")
        except jwt.InvalidIssuerError:
            raise ValueError("Invalid token issuer")
        except jwt.InvalidAudienceError:
            raise ValueError("Invalid token audience")
        except jwt.InvalidSignatureError:
            raise ValueError("Invalid signature")
        except Exception as e:
            raise ValueError(f"Token validation failed: {e}")

# SAFE: Signature verification for files
import hashlib
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding

def verify_file_signature(file_path, signature, public_key_pem):
    from cryptography.hazmat.backends import default_backend

    # Load public key
    public_key = serialization.load_pem_public_key(
        public_key_pem.encode(),
        backend=default_backend()
    )

    # Read file content
    with open(file_path, 'rb') as f:
        file_content = f.read()

    # Verify signature
    try:
        public_key.verify(
            signature,
            file_content,
            padding.PSS(
                mgf=padding.MGF1(hashes.SHA256()),
                salt_length=padding.PSS.MAX_LENGTH
            ),
            hashes.SHA256()
        )
        return True
    except Exception:
        return False
// SAFE: JWT verification with algorithm restriction
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.SignatureException;

public class SecureJwtVerifier {

    private final Key publicKey;
    private final String expectedIssuer;
    private final String expectedAudience;

    public SecureJwtVerifier(Key publicKey, String issuer, String audience) {
        this.publicKey = publicKey;
        this.expectedIssuer = issuer;
        this.expectedAudience = audience;
    }

    public Claims verifyToken(String token) throws JwtException {
        JwtParser parser = Jwts.parserBuilder()
            .setSigningKey(publicKey)
            .requireIssuer(expectedIssuer)
            .requireAudience(expectedAudience)
            .build();

        Jws<Claims> jws = parser.parseClaimsJws(token);

        // Additional algorithm verification
        String algorithm = jws.getHeader().getAlgorithm();
        if (!"RS256".equals(algorithm)) {
            throw new SignatureException("Invalid algorithm: " + algorithm);
        }

        return jws.getBody();
    }
}

// SAFE: Proper certificate validation
import javax.net.ssl.*;
import java.security.cert.*;

public class SecureSSLContext {

    public static SSLContext createSecureContext(String truststorePath, char[] password)
            throws Exception {

        // Load truststore
        KeyStore trustStore = KeyStore.getInstance("JKS");
        try (FileInputStream fis = new FileInputStream(truststorePath)) {
            trustStore.load(fis, password);
        }

        // Create trust manager with certificate validation
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(
            TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(trustStore);

        // Create SSL context
        SSLContext sslContext = SSLContext.getInstance("TLSv1.3");
        sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());

        return sslContext;
    }

    // Custom certificate validator with additional checks
    public static X509TrustManager createStrictTrustManager(KeyStore trustStore)
            throws Exception {

        TrustManagerFactory tmf = TrustManagerFactory.getInstance(
            TrustManagerFactory.getDefaultAlgorithm());
        tmf.init(trustStore);

        X509TrustManager defaultTm = (X509TrustManager) tmf.getTrustManagers()[0];

        return new X509TrustManager() {
            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType)
                    throws CertificateException {
                // Default validation
                defaultTm.checkServerTrusted(chain, authType);

                // Additional checks
                X509Certificate cert = chain[0];

                // Check expiration
                cert.checkValidity();

                // Verify key usage
                boolean[] keyUsage = cert.getKeyUsage();
                if (keyUsage != null && !keyUsage[0]) {  // digitalSignature
                    throw new CertificateException("Certificate key usage invalid");
                }
            }

            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType)
                    throws CertificateException {
                defaultTm.checkClientTrusted(chain, authType);
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return defaultTm.getAcceptedIssuers();
            }
        };
    }
}
// SAFE: JWT verification with explicit algorithms
const jwt = require('jsonwebtoken');
const fs = require('fs');

class SecureJwtValidator {
    constructor(publicKey, options = {}) {
        this.publicKey = publicKey;
        this.issuer = options.issuer;
        this.audience = options.audience;
        this.allowedAlgorithms = ['RS256'];  // Only asymmetric
    }

    verify(token) {
        try {
            const payload = jwt.verify(token, this.publicKey, {
                algorithms: this.allowedAlgorithms,  // Explicit whitelist
                issuer: this.issuer,
                audience: this.audience,
                complete: false
            });

            return payload;
        } catch (error) {
            if (error instanceof jwt.TokenExpiredError) {
                throw new Error('Token has expired');
            }
            if (error instanceof jwt.JsonWebTokenError) {
                throw new Error(`Invalid token: ${error.message}`);
            }
            throw error;
        }
    }

    // Verify with additional header checks
    verifyStrict(token) {
        // First decode without verification to check header
        const decoded = jwt.decode(token, { complete: true });

        if (!decoded) {
            throw new Error('Invalid token format');
        }

        // Verify algorithm in header
        if (!this.allowedAlgorithms.includes(decoded.header.alg)) {
            throw new Error(`Algorithm ${decoded.header.alg} not allowed`);
        }

        // Verify token type
        if (decoded.header.typ !== 'JWT') {
            throw new Error('Invalid token type');
        }

        // Now verify signature
        return this.verify(token);
    }
}

// SAFE: File signature verification
const crypto = require('crypto');

function verifyFileSignature(filePath, signature, publicKey) {
    const fileContent = fs.readFileSync(filePath);

    const verify = crypto.createVerify('RSA-SHA256');
    verify.update(fileContent);
    verify.end();

    const isValid = verify.verify(publicKey, signature, 'base64');

    if (!isValid) {
        throw new Error('Signature verification failed');
    }

    return true;
}

// Usage
const publicKey = fs.readFileSync('public.pem');
const validator = new SecureJwtValidator(publicKey, {
    issuer: 'https://auth.example.com',
    audience: 'https://api.example.com'
});

try {
    const payload = validator.verifyStrict(token);
    console.log('Token valid:', payload);
} catch (error) {
    console.error('Token invalid:', error.message);
}

Exploited in the Wild

Auth0 JWT Library Vulnerability (2015)

Multiple JWT libraries were found vulnerable to algorithm confusion attacks where attackers could change RS256 to HS256 and sign tokens with the public key, bypassing signature verification entirely.

CVE-2015-9235 - JWT "none" Algorithm

Libraries that accepted the "none" algorithm allowed attackers to create unsigned JWTs that were accepted as valid, completely bypassing authentication.

Apple goto fail (2014)

A coding error in Apple's SSL implementation caused signature verification to be skipped, allowing man-in-the-middle attacks on iOS and macOS devices.


Tools to test/exploit


CVE Examples


References

  1. MITRE. "CWE-347: Improper Verification of Cryptographic Signature." https://cwe.mitre.org/data/definitions/347.html

  2. Auth0. "Critical Vulnerabilities in JSON Web Token Libraries." https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/