Using an RSA public key generated by OpenSSL in iOS

Apple have gone to pains to make crypto in iOS (and MacOS in general) secure, building a layer between applications and the low-level stuff, like OpenSSL. The principle is to keep these functions in separate address space thus significantly reducing the surface area available for malicious code to find a weakness. In iOS this separation is enforced and, significantly, the documentation is sparse and terse. Public/private key use without also using certificates is mentioned but only in the context of using keys generated on the device. Posts on the Apple Developer forums indicate that using certificates is suggested because using public/private key pairs is “involved”. It turns out that the reason it’s involved is because of some odd implementation details and the aforementioned lack of documentation or useful examples.

I was developing a mechanism to verify some data that was generated outside the device. Using a simple key pair generated by OpenSSL at a command line it was very simple to create scripts in Perl and PHP to produce (and sign) and then decode (and validate) some data using this key pair. The functions to add a public or a private key to the keychain are there in iOS but they don’t work as expected.

It’s all in the format

There are many ways to transmit a key for use by another system but there are also some defacto standards. For an RSA public or private key, which has a pretty straightforward internal structure, that would be defined by PKCS#1.

There are two common forms to transmitting and using an RSA public or private key. ASN.1 DER – which is a non-displayable (i.e., not ASCII) format, or the same thing but base64 encoded and referred to as PEM – which is 7-bit ASCII and thus displayable. The base64 format is usually wrapped with something like

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
-----BEGIN PUBLIC KEY-----[/cci] (which is what OpenSSL produces) or [cci]-----BEGIN RSA PUBLIC KEY-----[/cci].

You can have OpenSSL generate a key pair very easily and you select the output form right on the command line:

[cc]
chrisy@baud:~/keytmp$ openssl genrsa -out testkeypair.pem 1024
Generating RSA private key, 1024 bit long modulus
.......++++++
...........++++++
e is 65537 (0x10001)
chrisy@baud:~/keytmp$ more testkeypair.pem
-----BEGIN RSA PRIVATE KEY-----
MIICXgIBAAKBgQDBm8yuHmd0P6scl48DEi+xp47wXVZaKWRygGKtA2XkdRuCU99f
0Tq07Llcgf8XuR+Wnk+z2CdMMFMzOGhCePblVIAn33dcBVlDokpBF7AnTClsaLci
xxZw1LIUiaPaBdN7oG8vt3G2caLHRrrkoEnccY+6GadfH7iuHdcVsz1mowIDAQAB
AoGAWEt1TPMQuzNOFfwIfJ4OojaIOZZXi0bVSGLEnaKvFUFTCly1wjzpSRmsb0PZ
0jfa8BXCw4IQae6gAvv2kFoaPjAiohDRzsNL7r5VfWqYh2rvXM7FEa5Zl6EvhHm1
MdLVgqKW2gAN5N1dBqpRvzo0H8zEcbqH7a4gAyQivaxGXgECQQDz59utDOP1VS5L
VVnr57M4x99/lrxHNuiTmKdwKtjhB2bZQy2R5SPC7xHF5lFfMOW35tg/6ZjCeEC/
KvPYZXNNAkEAyzV4KKcL4+7S7AZ7LcmraYY2UHFAyGkS/RBVLLaTcGIZOyrw9Pez
M+S8kRERO7lblStcptCd4leTtPXY0X1prwJBAOiqk7bXZhmg4SGB0N6lzyRqHfzD
GOXCLkilxYvNg8fd3LGCUNUsxVlt3wFufM8WgPxWHJGTT2KrffAelDAoTr0CQQCA
9DSFb8Ru596340EGBIWfmIkdMVGQHIXtTBERJ+eWmNo0HwL8Ibh6BPzY/kC2auFA
X10Tiy22NidI3f6yqmiHAkEA1H/bkwBulMSoo1ylLCF1m482ucOY7wWnJ77ARc3X
f5KJtsWSDfQiHP1UyJrnlZz+JLWH4fWuFm1ZPHZca38eBg==
-----END RSA PRIVATE KEY-----

chrisy@baud:~/keytmp$ openssl rsa -in testkeypair.pem -pubout -outform PEM -out testpublic.pem
writing RSA key
chrisy@baud:~/keytmp$ more testpublic.pem
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBm8yuHmd0P6scl48DEi+xp47w
XVZaKWRygGKtA2XkdRuCU99f0Tq07Llcgf8XuR+Wnk+z2CdMMFMzOGhCePblVIAn
33dcBVlDokpBF7AnTClsaLcixxZw1LIUiaPaBdN7oG8vt3G2caLHRrrkoEnccY+6
GadfH7iuHdcVsz1mowIDAQAB
-----END PUBLIC KEY-----

[/cc]

<h2>Sign here</h2>

So far so good. In PHP we can use the PEM encoded keys directly. For example, to acquire the signature of some data, one could do this:

[cc lang="php"]<?php
function sign_data($data, $rsaprivkeyfile) {
$rsakey = openssl_pkey_get_private($rsaprivkeyfile);
if(!$rsakey) return false;

$signed_data = false;
if(!@openssl_sign($data, $signed_data, array($rsakey, ''))) return false;

return "\n-----BEGIN DATA-----\n".
    chunk_split(base64_encode($license_data)).
    "-----END DATA-----\n".
    "-----BEGIN SIGNATURE-----\n".
    chunk_split(base64_encode($signed_data)).
    "-----END SIGNATURE-----";
}
?>[/cc]

The format being returned is not a standard, it's something I made up, but it's easily transmitted and base64 implementations are pretty universal.

How would we verify the integrity of this message? Here's a crude example:

[cc lang="php"]<?php
function verify_message($message, $rsapubkeyfile) {
$rsakey = openssl_pkey_get_public($rsapubkeyfile);
if(!$rsakey) return false;

    // Extract the two sections
    $lines = explode("\n", $message);
    $a_dat = ''; $a_sig = '';
    $f_dat = false; $f_sig = false;
    foreach($lines as $line) {
        $l = trim($line);
        if($l == '-----BEGIN DATA-----') {
            $f_dat = true; $f_sig = false;
        } else if($l == '-----END DATA-----') {
            $f_dat = false; $f_sig = false;
        } else if($l == '-----BEGIN SIGNATURE-----') {
            $f_dat = false; $f_sig = true;
        } else if($l == '-----END SIGNATURE-----') {
            $f_dat = false; $f_sig = false;
        } else if($f_dat) {
            $a_dat .= $line."\n";
        } else if($f_sig) {
            $a_sig .= $line."\n";
        }
    }
    if(!$a_dat || empty($a_dat)) return false;
    if(!$a_sig || empty($a_sig)) return false;
    $data = @base64_decode($a_dat);
    $sig = @base64_decode($a_sig);

    // Validate signature
    $result = @openssl_verify($data, $sig, $rsakey);
    if(!$result) return false;
return true;
}
?>[/cc]

With this little nugget of code it becomes trivial to find out if someone has been tampering with the data. This is useful, for example, for software licenses. Assuming the private key is kept secure you have a degree of trust that anything signed by it is also secure.

<h2>So what's the problem with iOS?</h2>

iOS provides a security framework but if you try to use it for keys you generated elsewhere then it doesn't work and gives only very limited indications <em>why</em> it doesn't work.

iOS does not provide documented direct access to the OpenSSL API. If it's there it wouldn't matter - being undocumented means that it would face App Store rejection if their static analysis showed use of the undocumented library.

You <em>could</em> statically link an OpenSSL library that you compiled and it would work but in that situation it's your app providing the cryptography code and thus you will need to jump some hurdles (you may need a <a href="http://en.wikipedia.org/wiki/Commodity_Classification_Automated_Tracking_System">CCATS</a>) to get export clearance for your app (although it's not that simple in general - read the <em>Export Compliance</em> FAQ in iTunes Connect). In my case, I'm using a signed software license to prevent piracy - digital rights management - and thus does not need US Government clearance - but only if I include only enough code to perform that function. If I link against the whole of OpenSSL then what my app is doing will be less clearly defined.

So the key to a simple life is to work out why the provided functions don't work.

<h2>What's in a key?</h2>

It comes down to format.

The first clue is that if you use iOS to generate a key pair then the resulting form is not ASCII. So that rules out PEM and means that if you have a PEM formatted key, you'll also need a base64 decoder - which iOS does <em>not</em> provide. There's plenty of them around. I used <a href="http://cocoawithlove.com/2009/06/base64-encoding-options-on-mac-and.html">[cci]NSData+Base64.m[/cci]</a> which if you Google for you will find many varied implementations and derivations of.

But that is not enough, even though an Apple employee confirms on the dev forums that iOS wants a PKCS#1 key and if you're doing signature verification, that the signature is of the SHA1 hash of the data, as I would expect.

The next clue came when I noticed the keys produced by iOS don't have the normal binary ASN.1 preamble to identify itself as a PKCS#1 key. This prompted some searching and I came across <a href="http://blog.wingsofhermes.org/?p=42">this post</a> talking exactly about this issue. I have no idea why it did not come up in my earlier searching. In it Berin explains his similar discovery, but also that the payload still looks like a binary PKCS#1 key, just without the preamble. In his application he had to add the header so he could import the key elsewhere. What I wanted was the reverse.

And so I did the reverse. And it worked.

And then I came across <a href="http://blog.wingsofhermes.org/?p=75">this later post by Berin</a> also doing what I wanted. Doh. Our implementations are not identical, but functionally similar.

<h2>Code or it didn't happen</h2>

Assuming you have your OpenSSL generated RSA public key in an NSData object, this method will verify that it is in fact a PKCS#1 key and strip the header:

[cc lang="objc"]
- (NSData *)stripPublicKeyHeader:(NSData *)d_key {
        // Skip ASN.1 public key header
        if(d_key == nil) return nil;

        unsigned int len = [d_key length];
        if(!len) return nil;

        unsigned char *c_key = (unsigned char *)[d_key bytes];
        unsigned int idx = 0;
       
        if(c_key[idx++] != 0x30) return nil;
        if(c_key[idx] > 0x80) idx += c_key[idx] - 0x80 + 1;
        else idx++;

        // PKCS #1 rsaEncryption szOID_RSA_RSA
        static unsigned char seqiod[] = {0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00};
        if(memcmp(&c_key[idx], seqiod, 15)) return nil;
        idx += 15;
       
        if(c_key[idx++] != 0x03) return nil;
        if(c_key[idx] > 0x80) idx += c_key[idx] - 0x80 + 1;
        else idx++;
       
        if(c_key[idx++] != '\0') return nil;
       
        // Now make a new NSData from this buffer
        return [NSData dataWithBytes:&c_key[idx] length:len-idx];
}[/cc]

and the result can be fed into the iOS functions in the expected manner. For example, this code adds a keychain reference to a public key to an array. If you use this, don't forget to eventually  [cci lang="objc"]CFRelease()[/cci] each SecKeyRef you acquire, and don't forget that [cci]tag

needs to be unique for every key you add.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
- (BOOL)addPublicKey:(NSString *)key withTag:(NSString *)tag
{
    NSString *s_key = [NSString string];
    NSArray  *a_key = [key componentsSeparatedByString:@"\n"];
    BOOL     f_key  = FALSE;

    for (NSString *a_line in a_key) {
        if ([a_line isEqualToString:@"-----BEGIN PUBLIC KEY-----"]) {
            f_key = TRUE;
        }
        else if ([a_line isEqualToString:@"-----END PUBLIC KEY-----"]) {
            f_key = FALSE;
        }
        else if (f_key) {
            s_key = [s_key stringByAppendingString:a_line];
        }
    }
    if (s_key.length == 0) return(FALSE);

    // This will be base64 encoded, decode it.
    NSData *d_key = [NSData dataFromBase64String:s_key];
    d_key = [self stripPublicKeyHeader:d_key];
    if (d_key == nil) return(FALSE);

    NSData *d_tag = [NSData dataWithBytes:[tag UTF8String] length:[tag length]];

    // Delete any old lingering key with the same tag
    NSMutableDictionary *publicKey = [[NSMutableDictionary alloc] init];
    [publicKey setObject:(id) kSecClassKey forKey:(id)kSecClass];
    [publicKey setObject:(id) kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType];
    [publicKey setObject:d_tag forKey:(id)kSecAttrApplicationTag];
    SecItemDelete((CFDictionaryRef)publicKey);

    CFTypeRef persistKey = nil;

    // Add persistent version of the key to system keychain
    [publicKey setObject:d_key forKey:(id)kSecValueData];
    [publicKey setObject:(id) kSecAttrKeyClassPublic forKey:(id)
     kSecAttrKeyClass];
    [publicKey setObject:[NSNumber numberWithBool:YES] forKey:(id)
     kSecReturnPersistentRef];

    OSStatus secStatus = SecItemAdd((CFDictionaryRef)publicKey, &persistKey);
    if (persistKey != nil) CFRelease(persistKey);

    if ((secStatus != noErr) && (secStatus != errSecDuplicateItem)) {
        [publicKey release];
        return(FALSE);
    }

    // Now fetch the SecKeyRef version of the key
    SecKeyRef keyRef = nil;

    [publicKey removeObjectForKey:(id)kSecValueData];
    [publicKey removeObjectForKey:(id)kSecReturnPersistentRef];
    [publicKey setObject:[NSNumber numberWithBool:YES] forKey:(id)kSecReturnRef
    ];
    [publicKey setObject:(id) kSecAttrKeyTypeRSA forKey:(id)kSecAttrKeyType];
    secStatus = SecItemCopyMatching((CFDictionaryRef)publicKey,
                                    (CFTypeRef *)&keyRef);

    [publicKey release];

    if (keyRef == nil) return(FALSE);

    // Add to our pseudo keychain
    [keyRefs addObject:[NSValue valueWithBytes:&keyRef objCType:@encode(
                            SecKeyRef)]];

    return(TRUE);
}


[/ cc]

Lastly, to verify a message in the same way we did in PHP earlier,
the code might look like this :

[cc lang = "objc"]
- (BOOL)verifyMessage : (NSString *)msg {
    // Search for the two sections: Data and a signature.
    NSString *s_data = [NSString string], *s_signature = [NSString string];
    NSArray  *a_key  = [msg componentsSeparatedByString:@"\n"];
    BOOL     f_data  = FALSE, f_signature = FALSE;
    for (NSString *a_line in a_key) {
        if ([a_line isEqualToString:@"-----BEGIN DATA-----"]) {
            f_data      = TRUE;
            f_signature = FALSE;
        }
        else if ([a_line isEqualToString:@"-----END DATA-----"]) {
            f_data      = FALSE;
            f_signature = FALSE;
        }
        else if ([a_line isEqualToString:@"-----BEGIN SIGNATURE-----"]) {
            f_data      = FALSE;
            f_signature = TRUE;
        }
        else if ([a_line isEqualToString:@"-----END SIGNATURE-----"]) {
            f_data      = FALSE;
            f_signature = FALSE;
        }
        else if (f_data) {
            s_data = [s_data stringByAppendingString:a_line];
        }
        else if (f_signature) {
            s_signature = [s_signature stringByAppendingString:a_line];
        }
    }
    if ((s_data.length == 0) || (s_signature.length == 0)) return(FALSE);

    // These will be base64 encoded, decode them.
    NSData *d_data = [NSData dataFromBase64String:s_data];
    if (d_data == nil) return(FALSE);

    NSData *d_signature = [NSData dataFromBase64String:s_signature];
    if (d_signature == nil) return(FALSE);

    // Make SHA-1 hash of the data
    uint8_t h_data[CC_SHA1_DIGEST_LENGTH];
    CC_SHA1(d_data.bytes, d_data.length, h_data);
    d_hash = [NSData dataWithBytes:h_data length:CC_SHA1_DIGEST_LENGTH];

    // The signature is generated against the binary form of the data, validate
    //it.
    BOOL valid = FALSE;
    for (NSValue *refVal in keyRefs) {
        SecKeyRef p_key = NULL;
        [refVal getValue:&p_key];
        if (p_key == NULL) continue;
        OSStatus secStatus = SecKeyRawVerify(p_key, kSecPaddingPKCS1SHA1,
                                             d_hash.bytes, d_hash.length,
                                             d_signature.bytes,
                                             d_signature.length);
        if (secStatus == errSecSuccess) {
            valid = TRUE;
            break;
        }
    }
    return(valid);
}
 

So, yes. It’s involved. But I think it’s worth it.

Also, for what it is worth, this does work in the Simulator, certainly at least with the current iOS SDK (4.2).

Tags: , , , , , , ,
Posted in: iOS
Both comments and pings are currently closed.

Comments are closed.