//
//  Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//

#import <XCTest/XCTest.h>
#import "Cryptography.h"
#import "NSData+Base64.h"

NS_ASSUME_NONNULL_BEGIN

@interface Cryptography (TestingPrivateMethods)

+ (nullable NSData *)decryptAESGCMWithInitializationVector:(NSData *)initializationVector
                                                ciphertext:(NSData *)ciphertext
                                                   authTag:(NSData *)authTagFromEncrypt
                                                       key:(OWSAES256Key *)key;

@end

@interface CryptographyTests : XCTestCase

@end

@interface Cryptography (Test)
+ (NSData *)truncatedSHA256HMAC:(NSData *)dataToHMAC withHMACKey:(NSData *)HMACKey truncation:(int)bytes;
+ (NSData *)encryptCBCMode:(NSData *)dataToEncrypt
                   withKey:(NSData *)key
                    withIV:(NSData *)iv
               withVersion:(NSData *)version
               withHMACKey:(NSData *)hmacKey
              withHMACType:(TSMACType)hmacType
              computedHMAC:(NSData **)hmac;

+ (NSData *)decryptCBCMode:(NSData *)dataToDecrypt
                       key:(NSData *)key
                        IV:(NSData *)iv
                   version:(NSData *)version
                   HMACKey:(NSData *)hmacKey
                  HMACType:(TSMACType)hmacType
              matchingHMAC:(NSData *)hmac;
@end

@implementation CryptographyTests

- (void)testEncryptAttachmentData
{

    NSString *plainText = @"SGF3YWlpIGlzIEF3ZXNvbWUh";
    NSData *plainTextData = [NSData dataFromBase64String:plainText];

    // Sanity
    XCTAssertNotNil(plainTextData);

    NSData *generatedKey;
    NSData *generatedDigest;

    NSData *cipherText =
        [Cryptography encryptAttachmentData:plainTextData outKey:&generatedKey outDigest:&generatedDigest];

    NSData *decryptedData = [Cryptography decryptAttachment:cipherText withKey:generatedKey digest:generatedDigest];

    XCTAssertEqualObjects(plainTextData, decryptedData);
}

- (void)testDecryptAttachmentWithBadKey
{
    NSString *plainText = @"SGF3YWlpIGlzIEF3ZXNvbWUh";
    NSData *plainTextData = [NSData dataFromBase64String:plainText];

    // Sanity
    XCTAssertNotNil(plainTextData);

    NSData *generatedKey;
    NSData *generatedDigest;

    NSData *cipherText =
        [Cryptography encryptAttachmentData:plainTextData outKey:&generatedKey outDigest:&generatedDigest];

    NSData *badKey = [Cryptography generateRandomBytes:64];

    NSData *decryptedData = [Cryptography decryptAttachment:cipherText withKey:badKey digest:generatedDigest];

    XCTAssertNil(decryptedData);
}

- (void)testDecryptAttachmentWithBadDigest
{
    NSString *plainText = @"SGF3YWlpIGlzIEF3ZXNvbWUh";
    NSData *plainTextData = [NSData dataFromBase64String:plainText];

    // Sanity
    XCTAssertNotNil(plainTextData);

    NSData *generatedKey;
    NSData *generatedDigest;

    NSData *cipherText =
        [Cryptography encryptAttachmentData:plainTextData outKey:&generatedKey outDigest:&generatedDigest];

    NSData *badDigest = [Cryptography generateRandomBytes:32];

    NSData *decryptedData = [Cryptography decryptAttachment:cipherText withKey:generatedKey digest:badDigest];

    XCTAssertNil(decryptedData);
}

- (void)testComputeSHA256Digest
{
    NSString *plainText = @"SGF3YWlpIGlzIEF3ZXNvbWUh";
    NSData *plainTextData = [NSData dataFromBase64String:plainText];
    NSData *digest = [Cryptography computeSHA256Digest:plainTextData];

    const uint8_t expectedBytes[] = {
        0xba, 0x5f, 0xf1, 0x26,
        0x82, 0xbb, 0xb2, 0x51,
        0x8b, 0xe6, 0x06, 0x48,
        0xc5, 0x53, 0xd0, 0xa2,
        0xbf, 0x71, 0xf1, 0xec,
        0xb4, 0xdb, 0x02, 0x12,
        0x5f, 0x80, 0xea, 0x34,
        0xc9, 0x8d, 0xee, 0x1f
    };

    NSData *expectedDigest = [NSData dataWithBytes:expectedBytes length:32];
    XCTAssertEqualObjects(expectedDigest, digest);

    NSData *expectedTruncatedDigest = [NSData dataWithBytes:expectedBytes length:10];
    NSData *truncatedDigest = [Cryptography computeSHA256Digest:plainTextData truncatedToBytes:10];
    XCTAssertEqualObjects(expectedTruncatedDigest, truncatedDigest);
}

- (void)testGCMRoundTrip
{
    NSData *plainTextData = [@"Super🔥secret🔥test🔥data🏁🏁" dataUsingEncoding:NSUTF8StringEncoding];
    // Sanity Check
    XCTAssertEqual(39, plainTextData.length);

    OWSAES256Key *key = [OWSAES256Key new];
    NSData *_Nullable encryptedData = [Cryptography encryptAESGCMWithData:plainTextData key:key];

    const NSUInteger ivLength = 12;
    const NSUInteger tagLength = 16;
    
    XCTAssertEqual(ivLength + plainTextData.length + tagLength, encryptedData.length);

    NSData *_Nullable decryptedData = [Cryptography decryptAESGCMWithData:encryptedData key:key];
    XCTAssert(decryptedData != nil);
    XCTAssertEqual(39, decryptedData.length);
    XCTAssertEqualObjects(plainTextData, decryptedData);
    XCTAssertEqualObjects(@"Super🔥secret🔥test🔥data🏁🏁", [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding]);
}

- (void)testGCMWithBadTag
{
    NSData *plainTextData = [@"Super🔥secret🔥test🔥data🏁🏁" dataUsingEncoding:NSUTF8StringEncoding];
    // Sanity Check
    XCTAssertEqual(39, plainTextData.length);

    OWSAES256Key *key = [OWSAES256Key new];
    NSData *_Nullable encryptedData = [Cryptography encryptAESGCMWithData:plainTextData key:key];

    const NSUInteger ivLength = 12;
    const NSUInteger tagLength = 16;

    XCTAssertEqual(ivLength + plainTextData.length + tagLength, encryptedData.length);

    // Logic to slice up encryptedData copied from `[Cryptography decryptAESGCMWithData:key:]`

    // encryptedData layout: initializationVector || cipherText || authTag
    NSUInteger cipherTextLength = encryptedData.length - ivLength - tagLength;

    NSData *initializationVector = [encryptedData subdataWithRange:NSMakeRange(0, ivLength)];
    NSData *cipherText = [encryptedData subdataWithRange:NSMakeRange(ivLength, cipherTextLength)];
    NSData *authTag = [encryptedData subdataWithRange:NSMakeRange(ivLength + cipherTextLength, tagLength)];

    NSData *_Nullable decryptedData = [Cryptography decryptAESGCMWithInitializationVector:initializationVector
                                                                               ciphertext:cipherText
                                                                                  authTag:authTag
                                                                                      key:key];

    // Before we corrupt the tag, make sure we can decrypt the text as a sanity check to ensure we divided up the
    // encryptedData correctly.
    XCTAssert(decryptedData != nil);
    XCTAssertEqualObjects(
        @"Super🔥secret🔥test🔥data🏁🏁", [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding]);

    // Now that we know it decrypts, try again with a bogus authTag
    NSMutableData *bogusAuthTag = [authTag mutableCopy];

    // Corrupt one byte in the bogusAuthTag
    uint8_t flippedByte;
    [bogusAuthTag getBytes:&flippedByte length:1];
    flippedByte = flippedByte ^ 0xff;
    [bogusAuthTag replaceBytesInRange:NSMakeRange(0, 1) withBytes:&flippedByte];

    decryptedData = [Cryptography decryptAESGCMWithInitializationVector:initializationVector
                                                             ciphertext:cipherText
                                                                authTag:bogusAuthTag
                                                                    key:key];

    XCTAssertNil(decryptedData, @"Should have failed to decrypt");
}

@end

NS_ASSUME_NONNULL_END
