Search code examples
swiftrsax509certificate

How do I parse an x509 certificate and extract its key's signature in Swift?


I'm trying to generate an RSA pair key, then add an X.509 certificate to the header of Public Key. For this, I'm using library SwiftyRSA

The problem is that when I add the X.509 certificate to the header I cannot parse the Public Key. When I print the Public Key after that, it still shows the same as it was without the certificate.

But when I print bytes it shows different bytes, without the certificate it shows 270 bytes, with the certificate it shows 294bytes. This means it's adding the certificate but it's not parsing it with the certificate.

After reading deeper in SwiftyRSA library, it says:

Warning : Storing (with SwiftyRSA's methods) or creating a PublicKey instance will automatically strip the header from the key. For more info, see Under the hood above.

If worth mentioning even the case that is still Opened there.

I'm still looking for a solution but cannot find any. The code that I tried to implement:

    if let password = passwordTextField.text {

        //Generate RSA
        guard let keyPair = try? SwiftyRSA.generateRSAKeyPair(sizeInBits: 2048),
              let privateKeyPem = try? keyPair.privateKey.pemString(),
              let publicKeyPem = try? keyPair.publicKey.pemString()
        else {
            return
        }
        
        /// Generate Certificate in format X.509 from Public Key
        let publicKey = try! PublicKey(data: keyPair.publicKey.data())
        let publicKeyData = try! keyPair.publicKey.data()
        let publicKey_with_X509_header = try! SwiftyRSA.prependX509KeyHeader(keyData: publicKeyData)
        
        let publicKey509 = try! PublicKey(data: publicKey_with_X509_header)
        
        print(try! publicKey.pemString()) // Without Certificate
        print(try! publicKey509.pemString()) // With Certificate
        // These two print results are completely the same, but it should be different.

        // Encrypt private key
        let salt = String.random(length: 32)
        let aesKey = Array(String((password + salt).prefix(32)).utf8)
        let iv = [UInt8](String(salt.prefix(16)).utf8)
        guard let aes = try? AES(key: aesKey, blockMode: CBC(iv: iv), padding: .pkcs7),
              let inputData = privateKeyPem.data(using: .utf8),
              let encryptedBytes = try? aes.encrypt(inputData.bytes)
        else {
            return
        }

        let encryptedData = NSData(bytes: encryptedBytes, length: encryptedBytes.count)
        let base64String = encryptedData.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))

        // Print keys and salt
        print(try! publicKey509.pemString())
        print(base64String)
        print(salt)
        
    }

How can I parse the Public Key with an x509 certificate? I would appreciate any contribution.


Solution

  • As mentioned above, you won't be able to create x509 certificate with SwiftyRSA. You can actually do this with Security framework.

    First, you need to create an extension of Data, which will be used to add the certificate to the public key.

    extension Data {
        public func dataByPrependingX509Header() -> Data { 
            let result = NSMutableData()
            let encodingLength: Int = (self.count + 1).encodedOctets().count
            let OID: [CUnsignedChar] = [0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00]
            var builder: [CUnsignedChar] = []
            
            // ASN.1 SEQUENCE
            builder.append(0x30)
            
            // Overall size, made of OID + bitstring encoding + actual key
            
            let size = OID.count + 2 + encodingLength + self.count
            let encodedSize = size.encodedOctets()
            builder.append(contentsOf: encodedSize)
            result.append(builder, length: builder.count)
            result.append(OID, length: OID.count)
            builder.removeAll(keepingCapacity: false)
            builder.append(0x03)
            builder.append(contentsOf: (self.count + 1).encodedOctets())
            builder.append(0x00)
            result.append(builder, length: builder.count)
            
            // Actual key bytes
            result.append(self)
            return result as Data
            
        }
        
    }
    

    Then, make sure you import Security and, you change your code to this:

        // Generate keys
        if let password = passwordTextField.text {
            //Generate RSA
            let attributes: [String: Any] = [
                String(kSecAttrKeyType):            kSecAttrKeyTypeRSA,
                String(kSecAttrKeySizeInBits):      4096, // you can change the size to 2048, but 4096 has higher security.
                String(kSecPrivateKeyAttrs): [
                    String(kSecAttrIsPermanent):    true,
                    String(kSecAttrApplicationTag): "com.example.keys.mykey"]
            ]
            var error: Unmanaged<CFError>?
            
            // Private Key
            guard let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) else {
                fatalError("Error: \(error.debugDescription)")
            }
            
            let privateKeyData = SecKeyCopyExternalRepresentation(privateKey, &error)
            let privData = privateKeyData as Data?
            let privateKeyString = privData!.base64EncodedString()
            print("Private Key")
            print(privateKeyString)
    
            // You'll probably need to structure Private and public key to go in new line every time after 76 character
            let structuredPrivateKeyString = privateKeyString.inserting(separator: "\n", every: 76)
            let privateKeyFinal = "-----BEGIN RSA PRIVATE KEY-----\n" + structuredPrivateKeyString + "\n\n-----END RSA PRIVATE KEY-----" 
            print(privateKeyFinal)
            
            // Public Key
            let publicKey = SecKeyCopyPublicKey(privateKey) // generate public key from private key
            let publicKeyData = SecKeyCopyExternalRepresentation(publicKey!, &error)
            let pubData = publicKeyData as Data? // We need to turn it into data so we can add the certificate or print it as string
            print("Public Key")
            print(pubData!.base64EncodedString())
            
            // Add x509 certificate to public key
            let publicKeyX509 = SecKeyCopyPublicKey(privateKey)
            let publicKeyDataX509 = SecKeyCopyExternalRepresentation(publicKeyX509!, &error)
            let pubData1 = publicKeyDataX509 as Data?
            let x509Data = pubData1!.dataByPrependingX509Header() // Here we add the certificate (Function that we created previosly in Data extension).
            var publicKeyX509String = x509Data.base64EncodedString()
            print("Public Key x509")
            print(publicKeyX509String)
            
            let structuredPublicKeyX509String = publicKeyX509String.inserting(separator: "\n", every: 76)
            
            let publicKeyFinal = "-----BEGIN RSA PUBLIC KEY-----\n\(structuredPublicKeyX509String)\n\n-----END RSA PUBLIC KEY-----"
            print("Public Key for the API")
            print(publicKeyFinal)
            
            // Encrypt private key
            let salt = String.random(length: 32)
            let aesKey = Array(String((password + salt).prefix(32)).utf8)
            let iv = [UInt8](String(salt.prefix(16)).utf8)
            guard let aes = try? AES(key: aesKey, blockMode: CBC(iv: iv), padding: .pkcs7),
                  let inputData = privateKeyFinal.data(using: .utf8),
                  let encryptedBytes = try? aes.encrypt(inputData.bytes)
            else {
                setLoadingView(visible: false)
                return
            }
    
            let encryptedData = NSData(bytes: encryptedBytes, length: encryptedBytes.count)
            let base64String = encryptedData.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
    
            // Print keys and salt
            print(publicKeyFinal)
            print(base64String)
            print(salt)
        }
    

    Note: I added a comment on each line that I thought is important, so you will now what's happening.