Browse Source

raw and digest_pkcs1v15_RAW signature variant support.

Brandon Toms 3 years ago
parent
commit
0a426d437f
2 changed files with 119 additions and 35 deletions
  1. 74 19
      Sources/CryptoSwift/RSA/RSA+Signature.swift
  2. 45 16
      Tests/CryptoSwiftTests/RSATests.swift

+ 74 - 19
Sources/CryptoSwift/RSA/RSA+Signature.swift

@@ -37,7 +37,7 @@ extension RSA: Signature {
     /// Calculate the Signature
     let signedData = BigUInteger(Data(hashedAndEncoded)).power(d, modulus: self.n).serialize().bytes
 
-    return signedData
+    return variant.formatSignedBytes(signedData, blockSize: self.keySize / 8)
   }
 
   public func verify(signature: ArraySlice<UInt8>, for expectedData: ArraySlice<UInt8>) throws -> Bool {
@@ -56,7 +56,13 @@ extension RSA: Signature {
     /// Step 1: Ensure the signature is the same length as the key's modulus
     guard signature.count == (self.keySize / 8) || (signature.count - 1) == (self.keySize / 8) else { throw Error.invalidSignatureLength }
 
-    let expectedData = try Array<UInt8>(RSA.hashedAndEncoded(bytes, variant: variant, keySizeInBytes: self.keySize / 8).dropFirst())
+    //print(try Array<UInt8>(RSA.hashedAndEncoded(bytes, variant: variant, keySizeInBytes: self.keySize / 8)))
+    //print(try Array<UInt8>(RSA.hashedAndEncoded(bytes, variant: variant, keySizeInBytes: self.keySize / 8)).count)
+    var expectedData = try RSA.hashedAndEncoded(bytes, variant: variant, keySizeInBytes: self.keySize / 8)
+    if expectedData.count == self.keySize / 8 && expectedData.prefix(1) == [0x00] { expectedData = Array(expectedData.dropFirst()) }
+    //print(expectedData)
+    //print(expectedData.count)
+    //let expectedData = try Array<UInt8>(RSA.hashedAndEncoded(bytes, variant: variant, keySizeInBytes: self.keySize / 8))
 
     /// Step 2: 'Decrypt' the signature
     let signatureResult = BigUInteger(Data(signature)).power(self.e, modulus: self.n).serialize().bytes
@@ -74,20 +80,23 @@ extension RSA: Signature {
     /// 1.  Apply the hash function to the message M to produce a hash
     let hashedMessage = variant.calculateHash(bytes)
 
-    guard variant.enforceLength(hashedMessage) else { throw RSA.Error.invalidMessageLengthForSigning }
-
+    guard variant.enforceLength(hashedMessage, keySizeInBytes: keySizeInBytes) else { throw RSA.Error.invalidMessageLengthForSigning }
+    
     /// 2. Encode the algorithm ID for the hash function and the hash value into an ASN.1 value of type DigestInfo
     /// PKCS#1_15 DER Structure (OID == sha256WithRSAEncryption)
-    let asn: ASN1.Node = .sequence(nodes: [
-      .sequence(nodes: [
-        .objectIdentifier(data: Data(variant.identifier)),
-        .null
-      ]),
-      .octetString(data: Data(hashedMessage))
-    ])
-
-    let t = ASN1.Encoder.encode(asn)
+//    let asn: ASN1.Node = .sequence(nodes: [
+//      .sequence(nodes: [
+//        .objectIdentifier(data: Data(variant.identifier)),
+//        .null
+//      ]),
+//      .octetString(data: Data(hashedMessage))
+//    ])
+//
+//    let t = ASN1.Encoder.encode(asn)
+    let t = variant.encode(hashedMessage)
 
+    if case .raw = variant { return t }
+    
     /// 3.  If emLen < tLen + 11, output "intended encoded message length too short" and stop
     //print("Checking Key Size: \(keySizeInBytes) < \(t.count + 11)")
     if keySizeInBytes < t.count + 11 { throw RSA.Error.invalidMessageLengthForSigning }
@@ -108,6 +117,8 @@ extension RSA: Signature {
 
 extension RSA {
   public enum SignatureVariant {
+    /// rsaSignatureRaw
+    case raw
     /// Hashes the raw message using MD5 before signing the data
     case message_pkcs1v15_MD5
     /// Hashes the raw message using SHA1 before signing the data
@@ -145,7 +156,7 @@ extension RSA {
 
     internal var identifier: Array<UInt8> {
       switch self {
-        case .digest_pkcs1v15_RAW: return []
+        case .raw, .digest_pkcs1v15_RAW: return []
         case .message_pkcs1v15_MD5, .digest_pkcs1v15_MD5: return Array<UInt8>(arrayLiteral: 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05)
         case .message_pkcs1v15_SHA1, .digest_pkcs1v15_SHA1: return Array<UInt8>(arrayLiteral: 0x2b, 0x0e, 0x03, 0x02, 0x1a)
         case .message_pkcs1v15_SHA256, .digest_pkcs1v15_SHA256: return Array<UInt8>(arrayLiteral: 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01)
@@ -175,7 +186,8 @@ extension RSA {
           return Digest.sha2(bytes, variant: .sha224)
         case .message_pkcs1v15_SHA512_256:
           return Digest.sha2(bytes, variant: .sha256)
-        case .digest_pkcs1v15_RAW,
+        case .raw,
+             .digest_pkcs1v15_RAW,
              .digest_pkcs1v15_MD5,
              .digest_pkcs1v15_SHA1,
              .digest_pkcs1v15_SHA224,
@@ -188,8 +200,10 @@ extension RSA {
       }
     }
 
-    internal func enforceLength(_ bytes: Array<UInt8>) -> Bool {
+    internal func enforceLength(_ bytes: Array<UInt8>, keySizeInBytes:Int) -> Bool {
       switch self {
+        case .raw, .digest_pkcs1v15_RAW:
+          return bytes.count <= (keySizeInBytes)
         case .digest_pkcs1v15_MD5:
           return bytes.count <= 16
         case .digest_pkcs1v15_SHA1:
@@ -215,14 +229,55 @@ extension RSA {
              .message_pkcs1v15_SHA512_224,
              .message_pkcs1v15_SHA512_256:
           return true
-        default:
-          return false
       }
     }
+    
+    internal func encode(_ bytes:Array<UInt8>) -> Array<UInt8> {
+      switch self {
+      case .raw, .digest_pkcs1v15_RAW:
+        return bytes
+        
+      default:
+        let asn: ASN1.Node = .sequence(nodes: [
+          .sequence(nodes: [
+            .objectIdentifier(data: Data(self.identifier)),
+            .null
+          ]),
+          .octetString(data: Data(bytes))
+        ])
+
+        return ASN1.Encoder.encode(asn)
+      }
+      
+    }
 
     /// Right now the only Padding Scheme supported is [EMCS-PKCS1v15](https://www.rfc-editor.org/rfc/rfc8017#section-9.2) (others include [EMSA-PSS](https://www.rfc-editor.org/rfc/rfc8017#section-9.1))
     internal func pad(bytes: Array<UInt8>, to blockSize: Int) -> Array<UInt8> {
-      return Padding.emsa_pkcs1v15.add(to: bytes, blockSize: blockSize)
+      switch self {
+      case .raw:
+        return bytes
+      default:
+        return Padding.emsa_pkcs1v15.add(to: bytes, blockSize: blockSize)
+      }
+    }
+    
+    /// Zero pads a signature to the specified block size
+    /// - Parameters:
+    ///   - bytes: The signed bytes
+    ///   - blockSize: The block size to pad until
+    /// - Returns: A zero padded (prepended) bytes array of length blockSize
+    internal func formatSignedBytes(_ bytes: Array<UInt8>, blockSize: Int) -> Array<UInt8> {
+      switch self {
+      //case .raw:
+      
+      default:
+        // Format the encrypted bytes before returning
+        var bytes = bytes
+        while bytes.count != blockSize {
+          bytes.insert(0x00, at: 0)
+        }
+        return bytes
+      }
     }
   }
 }

+ 45 - 16
Tests/CryptoSwiftTests/RSATests.swift

@@ -240,6 +240,29 @@ final class RSATests: XCTestCase {
       XCTFail("\(error)")
     }
   }
+  
+  /// This test focuses on ensuring that the signature & signature verification works as expected.
+  ///
+  /// This test enforces that
+  /// 1) We can sign and then verify a random integer
+  /// 2) if we modify the signature or the message in any way, the verify function returns false or throws an error.
+  func testSignatureVerificationRandomIntegers() {
+    do {
+      let rsa = try RSA(keySize: 1024)
+
+      for _ in 0..<5 {
+        let message = BigUInteger.randomInteger(withMaximumWidth: 256).serialize().bytes
+
+        let signature = try rsa.sign(message, variant: .message_pkcs1v15_SHA256)
+        XCTAssertTrue(try rsa.verify(signature: signature, for: message, variant: .message_pkcs1v15_SHA256), "Failed to Verify Signature for `\(message)`")
+        XCTAssertFalse(try rsa.verify(signature: signature, for: message.shuffled()))
+        XCTAssertFalse(try rsa.verify(signature: signature.shuffled(), for: message))
+        XCTAssertThrowsError(try rsa.verify(signature: signature.dropLast(), for: message))
+      }
+    } catch {
+      XCTFail("\(error)")
+    }
+  }
 
   /// This test walks through the PKCS1 Signature scheme manually
   ///
@@ -351,7 +374,7 @@ final class RSATests: XCTestCase {
   ///   - Ensure that we can verify that the signed data was in fact signed with this public keys corresponding private key
   func testRSAKeys() {
     // These tests can take a very long time. Therefore the larger keys have been commented out in order to make the tests complete a little quicker.
-    let fixtures = [TestFixtures.RSA_1024, TestFixtures.RSA_1056, TestFixtures.RSA_2048] //, TestFixtures.RSA_3072, TestFixtures.RSA_4096]
+    let fixtures = [TestFixtures.RSA_1024, TestFixtures.RSA_1056, TestFixtures.RSA_2048]//, TestFixtures.RSA_3072, TestFixtures.RSA_4096]
 
     do {
       /// Public Key Functionality
@@ -387,6 +410,8 @@ final class RSATests: XCTestCase {
               print("Warning::RSA<\(fixture.keySize)>::Skipping Encryption Algorithm \(test.key)")
               continue
             }
+            
+            //print("Testing \(rsa) Encryption<\(variant)> - Encrypting Message `\(message.key)`")
 
             if variant == .raw {
               if test.value == "" {
@@ -421,22 +446,24 @@ final class RSATests: XCTestCase {
               continue
             }
 
+            //print("Testing \(rsa) Signature<\(variant)> - Signing Message `\(message.key)`")
+            
             // Signing data requires access to the private key, therefore this should throw an error when called on a public key
             XCTAssertThrowsError(try rsa.sign(message.key.bytes, variant: variant))
 
             // Sometimes the message is too long to be safely signed by our key. When this happens we should encouter an error and our test value should be empty.
             if test.value == "" {
-              XCTAssertThrowsError(try rsa.verify(signature: Data(base64Encoded: test.value)!.bytes, for: message.key.bytes, variant: variant))
+              XCTAssertThrowsError(try rsa.verify(signature: Data(base64Encoded: test.value)!.bytes, for: message.key.bytes, variant: variant), "Signature<\(test.key)>::Did not throw error")
             } else {
               // Ensure the signature is valid for the test fixtures rawMessage
               XCTAssertTrue(try rsa.verify(signature: Data(base64Encoded: test.value)!.bytes, for: message.key.bytes, variant: variant), "Signature<\(test.key)>::Verification Failed")
               // Ensure a modifed message results in a false / invalid signature verification
-              XCTAssertFalse(try rsa.verify(signature: Data(base64Encoded: test.value)!.bytes, for: message.key.bytes + [0x00], variant: variant), "Signature<\(test.key)>::Verified a False signature for message `\(message.key)`")
+              XCTAssertFalse(try rsa.verify(signature: Data(base64Encoded: test.value)!.bytes, for: message.key.bytes + [0x00], variant: variant), "Signature<\(test.key)>::Verified a signature for an incorrect message `\(message.key)`")
               if !message.key.bytes.isEmpty {
-                XCTAssertFalse(try rsa.verify(signature: Data(base64Encoded: test.value)!.bytes, for: message.key.bytes.dropLast(), variant: variant))
+                XCTAssertFalse(try rsa.verify(signature: Data(base64Encoded: test.value)!.bytes, for: message.key.bytes.dropLast(), variant: variant), "Signature<\(test.key)>::Verified a signature for an incorrect message `\(message.key)`")
               }
-              // Ensure a modifed signature results in a false / invalid signature verification
-              XCTAssertFalse(try rsa.verify(signature: Data(base64Encoded: test.value)!.bytes.shuffled(), for: message.key.bytes, variant: variant), "Signature<\(test.key)>::Verified a False signature for message `\(message.key)`")
+              // Ensure a modifed signature results in a false / invalid signature verification (we replace the last element with a 1 in case the signature is all 0's)
+              XCTAssertFalse(try rsa.verify(signature: Data(base64Encoded: test.value)!.bytes.shuffled().dropLast() + [0x01], for: message.key.bytes, variant: variant), "Signature<\(test.key)>::Verified a False signature for message `\(message.key)`")
               // Ensure an invalid signature results in an error being thrown
               XCTAssertThrowsError(try rsa.verify(signature: Data(base64Encoded: test.value)!.bytes.dropLast(), for: message.key.bytes, variant: variant), "Signature<\(test.key)>::Verified a False signature for message `\(message.key)`")
             }
@@ -479,6 +506,8 @@ final class RSATests: XCTestCase {
               continue
             }
 
+            //print("Testing \(rsa) Encryption<\(variant)> - Encrypting Message `\(message.key)`")
+            
             if variant == .raw {
               if test.value == "" {
                 XCTAssertThrowsError(try rsa.encrypt(message.key.bytes, variant: variant))
@@ -517,26 +546,26 @@ final class RSATests: XCTestCase {
               continue
             }
 
-            print("Testing \(rsa) Signature<\(variant)>")
+            //print("Testing \(rsa) Signature<\(variant)> - Signing Message `\(message.key)`")
 
             // Our Message is too long for some of our hashing / padding schemes. When this happens we should encouter an error and our test value should be empty.
             if test.value == "" {
-              XCTAssertThrowsError(try rsa.sign(message.key.bytes, variant: variant))
+              XCTAssertThrowsError(try rsa.sign(message.key.bytes, variant: variant), "Signature<\(test.key)>::Did not throw error")
             } else {
               let signature = try rsa.sign(message.key.bytes, variant: variant)
               XCTAssertEqual(signature, Data(base64Encoded: test.value)?.bytes)
 
               // Ensure the signature is valid for the test fixtures rawMessage
-              XCTAssertTrue(try rsa.verify(signature: Data(base64Encoded: test.value)!.bytes, for: message.key.bytes, variant: variant))
+              XCTAssertTrue(try rsa.verify(signature: Data(base64Encoded: test.value)!.bytes, for: message.key.bytes, variant: variant), "Signature<\(test.key)>::Verification Failed")
               // Ensure a modifed message results in a false / invalid signature verification
-              XCTAssertFalse(try rsa.verify(signature: Data(base64Encoded: test.value)!.bytes, for: message.key.bytes + [0x00], variant: variant))
+              XCTAssertFalse(try rsa.verify(signature: Data(base64Encoded: test.value)!.bytes, for: message.key.bytes + [0x00], variant: variant), "Signature<\(test.key)>::Verified a signature for an incorrect message `\(message.key)`")
               if !message.key.bytes.isEmpty {
-                XCTAssertFalse(try rsa.verify(signature: Data(base64Encoded: test.value)!.bytes, for: message.key.bytes.dropLast(), variant: variant))
+                XCTAssertFalse(try rsa.verify(signature: Data(base64Encoded: test.value)!.bytes, for: message.key.bytes.dropLast(), variant: variant), "Signature<\(test.key)>::Verified a signature for an incorrect message `\(message.key)`")
               }
-              // Ensure a modifed signature results in a false / invalid signature verification
-              XCTAssertFalse(try rsa.verify(signature: Data(base64Encoded: test.value)!.bytes.shuffled(), for: message.key.bytes, variant: variant))
+              // Ensure a modifed signature results in a false / invalid signature verification (we replace the last element with a 1 in case the signature is all 0's)
+              XCTAssertFalse(try rsa.verify(signature: Data(base64Encoded: test.value)!.bytes.shuffled().dropLast() + [0x01], for: message.key.bytes, variant: variant), "Signature<\(test.key)>::Verified a False signature for message `\(message.key)`")
               // Ensure an invalid signature results in an error being thrown
-              XCTAssertThrowsError(try rsa.verify(signature: Data(base64Encoded: test.value)!.bytes.dropLast(), for: message.key.bytes, variant: variant))
+              XCTAssertThrowsError(try rsa.verify(signature: Data(base64Encoded: test.value)!.bytes.dropLast(), for: message.key.bytes, variant: variant), "Signature<\(test.key)>::Verified a False signature for message `\(message.key)`")
             }
           }
         }
@@ -552,9 +581,9 @@ final class RSATests: XCTestCase {
   private func signatureAlgoToVariant(_ algoString: String) -> RSA.SignatureVariant? {
     switch algoString {
       case "algid:sign:RSA:raw":
-        return nil
+        return .raw
       case "algid:sign:RSA:digest-PKCS1v15":
-        return nil // No Object Identifier
+        return .digest_pkcs1v15_RAW
       case "algid:sign:RSA:digest-PKCS1v15:SHA1":
         return .digest_pkcs1v15_SHA1
       case "algid:sign:RSA:digest-PKCS1v15:SHA224":