Search code examples
iossecurityswiftx509

Generate base64 url-encoded X.509 format 2048-bit RSA public key with Swift?


Working in Apple Swift for iOS. I have to generate this for the backend as it's a secure app.

I'm new to security and certificates and have been searching for a day now with no results.

How can I generate base64 url-encoded X.509 format 2048-bit RSA public key with swift?

Any help is highly appreciated.


Solution

  • There's a library for handling public-private key pairs in Swift that I recently created called Heimdall, which allows you to easily export the X.509 formatted Base64 string of the public key.

    To comply with SO rules, I will also include the implementation in this answer (so that it is self-explanatory)

    public func X509PublicKey() -> NSString? {
        // Fetch the key, so that key = NSData of the public key
        let result = NSMutableData()
    
        let encodingLength: Int = {
            if key.length + 1 < 128 {
                return 1
            } else {
                return ((key.length + 1) / 256) + 2
            }
        }()
    
        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 + key.length
        let encodedSize = encodeLength(size)
        builder.extend(encodedSize)
        result.appendBytes(builder, length: builder.count)
        result.appendBytes(OID, length: OID.count)
        builder.removeAll(keepCapacity: false)
    
        builder.append(0x03)
        builder.extend(encodeLength(key.length + 1))
        builder.append(0x00)
        result.appendBytes(builder, length: builder.count)
    
        // Actual key bytes
        result.appendData(key)
    
        // Convert to Base64 and make safe for URLs
        var string = result.base64EncodedStringWithOptions(.allZeros)
        string = string.stringByReplacingOccurrencesOfString("/", withString: "_")
        string = string.stringByReplacingOccurrencesOfString("+", withString: "-")
    
        return string
    }
    
    public func encodeLength(length: Int) -> [CUnsignedChar] {
        if length < 128 {
            return [CUnsignedChar(length)];
        }
    
        var i = (length / 256) + 1
        var len = length
        var result: [CUnsignedChar] = [CUnsignedChar(i + 0x80)]
    
        for (var j = 0; j < i; j++) {
            result.insert(CUnsignedChar(len & 0xFF), atIndex: 1)
            len = len >> 8
        }
    
        return result
    }
    

    I have removed the data fetching code, refer to either source of Heimdall or Jeff Hay's answer