I am trying to convert a generated public RSA key to a SSH using Apples security framework.
Here is the code I am using for generating the key pairs:
- (void)generatePrivateKey {
NSDictionary *privateKeyAttr = @{(__bridge id)kSecAttrIsPermanent: @YES,
(__bridge id)kSecAttrApplicationTag: self.privateTag};
NSDictionary *publicKeyAttr = @{(__bridge id)kSecAttrIsPermanent: @YES,
(__bridge id)kSecAttrApplicationTag: self.publicTag};
NSDictionary *keyPairAttr = @{(__bridge id)kSecAttrKeySizeInBits: @1024,
(__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeRSA,
(__bridge id)kSecPrivateKeyAttrs: privateKeyAttr,
(__bridge id)kSecPublicKeyAttrs: publicKeyAttr};
SecKeyRef publicKey;
SecKeyRef privateKey;
SecKeyGeneratePair((__bridge CFDictionaryRef)keyPairAttr, &publicKey, &privateKey);
}
- (NSString *)getPublicKey {
NSString *contents = [self keyForTag:self.publicTag];
return [NSString stringWithFormat:@"-----BEGIN RSA PUBLIC KEY-----\n%@\n-----END RSA PUBLIC KEY-----", contents];
}
- (NSString *)getPrivateKey {
NSString *contents = [self keyForTag:self.privateTag];
return [NSString stringWithFormat:@"-----BEGIN RSA PRIVATE KEY-----\n%@\n-----END RSA PRIVATE KEY-----", contents];
}
- (NSString *)keyForTag:(NSData *)tag {
NSDictionary *queryKey = @{(__bridge id)kSecClass: (__bridge id)kSecClassKey,
(__bridge id)kSecAttrApplicationTag: tag,
(__bridge id)kSecAttrKeyType: (__bridge id)kSecAttrKeyTypeRSA,
(__bridge id)kSecReturnData: @YES};
// Get the key bits.
CFDataRef keyBits;
OSStatus sanityCheck = SecItemCopyMatching((__bridge CFDictionaryRef)queryKey, (CFTypeRef *)&keyBits);
NSData *passDat = (__bridge_transfer NSData *)keyBits;
if (sanityCheck != noErr) {
passDat = nil;
}
NSString *contents = [passDat base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
return contents;
}
Then I end up with a private key like this:
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQD0SvIfWFU1EzKD8rSUMWVcE9t52WtJCIKDyMPYiu3/VV9TBci7
7QocSl400yqjn5eX2piGRO5o/Wh/BKUdC/IzZbAb15jboKvgkE0R+EBnAs+zo2HJ
Y1sZwT4Bc9d1ClvhHpYdE9EwpPc1IIfsz+Sa41Z0wDXqWk90A33BqIcs3wIDAQAB
AoGBAMYvxx4G25mjaWgCjt1q9YAt2/COoqstbDTdu4UBsPNkn2ELYD6Vn440Bxlz
9zOnVaSsgvDrGz+x1gS2D/3woxtuqSHThEOQSHugbtDMEaZ7Pawrj7zVAZQ4PPhD
l9HNf4huhAnvDA9YE73rfQst4+vq0KpPjFHCKcEaQxLu7Y4BAkEB/UeBEdXbJ2e8
ReGmH11/glszpiS+MosJIk/d68la1B5A5gSw6MO4GHNzPUnsNVKJXPYKYPmLDP33
zrYXXXb/MQJAesyFhqM6TtY6IHXQyu43e6iemzAPjx2s9l0SgNAjH25JLS7tHcXo
2gA5JcA+LqS93ei/4lLwOCd7uX9qko2JDwJBARhfdT9MbQqUoaIXSE2cO8aYTyb4
s30/7hdlwNc+UzLUNQZtLrf2iDNt29OyDsiMV/NFwRECUPsmFndG6DYcfQECQHe/
ISZd3eoq9ZvZx7Vb/zbTA3eJsmJ5KcVElVqPnPB1d15cOFWkPKD5PsEVao3JkGzp
HtTw09euiPQm0CIBavkCQQC/GU6l+khFqewNmO5+cok6wSVpqw3paGL8bxIwgifT
yPv42Biyf3gmXtdP//tpyXPlzn6HPqf6PqFGEGvSpPDk
-----END RSA PRIVATE KEY-----
Putting this into ssh-keygen
produces the following public key:
$ ssh-keygen -f test.p12 -y
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC7VWIi0kXyx/UCGG91iGqKjohRLIj9hp44Xwd/pIApBHo38/noUeqN07S4oGgx47zZthg3zKFP90eEdKKOXZ0yuQKOy+yB5YAYg7e9FVvxfXCOVrGaZohh37HLUql/bdOzTK6/Upjl0ZZNYpxWyfIZ/8jKCAaTG6BhPQhLmWxCQw==
My question is how to do the ssh-keygen
with the Apple Security framework or OpenSSH?
UPDATE:
I am now using OpenSSL directly and get a valid RSA
key. However the conversion is still not working correctly:
- (NSString *)publicKey {
RSA *rsa = self.rsaPrivateKey;
// Encode the "ssh-rsa" string
NSMutableData *sshrsa = [[NSMutableData alloc] init];
const char start[] = {0, 0, 0, 7, 's', 's', 'h', '-', 'r', 's', 'a'};
[sshrsa appendBytes:start length:sizeof(start)];
// Encode the RSA encoding exponent e
{
const char *e = BN_bn2dec(rsa->e);
NSString *length = [NSString stringWithFormat:@"%04lu", strlen(e)] ;
[sshrsa appendData:[length dataUsingEncoding:NSUTF8StringEncoding]];
[sshrsa appendBytes:e length:strlen(e)];
}
// Encode the RSA modul n
{
const char *n = BN_bn2dec(rsa->n);
NSString *length = [NSString stringWithFormat:@"%04lu", strlen(n)] ;
[sshrsa appendData:[length dataUsingEncoding:NSUTF8StringEncoding]];
[sshrsa appendBytes:n length:strlen(n)];
}
return [sshrsa base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithCarriageReturn];;
}
The new key is
ssh-rsa AAAAB3NzaC1yc2EAAAAG4AAAATYwvdcavn8AALOV1CoAAAAG4HHTGr5/AAAwyNcavn8AAPAq1xq+fwAAkPBhBwEAAAC2hnJUVxt7AO3JLixNzOwBALilG75/AABQsaUbvn8AAJC2pRu+fwAAELGlG75/AABZVMwDAQAAAECupRu+fwAAsJsHXP9/AAAIC7kDAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHB3QAAAAAAA2IRA8A/wGr5/AAAAUvAavn8AAJBV4xq+fwAAAFLwGr5/AACQc4Ybvn8AAABS8Bq+fwAAAI4CHr5/AACQc4Ybvn8AAJBzhhu+fwAA4DGwG75/AACfAZIEAQAAAABS8Bq+fwAAUACBG75/AAA9j24JAQAAAFAAgRu+fwAAAFLwGr5/AADgMbAbvn8AAAAAAAAAAAAA4A==
which is way longer than it should be:
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC+gK0cEEgn/dDk+Sf/AdQtHp2rJoG7DxMuw2hL0+96rdKeixXVXWCE8GqMg7xIU8kSn0lrfcCJDhVBkArmlnlrZDfv1lTItBU7PvV4eDLT+3kApoqYMUmmo/ecDRyAaaOecoVZTl27RZghXcS7yxABlbVhYLzJEywOi46A9yBMFQ==
Meta: At least part of an answer, but I don't know ObjC so making CW for improvement.
Note: This is nearly a duplicate of Convert pem key to ssh-rsa format except that is in C not ObjC, and it starts from a publickey file instead of a privatekey file -- but OpenSSL's in-memory structure for an RSA key is the same for a publickey or privatekey, with privatekey-specific fields ignored for a publickey. And it can be improved.
Your code (apparently) generates a length value in decimal as 4 digits and a decimal representation of the magnitude (without sign since the value is always positive) for each of e and n, but unbase64'ing your posted output doesn't show any of these actually included in the result, which after the correct initial part matching your
start
appears to be garbage, and I don't know why. You may need some ObjC debugging help there.Anyway, the correct encoding is 4-byte binary (bigendian) length, followed by a binary bigendian representation of the value in two's complement, which requires adding a leading zero byte for a positive number in the range 28k/2 to 28k-1; this is usually the case for n because RSA key size is usually chosen a multiple of 8 (actually a power of 2 or small multiple thereof). e is rarely chosen this way although it can be. See "string" and "mpint" in https://www.rfc-editor.org/rfc/rfc4251#section-5 .
You can do this as in #1011572 by calling
BN_bn2bin
to get the binary bigendian magnitude into a large-enough buffer, then encode the 4-byte length, possible 1-byte sign and magnitude, again into a large-enough buffer. Or OpenSSL can actually do much of this for you; callBN_bn2mpi
with a large-enough buffer and it will do the length, possible sign and magnitude.How to allocate and manage the buffer(s?) in ObjC I leave to you or someone else. Do note that both the length fields and the value parts can and frequently will use the zero byte as a valid byte value; it must not be treated as a terminator or otherwise special. A little googling suggests to me this may be a problem for
NSString
but I could easily be wrong.