Search code examples
iossecurityrsapublic-keycrypto++

Error verifying message using Crypto++ on iOS


Problem

I am trying to verify a given message with its signature and public key. It works fine using the iOS provided Security Framework, but I cannot manage to make it work using the Crypto++ library (must use).

I followed the same steps using the CryptoPP Library and verified everything 10 times, rewrote some parts differently, but it still throws the same exception:

"PK_Signer: key too short for this signature scheme"

context

Data worked with

  • I receive a JWT (Json Web Token) with a header, payload and signature.
  • I retrieve the service's base64 encoded X509 certificate (which includes the public key).

Steps followed for verification

  1. Certificate

    1. Base64 decode the certificate
    2. Extract the public key from certificate
  2. Signature (third segment of a JWB)

    1. Pad the signature to a multiple of 4 with some "="
    2. URLBase64 decode it
  3. Message to verify

    1. Message = (JSW Header) + "." + (JWT Payload). This is already done in the code, message is argument named "headerAndPayload.
  4. Verify SHA256 bytes with PKCS1, RSA

    1. SHA256 digest of the Message
    2. Verification using:
      1. Public Key
      2. SHA256 Digest of the message
      3. Signature

iOS Working Code

(Only parts that matter, as verification works fine on iOS)

  • Certificate

    1. NSData *certificateData = [[NSData alloc] initWithBase64EncodedString:certificateString options:0];

    2. SecKeyRef getPublicKeyFromCertificate(certificateData) found online, works fine.

  • Verify SHA256 bytes with PKCS1, RSA

    BOOL PKCSVerifyBytesSHA256withRSA(NSData* message, NSData* signature, SecKeyRef publicKey)
    {
        size_t signedHashBytesSize = SecKeyGetBlockSize(publicKey);
        const void* signedHashBytes = [signature bytes];
    
        size_t hashBytesSize = CC_SHA256_DIGEST_LENGTH;
        void* hashBytes = malloc(hashBytesSize);
        if (!CC_SHA256([message bytes], (CC_LONG)[message length], hashBytes)) {
            return NULL;
        }
    
        OSStatus status = SecKeyRawVerify(publicKey,
                                      kSecPaddingPKCS1SHA256,
                                      hashBytes,
                                      hashBytesSize,
                                      signedHashBytes,
                                      signedHashBytesSize);
    
        return status == errSecSuccess;
    }
    

Code using CryptoPP Library (working with same set of data)

I copy/paste the whole code with numbers corresponding to the description and some additional comments, like size of structures returned.

+(bool)verifyBase64EncodedCertificate:(NSString *)certificateString
         base64URLEncodedJWTSignature:(NSString *)urlEncodedSignature
                              message:(NSString *)headerAndPayload
{
    // 1. Certificate
    // 1.1 Decode the certificate
    std::string base64EncodedCertificate = certificateString.UTF8String;
    std::string decodedCertificate;
    CryptoPP::StringSource ss(base64EncodedCertificate,
                              true,
                              new CryptoPP::Base64Decoder(new CryptoPP::StringSink(decodedCertificate))
                              );

    // 1.2 Extract Public Key from certificate
    CryptoPP::ByteQueue certificateByteQueue, publicKeyByteQueue;
    certificateByteQueue.Put((byte *)&decodedCertificate[0], decodedCertificate.size());
    certificateByteQueue.MessageEnd();
    try
    {
        GetPublicKeyFromCert(certificateByteQueue, publicKeyByteQueue);
        // This method comes from CryptoPP docs so I assume it works... certificate gets checked again later on.
    }
    catch(std::exception &)
    {
        std::cerr << "Failed to extract the public key from the CA certificate." << std::endl;
        return nil;
    }
    //publicKeyByteQueue.CurrentSize() = 294


    // 2. Decode Signature
    std::string base64URLEncodedSignature = urlEncodedSignature.UTF8String;
    unsigned long paddingForURLEncodedSignature = 4 - (base64URLEncodedSignature.length() % 4);
    base64URLEncodedSignature.insert(base64URLEncodedSignature.begin(), paddingForURLEncodedSignature, '=');
    std::string decodedSignature;
    CryptoPP::StringSource ss1(base64URLEncodedSignature,
                               true,
                               new CryptoPP::Base64URLDecoder(new CryptoPP::StringSink(decodedSignature))
                               );
    const byte *decodedSignaturePointer = (byte *)&decodedSignature[0];
    size_t decodedSignatureSize = decodedSignature.size();

    // Certificate Signature as Byte Block
    CryptoPP::SecByteBlock certSignature;
    certSignature.Assign(decodedSignaturePointer, decodedSignatureSize);

    // decodedSignatureSize = 256
    // certSignature.size() = 256


    // 3. Message to verify (available already concatenated)
    std::string message = headerAndPayload.UTF8String;
    const byte *messagePointer = (const byte *)message.c_str();
    const size_t messageLength = message.length();        
    // MessageLength = 693

    // 4.1 hash message using SHA256
    byte digest [CryptoPP::SHA256::DIGESTSIZE];
    CryptoPP::SHA256().CalculateDigest(digest, messagePointer, messageLength);

    // 4.2  Create Verifier assigned public key and test
    CryptoPP::AutoSeededRandomPool prng;
    CryptoPP::RSASS<CryptoPP::PKCS1v15, CryptoPP::SHA256>::Verifier verifier;
    verifier.AccessKey().Load(publicKeyByteQueue);
    if (!verifier.AccessKey().Validate(prng, 3))
    {
        throw CryptoPP::Exception(CryptoPP::Exception::OTHER_ERROR, "Failed to validate public key");
    }        

    // verifier.SignatureLength() = 256 = certSignature.size()
    if(certSignature.size() != verifier.SignatureLength())
    {
        std::cerr << "The signature size is does not match the algorithm used for signing." << std::endl;
        return 0;
    }

    // 4. Actual Verification (1st way of doing it)
    CryptoPP::SignatureVerificationFilter vf(verifier);
    try
    {
        vf.Put(digest, CryptoPP::SHA256::DIGESTSIZE);
        vf.Put(certSignature, certSignature.size());
        vf.MessageEnd(); // Throws exception here PK_Signer: key too short for this signature scheme
    }
    catch(std::exception &e)
    {
        std::cerr << "Caught an exception while verifying the signature:" << std::endl;
        std::cerr << "\t" << e.what() << std::endl;
        return 0;
    }
    if(vf.GetLastResult())
    {
        std::cout << "The signature verified." << std::endl;
    }
    else
    {
        std::cout << "Signature verification failed." << std::endl;
    }
    return 1;


    // 4. Actual Verification (2d way of doing it)
    bool verified = verifier.VerifyMessage(digest,                  CryptoPP::SHA256::DIGESTSIZE,
                                           decodedSignaturePointer, decodedSignatureSize);
    // Also throw same exception PK_Signer: key too short for this signature scheme

    return verified;

The only difference I can see between the pure iOS code and the CryptoPP code is during the verification process, the iOS method takes an additional argument kSecPaddingPKCS1SHA256

SecKeyRawVerify(publicKey,
                kSecPaddingPKCS1SHA256,
                ...)

But otherwise I feel like I have replicated exactly the same concepts using the CryptoPP library.

Any help is very appreciated, thanks.


Solution

  • vf.Put(digest, CryptoPP::SHA256::DIGESTSIZE);

    The signature is not the hash size. The signature is the size of the modulus (or more correctly, [0,n-1]). After protocol framing, the signature may be larger than the modulus size. Also see What is the length of an RSA signature? on the Cryptography Stack Exchange.

    As for creating an equivalent iOS example, using the "Raw Sign" or "Raw Encrypt", see Raw RSA on the Crypto++ wiki. Its usually a bad idea for you to do the low level things like a modular exponentiation. You should try to stay in the protocols and cryptosystems, like RSASSA_PKCS1v15_SHA_Signer and RSASSA_PKCS1v15_SHA_Verifier.

    Also checkout the RSASS class, which is RSA Signature Scheme. I'm guessing you will probably want a RSASS<PKCS1v15, SHA256>::Signer and RSASS<PKCS1v15, SHA256>::Verifier:

    $ grep -IR Signer * | grep typedef
    luc.h:typedef LUCSS<PKCS1v15, SHA>::Signer LUCSSA_PKCS1v15_SHA_Signer;
    pubkey.h:   typedef PK_FinalTemplate<TF_SignerImpl<SchemeOptions> > Signer;
    pubkey.h:   typedef PK_FinalTemplate<DL_SignerImpl<SchemeOptions> > Signer;
    rsa.h:typedef RSASS<PKCS1v15, SHA>::Signer RSASSA_PKCS1v15_SHA_Signer;
    rsa.h:typedef RSASS<PKCS1v15, Weak1::MD2>::Signer RSASSA_PKCS1v15_MD2_Signer;
    rsa.h:typedef RSASS<PKCS1v15, Weak1::MD5>::Signer RSASSA_PKCS1v15_MD5_Signer;