Created
March 20, 2014 11:01
-
-
Save volodymyrsmirnov/9661404 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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