Re: openssl-users Digest, Vol 11, Issue 5

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

Re: openssl-users Digest, Vol 11, Issue 5

David Lobron-2

> Your attached sample certificate and private key (1024 bit RSA) works fine.
> I am reading it with PEM_read_PrivateKey( fp, &key, NULL, NULL), and also
> PEM_read_bio_PrivateKey(pkeybio, NULL, 0, NULL) works.
>
> If you could post the code or code fragment that creates the problem?
> d2i_RSAPrivateKey() is not reading PEM, just making sure...

Thanks very much, Frank.  My code reads the PEM file, base64-decodes it, and passes the resulting DER value to d2i_RSAPrivateKey.  I verified that I can extract the private key with d2i_PrivateKey from the DER formatted data, and I can call SSL_CTX_use_PrivateKey with it on my SSL context without a problem.  It's only when I call d2i_RSAPrivateKey I encounter a problem.  I have included the code below, with annotations (it's in Objective-C).  

I've got custom classes for X509Certificate and X509PrivateKey.  I use them like this:

X509Certificate *cert = [X509Certificate certificateWithPemEncodedFile:certFile];
X509PrivateKey *privKey = [X509PrivateKey privateKeyWithPemEncodedFile:keyFile];
[cert validateWithPrivateKey:privKey];
[privKey validate];

That last call to [privKey validate] is where things fail currently.  The validateWithPrivateKey method works fine, and it looks like this:

- (void)validateWithPrivateKey:(X509PrivateKey *)key
{
    SSL_CTX *sslContext;
   
    [self validate];
    sslContext = SSL_CTX_new(TLSv1_server_method());
    NS_DURING {
        NSData *d = [key der];
        const unsigned char *p = (const unsigned char *)[d bytes];
        EVP_PKEY *pkey = d2i_PrivateKey(EVP_PKEY_RSA, NULL, &p, [d length]);
        if (!sslContext)
            [NSException raise:X509CertificateExcInternalError format:@"SSL_CTX_new failed: %@", sslErrorString()];
        if (SSL_CTX_use_certificate(sslContext, _x) != 1)
            [NSException raise:X509CertificateExcInvalidCertificate format:@"SSL_CTX_use_certificate failed: %@", sslErrorString()];
        if (SSL_CTX_use_PrivateKey(sslContext, pkey) != 1)
            [NSException raise:X509CertificateExcInvalidPrivateKey format:@"SSL_CTX_use_PrivateKey_ASN1 failed: %@", sslErrorString()];
        SSL_CTX_free(sslContext);
    } NS_HANDLER {
        if (sslContext)
            SSL_CTX_free(sslContext);
        [localException raise];
    } NS_ENDHANDLER
}

(That initial call to "self validate" simply validates the cert object's SSL_CTX).

I initialize my private key object as follows:

- (id)initWithPemEncodedFile:(NSString *)path
{
    NSData *d = nil;
    NS_DURING {
        NSString *s;
        NSArray *inputLines;
       
        // read the file
        s = [NSString stringWithContentsOfFile:path];
        if (s == nil || [s length] == 0)
            [NSException raise:X509CertificateExcParameterError format:@"File %@ is empty or cannot be read", path];
        inputLines = [s componentsSeparatedByString:@"\n"];
        d = [X509Certificate decodePemFragmentFromLines:inputLines withBoundaryPhrases:[NSArray arrayWithObjects:@"PRIVATE KEY", @"RSA PRIVATE KEY", nil]];
    } NS_HANDLER {
        [self release];
        [localException raise];
    } NS_ENDHANDLER
    return [self initWithDer:d];

}

The decodePemFragmentFromLines method looks like this:

// Extract part of a PEM-encoded message, base64-decode it, and return an NSData object
+ (NSData *)decodePemFragmentFromLines:(NSArray *)inputLines withBoundaryPhrases:(NSArray *)boundaryPhrases
{
    NSEnumerator *e = [inputLines objectEnumerator];
    NSMutableString *b64 = [NSMutableString string];
    NSString *s;
    NSString *boundaryPhrase = nil;
    NSString *startBoundary = nil;
    NSString *endBoundary = nil;
   
    while ((s = [e nextObject]) != nil) {
        NSEnumerator *e = [boundaryPhrases objectEnumerator];
        while ((boundaryPhrase = [e nextObject]) != nil) {
            startBoundary = [NSString stringWithFormat:@"-----BEGIN %@-----", boundaryPhrase];
            if ([s isEqualToString:startBoundary]) {
                endBoundary = [NSString stringWithFormat:@"-----END %@-----", boundaryPhrase];
                break;
            }
        }
        if (endBoundary != nil)
            break;
    }
    if (s == nil)
        [NSException raise:X509CertificateExcParameterError format:@"Start boundary \"%@\" not found", startBoundary];
    while ((s = [e nextObject]) != nil) {
        if ([s isEqualToString:endBoundary])
            break;
        [b64 appendString:s];
    }
    if (s == nil)
        [NSException raise:X509CertificateExcParameterError format:@"End boundary \"%@\" not found", endBoundary];
    return base64Decode(b64);
}

The initWithDer method is simply:

- (id)initWithDer:(NSData *)der
{
    if ((self = [super init]) != nil) {
        _der = [der copy];
    }
    return self;
}

All of the above works as expected, but when I call d2i_RSAPrivateKey on the _der object, it fails.  Here is the code that throws the exception:

// validate; throws exception if key invalid
- (void)validate
{
    const unsigned char *p = (unsigned char *)[_der bytes];
    RSA *r = d2i_RSAPrivateKey(0, &p, [_der length]);
    int n;
    if (r == 0)
        [NSException raise:X509CertificateExcInvalidPrivateKey format:@"cannot decode RSA private key"];
    NS_DURING {
        switch (n = RSA_check_key(r)) {
            case 1: // ok
                break;
            default:
                [NSException raise:X509CertificateExcInvalidPrivateKey format:@"RSA_check_key() returned %d", n];
        }
    } NS_HANDLER {
        RSA_free(r);
        [localException raise];
    } NS_ENDHANDLER
    RSA_free(r);

}

Thanks for any help you can give here!

--David



_______________________________________________
openssl-users mailing list
To unsubscribe: https://mta.openssl.org/mailman/listinfo/openssl-users

smime.p7s (3K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: d2i_RSAPrivateKey not working on a private key

Frank Migge-2
Hi David,

I didn't spot the error in your code. But since d2i_RSAPrivateKey() fails on the DER data, how about a workaround? If your remaining code works fine, you could extract the RSA key from the EVP_PKEY object (which you are getting with d2i_PrivateKey), e.g.

   RSA *rsakey;
   rsakey = EVP_PKEY_get1_RSA(privkey);  // this reads EVP_PKEY, best to test before to ensure it is RSA

   if (RSA_check_key(rsakey)) { printf("RSA key is valid.\n"); }
   else { printf("Error validating RSA key.\n"); }

   RSA_print_fp(stdout, rsakey, 3);
   ...

Alternatively, building a test case around
d2i_RSAPrivateKey() to see if the DER format is valid, e.g. converting the PEM key into DER using the OpenSSL commandline, and binary-compare with the programs DER data.

openssl rsa -inform PEM -in test-key.pem -outform DER -out key.bin

Best Wishes,
Frank

Monday, October 12, 2015 11:34 PM

Thanks very much, Frank. My code reads the PEM file, base64-decodes it, and passes the resulting DER value to d2i_RSAPrivateKey. I verified that I can extract the private key with d2i_PrivateKey from the DER formatted data, and I can call SSL_CTX_use_PrivateKey with it on my SSL context without a problem. It's only when I call d2i_RSAPrivateKey I encounter a problem. I have included the code below, with annotations (it's in Objective-C).

I've got custom classes for X509Certificate and X509PrivateKey. I use them like this:

X509Certificate *cert = [X509Certificate certificateWithPemEncodedFile:certFile];
X509PrivateKey *privKey = [X509PrivateKey privateKeyWithPemEncodedFile:keyFile];
[cert validateWithPrivateKey:privKey];
[privKey validate];

That last call to [privKey validate] is where things fail currently. The validateWithPrivateKey method works fine, and it looks like this:

- (void)validateWithPrivateKey:(X509PrivateKey *)key
{
SSL_CTX *sslContext;

[self validate];
sslContext = SSL_CTX_new(TLSv1_server_method());
NS_DURING {
NSData *d = [key der];
const unsigned char *p = (const unsigned char *)[d bytes];
EVP_PKEY *pkey = d2i_PrivateKey(EVP_PKEY_RSA, NULL, &p, [d length]);
if (!sslContext)
[NSException raise:X509CertificateExcInternalError format:@"SSL_CTX_new failed: %@", sslErrorString()];
if (SSL_CTX_use_certificate(sslContext, _x) != 1)
[NSException raise:X509CertificateExcInvalidCertificate format:@"SSL_CTX_use_certificate failed: %@", sslErrorString()];
if (SSL_CTX_use_PrivateKey(sslContext, pkey) != 1)
[NSException raise:X509CertificateExcInvalidPrivateKey format:@"SSL_CTX_use_PrivateKey_ASN1 failed: %@", sslErrorString()];
SSL_CTX_free(sslContext);
} NS_HANDLER {
if (sslContext)
SSL_CTX_free(sslContext);
[localException raise];
} NS_ENDHANDLER
}

(That initial call to "self validate" simply validates the cert object's SSL_CTX).

I initialize my private key object as follows:

- (id)initWithPemEncodedFile:(NSString *)path
{
NSData *d = nil;
NS_DURING {
NSString *s;
NSArray *inputLines;

// read the file
s = [NSString stringWithContentsOfFile:path];
if (s == nil || [s length] == 0)
[NSException raise:X509CertificateExcParameterError format:@"File %@ is empty or cannot be read", path];
inputLines = [s componentsSeparatedByString:@"\n"];
d = [X509Certificate decodePemFragmentFromLines:inputLines withBoundaryPhrases:[NSArray arrayWithObjects:@"PRIVATE KEY", @"RSA PRIVATE KEY", nil]];
} NS_HANDLER {
[self release];
[localException raise];
} NS_ENDHANDLER
return [self initWithDer:d];

}

The decodePemFragmentFromLines method looks like this:

// Extract part of a PEM-encoded message, base64-decode it, and return an NSData object
+ (NSData *)decodePemFragmentFromLines:(NSArray *)inputLines withBoundaryPhrases:(NSArray *)boundaryPhrases
{
NSEnumerator *e = [inputLines objectEnumerator];
NSMutableString *b64 = [NSMutableString string];
NSString *s;
NSString *boundaryPhrase = nil;
NSString *startBoundary = nil;
NSString *endBoundary = nil;

while ((s = [e nextObject]) != nil) {
NSEnumerator *e = [boundaryPhrases objectEnumerator];
while ((boundaryPhrase = [e nextObject]) != nil) {
startBoundary = [NSString stringWithFormat:@"-----BEGIN %@-----", boundaryPhrase];
if ([s isEqualToString:startBoundary]) {
endBoundary = [NSString stringWithFormat:@"-----END %@-----", boundaryPhrase];
break;
}
}
if (endBoundary != nil)
break;
}
if (s == nil)
[NSException raise:X509CertificateExcParameterError format:@"Start boundary \"%@\" not found", startBoundary];
while ((s = [e nextObject]) != nil) {
if ([s isEqualToString:endBoundary])
break;
[b64 appendString:s];
}
if (s == nil)
[NSException raise:X509CertificateExcParameterError format:@"End boundary \"%@\" not found", endBoundary];
return base64Decode(b64);
}

The initWithDer method is simply:

- (id)initWithDer:(NSData *)der
{
if ((self = [super init]) != nil) {
_der = [der copy];
}
return self;
}

All of the above works as expected, but when I call d2i_RSAPrivateKey on the _der object, it fails. Here is the code that throws the exception:

// validate; throws exception if key invalid
- (void)validate
{
const unsigned char *p = (unsigned char *)[_der bytes];
RSA *r = d2i_RSAPrivateKey(0, &p, [_der length]);
int n;
if (r == 0)
[NSException raise:X509CertificateExcInvalidPrivateKey format:@"cannot decode RSA private key"];
NS_DURING {
switch (n = RSA_check_key(r)) {
case 1: // ok
break;
default:
[NSException raise:X509CertificateExcInvalidPrivateKey format:@"RSA_check_key() returned %d", n];
}
} NS_HANDLER {
RSA_free(r);
[localException raise];
} NS_ENDHANDLER
RSA_free(r);

}

Thanks for any help you can give here!

--David


_______________________________________________
openssl-users mailing list
To unsubscribe: https://mta.openssl.org/mailman/listinfo/openssl-users

--
Sent with Postbox

_______________________________________________
openssl-users mailing list
To unsubscribe: https://mta.openssl.org/mailman/listinfo/openssl-users
Reply | Threaded
Open this post in threaded view
|

Re: d2i_RSAPrivateKey not working on a private key

Benjamin Kaduk
Hi Frank,

Thanks for these thoughts.

On 10/13/2015 09:57 PM, Frank Migge wrote:
Hi David,

I didn't spot the error in your code. But since d2i_RSAPrivateKey() fails on the DER data, how about a workaround? If your remaining code works fine, you could extract the RSA key from the EVP_PKEY object (which you are getting with d2i_PrivateKey), e.g.

   RSA *rsakey;
   rsakey = EVP_PKEY_get1_RSA(privkey);  // this reads EVP_PKEY, best to test before to ensure it is RSA

   if (RSA_check_key(rsakey)) { printf("RSA key is valid.\n"); }
   else { printf("Error validating RSA key.\n"); }

   RSA_print_fp(stdout, rsakey, 3);
   ...

Alternatively, building a test case around
d2i_RSAPrivateKey() to see if the DER format is valid, e.g. converting the PEM key into DER using the OpenSSL commandline, and binary-compare with the programs DER data.

I worked with David some offline, and it seems that the problematic file was the DER encoding of a PKCS8_PRIV_KEY_INFO, with the pkey ASN1_ANY element being an octet string containing the DER encoding of the actual RSAPrivateKey object (checked using openssl asn1parse).  So it seems that calling d2i_RSAPrivateKey() on it directly would necessarily fail, since that ASN.1 decoder has no provision to skip the bits in the PKCS#8 container.

I think we're in agreement that the EVP interfaces are friendlier to use, at this point, though.

-Ben

_______________________________________________
openssl-users mailing list
To unsubscribe: https://mta.openssl.org/mailman/listinfo/openssl-users