Secure Memory For Swift Objects
Asked Answered
F

2

31

I am writing a swift application that requires handling private keys in memory. Because of the sensitivity of such objects, the keys need to be cleared (a.k.a. written to all zeros) when the object is deallocated, and the memory cannot be paged to disk (which is typically done using mlock()).

In Objective-C, you can provide a custom CFAllocator object, which allows you to use your own functions to allocate/deallocate/reallocate the memory used by an object.

So one solution is to just implement a "SecureData" object in objective-c, which internally creates an NSMutableData object using a custom CFAllocator (also in objective-c).

However, is there any way for me to provide my own custom memory allocation functions for a pure swift object (for example, a struct or a [UInt8])? Or is there a better, "proper" way to implement secure memory like this in swift?

Fiver answered 31/12, 2014 at 4:6 Comment(5)
Why do you not just implement the deinit method of that object that holds the key and which clears the key?Erny
@0x7fffffff – what kind of additional info are you looking for over what my answer gave? I will have a go at adding it.Xerox
@AirspeedVelocity The answer you gave is already exceptional. If anything, I'm just looking for an additional example, or if possible, an explanation of how one might try to get around the issues associated with strings and arrays. (mostly just strings) Thanks for the follow up.Rubbery
@0x7fffffff Ah, gotcha. Thanks. I don’t think there’s any solution to the array/string problem other than using custom alternatives that are also secure, will clarify that. I have a bare-bones array equivalent that allocates its own memory that I could append (quite a lot of code though).Xerox
@AirspeedVelocity Perhaps a link to a gist/github repoFiver
X
43

If you want complete control over a region of memory you allocate yourself, you can use UnsafePointer and co:

// allocate enough memory for ten Ints
var ump = UnsafeMutablePointer<Int>.alloc(10)
// memory is in an uninitialized raw state

// initialize that memory with Int objects
// (here, from a collection)
ump.initializeFrom(reverse(0..<10))

// memory property gives you access to the underlying value
ump.memory // 9

// UnsafeMutablePointer acts like an IndexType
ump.successor().memory // 8
// and it has a subscript, but it's not a CollectionType
ump[3] // = 6

// wrap it in an UnsafeMutableBufferPointer to treat it
// like a collection (or UnsafeBufferPointer if you don't
// need to be able to alter the values)
let col = UnsafeMutableBufferPointer(start: ump, count: 10)
col[3] = 99
println(",".join(map(col,toString)))
// prints 9,8,7,99,5,4,3,2,1,0

ump.destroy(10)
// now the allocated memory is back in a raw state
// you could re-allocate it...
ump.initializeFrom(0..<10)
ump.destroy(10)

// when you're done, deallocate the memory
ump.dealloc(10)

You can also have UnsafePointer point to other memory, such as memory you’re handed by some C API.

UnsafePointer can be passed into C functions that take a pointer to a contiguous block of memory. So for your purposes, you could then pass this pointer into a function like mlock:

let count = 10
let ump = UnsafeMutablePointer.allocate<Int>(count)
mlock(ump, UInt(sizeof(Int) * count))
// initialize, use, and destroy the memory
munlock(ump, UInt(sizeof(Int) * count))
ump.dealloc(count)

You can even hold your own custom types:

struct MyStruct {
    let a: Int
    let b: Int
}

var pointerToStruct = UnsafeMutablePointer<MyStruct>.alloc(1)
pointerToStruct.initialize(MyStruct(a: 1, b: 2))
pointerToStruct.memory.b  // 2
pointerToStruct.destroy()
pointerToStruct.dealloc(1)

However be aware if doing this with classes, or even arrays or strings (or a struct that contains them), that all you will be holding in your memory is pointers to other memory that these objects allocate and own. If this matters to you (i.e. you are doing something special to this memory such as securing it, in your example), this is probably not what you want.

So either you need to use fixed-size objects, or make further use of UnsafePointer to hold pointers to more memory regions. If they don't need to dynamically resize, then just a single allocation of an unsafe pointer, possibly wrapped in a UnsafeBufferPointer for a collection interface, could do it.

If you need more dynamic behavior, below is a very bare-bones implementation of a collection that can resize as necessary, that could be enhanced to cover specialty memory-handling logic:

// Note this is a class not a struct, so it does NOT have value semantics,
// changing a copy changes all copies.
public class UnsafeCollection<T> {
    private var _len: Int = 0
    private var _buflen: Int = 0
    private var _buf: UnsafeMutablePointer<T> = nil

    public func removeAll(keepCapacity: Bool = false) {
        _buf.destroy(_len)
        _len = 0
        if !keepCapacity {
            _buf.dealloc(_buflen)
            _buflen = 0
            _buf = nil
        }
    }

    public required init() { }
    deinit { self.removeAll(keepCapacity: false) }

    public var count: Int { return _len }
    public var isEmpty: Bool { return _len == 0 }
}

To cover the requirements of MutableCollectionType (i.e. CollectionType plus assignable subscript):

extension UnsafeCollection: MutableCollectionType {
    typealias Index = Int
    public var startIndex: Int { return 0 }
    public var endIndex: Int { return _len }

    public subscript(idx: Int) -> T {
        get {
            precondition(idx < _len)
            return _buf[idx]
        }
        set(newElement) {
            precondition(idx < _len)
            let ptr = _buf.advancedBy(idx)
            ptr.destroy()
            ptr.initialize(newElement)
        }
    }

    typealias Generator = IndexingGenerator<UnsafeCollection>
    public func generate() -> Generator {
        return Generator(self)
    }
}

And ExtensibleCollectionType, to allow for dynamic growth:

extension UnsafeCollection: ExtensibleCollectionType {
    public func reserveCapacity(n: Index.Distance) {
        if n > _buflen {
            let newBuf = UnsafeMutablePointer<T>.alloc(n)
            newBuf.moveInitializeBackwardFrom(_buf, count: _len)
            _buf.dealloc(_buflen)
            _buf = newBuf
            _buflen = n
        }
    }

    public func append(x: T) {
        if _len == _buflen {
            reserveCapacity(Int(Double(_len) * 1.6) + 1)
        }
        _buf.advancedBy(_len++).initialize(x)
    }

    public func extend<S: SequenceType where S.Generator.Element == T>
      (newElements: S) {
        var g = newElements.generate()
        while let x: T = g.next() {
            self.append(x)
        }
    }
}
Xerox answered 31/12, 2014 at 12:53 Comment(2)
Thanks! This seems to get most of the way there. Is the memory allocated in a contiguous chunk? For example with this code: let myPtr = UnsafeMutablePointer<UInt8>.alloc(10) I would then do something like mlock(myPtr, 10) to prevent the memory from being paged to disk.Fiver
I believe so (also means you can pass it as an array-pointer-like object into C functions)Xerox
T
3

I know this question is old but something for those who land here: since iOS 10 you can use Secure Enclave to store private keys securely. The way it works is that all the operations that require decryption is performed inside the Secure Enclave so you do not have to worry about runtime hooking of your classes or memory leaks.

Take a look here: https://developer.apple.com/documentation/security/certificate_key_and_trust_services/keys/storing_keys_in_the_secure_enclave

Turboprop answered 6/7, 2020 at 14:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.