Max Fratane
Max Fratane

Reputation: 876

iOS generating invalid signature with SecureEnclave protected key pair

I'm facing an issue with 0.01% of user base having problems generating signature. Basically, they are able to generate key pair but the resulting signature is invalid when validated with the public key.

The signature is being created with the following code.

  @objc public func signData(
    _ text: String,
    resolver resolve: RCTPromiseResolveBlock,
    rejecter reject: RCTPromiseRejectBlock
  ) -> Void {
    guard let privateKey = self.getPrivateKey() else {
      reject(nil, "error fetching private key", nil)
      return
    }

    let data =  text.data(using: .utf8)!

    var error: Unmanaged<CFError>?
    let signature = SecKeyCreateSignature(privateKey, .ecdsaSignatureMessageX962SHA256, data as CFData, &error) as Data?
    if error != nil {
      reject(nil, "Error in creating signature", nil)
      return
    }
    
    resolve(signature!.base64EncodedString())
  }

The keypair is generated as follows:

  @objc public func generateKeyPair(
    _ resolve: RCTPromiseResolveBlock,
    rejecter reject: RCTPromiseRejectBlock
  ) -> Void {
    let access = SecAccessControlCreateWithFlags(
      kCFAllocatorDefault,
      kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
      .privateKeyUsage,
      nil)!

    let attributes: NSDictionary = [
      kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
      kSecAttrKeySizeInBits: 256,
      kSecAttrTokenID: kSecAttrTokenIDSecureEnclave, // Enable secure enclave
      kSecPrivateKeyAttrs: [
        kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, // Do not share across devices with same appleid
        kSecAttrIsPermanent: true,
        kSecAttrApplicationTag: privateKeyTag,
        kSecAttrAccessControl: access
      ],
      kSecPublicKeyAttrs: [
        kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, // Do not share across devices with same appleid
        kSecAttrIsPermanent: true,
        kSecAttrApplicationTag: publicKeyTag,
      ]
    ]
    
    var error: Unmanaged<CFError>?
    guard let privateKey = SecKeyCreateRandomKey(attributes, &error) else {
      reject(nil, "error creating private key", error!.takeRetainedValue() as Error)
      return
    }
    
    let publicKey = SecKeyCopyPublicKey(privateKey)
    if publicKey == nil {
      reject(nil, "error creating public key", nil)
      return
    }

    resolve(self.PEMFormattedPublicKey(key: publicKey!))
  }
  private func PEMFormattedPublicKey(key: SecKey) -> String? {
    guard let publicKeyData = self.dataForKey(key: key) else {
      return nil
    }
    
    // ans1 headers format. From https://lapo.it/asn1js/#MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEo0WcFrFClvBE8iZ1Gdlj-mTgZoJWMNE3kDKTfRny2iaguwuSYxo-jCnXqzkR-kyFK9CR3D-pypD1sGb1BnfWAA
    // Requred for 256 EC keys
    let ecHeader: [UInt8] = [
        0x30, 0x59, // Sequence
        0x30, 0x13, // Algorithm identifier
        0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, // (ANSI X9.62 public key type)
        0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, // (ANSI X9.62 named elliptic curve)
        0x07, 0x03, 0x42, 0x00 // bit headers
    ]

    var asn1 = Data()
    asn1.append(Data(ecHeader))
    asn1.append(publicKeyData as Data)
    
    let encoded = asn1.base64EncodedString(options: .lineLength64Characters)
    let pemString = "-----BEGIN PUBLIC KEY-----\r\n\(encoded)\r\n-----END PUBLIC KEY-----\r\n"
    
    return pemString
  }

And in the backend service, the check is made with the following code:

 const publicKey = Buffer.from(publicKeyB64, 'base64').toString('utf-8');
 const key = {
   key: publicKey,
 };
 const verifier = createVerify('SHA256');
 verifier.update(challenge);

 return verifier.verify(key, digest, 'base64');

This is strange since is only happening to a very few users. iOS versions ranging from 17 to 18.1.

Sample of corrupted digest

challenge: ac13288a-3ef7-4eb8-9198-5cb7f3a18665

signature: MEUCIEQMq6unQLES+vBMhalDG0ZxTX9TNWukb63Qqd7W4ReRAiEAyqTwMMQC/HkU4AbLmwbBTm6DBFDpA8yo+3Cigg0H0VA=

publicKeyB64: LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0NCk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRXJENjNrSXdRaC83bVNia1R1QllUUEJtSGdOVmwNCnJueE4rNGVER2pHSDNRZmdVaGhKNlZYTjVSWTZhVW9iRjd3MjdFQXMwZ0JHaTdjOXdPdndQbmhxNEE9PQ0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tDQo=

Upvotes: 0

Views: 66

Answers (0)

Related Questions