Can I get the modulus or exponent from a SecKeyRef object in Swift?
Asked Answered
D

9

28

In Swift, I created a SecKeyRef object by calling SecTrustCopyPublicKey on some raw X509 certificate data. This is what this SecKeyRef object looks like.

Optional(<SecKeyRef algorithm id: 1,
key type: RSAPublicKey,
version: 3, block size: 2048 bits,
exponent: {hex: 10001, decimal: 65537},
modulus: <omitted a bunch of hex data>,
addr: 0xsomeaddresshere>)

Basically, this SecKeyRef object holds a whole bunch of information about the public key, but there seems to be no way to actually convert this SecKeyRef into a string, NSData, or anything else (this is my goal, is just to get a base64 public key).

However, I have a function that I can give a modulus and an exponent, and it will just calculate what the public key is. I've tested it by passing in the data that's logged from the above SecKeyRef.

But somehow I can't access those properties from the SecKeyRef object (I can only see the whole object in the console; for example, I cannot do SecKeyRef.modulus or anything of the sort, it seems).

My question: how can I access SecKeyRef.modulus, or alternatively, convert this SecKeyRef into NSData or something similar? Thanks

Edit

(for more information)

I am creating my SecKeyRef dynamically, through this function I have:

func bytesToPublicKey(certData: NSData) -> SecKeyRef? {
    guard let certRef = SecCertificateCreateWithData(nil, certData) else { return nil }
    var secTrust: SecTrustRef?
    let secTrustStatus = SecTrustCreateWithCertificates(certRef, nil, &secTrust)
    if secTrustStatus != errSecSuccess { return nil }
    var resultType: SecTrustResultType = UInt32(0) // result will be ignored.
    let evaluateStatus = SecTrustEvaluate(secTrust!, &resultType)
    if evaluateStatus != errSecSuccess { return nil }
    let publicKeyRef = SecTrustCopyPublicKey(secTrust!)

    return publicKeyRef
}

What that does is takes the raw byte stream from a certificate (which can be broadcasted from, say, a piece of hardware using PKI), and then turns that into a SecKeyRef.

Edit 2

(comments on existing answers as of 7 January 2015)

This does not work:

let mirror = Mirror(reflecting: mySecKeyObject)

for case let (label?, value) in mirror.children {
    print (label, value)
}

This results in this output in the console:

Some <Raw SecKeyRef object>

Not sure what the string "Some" means.

Additionally, mirror.descendant("exponent") (or "modulus") results in nil, even though when printing the raw object in the console, I can clearly see that those properties exist, and that they are in fact populated.

Also, if at all possible, I would like to avoid having to save to the keychain, reading as NSData, and then deleting from the keychain. As stated in the bounty description, if this is the only way possible, please cite an authoritative reference. Thank you for all answers provided so far.

Dovecote answered 22/12, 2015 at 5:10 Comment(13)
Have you looked at this answer yet? Or any of the answers listed here? They are Obj-C but it is not difficult to translate.Heall
@Caleb, thanks for your comment. Unfortunately yes I've been combing through those for a few hours now, and haven't had much luck. They don't seem to be quite what I'm looking for exactly (they mostly deal with generating key pairs, it seems). Maybe I'm wrong.Dovecote
That is unfortunate. I could not really find anything else on this topic and anything recent has been ignored. I do not have any knowledge specific to this subject. Good luck and sorry I could not be of help!Heall
#27758946Himalayas
let mirrorKey = Mirror(reflecting: secKey); let exponent = mirrorKey.descendant("exponent"); let modulus = mirrorKey.descendant("modulus");Himalayas
Write it into keychain and you will get a NSData back. You can delete it after that. This way is also what I did in my production app.Sideward
Early on you say '...this is my goal, is just to get a base64 public key' and yet the function that is building the SecKeyRef is taking an array of bytes (in NSData form) as its parameter. If you want the public key in base64 you can get that from NSData.Binaural
@Binaural thanks for your response. How so? The array of bytes (in NSData form) is an entire X509 certificate.Dovecote
@JoshBeam My mistake, I misread that paragraph to mean that you were passing in public key in NSData form. Caffeine shortage. If you have the certificate with public and private keys, I am 100% certain I know how to get the public key out using obj-c. Give me a few to translate that to Swift.Binaural
@Binaural I know of caffeine shortage all too well. Thanks! Look forward to hearing back from you.Dovecote
only a side note: using SecKeyCopyModulus in an iOS app will b refused from Apple: I got from iTunesConnect: We have discovered one or more issues with your recent delivery for ".....". To process your delivery, the following issues must be corrected: Non-public API usage: The app references non-public symbols in .....: _SecKeyCopyModulus so be careful.Woodbridge
"I have a function that I can give a modulus and an exponent, and it will just calculate what the public key is" -- modulus and exponent together are the public key! The rest is only wrapping/dressing things up.Obscurant
@JoshBeam have u found the solution for this?Sweated
O
12

It is indeed possible to extract modulus and exponent using neither keychains nor private API.

There is the (public but undocumented) function SecKeyCopyAttributes which extracts a CFDictionary from a SecKey. A useful source for attribute keys is SecItemConstants.c

Inspecting the content of this dictionary, we find an entry "v_Data" : <binary>. Its content is DER-encoded ASN for

SEQUENCE {
    modulus           INTEGER, 
    publicExponent    INTEGER
}

Be aware that integers are padded with a zero byte if they are positive and have a leading 1-bit (so as not to confuse them with a two-complement negative number), so you may find one byte more than you expect. If that happens, just cut it away.

You can implement a parser for this format or, knowing your key size, hard-code the extraction. For 2048 bit keys (and 3-byte exponent), the format turns out to be:

30|82010(a|0)        # Sequence of length 0x010(a|0)
    02|82010(1|0)    # Integer  of length 0x010(1|0)
        (00)?<modulus>
    02|03            # Integer  of length 0x03
        <exponent>

For a total of 10 + 1? + 256 + 3 = 269 or 270 bytes.

import Foundation
extension String: Error {}

func parsePublicSecKey(publicKey: SecKey) -> (mod: Data, exp: Data) {
    let pubAttributes = SecKeyCopyAttributes(publicKey) as! [String: Any]

    // Check that this is really an RSA key
    guard    Int(pubAttributes[kSecAttrKeyType as String] as! String)
          == Int(kSecAttrKeyTypeRSA as String) else {
        throw "Tried to parse non-RSA key as RSA key"
    }

    // Check that this is really a public key
    guard    Int(pubAttributes[kSecAttrKeyClass as String] as! String) 
          == Int(kSecAttrKeyClassPublic as String) 
    else {
        throw "Tried to parse non-public key as public key"
    }

    let keySize = pubAttributes[kSecAttrKeySizeInBits as String] as! Int

    // Extract values
    let pubData  = pubAttributes[kSecValueData as String] as! Data
    var modulus  = pubData.subdata(in: 8..<(pubData.count - 5))
    let exponent = pubData.subdata(in: (pubData.count - 3)..<pubData.count) 

    if modulus.count > keySize / 8 { // --> 257 bytes
        modulus.removeFirst(1)
    }

    return (mod: modulus, exp: exponent)
}

(I ended up writing a full ASN parser, so this code is not tested, beware!)


Note that you can extract details of private keys in very much the same way. Using DER terminology, this is the format of v_Data:

PrivateKey ::= SEQUENCE {
    version           INTEGER,
    modulus           INTEGER,  -- n
    publicExponent    INTEGER,  -- e
    privateExponent   INTEGER,  -- d
    prime1            INTEGER,  -- p
    prime2            INTEGER,  -- q
    exponent1         INTEGER,  -- d mod (p-1) (dmp1)
    exponent2         INTEGER,  -- d mod (q-1) (dmq1)
    coefficient       INTEGER,  -- (inverse of q) mod p (coeff)
    otherPrimeInfos   OtherPrimeInfos OPTIONAL
 }

Parsing this by hand is probably ill-advised since any of the integers may have been padded.


Nota bene: The format of the public key is different if the key has been generated on macOS; the structure given above is wrapped like so:

SEQUENCE {
    id              OBJECTID,
    PublicKey       BITSTRING
}

The bit-string is DER-encoded ASN of the form above.

Obscurant answered 5/4, 2017 at 8:20 Comment(4)
Note bene: If you want to program more offensively, SecKeyCopyExternalRepresentation(publicKey, nil) gives you v_Data directly, as jamone notes.Obscurant
Cool, thanks for your insights! Is the full ASN parser you mentioned you wrote available somewhere?Quinte
@dnlggr So far, now. Thanks for the reminder; I'll ask if I'm allowed to open-source it. (It's not a complete or nice implementation, iirc, but most types are included.)Obscurant
Sweet! Thanks for looking into it.Quinte
C
9

Update The answer below might result in your app being rejected due to usage of non-public API's.

The answer lies in the SecRSAKey.h file from Apple's opensource website (Security is part of the code that Apple opensourced). The file is not big, and among other stuff it declares the following two important functions:

CFDataRef SecKeyCopyModulus(SecKeyRef rsaPublicKey);
CFDataRef SecKeyCopyExponent(SecKeyRef rsaPublicKey);

You can add those functions to your bridging header to be able to call them from Swift, also while doing this you can switch from CFDataRef to NSData* as the two types toll-free bridged:

NSData* SecKeyCopyModulus(SecKeyRef rsaPublicKey);
NSData* SecKeyCopyExponent(SecKeyRef rsaPublicKey);

Demo Swift usage:

let key = bytesToPublicKey(keyData)
let modulus = SecKeyCopyModulus(key)
let exponent = SecKeyCopyExponent(key)
print(modulus, exponent)

This is a private API though, and there might be a chance it will no longer be available at some point, however I looked over the versions of Security made public (http://www.opensource.apple.com/source/Security), and looks like the two functions are present in all of them. More, since Security is a critical component of the OS, it's unlikely Apple will do major changes over it.

Tested on iOS 8.1, iOS 9.2, and OSX 10.10.5, and the code works on all three platforms.

Cheri answered 14/1, 2016 at 7:43 Comment(10)
Thanks a lot for your answer Cristik, I appreciate the research that went into it. However, there is a comment in the source code in the link you provided: "Given an RSA public key in encoded form return a SecKeyRef representing that key." It looks like, in order to use SecKeyCopyModulus (for example), you must create your SecKeyRef object with uint8_t *keyData, which would imply I must already have the public key data (however, I do not have this; I simply have the raw bytes of an entire certificate). Did I overlook something?Dovecote
That comment is for SecKeyCreateRSAPublicKey, in case you create the key yourself. In your case you obtain the key with SecTrustCopyPublicKey, which extracts the key from the certificate loaded from file with SecCertificateCreateWithData.Cheri
So you do have the bytes of the key, they are stored within the SecKeyRef opaque struct. See SecKeyPriv.h - SecKeyRef is an alias to __SecKey, which has a void *key; field holding the raw data for the key.Cheri
@JoshBeam let me know if the last 2 comments clarify things, or not :)Cheri
Wow, I'm not sure how I missed that. In theory, this makes perfect sense. I'll need to do some actual tests to see if it works. Thanks again for you help. Regarding the bounty, unfortunately I already awarded it without realizing the additional parts to this answer that you have mentioned. My mistake.Dovecote
@JoshBeam looking forward to your results then, to find out if my efforts were or were not in vain :)Cheri
@Cheri You may be interested in this solution that does not need private API. (That said, I'd much rather use a single function than dig around in binary data manually...)Obscurant
Would like to inform everyone that as of today, your app will be blocked if you use that method. Happened to me just now. The app references non-public symbols in ___: _SecKeyCopyModulusTenno
@Tenno thanks for the hint, I added a disclaimer about this at the beginning of the answerCheri
Thanks @Cristik, it is a great answer regardless.Tenno
L
7

I've been down the same path trying to do SSL Public Key Pinning. The API's are pretty much non-existent, and the solution I found was to put it in the Keychain which you can then retrieve as NSData (which can then be Base64 Encoded). It's horrible but the only thing I could find after a day or so of research (without resorting to bundling OpenSSL with my app).

I ported some of my code over to Swift, but I haven't tested it very much so I'm not 100% sure that it works: https://gist.github.com/chedabob/64a4cdc4a1194d815814

It's based off this Obj-C code (which I'm confident works as it's in a production app): https://gist.github.com/chedabob/49eed109a3dfcad4bd41

Laban answered 1/1, 2016 at 23:3 Comment(3)
Thank you for your answer, I will take a look at those resources and let you know if I have any luck with them.Dovecote
Hello, I awarded the bounty because this is the best answer so far. However, I've opened another bounty because I'm hoping to find authoritative references and alternative methods (please see the new bounty description if you are interested). Thank you.Dovecote
I this answer is OK from Apple side? Or would I just get rejected by using it?Bristling
R
4

I found how to get data for a SecKey.

let publicKey: SecKey = ...
let data = SecKeyCopyExternalRepresentation(publicKey, nil)

This seems to work well and I have been able to successfully compare public keys.

This is in Swift 3 (Xcode 8 beta 3)

Revolutionary answered 20/7, 2016 at 16:38 Comment(3)
Thanks for the reply. What's the return value of SecKeyCopyExternalRepresentation?Dovecote
It's a CFData if let pinnedPublicKeyData = SecKeyCopyExternalRepresentation(pinnedPublicKey, nil), let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, nil), pinnedPublicKeyData == publicKeyData { // they match }Revolutionary
is there a way to do this for iOS versions older than iOS 10?Unison
E
3

SecKeyRefis a struct so there is a chance that it can be reflected with Mirror() to retrieve the wanted values.

struct myStruct {
let firstString = "FirstValue"
let secondString = "SecondValue"}

let testStruct = myStruct()
let mirror = Mirror(reflecting: testStruct)

for case let (label?, value) in mirror.children {
    print (label, value)
}

/**
Prints: 
firstString FirstValue
secondString SecondValue
*/
Exanthema answered 6/1, 2016 at 8:44 Comment(2)
Thank you for your answer, however Mirror does not work in this case. It seems like it should, but it does not actually show the properties inside the SecKey.Dovecote
Did you actually test this?Obscurant
B
3

I've found a single Obj-c re-implementation of the ASN.1 parser in an abandoned project, that appears to work. Problem is, it uses a great deal of pointer tricks that I don't know how to translate into Swift (not even sure some of it is possible). It should be possible to create a swift friendly wrapper around it, since the only input it takes is the NSData.

Everything on the net is using the store and retrieve in the Keychain trick to get to the pub key data, even really popular libs like TrustKit. I found reference in the Apple docs on SecKeyRef to the root cause (I think):

A SecKeyRef object for a key that is stored in a keychain can be safely cast to a SecKeychainItemRef for manipulation as a keychain item. On the other hand, if the SecKeyRef is not stored in a keychain, casting the object to a SecKeychainItemRef and passing it to Keychain Services functions returns errors.

Since SecCertificateCopyValues isn't available on iOS at this time, you're limited to either parsing the certificate data, or doing the Keychain Item shuffle.

Binaural answered 12/1, 2016 at 17:58 Comment(0)
P
2

Did you think about using SecCertificateCopyData()? The resulting CFData is toll-Free bridged, I think.

Refer to https://developer.apple.com/library/ios/documentation/Security/Reference/certifkeytrustservices/ to see the relevant documentation of the API.

Plain answered 7/1, 2016 at 16:2 Comment(4)
Thank you very much for your answer, but this does not work. Please let me know if I can offer you any additional information.Dovecote
What exactly is it that is not working? I remember a lot of casting and bending to get this to work with swift.Plain
I'm probably misunderstanding something. Would be able to provide a minimal example of how SecCertificateCopyData() can be used to extract either the public key from the SecKeyRef object, or the exponent/modulus? Thank you.Dovecote
I'm sorry; you would seem to need a SecCertificateRef for that. But you have one of those matching your public key in the trust you're copying the SecKeyRef from.Plain
S
1

From How do I encode an unmanaged<SecKey> to base64 to send to another server? :

func convertSecKeyToBase64(inputKey: SecKey) ->String? {
    // Add to keychain
    let tempTag = "net.example." + NSUUID().UUIDString
    let addParameters :[String:AnyObject] = [
        String(kSecClass): kSecClassKey,
        String(kSecAttrApplicationTag): tempTag,
        String(kSecAttrKeyType): kSecAttrKeyTypeRSA,
        String(kSecValueRef): inputKey,
        String(kSecReturnData):kCFBooleanTrue
    ]

    var result: String?
    var keyPtr: AnyObject?
    if (SecItemAdd(addParameters, &keyPtr) == noErr) {
        let data = keyPtr! as! NSData
        result = data.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
    }
    // Remove from Keychain:
    SecItemDelete(addParameters)
    return result
}

But if you want to avoid adding to keychain, you can use Mirror:

let mirrorKey = Mirror(reflecting: secKey)
let exponent = mirrorKey.descendant("exponent")
let modulus = mirrorKey.descendant("modulus");

[edit: Mirror not working according to Josh]

Sandstorm answered 6/1, 2016 at 9:28 Comment(4)
Thanks a lot for your answer. The Mirror, unfortunately, does not work. Meaning, mirrorKey.descendant("exponent") and mirrorKey.descendant("modulus") both result in nil.Dovecote
@JoshBeam, I'd like to help. How can I get a SecTrust object to test?Himalayas
Hey thanks a lot. I added something to the bottom of my question under "edit" which shows the function I'm using to generate the object. Basically, I start with a raw certificate byte stream, and I create a SecKeyRef from that. Currently, the only way I've been able to parse the public key from that is to literally go byte-by-byte and extract the public key (reverse engineered based on knowing what the public key should be since I already have that data for one of my certificates). The way that seems better is to use a SecKeyRef, thus I've asked this question.Dovecote
Please let me know if I can give you any more information.Dovecote
S
0

I wrote this one base on some other's answer in stackoverflow. Currently I am using it in my production but I am happy to use another solution that doesn't require to write into keychain.

- (NSData *)getPublicKeyBitsFromKey:(SecKeyRef)givenKey host:(NSString*)host {
    NSString *tag = [NSString stringWithFormat:@"%@.%@",[[NSBundle mainBundle] bundleIdentifier], host];
    const char* publicKeyIdentifier = [tag cStringUsingEncoding:NSUTF8StringEncoding];
    NSData *publicTag = [[NSData alloc] initWithBytes:publicKeyIdentifier length:strlen(publicKeyIdentifier) * sizeof(char)];

    OSStatus sanityCheck = noErr;
//    NSData * publicKeyBits = nil;
    CFTypeRef publicKeyBits;

    NSMutableDictionary * queryPublicKey = [[NSMutableDictionary alloc] init];

    // Set the public key query dictionary.
    [queryPublicKey setObject:(id)kSecClassKey forKey:(id)kSecClass];
    [queryPublicKey setObject:publicTag forKey:(id)kSecAttrApplicationTag];
    [queryPublicKey setObject:(id)kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType];
    [queryPublicKey setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnData];
    [queryPublicKey setObject:(__bridge id)givenKey forKey:(__bridge id)kSecValueRef];

    // Get the key bits.
    NSData *data = nil;
    sanityCheck = SecItemCopyMatching((CFDictionaryRef)queryPublicKey, &publicKeyBits);
    if (sanityCheck == errSecSuccess) {
        data = CFBridgingRelease(publicKeyBits);
        //I don't want to leak this information
        (void)SecItemDelete((__bridge CFDictionaryRef) queryPublicKey);
    }else {
        sanityCheck = SecItemAdd((CFDictionaryRef)queryPublicKey, &publicKeyBits);
        if (sanityCheck == errSecSuccess)
        {
            data = CFBridgingRelease(publicKeyBits);
            (void)SecItemDelete((__bridge CFDictionaryRef) queryPublicKey);
        }
    }

    return data;
}
Sideward answered 6/1, 2016 at 9:13 Comment(3)
Hi, thank you very much for your answer. Is this ported to Swift somewhere? Also, have you found an answer that doesn't require you saving to the keychain? Thank you.Dovecote
No, I am still looking for other solution. There are others answer that do the same thing by using swift you can use. I only make a check that the key exists in the keychain or not. I got a case that the key is added and doesn't get removed. It will cause the app fail to get the public key data after that.Sideward
@Sideward You may be interested in my answer. It is possible, by going the long route in some sense.Obscurant

© 2022 - 2024 — McMap. All rights reserved.