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

Josh Beam picture Josh Beam · Dec 22, 2015 · Viewed 7.3k times · Source

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.

Answer

Raphael picture Raphael · Apr 5, 2017

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.