203 lines
8.5 KiB
Swift
203 lines
8.5 KiB
Swift
// Copyright © 2019 IBM. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
|
|
import CommonCrypto
|
|
#elseif os(Linux)
|
|
import OpenSSL
|
|
#endif
|
|
|
|
/// The signature produced by applying an Elliptic Curve Digital Signature Algorithm to some Plaintext data.
|
|
/// It consists of two binary unsigned integers, `r` and `s`.
|
|
@available(macOS 10.13, iOS 11, watchOS 4.0, tvOS 11.0, *)
|
|
public struct ECSignature {
|
|
|
|
// MARK: Signature Values
|
|
|
|
/// The r value of the signature.
|
|
/// The size of the signature data depends on the Secure Hash Algorithm used; it will be 32 bytes of data for SHA256, 48 bytes for SHA384, or 66 bytes for SHA512.
|
|
public let r: Data
|
|
|
|
/// The s value of the signature.
|
|
/// The size of the signature data depends on the Secure Hash Algorithm used; it will be 32 bytes of data for SHA256, 48 bytes for SHA384, or 66 bytes for SHA512.
|
|
public let s: Data
|
|
|
|
/// The r and s values of the signature encoded into an ASN1 sequence.
|
|
public let asn1: Data
|
|
|
|
// MARK: Initializers
|
|
|
|
/// Initialize an ECSignature by providing the r and s values.
|
|
/// These must be the same length and either 32, 48 or 66 bytes (Depending on the curve used).
|
|
/// - Parameter r: The r value of the signature as raw data.
|
|
/// - Parameter s: The s value of the signature as raw data.
|
|
/// - Returns: A new instance of `ECSignature`.
|
|
/// - Throws: An ECError if the r or s values are not a valid length.
|
|
public init(r: Data, s: Data) throws {
|
|
let asn1 = try ECSignature.rsSigToASN1(r: r, s: s)
|
|
self.r = r
|
|
self.s = s
|
|
self.asn1 = asn1
|
|
}
|
|
|
|
/// Initialize an ECSignature by providing an ASN1 encoded sequence containing the r and s values.
|
|
/// - Parameter asn1: The r and s values of the signature encoded as an ASN1 sequence.
|
|
/// - Returns: A new instance of `ECSignature`.
|
|
/// - Throws: An ECError if the ASN1 data can't be decoded.
|
|
public init(asn1: Data) throws {
|
|
self.asn1 = asn1
|
|
let (r,s) = try ECSignature.asn1ToRSSig(asn1: asn1)
|
|
self.r = r
|
|
self.s = s
|
|
}
|
|
|
|
// MARK: Verify Signature
|
|
|
|
/// Verify the signature for a given String using the provided public key.
|
|
/// The Data is verified using ECDSA with either SHA256, SHA384 or SHA512, depending on the key's curve.
|
|
/// - Parameter plaintext: The String that was originally signed to produce the signature.
|
|
/// - Parameter using ecPublicKey: The ECPublicKey that will be used to verify the plaintext.
|
|
/// - Returns: true if the plaintext is valid for the provided signature. Otherwise it returns false.
|
|
public func verify(plaintext: String, using ecPublicKey: ECPublicKey) -> Bool {
|
|
let plainTextData = Data(plaintext.utf8)
|
|
return verify(plaintext: plainTextData, using: ecPublicKey)
|
|
}
|
|
|
|
/// Verify the signature for the given Data using the provided public key.
|
|
/// The Data is verified using ECDSA with either SHA256, SHA384 or SHA512, depending on the key's curve.
|
|
/// - Parameter plaintext: The Data that was originally signed to produce the signature.
|
|
/// - Parameter using ecPublicKey: The ECPublicKey that will be used to verify the plaintext.
|
|
/// - Returns: true if the plaintext is valid for the provided signature. Otherwise it returns false.
|
|
public func verify(plaintext: Data, using ecPublicKey: ECPublicKey) -> Bool {
|
|
#if os(Linux)
|
|
let md_ctx = EVP_MD_CTX_new_wrapper()
|
|
let evp_key = EVP_PKEY_new()
|
|
defer {
|
|
EVP_PKEY_free(evp_key)
|
|
EVP_MD_CTX_free_wrapper(md_ctx)
|
|
}
|
|
guard EVP_PKEY_set1_EC_KEY(evp_key, .make(optional: ecPublicKey.nativeKey)) == 1 else {
|
|
return false
|
|
}
|
|
|
|
EVP_DigestVerifyInit(md_ctx, nil, .make(optional: ecPublicKey.curve.signingAlgorithm), nil, evp_key)
|
|
guard plaintext.withUnsafeBytes({ (message: UnsafeRawBufferPointer) -> Int32 in
|
|
return EVP_DigestUpdate(md_ctx, message.baseAddress?.assumingMemoryBound(to: UInt8.self), plaintext.count)
|
|
}) == 1 else {
|
|
return false
|
|
}
|
|
let rc = self.asn1.withUnsafeBytes({ (sig: UnsafeRawBufferPointer) -> Int32 in
|
|
return SSL_EVP_digestVerifyFinal_wrapper(md_ctx, sig.baseAddress?.assumingMemoryBound(to: UInt8.self), self.asn1.count)
|
|
})
|
|
return rc == 1
|
|
#else
|
|
let hash = ecPublicKey.curve.digest(data: plaintext)
|
|
|
|
// Memory storage for error from SecKeyVerifySignature
|
|
var error: Unmanaged<CFError>? = nil
|
|
return SecKeyVerifySignature(ecPublicKey.nativeKey,
|
|
ecPublicKey.curve.signingAlgorithm,
|
|
hash as CFData,
|
|
self.asn1 as CFData,
|
|
&error)
|
|
#endif
|
|
}
|
|
// ASN1 encode the r and s values.
|
|
static func rsSigToASN1(r: Data, s: Data) throws -> Data {
|
|
|
|
guard r.count == s.count, r.count == 32 || r.count == 48 || r.count == 66 else {
|
|
throw ECError.invalidRSLength
|
|
}
|
|
// Convert r,s signature to ASN1 for SecKeyVerifySignature
|
|
var asnSignature = Data()
|
|
// r value is first 32 bytes
|
|
var rSig = r
|
|
// If first bit is 1, add a 00 byte to mark it as positive for ASN1
|
|
if rSig[0] == 0 {
|
|
rSig = rSig.advanced(by: 1)
|
|
}
|
|
if rSig[0].leadingZeroBitCount == 0 {
|
|
rSig = Data(count: 1) + rSig
|
|
}
|
|
// r value is last 32 bytes
|
|
var sSig = s
|
|
// If first bit is 1, add a 00 byte to mark it as positive for ASN1
|
|
if sSig[0] == 0 {
|
|
sSig = sSig.advanced(by: 1)
|
|
}
|
|
if sSig[0].leadingZeroBitCount == 0 {
|
|
sSig = Data(count: 1) + sSig
|
|
}
|
|
// Count Byte lengths for ASN1 length bytes
|
|
let rLengthByte = UInt8(rSig.count)
|
|
let sLengthByte = UInt8(sSig.count)
|
|
// total bytes is r + s + rLengthByte + sLengthByte byte + Integer marking bytes
|
|
let tLengthByte = rLengthByte + sLengthByte + 4
|
|
// 0x30 means sequence, 0x02 means Integer
|
|
if tLengthByte > 127 {
|
|
asnSignature.append(contentsOf: [0x30, 0x81, tLengthByte])
|
|
} else {
|
|
asnSignature.append(contentsOf: [0x30, tLengthByte])
|
|
}
|
|
asnSignature.append(contentsOf: [0x02, rLengthByte])
|
|
asnSignature.append(rSig)
|
|
asnSignature.append(contentsOf: [0x02, sLengthByte])
|
|
asnSignature.append(sSig)
|
|
return asnSignature
|
|
}
|
|
|
|
static func asn1ToRSSig(asn1: Data) throws -> (Data, Data) {
|
|
|
|
let signatureLength: Int
|
|
if asn1.count < 96 {
|
|
signatureLength = 64
|
|
} else if asn1.count < 132 {
|
|
signatureLength = 96
|
|
} else {
|
|
signatureLength = 132
|
|
}
|
|
|
|
// Parse ASN into just r,s data as defined in:
|
|
// https://tools.ietf.org/html/rfc7518#section-3.4
|
|
let (asnSig, _) = ASN1.toASN1Element(data: asn1)
|
|
guard case let ASN1.ASN1Element.seq(elements: seq) = asnSig,
|
|
seq.count >= 2,
|
|
case let ASN1.ASN1Element.bytes(data: rData) = seq[0],
|
|
case let ASN1.ASN1Element.bytes(data: sData) = seq[1]
|
|
else {
|
|
throw ECError.failedASN1Decoding
|
|
}
|
|
// ASN adds 00 bytes in front of negative Int to mark it as positive.
|
|
// These must be removed to make r,a a valid EC signature
|
|
let trimmedRData: Data
|
|
let trimmedSData: Data
|
|
let rExtra = rData.count - signatureLength/2
|
|
if rExtra < 0 {
|
|
trimmedRData = Data(count: 1) + rData
|
|
} else {
|
|
trimmedRData = rData.dropFirst(rExtra)
|
|
}
|
|
let sExtra = sData.count - signatureLength/2
|
|
if sExtra < 0 {
|
|
trimmedSData = Data(count: 1) + sData
|
|
} else {
|
|
trimmedSData = sData.dropFirst(sExtra)
|
|
}
|
|
return (trimmedRData, trimmedSData)
|
|
}
|
|
}
|