11

Given a SecKeyRef loaded using SecItemImport from an RSA private key is there a way to obtain or create a SecKeyRef for only the public key components? In OpenSSL this could be done by copying the modulus and public exponent to a new struct, but SecKeyRef is opaque and I've been unable to find a function that performs this operation.

Alex Gaynor
  • 14,353
  • 9
  • 63
  • 113
Paul Kehrer
  • 13,466
  • 4
  • 40
  • 57

2 Answers2

1

As of macOS 10.12, iOS/tvOS 10, and watchOS 3 the function SecKeyCopyPublicKey now exists to do this.

Paul Kehrer
  • 13,466
  • 4
  • 40
  • 57
0

Old question, but because I was fighting with it today, I found a way. Let's say you have base64 encoded public RSA key (it's not certificate, it's not der, pem, ...), created in Java (X509 certificate public key), you can create SecKeyRef in this way:

- (NSData *)stripPublicKeyHeader2:(NSData *)keyBits {
  // Skip ASN.1 public key header
  if (keyBits == nil) {
    return nil;
  }

  unsigned int len = [keyBits length];
  if (!len) {
    return nil;
  }

  unsigned char *c_key = (unsigned char *)[keyBits 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++;
  }

  if (idx >= len) {
    return nil;
  }

  if (c_key[idx] != 0x30) {
    return nil;
  }

  idx += 15;

  if (idx >= len - 2) {
    return nil;
  }

  if (c_key[idx++] != 0x03) {
    return nil;
  }

  if (c_key[idx] > 0x80) {
    idx += c_key[idx] - 0x80 + 1;
  }
  else {
    idx++;
  }

  if (idx >= len) {
    return nil;
  }

  if (c_key[idx++] != 0x00) {
    return nil;
  }

  if (idx >= len) {
    return nil;
  }

  // Now make a new NSData from this buffer
  return([NSData dataWithBytes:&c_key[idx] length:len - idx]);
}

- (SecKeyRef)publicKey:(NSData *)d_key withTag:(NSString *)tag
{
  NSData *d_tag = [NSData dataWithBytes:[tag UTF8String] length:[tag length]];

  NSDictionary *saveDict = @{
                             (__bridge id) kSecClass : (__bridge id) kSecClassKey,
                             (__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeRSA,
                             (__bridge id) kSecAttrApplicationTag : d_tag,
                             (__bridge id) kSecAttrKeyClass : (__bridge id) kSecAttrKeyClassPublic,
                             (__bridge id) kSecValueData : d_key
                             };

  OSStatus secStatus = SecItemAdd((__bridge CFDictionaryRef)saveDict, NULL );
  if (secStatus == errSecDuplicateItem ) {
    SecItemDelete((__bridge CFDictionaryRef)saveDict);
    secStatus = SecItemAdd((__bridge CFDictionaryRef)saveDict, NULL );
  }

  if ( secStatus != noErr ) {
    return NULL;
  }

  NSDictionary *queryDict = @{
                              (__bridge id) kSecClass : (__bridge id) kSecClassKey,
                              (__bridge id) kSecAttrKeyType : (__bridge id) kSecAttrKeyTypeRSA,
                              (__bridge id) kSecAttrApplicationTag : tag,
                              (__bridge id) kSecAttrKeyClass : (__bridge id)kSecAttrKeyClassPublic,
                              (__bridge id) kSecReturnRef : (__bridge id) kCFBooleanTrue
                              };

  // Now fetch the SecKeyRef version of the key
  SecKeyRef keyRef = nil;
  secStatus = SecItemCopyMatching((__bridge CFDictionaryRef)queryDict,
                                  (CFTypeRef *)&keyRef);

  if ( secStatus != noErr ) {
    return NULL;
  }

  return keyRef;
}

NSData *keyData = [[NSData alloc] initWithBase64EncodedString:@"base64encoded X509 private key"
                                                              options:0];
certificateData = [self stripPublicKeyHeader2:keyData];
SecKeyRef key = [self publicKey:certificateData withTag:[[NSUUID UUID] UUIDString]];

Little bit awkward, not my code, was Googling for the whole day, many pieces glued together. Have to clean it, ... Take it as a hack compiled from the internet.

Tested against server where code is in Java and this is the only way how to do it. The only way I did find. Maybe there are another ways, but only this one does work for me and I can encrypt with this RSA public key (and our server code (Java) can decrypt it. And it does work only without padding (not recommended) or with kSecPaddingOAEP padding on iOS side and with RSA/NONE/OAEPWithSHA1AndMGF1Padding on Java side.

zrzka
  • 20,249
  • 5
  • 47
  • 73