SecItemAdd returns 0 but key ref is null -- only after iOS 11 update

My call to SecItemAdd is returning 0 but the public key reference I pass in is nil. This happens ONLY with iOS 11. I am importing an RSA public key and formatting it for BasicEncodingRules using a helper library called 'SCZ-BasicEncodingRules'.

The same problem started happening with iOS 9, but was easily solved by placing a null character before the modulus data. I'm not sure if this is related or not. I'm including relevant sections of code below.


Where error is happening:


-(BOOL) myMethod

{

SecKeyRef publicKeyRef = NULL;

CFTypeRef sessionKeyRef = NULL;

NSMutableDictionary *publicKeyAttrs = NULL;

NSMutableDictionary *sessionKeyAttrs = NULL;

OSStatus status;


@try {


NSData *publicKeyData = [MyUtility getBerDataFromPublicKeyBlob:_serverPublicKey]; // below

if (!publicKeyData) {

return FALSE;

}

publicKeyTag = [self makePublicKeyTag];


publicKeyAttrs = [[NSMutableDictionary alloc] init];

[publicKeyAttrs setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];

[publicKeyAttrs setObject:(__bridge id)kSecAttrKeyClassPublic forKey:(__bridge id)kSecAttrKeyClass];

[publicKeyAttrs setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];

[publicKeyAttrs setObject:publicKeyTag forKey:(__bridge id)kSecAttrApplicationTag];

[publicKeyAttrs setObject:publicKeyData forKey:(__bridge id)kSecValueData];

[publicKeyAttrs setObject:[NSNumber numberWithBool:YES] forKey:(__bridge id)kSecReturnRef];


status = SecItemAdd((__bridge CFDictionaryRef) publicKeyAttrs, (CFTypeRef*) &publicKeyRef);


if (status == errSecDuplicateItem) {

DebugLog(@"%%%%%% warning - public key %@ was left in keychain ", [[NSString alloc] initWithData:publicKeyTag encoding:NSASCIIStringEncoding]);

SecItemDelete((__bridge CFDictionaryRef)publicKeyAttrs);

status = SecItemAdd((__bridge CFDictionaryRef) publicKeyAttrs, (CFTypeRef*) &publicKeyRef);

}

if (status != noErr) {

return FALSE;

}

if (!publicKeyRef) {

/* fails here -- did NOT before iOS 11 */

SecItemDelete((__bridge CFDictionaryRef)publicKeyAttrs);

return FALSE;

}


// ... more code not directly related, never reaches here anyway

} @finally {


if (publicKeyRef) {

CFRelease(publicKeyRef);

publicKeyRef = NULL;


/

status = SecItemDelete((__bridge CFDictionaryRef) publicKeyAttrs);

if (status != noErr) {

}

publicKeyTag = NULL;

}

if (sessionKeyRef) {

CFRelease(sessionKeyRef);

sessionKeyRef = NULL;

}


}


return TRUE;

}

/* MyUtility getBerDataFromPublicKeyBlob */


+(NSData*) getBerDataFromPublicKeyBlob: (NSString*) blobHex

{

NSData *blobData = [MyUtility toDataFromHexStr:blobHex];

const uint8_t *blobBytes = [blobData bytes];


int pos = 0;


if (blobBytes[pos++] == PKB_PUBLIC_KEY_ID) {

/

} else {

DebugLog(@"problem - public key not found");

return nil;

}


pos++; // version


pos += 2; // reserved


uint32_t alg = *((uint32_t*) &blobBytes[pos]);

pos += sizeof(uint32_t);


if (alg == PKB_KEYX_ALG) {

/

} else if (alg == PKB_SIGN_ALG) {

/

} else {

return nil;

}


uint32_t magic = *((uint32_t*) &blobBytes[pos]);

pos += sizeof(uint32_t);


if (magic == PKB_MAGIC) {

/

} else {

return nil;

}


uint32_t bitlen = *((uint32_t*) &blobBytes[pos]);

pos += sizeof(uint32_t);

NSUInteger modLength = bitlen / 8;


uint32_t exponent = *((uint32_t*) &blobBytes[pos]);

pos += sizeof(uint32_t);

exponent = ntohl(exponent);

NSData *expData = [NSData dataWithBytes:&exponent length:sizeof(uint32_t)];


NSMutableData *modData = [[NSMutableData alloc] initWithCapacity:modLength + 1];

[modData appendBytes:&blobBytes[pos] length:modLength];


/

starting with iOS 9:

need null byte in front of modulus data;

appending it to the end here because it will be reversed

*/

static const uint8_t byte0 = 0;

[modData appendBytes:&byte0 length:1];


[MyUtility reverseMutableData:modData];


NSArray *attrs = [NSArray arrayWithObjects:modData, expData, nil];

NSData *berData = [SCZ_BasicEncodingRules_Modified berData:attrs]; // below


return berData;

}




/* SCZ_BasicEncodingRules_Modified */



// Modified from SCZ-BasicEncodingRules - Created by Peter Suk on 5/16/12 - modified to work without the use of category methods



#import "SCZ_BasicEncodingRules_Modified.h"

@implementation SCZ_BasicEncodingRules_Modified

#define kBerTypeConstructed 0x20

#define BER_SEQUENCE 0x10

#define BER_INTEGER 0x02

+ (NSData*) berData: (NSArray*) array

{

static uint8_t bitfieldTag[] = { (kBerTypeConstructed | BER_SEQUENCE) }; /

static uint8_t integerTag[] = { BER_INTEGER }; /

NSMutableData *berData = [[NSMutableData alloc] init];

[berData appendBytes:bitfieldTag length:1];

NSUInteger arrayContentsLength = 0;

for (NSData *d in array) {

arrayContentsLength += [SCZ_BasicEncodingRules_Modified berLengthBytes:d];

}

[berData appendData:[SCZ_BasicEncodingRules_Modified lengthStorageData:arrayContentsLength]];


for (NSData *d in array) {

NSMutableData *ber = [[NSMutableData alloc] init];

[ber appendBytes:integerTag length:1];

[ber appendData:[SCZ_BasicEncodingRules_Modified lengthStorageData:d.length]];

[ber appendData:d];

[berData appendData:ber];

}


return berData;

}

+ (NSData*)lengthStorageData: (NSUInteger) contentsLength

{

NSMutableData *lengthStorageData = [[NSMutableData alloc] init];

uint8_t lengthBytesTag[1];

if (contentsLength > 0x7F) {

NSUInteger lengthStorageBytes = [SCZ_BasicEncodingRules_Modified lengthBytesLog8:contentsLength];

if (lengthStorageBytes > sizeof(NSUInteger))

[NSException

raise:@"Invalid length value"

format:@"length storage greater than %lu bytes is invalid", (unsigned long) lengthStorageBytes];

lengthBytesTag[0] = 0x80 + lengthStorageBytes;

[lengthStorageData appendBytes:lengthBytesTag length:1];

uint8_t temp[sizeof(NSUInteger)];

NSInteger bitOffset;

for (NSInteger i = 0; i < lengthStorageBytes; i++) {

bitOffset = (lengthStorageBytes - i - 1)*8;

temp[i] = (contentsLength & (0xFF << bitOffset)) >> bitOffset;

}

[lengthStorageData appendBytes:temp length:lengthStorageBytes];

}

else {

lengthBytesTag[0] = contentsLength;

[lengthStorageData appendBytes:lengthBytesTag length:1];

}

return lengthStorageData;

}

+ (NSUInteger)lengthBytesLog8: (NSUInteger) length

{

NSUInteger lengthBytes = 0;

for (NSUInteger tempLength = length; tempLength > 0; lengthBytes++) {

tempLength >>= 8;

}

return lengthBytes;

}

+ (NSUInteger)berLengthBytes: (NSData*) data

{

NSUInteger berContentsLengthBytes = data.length;

if (berContentsLengthBytes <= 0x7F) {

return 2 + berContentsLengthBytes;

}

return 2 + [SCZ_BasicEncodingRules_Modified lengthBytesLog8:berContentsLengthBytes] + berContentsLengthBytes;

}

@end

Accepted Reply

That data is malformed. I put it in a file:

$ hexdump -Cv tmp.asn1 
00000000  30 81 8a 02 81 81 00 f8  21 27 bb 4f 63 27 ac 29  |0.......!'.Oc'.)|
00000010  a3 84 88 34 b0 cf 4d 3a  a6 70 07 e5 b5 f8 c6 ff  |...4..M:.p......|
00000020  6d 7e 79 fe 46 55 63 48  f3 fb fd 1e 09 a0 84 ce  |m~y.FUcH........|
00000030  71 fe 61 e3 06 7d 41 f5  df aa 29 4a 7b a6 20 e8  |q.a..}A...)J{. .|
00000040  f3 9c 96 e1 63 71 63 c0  47 65 1e aa 7b b7 9f b3  |....cqc.Ge..{...|
00000050  7d b4 f6 29 52 0e 60 11  62 7c 1d ec 8a eb 5f e4  |}..)R.`.b|...._.|
00000060  76 4e a1 27 7b 21 58 a3  83 b2 46 4e 37 74 c9 07  |vN.'{!X...FN7t..|
00000070  41 5f 61 45 3c 9b 1f 82  ff 86 4a b0 79 e8 9d 43  |A_aE<.....J.y..C|
00000080  23 d9 a2 b3 f5 e8 67 02  04 00 01 00 01           |#.....g......|
0000008d

and then dumped it with dumpasn1 and this is what I got:

$ dumpasn1 -a tmp.asn1 
   0  138: SEQUENCE {
   3  129:   INTEGER
         :     00 F8 21 27 BB 4F 63 27 AC 29 A3 84 88 34 B0 CF
         :     4D 3A A6 70 07 E5 B5 F8 C6 FF 6D 7E 79 FE 46 55
         :     63 48 F3 FB FD 1E 09 A0 84 CE 71 FE 61 E3 06 7D
         :     41 F5 DF AA 29 4A 7B A6 20 E8 F3 9C 96 E1 63 71
         :     63 C0 47 65 1E AA 7B B7 9F B3 7D B4 F6 29 52 0E
         :     60 11 62 7C 1D EC 8A EB 5F E4 76 4E A1 27 7B 21
         :     58 A3 83 B2 46 4E 37 74 C9 07 41 5F 61 45 3C 9B
         :     1F 82 FF 86 4A B0 79 E8 9D 43 23 D9 A2 B3 F5 E8
         :     67
 135    4:   INTEGER 65537
         :   }

0 warnings, 0 errors.

Normally this would flag any errors, so I was surprised when it didn’t. I then went digging deeper. I ended up stepping through the iOS code that imports this key to figure out the problem, but once I figured it out it’s easy to see in the

dumpasn1
output if you dump the hex:
$ dumpasn1 -a -hh tmp.asn1 
    …
   0  138: SEQUENCE {
    …
    <02 04 00 01 00 01>
 135    4:   INTEGER 65537
         :   }

0 warnings, 0 errors.

Here I’ve elided most of the output, focusing on the hex dump of the second

INTEGER
element, which is the root cause of this problem. There are three parts to this data:
  • 02 is the header, indicating an

    INTEGER
  • 04 is the length, indicating a 4-byte value

  • 00 01 00 01 is the bytes of that value

The problem is with the bytes. In ASN.1 DER the correct encoding for 65537 is 01 00 01. The leading zero is unnecessary because the top bit of the number is 0. Moreover, DER specifically requires that you not include a redundant leading zero.

Coming back to your code, you seem to have serious problems with how you’re constructing

INTEGER
values. Specifically:
  • For the modulus you unilaterally add a leading zero, even though that’s only necessary if the first bit of the modulus (the top bit of the leading byte) is 1.

  • For the exponent you always use 4 bytes, which is just wrong. You need to render the exponent to bytes using actual DER rules. Pasted in below is a snippet of code that I use for this.

    However, this may not be the right option depending on how your input data represents the exponent. Does

    blobBytes
    always store an exponent as 4 bytes? [And if it does that’s super weird; that’s no requirement that RSA exponents even fit in 4 bytes.]

Finally, if you’re going to monkey around with ASN.1 DER at this level, you really need to read up on it works. I strongly recommend that you get copies of the specs (X.680 and X.690) and keep them handy as you work on this code.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
static func int64(_ int64: Int64) -> NanoDER {
    var deaccumulator = int64
    var buffer: [UInt8] = []

    repeat {
        let thisByte = UInt8(deaccumulator & 0xff)
        buffer.append(thisByte)

        deaccumulator >>= 8
        if deaccumulator == 0 {
            if thisByte & 0x80 != 0 {
                buffer.append(0)
            }
            break
        } else if deaccumulator == -1 {
            if thisByte & 0x80 == 0 {
                buffer.append(0xff)
            }
            break
        }
    } while (true)

    return .integer(buffer.reversed())
}

Replies

First things first, I recommend that you import you key bytes using

SecKeyCreateWithData
. The technique you’re currently using was more of an artefact of the implementation of the keychain on iOS than an actual API and its support has always been super sketchy.

If

SecKeyCreateWithData
has problems with your key bytes then post a hex dump of those bytes and I’ll take a look.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Doing it that way, I get a -50 error on iOS 11. (It works in 10.3.1, although I can't seem to delete the key afterwards, but one thing at a time.)

Here's the hex dump of the NSData *publicKeyData object:


30818A02818100F82127BB4F6327AC29A3848834B0CF4D3AA67007E5B5F8C6FF6D7E79FE46556348F3FBFD1E09A084CE71FE61E3067D41F5DFAA294A7BA620E8F39C96E1637163C047651EAA7BB79FB37DB4F629520E6011627C1DEC8AEB5FE4764EA1277B2158A383B2464E3774C907415F61453C9B1F82FF864AB079E89D4323D9A2B3F5E867020400010001

Here is how I'm calling it:



SecKeyRef publicKeyRef = NULL;

CFTypeRef sessionKeyRef = NULL;

CFErrorRef error = NULL;

NSMutableDictionary *publicKeyAttrs = NULL;

NSMutableDictionary *sessionKeyAttrs = NULL;

OSStatus status;

uint32_t bitlen;



NSData *publicKeyData = [MyUtility getBerDataFromPublicKeyBlob:_serverPublicKey fillBitlength:&bitlen];

if (!publicKeyData) {

return FALSE;

}

DebugLogExtra(@"session %@ tran code %@ storing public key", sessionId, transRequest.m_TranCode);

publicKeyTag = [self makePublicKeyTag];

CFNumberRef cfKeySize = (__bridge CFNumberRef) [NSNumber numberWithInt:bitlen];

CFDataRef cfPublicKeyTag = (__bridge CFDataRef) publicKeyTag;

publicKeyAttrs = [[NSMutableDictionary alloc] init];


[publicKeyAttrs setObject:(__bridge id)kSecAttrKeyClassPublic forKey:(__bridge id)kSecAttrKeyClass];

[publicKeyAttrs setObject:(__bridge id)kSecAttrKeyTypeRSA forKey:(__bridge id)kSecAttrKeyType];

[publicKeyAttrs setObject:(__bridge id)cfPublicKeyTag forKey:(__bridge id)kSecValuePersistentRef];

[publicKeyAttrs setObject:(__bridge id)cfKeySize forKey:(__bridge id)kSecAttrKeySizeInBits]; // 1024

publicKeyRef = SecKeyCreateWithData((__bridge CFDataRef) publicKeyData, (__bridge CFDictionaryRef) publicKeyAttrs, &error);



if (error) {

//

}




/ MyUtility getBerDataFromPublicKeyBlob (modified to retrieve bitlength) */


+(NSData*) getBerDataFromPublicKeyBlob: (NSString*) blobHex fillBitlength: (uint32_t*) ptrBitlength


{

NSData *blobData = [MyUtility toDataFromHexStr:blobHex];

const uint8_t *blobBytes = [blobData bytes];


int pos = 0;


if (blobBytes[pos++] == PKB_PUBLIC_KEY_ID) {

/

} else {

DebugLog(@"problem - public key not found");

return nil;

}


pos++; // version


pos += 2; // reserved


uint32_t alg = *((uint32_t*) &blobBytes[pos]);

pos += sizeof(uint32_t);


if (alg == PKB_KEYX_ALG) {

/

} else if (alg == PKB_SIGN_ALG) {

/

} else {

return nil;

}


uint32_t magic = *((uint32_t*) &blobBytes[pos]);

pos += sizeof(uint32_t);


if (magic == PKB_MAGIC) {

/

} else {

return nil;

}


uint32_t bitlen = *((uint32_t*) &blobBytes[pos]);

if (ptrBitlength) {

*ptrBitlength = bitlen;

}

pos += sizeof(uint32_t);

NSUInteger modLength = bitlen / 8;


uint32_t exponent = *((uint32_t*) &blobBytes[pos]);

pos += sizeof(uint32_t);

exponent = ntohl(exponent);

NSData *expData = [NSData dataWithBytes:&exponent length:sizeof(uint32_t)];


NSMutableData *modData = [[NSMutableData alloc] initWithCapacity:modLength + 1];

[modData appendBytes:&blobBytes[pos] length:modLength];


/

starting with iOS 9:

need null byte in front of modulus data;

appending it to the end here because it will be reversed

*/

static const uint8_t byte0 = 0;

[modData appendBytes:&byte0 length:1];


[MyUtility reverseMutableData:modData];


NSArray *attrs = [NSArray arrayWithObjects:modData, expData, nil];

NSData *berData = [SCZ_BasicEncodingRules_Modified berData:attrs]; // same as first post


return berData;

}

That data is malformed. I put it in a file:

$ hexdump -Cv tmp.asn1 
00000000  30 81 8a 02 81 81 00 f8  21 27 bb 4f 63 27 ac 29  |0.......!'.Oc'.)|
00000010  a3 84 88 34 b0 cf 4d 3a  a6 70 07 e5 b5 f8 c6 ff  |...4..M:.p......|
00000020  6d 7e 79 fe 46 55 63 48  f3 fb fd 1e 09 a0 84 ce  |m~y.FUcH........|
00000030  71 fe 61 e3 06 7d 41 f5  df aa 29 4a 7b a6 20 e8  |q.a..}A...)J{. .|
00000040  f3 9c 96 e1 63 71 63 c0  47 65 1e aa 7b b7 9f b3  |....cqc.Ge..{...|
00000050  7d b4 f6 29 52 0e 60 11  62 7c 1d ec 8a eb 5f e4  |}..)R.`.b|...._.|
00000060  76 4e a1 27 7b 21 58 a3  83 b2 46 4e 37 74 c9 07  |vN.'{!X...FN7t..|
00000070  41 5f 61 45 3c 9b 1f 82  ff 86 4a b0 79 e8 9d 43  |A_aE<.....J.y..C|
00000080  23 d9 a2 b3 f5 e8 67 02  04 00 01 00 01           |#.....g......|
0000008d

and then dumped it with dumpasn1 and this is what I got:

$ dumpasn1 -a tmp.asn1 
   0  138: SEQUENCE {
   3  129:   INTEGER
         :     00 F8 21 27 BB 4F 63 27 AC 29 A3 84 88 34 B0 CF
         :     4D 3A A6 70 07 E5 B5 F8 C6 FF 6D 7E 79 FE 46 55
         :     63 48 F3 FB FD 1E 09 A0 84 CE 71 FE 61 E3 06 7D
         :     41 F5 DF AA 29 4A 7B A6 20 E8 F3 9C 96 E1 63 71
         :     63 C0 47 65 1E AA 7B B7 9F B3 7D B4 F6 29 52 0E
         :     60 11 62 7C 1D EC 8A EB 5F E4 76 4E A1 27 7B 21
         :     58 A3 83 B2 46 4E 37 74 C9 07 41 5F 61 45 3C 9B
         :     1F 82 FF 86 4A B0 79 E8 9D 43 23 D9 A2 B3 F5 E8
         :     67
 135    4:   INTEGER 65537
         :   }

0 warnings, 0 errors.

Normally this would flag any errors, so I was surprised when it didn’t. I then went digging deeper. I ended up stepping through the iOS code that imports this key to figure out the problem, but once I figured it out it’s easy to see in the

dumpasn1
output if you dump the hex:
$ dumpasn1 -a -hh tmp.asn1 
    …
   0  138: SEQUENCE {
    …
    <02 04 00 01 00 01>
 135    4:   INTEGER 65537
         :   }

0 warnings, 0 errors.

Here I’ve elided most of the output, focusing on the hex dump of the second

INTEGER
element, which is the root cause of this problem. There are three parts to this data:
  • 02 is the header, indicating an

    INTEGER
  • 04 is the length, indicating a 4-byte value

  • 00 01 00 01 is the bytes of that value

The problem is with the bytes. In ASN.1 DER the correct encoding for 65537 is 01 00 01. The leading zero is unnecessary because the top bit of the number is 0. Moreover, DER specifically requires that you not include a redundant leading zero.

Coming back to your code, you seem to have serious problems with how you’re constructing

INTEGER
values. Specifically:
  • For the modulus you unilaterally add a leading zero, even though that’s only necessary if the first bit of the modulus (the top bit of the leading byte) is 1.

  • For the exponent you always use 4 bytes, which is just wrong. You need to render the exponent to bytes using actual DER rules. Pasted in below is a snippet of code that I use for this.

    However, this may not be the right option depending on how your input data represents the exponent. Does

    blobBytes
    always store an exponent as 4 bytes? [And if it does that’s super weird; that’s no requirement that RSA exponents even fit in 4 bytes.]

Finally, if you’re going to monkey around with ASN.1 DER at this level, you really need to read up on it works. I strongly recommend that you get copies of the specs (X.680 and X.690) and keep them handy as you work on this code.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
static func int64(_ int64: Int64) -> NanoDER {
    var deaccumulator = int64
    var buffer: [UInt8] = []

    repeat {
        let thisByte = UInt8(deaccumulator & 0xff)
        buffer.append(thisByte)

        deaccumulator >>= 8
        if deaccumulator == 0 {
            if thisByte & 0x80 != 0 {
                buffer.append(0)
            }
            break
        } else if deaccumulator == -1 {
            if thisByte & 0x80 == 0 {
                buffer.append(0xff)
            }
            break
        }
    } while (true)

    return .integer(buffer.reversed())
}

Yes, blobBytes always stores the exponent in 4 bytes in this case. I was able to get this working on iOS 11 with the function you supplied, although some of it may not be necessary in my case. But, as you suggested, I'd like to have a better understanding. I am still a bit confused, unfortunately. I should clarify that it's been a while since I wrote the original code or done anything with iOS or dealt with anything related to this.


One thing - the X.680 link you posted actually points to the X.690 page. Changing the URL to 680 redirects to a page describing ASN.1 in general - I assume that's what you intended.


Looking at the X.690 page, this looks like the part which is applicable:


The most significant DER encoding constraints are:

  1. Length encoding must use the definite form

    Additionally, the shortest possible length encoding must be used

  2. Bitstring, octetstring, and restricted character strings must use the primitive encoding
  3. Elements of a Set are encoded in sorted order, based on their tag value


Point #2 should not apply here, and I don't believe #3 applies either, but correct me if I'm wrong.


For point #1, my helper library SCZ_BasicEncodingRules uses the definite form, so it's the shortest possible length that needed to be addressed here. Pertaining to the function you supplied, and your statement, "The leading zero is unnecessary because the top bit of the number is 0. Moreover, DER specifically requires that you not include a redundant leading zero.", I don't see where DER specifies the need for the leading null byte if the top bit of the first byte is a 1. Is this the same rule which is applied to the modulus? And, where exactly is this specified? (I had previously been putting the null ahead of the modulus regardless of the top bit because I read in a forum somewhere and it worked, but I can't seem to find the rule you describe in any official specification. It may be that I'm not looking for the correct terminology.)


I'm also confused about the second part of your loop where you check for a -1 and append 0xff, but only if the last byte did not have a leading 1. Is the 0xff just there because it's 2's complement for -1 and there's some reason that the leading byte shouldn't be there if it's a 0xff and the next one doesn't have a leading 1 bit?



Here are the modifications I made:

+(NSData*) getBerDataFromPublicKeyBlob: (NSString*) blobHex fillBitlength: (uint32_t*) ptrBitlength
{
    NSData *blobData = [MyUtility toDataFromHexStr:blobHex];
    const uint8_t *blobBytes = [blobData bytes];

    int pos = 0;

    if (blobBytes[pos++] == PKB_PUBLIC_KEY_ID) {
        //   
    } else {
        DebugLog(@"problem - public key not found");
        return nil;
    }

    pos++;        // version

    pos += 2;     // reserved

    uint32_t alg = *((uint32_t*) &blobBytes[pos]);
    pos += sizeof(uint32_t);

    if (alg == PKB_KEYX_ALG) {
        //  
    } else if (alg == PKB_SIGN_ALG) {
        //  
    } else {

        return nil;
    }

    uint32_t magic = *((uint32_t*) &blobBytes[pos]);
    pos += sizeof(uint32_t);

    if (magic == PKB_MAGIC) {
        //  
    } else {

        return nil;
    }

    uint32_t bitlen = *((uint32_t*) &blobBytes[pos]);
    if (ptrBitlength) {
        *ptrBitlength = bitlen;
    }
    pos += sizeof(uint32_t);
    NSUInteger modLength = bitlen / 8;

    uint32_t exponent = *((uint32_t*) &blobBytes[pos]);
    pos += sizeof(uint32_t);

    DebugAssert(pos + modLength == blobData.length);


    NSData *expData = [self nanoDER__Eskimo:(int64_t) exponent];

    NSMutableData *modData = [[NSMutableData alloc] init];

    const void *modBytes = [[self reverseData:[NSData dataWithBytes:&blobBytes[pos] length:modLength]] bytes];
    uint8_t leadingByte = ((uint8_t*) modBytes)[0];

    if (leadingByte & 0x80) {
        /*
             need null byte in front of modulus if first bit is 1
         */
        static const uint8_t byte0 = 0;
        [modData appendBytes:&byte0 length:1];
    }

    [modData appendBytes:modBytes length:modLength];

    NSArray *attrs = [NSArray arrayWithObjects:modData, expData, nil];
    NSData *berData = [SCZ_BasicEncodingRules_Modified berData:attrs];

    return berData;
}


... and my Objective-C version of your function:


+(NSData*) nanoDER__Eskimo: (int64_t) arg
{
    int64_t deaccumulator = arg;
    NSMutableData *buffer = [[NSMutableData alloc] init];
    uint8_t thisByte;

    for (;;) {

        thisByte = (uint8_t) (deaccumulator & 0xff);
        [buffer appendBytes:&thisByte length:1];

        deaccumulator >>= 8;
        if (deaccumulator == 0) {
            if ((thisByte & 0x80) != 0) {
                static const uint8_t byte0 = 0;
                [buffer appendBytes:&byte0 length:1];
            }
            break;
        } else if (deaccumulator == -1) {
            if ((thisByte & 0x80) == 0) {
                static const uint8_t byteFF = 0xff;
                [buffer appendBytes:&byteFF length:1];
            }
            break;
        }

    }

    return [self reverseData:buffer];
}


Thank you for all of your help.

One thing - the X.680 link you posted actually points to the X.690 page. Changing the URL to 680 redirects to a page describing ASN.1 in general - I assume that's what you intended.

Yes. I’ve gone back and fixed the link. Thanks for the correction.

With regards your other questions, the key thing you’re missing here is that

INTEGER
is always signed. So if you want to represent an integer 65535 you have to use 00 ff ff because ff ff is treated as -1 (and a none shortest-possible-length version of -1 at that).

Take a look at Section 8.3 of X.690, which says:

8.3.3 The contents octets shall be a two's complement binary number …

and:

8.3.2 If the contents octets of an integer value encoding consist of more than one octet, then the bits of the first octet and bit 8 of the second octet:

  • shall not all be ones; and

  • shall not all be zero.

NOTE – These rules ensure that an integer value is always encoded in the smallest possible number of octets.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I see that section in the ITU-T X.690 document now. That answers my questions. Thank you for your help.