trailofbits / SecureEnclaveCrypto

Demonstration library for using the Secure Enclave on iOS
Apache License 2.0
279 stars 41 forks source link

Verify signature #8

Closed hfossli closed 7 years ago

hfossli commented 7 years ago

Okay, so it's easy to verify the signature using the public key (SecKeyRef / SecKey) reference retreived from security framework. I want to eat my own dogfood and verify the signature using the public key hex string. Any ideas how I can do that?

hfossli commented 7 years ago

Example

func verify(signature: String, input: String, publicKey: SecKey) -> Bool {
    ????
}

let input = "aaa"
let signature = "MEQCIDupp6WmfmLPEOrd1xEgAP6wX7Qkpsi7eRUKumw2sJ3AAiAL5YGzuWFWD2FrjLCwuLKx4den9C2ZdpnhzxLVLauqcw=="
let publicKey = "BMD7LUJ/1DqB90hoASaTSEU1mcAI80O6XxOg8z2GqzJXJDSd7k/N9LjVl74EV2L77gmgFOTTXqT5ULvCdxtAnu8="
let valid = verify(signature: signature, publicKey: createKeyRef(publicKey), input: input)
hfossli commented 7 years ago

Allright, I'm able to

    @available(iOS 10.0, *)
    func createPublicKey(from string: String) throws -> SecureEnclaveKeyReference {
        let params: [String: Any] = [
            kSecAttrKeyType as String: attrKeyTypeEllipticCurve,
            kSecAttrKeySizeInBits as String: 256,
            kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
            ]
        let data = Data(base64Encoded: string)! as CFData
        var error: Unmanaged<CFError>? = nil
        guard let key = SecKeyCreateWithData(data, params as CFDictionary, &error) else {
            throw SecureEnclaveHelperError(message: "bad", osStatus: 0)
        }
        return SecureEnclaveKeyReference(key)
    }
hfossli commented 7 years ago

I'll open a pull request on this in near future

hfossli commented 7 years ago

@withzombies do you know how I can do the reverse of what's key_builder.rb is doing?

withzombies commented 7 years ago

I think you need to use SecCertificateCreateWithData on the base64 decoded public key. Then use SecCertificateCopyPublicKey to extract the public key as a SecKey. Then you can use the SecKeyRawVerify function.

hfossli commented 7 years ago

Awesome! I'll try that tomorrow.

hfossli commented 7 years ago

I think I managed to import the key at some point, but I found it easier to just verify the signature with openssl CLI. E.g.

/usr/local/opt/openssl/bin/openssl dgst -sha256 -verify key.pem -signature signature.dat dataToSign.dat
myke91 commented 3 years ago

Hi @hfossli I am currently struggling with verify a signature generated in security framework with openssl. Good you to know you're able to verify the signature in openssl.

Do you have any pointers that could help?

hfossli commented 3 years ago

Yep! I have some unpublished code for that! I'll try to publish this on a branch today.

hfossli commented 3 years ago

Can you btw share your failing code example?

hfossli commented 3 years ago

I don't know what issue you are facing.

Maybe you find one of these helpful

I recently encountered some issues with signatures and you may want to export them in a another binary format –for that issue have a look at EllipticCurveKeyPair.swift:388 in the zip file

        // https://crypto.stackexchange.com/a/1797
        // FIXME: R and S encoding issues https://github.com/IBM-Swift/BlueECC/blob/2c4ea97307b28c1338689b4f7a4391a14c0610a4/Sources/CryptorECC/ECSignature.swift#L120
        static func encodeAsn1(_ bytes: Data) -> Data {
            let (r, s) = split(bytes)
            var asn1 = Data()
            asn1.append(0x30)
            asn1.append(UInt8(2 + r.count + 2 + s.count))
            asn1.append(0x02)
            asn1.append(UInt8(r.count))
            asn1.append(r)
            asn1.append(0x02)
            asn1.append(UInt8(s.count))
            asn1.append(s)
            return asn1
        }
myke91 commented 3 years ago

sure...

here's the part that does the signing:

let privateKey: SecKey = retrieveKey(SCK_TAG)
        let algorithm: SecKeyAlgorithm = .ecdsaSignatureMessageX962SHA256

        guard SecKeyIsAlgorithmSupported(privateKey, .sign, algorithm) else {
            fatalError("signing algorithm not supported")
        }

        let dataToSign = sadJson.data(using: .utf8)
        var error: Unmanaged<CFError>?
        guard let signature = SecKeyCreateSignature(privateKey, algorithm, dataToSign! as CFData, &error) as Data? else {
            return ""
        }

        return signature.base64EncodedString()

The signature generated fails when verifying with openssl using: openssl dgst -sha256 -verify pubkey.pem -signature sig.bin ssasad.bin

but this signature verifies OK when used with: SecKeyVerifySignature(pubKey!, algorithm, dataToSign! as CFData, signature as CFData, nil)

hfossli commented 3 years ago

Either the exported public key is in a bad format or the signature is in a bad format or both.

hfossli commented 3 years ago

How do you export the public key?

myke91 commented 3 years ago

this is how the key is exported:

func exportECPublicKeyToPEM(_ rawPublicKeyBytes: Data, keyType: String, keySize: Int) -> String {
        return PEMKeyFromDERKey(exportECPublicKeyToDER(rawPublicKeyBytes, keyType: keyType, keySize: keySize))
    }

    /**
     * This method transforms a DER encoded key to PEM format. It gets a Base64 representation of
     * the key and then splits this base64 string in 64 character chunks. Then it wraps it in 
     * BEGIN and END key tags.
     */
    func PEMKeyFromDERKey(_ data: Data) -> String {
        // base64 encode the result
        let base64EncodedString = data.base64EncodedString(options: [])

        // split in lines of 64 characters.
        var currentLine = ""
        var resultString = kCryptoExportImportManagerPublicKeyInitialTag
        var charCount = 0
        for character in base64EncodedString {
            charCount += 1
            currentLine.append(character)
            if charCount == kCryptoExportImportManagerPublicNumberOfCharactersInALine {
                resultString += currentLine + "\n"
                charCount = 0
                currentLine = ""
            }
        }
        // final line (if any)
        if currentLine.count > 0 { resultString += currentLine + "\n" }
        // final tag
        resultString += kCryptoExportImportManagerPublicKeyFinalTag
        return resultString
    }
func exportECPublicKeyToDER(_ rawPublicKeyBytes: Data, keyType: String, keySize: Int) -> Data {
        print("Exporting EC raw key: \(rawPublicKeyBytes)")
        // first retrieve the header with the OID for the proper key  curve.
        let curveOIDHeader: [UInt8]
        let curveOIDHeaderLen: Int
        switch (keySize) {
        case kCryptoExportImportManagerSecp256r1CurveLen:
            curveOIDHeader = kCryptoExportImportManagerSecp256r1header
            curveOIDHeaderLen = kCryptoExportImportManagerSecp256r1headerLen
        case kCryptoExportImportManagerSecp384r1CurveLen:
            curveOIDHeader = kCryptoExportImportManagerSecp384r1header
            curveOIDHeaderLen = kCryptoExportImportManagerSecp384r1headerLen
        case kCryptoExportImportManagerSecp521r1CurveLen:
            curveOIDHeader = kCryptoExportImportManagerSecp521r1header
            curveOIDHeaderLen = kCryptoExportImportManagerSecp521r1headerLen
        default:
            curveOIDHeader = []
            curveOIDHeaderLen = 0
        }
        var data = Data(bytes: curveOIDHeader, count: curveOIDHeaderLen)

        // now add the raw data from the retrieved public key
        data.append(rawPublicKeyBytes)
        return data
    }
hfossli commented 3 years ago

If you have a look at the zip file I sent you then you will find React Native bindings. Tested and works on openssl and other envs like node.

hfossli commented 3 years ago

I'm not sure where your problem resides

myke91 commented 3 years ago

Is this in ES256ReactNativeBridge.swift ?