Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Request: example on loading an existing PEM public key in Readme #30

Closed
danshev opened this issue May 4, 2018 · 10 comments
Closed

Request: example on loading an existing PEM public key in Readme #30

danshev opened this issue May 4, 2018 · 10 comments

Comments

@danshev
Copy link

danshev commented May 4, 2018

Simple request to add an example of creating a public key from an existing PEM string to the README

@hfossli
Copy link
Contributor

hfossli commented May 5, 2018

Hi! Thanks for finding your way in here! You want to see how a public key from a PEM string could be imported to the keychain? I think it is somewhat out of scope for this project as this is more about the keypair and not so much about the single key wether private or public. I’ll think about it.

@danshev
Copy link
Author

danshev commented May 12, 2018

Gotcha -- thank you; at least you confirmed my suspicion that there was not a way to do it with this project. My use-cases is such that I need to use iOS to verify a signature where I'm only provided the public key.

@hfossli hfossli closed this as completed Jun 1, 2018
@jtormey
Copy link

jtormey commented Jun 13, 2018

I have the exact same use case for an app I'm working on. So far I haven't been able to figure it out, @danshev did you have any luck with this?

@danshev
Copy link
Author

danshev commented Jun 13, 2018

Nope; it's frustratingly difficult.

https://forums.developer.apple.com/thread/102801

@hfossli
Copy link
Contributor

hfossli commented Jun 13, 2018

@jtormey
Copy link

jtormey commented Jun 13, 2018

Looks like there might be hope after all, thanks for the help!

@hfossli
Copy link
Contributor

hfossli commented Jun 13, 2018

@danshev, I take you are danielfrombwoston on the forum and this is your use case.

  1. My server creates Apple Wallet passes (which contain information, displayed via a QR code).
  2. The server signs the QR code using an ECDSA private key (that is, the QR code ends up having both the information + the hex-encoded digital signature).
  3. I distribute the passes.
  4. (At some later point) I use mobile apps to scan the QR code (thus, reading in both the information + the hex-encoded digital signature).
  5. At this point in the “communication”, I need to be able to use a distributed Public key to verify the signature is legitimate.

What does the QR code contain? I assume

  • user id / transaction id (used as param to get the public key from server)
  • NONCE
  • information
  • signature

Or

  • public key
  • NONCE
  • information
  • signature

Do you a) read the public key from the QR code? Or do you b) retrieve it from server?

If you a) read it from server then I guess what Quinn says makes most sense (embedding the key in a certificate so that iOS may import it effortless).

If you b) read it from QR code I assume you have some restrictions on size and a certificate may be too long, maybe you just want the raw data for the key then. Anyway it should be straight forward to import the key as raw data as long as you hardcode the configuration for the key.

This is how I import and use an RSA key in objective-c. Notice that I set all the query parameters. This is needed because the key doesn't contain all the necessary info in the keyData bytes array.

#import <CommonCrypto/CommonCryptor.h>

CFDictionaryRef ButterflyRSAPublicKeyAddQuery(CFDataRef keyData) {
    CFMutableDictionaryRef query = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CFDictionarySetValue(query, kSecClass, kSecClassKey);
    CFDictionarySetValue(query, kSecAttrKeyClass, kSecAttrKeyClassPublic);
    CFDictionarySetValue(query, kSecAttrKeyType, kSecAttrKeyTypeRSA);
    CFDictionarySetValue(query, kSecValueData, keyData);
    CFDictionarySetValue(query, kSecReturnRef, kCFBooleanTrue);
    CFDictionarySetValue(query, kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked);
    CFDictionarySetValue(query, kSecAttrIsPermanent, kCFBooleanTrue);
    return query;
}

CFDictionaryRef ButterflyRSAPublicKeyDeleteQuery(CFDataRef keyData) {
    CFMutableDictionaryRef query = CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
    CFDictionarySetValue(query, kSecClass, kSecClassKey);
    CFDictionarySetValue(query, kSecAttrKeyClass, kSecAttrKeyClassPublic);
    CFDictionarySetValue(query, kSecAttrKeyType, kSecAttrKeyTypeRSA);
    CFDictionarySetValue(query, kSecValueData, keyData);
    return query;
}

OSStatus ButterflyRSAImportPublicKeyFromData(CFDataRef keyData, SecKeyRef *key) {
    CFDictionaryRef query = ButterflyRSAPublicKeyAddQuery(keyData);
    OSStatus status;
    SecKeyRef result;
    status = SecItemCopyMatching(query, (CFTypeRef *)&result);
    if (status != errSecSuccess) {
        status = SecItemAdd(query, (CFTypeRef *)&result);
        if (status == errSecDuplicateItem) {
            CFDictionaryRef deleteQuery = ButterflyRSAPublicKeyDeleteQuery(keyData);
            SecItemDelete(query);
            CFRelease(deleteQuery);
            status = SecItemAdd(query, (CFTypeRef *)&result);
        }
    }
    *key = result;
    CFRelease(query);
    return status;
}

OSStatus ButterflyRSAEncrypt(CFDataRef data, SecKeyRef key, CFDataRef *encrypted) {
    const size_t blockSize = SecKeyGetBlockSize(key);
    const size_t maxLength = blockSize;
    CFMutableDataRef result = CFDataCreateMutable(NULL, maxLength);
    CFDataSetLength(result, maxLength);
    size_t outLength = maxLength;
    OSStatus status = SecKeyEncrypt(key,                             // key
                                    kSecPaddingPKCS1,                // padding
                                    CFDataGetBytePtr(data),          // plainText
                                    CFDataGetLength(data),           // plainTextLength
                                    CFDataGetMutableBytePtr(result), // cipherText
                                    &outLength);                     // cipherTextLength
    if (status != errSecSuccess) {
        CFRelease(result);
        *encrypted = NULL;
        return status;
    }
    CFDataSetLength(result, outLength);
    *encrypted = result;
    return status;
}

Let me know if you want my help with coding this (paid by the hour or by water fall budget).

@danshev
Copy link
Author

danshev commented Jun 13, 2018

Correct assumption (it's me).

I embed basic user info in the QR code + a signature.

I retrieve the public key from a server.

I'm going to change my endpoint to return a certificate (vs. PEM string).

@hfossli
Copy link
Contributor

hfossli commented Jun 13, 2018

Sounds good :)

@hfossli
Copy link
Contributor

hfossli commented Jun 15, 2018

I've been experimenting a little bit and I got this working

func testOKSignature() {
    let PEM = """
        -----BEGIN PUBLIC KEY-----
        MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdDONNkwaP8OhqFTmjLxVcByyPa19
        ifY2IVDinFei3SvCBv8fgY8AU+Fm5oODksseV0sd4Zy/biSf6AMr0HqHcw==
        -----END PUBLIC KEY-----
        """
    let digest = [UInt8]([0x41, 0x42, 0x43])
    let signatureBytes = [UInt8]([0x30, 0x46, 0x02, 0x21, 0x00, 0x84, 0x25, 0x12, 0xba, 0xa1, 0x6a, 0x3e, 0xc9, 0xb9, 0x77, 0xd4, 0x45, 0x69, 0x23, 0x31, 0x94, 0x42, 0x34, 0x2e, 0x3f, 0xda, 0xe5, 0x4f, 0x24, 0x56, 0xaf, 0x0b, 0x7b, 0x8a, 0x09, 0x78, 0x6b, 0x02, 0x21, 0x00, 0xa1, 0xb8, 0xd7, 0x62, 0xb6, 0xcb, 0x3d, 0x85, 0xb1, 0x6f, 0x6b, 0x07, 0xd0, 0x6d, 0x28, 0x15, 0xcb, 0x06, 0x63, 0xe0, 0x67, 0xe0, 0xb2, 0xf9, 0xa9, 0xc9, 0x29, 0x3b, 0xde, 0x89, 0x53, 0xbb])
    let key: ECPublicKey
    do {
        let keyData = try ECPublicKeyData(PEM: PEM)
        key = try ECPublicKey(data: keyData)
        
        let sha = Data(digest).sha256()
        var shaBytes = [UInt8](repeating: 0, count: sha.count)
        sha.copyBytes(to: &shaBytes, count: sha.count)
        
        let status = SecKeyRawVerify(key.key, .PKCS1, &shaBytes, shaBytes.count, signatureBytes, signatureBytes.count)
        XCTAssert(status == errSecSuccess)
    } catch {
        XCTFail("Shouldn't throw \(error)")
        return
    }
}

Let me know if you are interested this piece. I have a full test suite and source code available :-) contact me on [email protected]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants