agens-no / EllipticCurveKeyPair

Sign, verify, encrypt and decrypt using the Secure Enclave
Other
708 stars 114 forks source link

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

Closed danshev closed 6 years ago

danshev commented 6 years ago

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

hfossli commented 6 years ago

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 commented 6 years ago

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.

jtormey commented 6 years ago

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 commented 6 years ago

Nope; it's frustratingly difficult.

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

hfossli commented 6 years ago

Maybe this might be of help https://github.com/DigitalLeaves/CryptoExportImportManager/

jtormey commented 6 years ago

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

hfossli commented 6 years ago

@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

Or

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 commented 6 years ago

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 commented 6 years ago

Sounds good :)

hfossli commented 6 years ago

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 hfossli@agens.no