Skip to content

Instantly share code, notes, and snippets.

@volodymyrsmirnov
Created March 20, 2014 11:01
Show Gist options
  • Save volodymyrsmirnov/9661404 to your computer and use it in GitHub Desktop.
Save volodymyrsmirnov/9661404 to your computer and use it in GitHub Desktop.
#import <Foundation/Foundation.h>
#include "openssl/bn.h"
#include "openssl/evp.h"
#include "openssl/err.h"
#include "openssl/pem.h"
#include "openssl/ssl.h"
// SSH key types
typedef enum SSHKeyType : short {
kRSA,
kDSA
} SSHKeyType;
@interface SSHKey : NSObject
/**
* Initialise SSHKey instance with private key content
*
* @param privateKeyData Private key in PEM format
* @param passPhrase Password for private key
* @param error Error
*
* @return SSHKey instance or NULL on error
*/
-(id)initWithPrivateKey:(NSString *)privateKeyData passphrase:(NSString *)passPhrase error:(NSError **)error;
/**
* Initialise SSHKey instance with private key file
*
* @param filePath Path to private key
* @param passPhrase Password for private key
* @param error Error
*
* @return SSHKey instance or NULL on error
*/
-(id)initWithPrivateKeyFile:(NSString *)filePath passphrase:(NSString *)passPhrase error:(NSError **)error;
/**
* Save decoded private key and public key in OpenSSH format
*
* @param privateKeyPath Path to private key
* @param publicKeyPath Path to public key
* @param error Error
*/
-(void)savePrivateKey:(NSString *)privateKeyPath publicKey:(NSString *)publicKeyPath error:(NSError **)error;
/**
* Save decoded private key in OpenSSH format
*
* @param privateKeyPath Path to private key
* @param error Error
*/
-(void)savePrivateKey:(NSString *)privateKeyPath error:(NSError **)error;
/**
* SSH key type
*/
@property (nonatomic, readonly) SSHKeyType type;
/**
* Private key content
*/
@property (nonatomic, readonly) NSString *private;
/**
* Public key content
*/
@property (nonatomic, readonly) NSString *public;
@end
#import "SSHKey.h"
@implementation SSHKey
@synthesize type = _type;
@synthesize private = _private;
@synthesize public = _public;
/**
* Convert u_int32_t to big endian byte order
*
* @param buffer Buffer for byte order
* @param number Number
*/
static void htonu32(unsigned char *buffer, u_int32_t number) {
buffer[0] = (number >> 24) & 0xFF;
buffer[1] = (number >> 16) & 0xFF;
buffer[2] = (number >> 8) & 0xFF;
buffer[3] = number & 0xFF;
}
/**
* Write BIGNUM to the buffer
*
* @param buffer Buffer
* @param bignum OpenSSL BIGNUM
* @param length Length of bignum
*
* @return Buffer
*/
static unsigned char *writeBignum(unsigned char *buffer, const BIGNUM *bignum, int length)
{
unsigned char *bufferPointer = buffer;
bufferPointer += 4;
*bufferPointer = 0;
BN_bn2bin(bignum, bufferPointer + 1);
if (!(*(bufferPointer + 1) & 0x80)) {
memmove(bufferPointer, bufferPointer + 1, --length);
}
htonu32(bufferPointer - 4, length);
return bufferPointer + length;
}
- (id)initWithPrivateKeyFile:(NSString *)filePath passphrase:(NSString *)passPhrase error:(NSError **)error
{
if (!self) self = [super init];
if (self != NULL)
{
// open file and read it all
NSString *privateKeyContent = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:error];
// if everything was ok - parse the private key
if (privateKeyContent != NULL)
{
return [self initWithPrivateKey:privateKeyContent passphrase:passPhrase error:error];
}
}
// oh no, god damn
*error = [NSError errorWithDomain:NSStringFromClass([self class]) code:100 userInfo:NULL];
return NULL;
}
- (id)initWithPrivateKey:(NSString *)privateKeyData passphrase:(NSString *)passPhrase error:(NSError **)error
{
if (!self) self = [super init];
BOOL publicExtracted = YES;
if (self != NULL)
{
// private and key
EVP_PKEY *privateKey = NULL;
// write NSString to BIO buffer
BIO *bufferPrivateKey = BIO_new_mem_buf((void*)[privateKeyData UTF8String], (int)[privateKeyData lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
// temporary buffers for public and private keys
BIO *bufferPrivateKeyDecrypted = NULL,
*bufferPublicKeyDecrypted = NULL;
// Buffer for Base64 encoding
BIO *base64EncodedBuffer = BIO_new(BIO_f_base64());
BIO_set_flags(base64EncodedBuffer, BIO_FLAGS_BASE64_NO_NL);
// char pointers for public and private keys output
char *bufferPublicKeyOut = NULL,
*bufferPrivateKeyOut = NULL;
// Public key miscellaneous
unsigned char *publicKeyBuffer = NULL;
unsigned char *publicKeyBufferPointer = NULL;
size_t publicKeyBufferLength;
// load all encryption algorithms
OpenSSL_add_all_algorithms();
// read private key from BIO buffer
privateKey = PEM_read_bio_PrivateKey(bufferPrivateKey, NULL, NULL, (void *)[passPhrase UTF8String]);
// panic on shit
if (privateKey == NULL)
{
unsigned long keyError = ERR_get_error();
*error = [NSError errorWithDomain:NSStringFromClass([self class]) code:101 userInfo:@{@"reason": [NSNumber numberWithInt:ERR_GET_REASON(keyError)]}];
publicExtracted = NO;
goto hell;
}
// key type is RSA
if (EVP_PKEY_type(privateKey->type) == EVP_PKEY_RSA)
{
_type = kRSA;
// exponent and modulus length
int eLength, nLength;
// key length
unsigned long keyLength;
// get length for bignums
eLength = BN_num_bytes(privateKey->pkey.rsa->e) + 1;
nLength = BN_num_bytes(privateKey->pkey.rsa->n) + 1;
// init buffer and pointer
keyLength = 4 + 7 + 4 + eLength + 4 + nLength;
publicKeyBuffer = (unsigned char *)malloc(keyLength * sizeof(unsigned char));
publicKeyBufferPointer = publicKeyBuffer;
/**
* OpenSSH RSA Key Format
* {0, 0, 0, 7}
* {'s', 's', 'h', '-', 'r', 's', 'a'}
* public exponent
* public modulus
*/
// push key type
htonu32(publicKeyBufferPointer, 7);
publicKeyBufferPointer += 4;
memcpy(publicKeyBufferPointer, "ssh-rsa", 7);
publicKeyBufferPointer += 7;
// write all bignums to the buffer
publicKeyBufferPointer = writeBignum(publicKeyBufferPointer, privateKey->pkey.rsa->e, eLength);
publicKeyBufferPointer = writeBignum(publicKeyBufferPointer, privateKey->pkey.rsa->n, nLength);
}
// key type is DSA
else if (EVP_PKEY_type(privateKey->type) == EVP_PKEY_DSA)
{
_type = kDSA;
// prime, subprime, generator, public key length
int pLength, qLength, gLength, kLength;
// key length
unsigned long keyLength;
// get length for bignums
pLength = BN_num_bytes(privateKey->pkey.dsa->p) + 1;
qLength = BN_num_bytes(privateKey->pkey.dsa->q) + 1;
gLength = BN_num_bytes(privateKey->pkey.dsa->g) + 1;
kLength = BN_num_bytes(privateKey->pkey.dsa->pub_key) + 1;
// init buffer and pointer
keyLength = 4 + 7 + 4 + pLength + 4 + qLength + 4 + gLength + 4 + kLength;
publicKeyBuffer = (unsigned char *)malloc(keyLength * sizeof(unsigned char));
publicKeyBufferPointer = publicKeyBuffer;
/**
* OpenSSH DSA Key Format
* {0, 0, 0, 7}
* {'s', 's', 'h', '-', 'd', 's', 's'}
* prime number
* 160-bit subprime
* generator of subgroup
* public key
*/
// push key type
htonu32(publicKeyBufferPointer, 7);
publicKeyBufferPointer += 4;
memcpy(publicKeyBufferPointer, "ssh-dss", 7);
publicKeyBufferPointer += 7;
// write all bignums to the buffer
publicKeyBufferPointer = writeBignum(publicKeyBufferPointer, privateKey->pkey.dsa->p, pLength);
publicKeyBufferPointer = writeBignum(publicKeyBufferPointer, privateKey->pkey.dsa->q, qLength);
publicKeyBufferPointer = writeBignum(publicKeyBufferPointer, privateKey->pkey.dsa->g, gLength);
publicKeyBufferPointer = writeBignum(publicKeyBufferPointer, privateKey->pkey.dsa->pub_key, kLength);
}
// unsupported key format
else
{
*error = [NSError errorWithDomain:NSStringFromClass([self class]) code:102 userInfo:NULL];
publicExtracted = NO;
goto hell;
}
// Calculate real buffer length
publicKeyBufferLength = (size_t)(publicKeyBufferPointer - publicKeyBuffer);
// Write public key type identifier to buffer
bufferPublicKeyDecrypted = BIO_new(BIO_s_mem());
BIO_printf(bufferPublicKeyDecrypted, "%s ", _type == kRSA ? "ssh-rsa" : "ssh-dss");
// Write key to base54 buffer
bufferPublicKeyDecrypted = BIO_push(base64EncodedBuffer, bufferPublicKeyDecrypted);
BIO_write(bufferPublicKeyDecrypted, (const void *)publicKeyBuffer, (int)publicKeyBufferLength);
// Terminate it with newline
bufferPublicKeyDecrypted = BIO_pop(base64EncodedBuffer);
BIO_printf(bufferPublicKeyDecrypted, "\n");
// create empty buffer for decripted private key and write it there
bufferPrivateKeyDecrypted = BIO_new(BIO_s_mem());
PEM_write_bio_PrivateKey(bufferPrivateKeyDecrypted, privateKey, NULL, NULL, 0, NULL, NULL);
// write private and public keys to NSString
long bufferPublicKeyLength = BIO_get_mem_data(bufferPublicKeyDecrypted, &bufferPublicKeyOut);
long bufferPrivateKeyLength = BIO_get_mem_data(bufferPrivateKeyDecrypted, &bufferPrivateKeyOut);
_private = [[NSString alloc] initWithBytes:bufferPrivateKeyOut length:bufferPrivateKeyLength encoding:NSUTF8StringEncoding];
_public = [[NSString alloc] initWithBytes:bufferPublicKeyOut length:bufferPublicKeyLength encoding:NSUTF8StringEncoding];
// burn the shit out
hell:
if (privateKey) EVP_PKEY_free(privateKey);
if (bufferPrivateKey) BIO_free_all(bufferPrivateKey);
if (bufferPrivateKeyDecrypted) BIO_free_all(bufferPrivateKeyDecrypted);
if (bufferPublicKeyDecrypted) BIO_free_all(bufferPublicKeyDecrypted);
if (base64EncodedBuffer) BIO_free_all(base64EncodedBuffer);
if (publicKeyBuffer) free(publicKeyBuffer);
EVP_cleanup();
// on any error return NULL
if (!publicExtracted)
{
return NULL;
}
}
return self;
}
- (void)savePrivateKey:(NSString *)privateKeyPath publicKey:(NSString *)publicKeyPath error:(NSError **)error
{
[_private writeToFile:privateKeyPath atomically:NO encoding:NSUTF8StringEncoding error:error];
[_public writeToFile:publicKeyPath atomically:NO encoding:NSUTF8StringEncoding error:error];
}
- (void)savePrivateKey:(NSString *)privateKeyPath error:(NSError **)error
{
[_private writeToFile:privateKeyPath atomically:NO encoding:NSUTF8StringEncoding error:error];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment