Skip to content

Commit

Permalink
Merge pull request #43 from LINE-Client/feature/ecdsa
Browse files Browse the repository at this point in the history
Change JWT verification from RSA to ECDSA
  • Loading branch information
onevcat authored and GitHub Enterprise committed Sep 21, 2018
2 parents f94b73a + 30b3f7f commit bd28545
Show file tree
Hide file tree
Showing 34 changed files with 799 additions and 216 deletions.
64 changes: 42 additions & 22 deletions LineSDK/LineSDK.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

100 changes: 100 additions & 0 deletions LineSDK/LineSDK/Crypto/Algorithms/ECDSA.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//
// ECDRA.swift
//
// Copyright (c) 2016-present, LINE Corporation. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by LINE Corporation.
//
// As with any software that integrates with the LINE Corporation platform, your use of this software
// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement].
// This copyright notice shall be included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

import Foundation
import CommonCrypto

/// Namespace for ECDRA related things.
struct ECDSA {}

/// ECDRA Digest Algorithms.
extension ECDSA {
enum Curve: String, Decodable {
case P256 = "P-256"
case P384 = "P-384"
case P521 = "P-521"

var signatureOctetLength: Int {
return coordinateOctetLength * 2
}

// Standards for Efficient Cryptography Group SEC 1:
// Elliptic Curve Cryptography
// http://www.secg.org/sec1-v2.pdf
var coordinateOctetLength: Int {
switch self {
case .P256:
return 32
case .P384:
return 48
case .P521:
return 66
}
}
}

enum Algorithm: CryptoAlgorithm {
case sha1, sha224, sha256, sha384, sha512

var length: CC_LONG {
switch self {
case .sha1: return CC_LONG(CC_SHA1_DIGEST_LENGTH)
case .sha224: return CC_LONG(CC_SHA224_DIGEST_LENGTH)
case .sha256: return CC_LONG(CC_SHA256_DIGEST_LENGTH)
case .sha384: return CC_LONG(CC_SHA384_DIGEST_LENGTH)
case .sha512: return CC_LONG(CC_SHA512_DIGEST_LENGTH)
}
}

var signatureAlgorithm: SecKeyAlgorithm {
switch self {
case .sha1: return .ecdsaSignatureMessageX962SHA1
case .sha224: return .ecdsaSignatureMessageX962SHA224
case .sha256: return .ecdsaSignatureMessageX962SHA256
case .sha384: return .ecdsaSignatureMessageX962SHA384
case .sha512: return .ecdsaSignatureMessageX962SHA512
}
}

var encryptionAlgorithm: SecKeyAlgorithm {
Log.fatalError("ECDSA should be only used for signing purpose.")
}

var digest: CryptoDigest {
switch self {
case .sha1: return CC_SHA1
case .sha224: return CC_SHA224
case .sha256: return CC_SHA256
case .sha384: return CC_SHA384
case .sha512: return CC_SHA512
}
}

var curve: Curve {
switch self {
case .sha1, .sha224: Log.fatalError("Too simple SHA algorithm. Not supported.")
case .sha256: return .P256
case .sha384: return .P384
case .sha512: return .P521
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// RSAAlgorithm.swift
// RSA.swift
//
// Copyright (c) 2016-present, LINE Corporation. All rights reserved.
//
Expand All @@ -22,15 +22,12 @@
import Foundation
import CommonCrypto

/// Namespace for RSA related things.
struct RSA {}

/// RSA Digest Algorithms.
extension RSA {

typealias Digest = (
_ data: UnsafeRawPointer?,
_ length: CC_LONG,
_ md: UnsafeMutablePointer<UInt8>?) -> UnsafeMutablePointer<UInt8>?

enum Algorithm {
enum Algorithm: CryptoAlgorithm {
case sha1, sha224, sha256, sha384, sha512

var length: CC_LONG {
Expand Down Expand Up @@ -63,7 +60,7 @@ extension RSA {
}
}

var digest: Digest {
var digest: CryptoDigest {
switch self {
case .sha1: return CC_SHA1
case .sha224: return CC_SHA224
Expand All @@ -74,11 +71,3 @@ extension RSA {
}
}
}

extension Data {
func digest(using algorithm: RSA.Algorithm) throws -> Data {
var hash = [UInt8](repeating: 0, count: Int(algorithm.length))
withUnsafeBytes { _ = algorithm.digest($0, CC_LONG(count), &hash) }
return Data(bytes: hash)
}
}
49 changes: 49 additions & 0 deletions LineSDK/LineSDK/Crypto/CryptoAlgorithm.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// CryptoAlgorithm.swift
//
// Copyright (c) 2016-present, LINE Corporation. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by LINE Corporation.
//
// As with any software that integrates with the LINE Corporation platform, your use of this software
// is subject to the LINE Developers Agreement [http://terms2.line.me/LINE_Developers_Agreement].
// This copyright notice shall be included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

import Foundation
import CommonCrypto

typealias CryptoDigest = (
_ data: UnsafeRawPointer?,
_ length: CC_LONG,
_ md: UnsafeMutablePointer<UInt8>?) -> UnsafeMutablePointer<UInt8>?

/// Represents an algorithm used in crypto.
protocol CryptoAlgorithm {
var length: CC_LONG { get }
var signatureAlgorithm: SecKeyAlgorithm { get }
var encryptionAlgorithm: SecKeyAlgorithm { get }
var digest: CryptoDigest { get }
}

extension Data {

/// Calculate the digest with a given algorithm.
///
/// - Parameter algorithm: The algorithm be used. It should provice a digest hash method at least.
/// - Returns: The digest data.
func digest(using algorithm: CryptoAlgorithm) -> Data {
var hash = [UInt8](repeating: 0, count: Int(algorithm.length))
withUnsafeBytes { _ = algorithm.digest($0, CC_LONG(count), &hash) }
return Data(bytes: hash)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// RSA.swift
// CryptoData.swift
//
// Copyright (c) 2016-present, LINE Corporation. All rights reserved.
//
Expand All @@ -20,38 +20,42 @@
//

import Foundation
import CommonCrypto

/// Namespace for RSA related things.
struct RSA {}

/// Define a data type under RSA domain. All RSA data types just behave as a container for raw data, but with
/// Defines a data type under crypto domain. All data types just behave as a container for raw data, but with
/// different operation.
protocol RSAData: Equatable {
protocol CryptoData: Equatable {
var raw: Data { get }
init(raw: Data)
func digest(using algorithm: RSA.Algorithm) throws -> Data
func digest(using algorithm: CryptoAlgorithm) throws -> Data
}


// Some convenience methods.
extension RSAData {
extension CryptoData {

/// Creates a data object in crypto domian for operation from a base64 string.
///
/// - Parameter string: The raw data in base64 encoding.
/// - Throws: A `CryptoError` if something wrong happens.
init(base64Encoded string: String) throws {
guard let data = Data(base64Encoded: string) else {
throw CryptoError.generalError(reason: .base64ConversionFailed(string: string))
}
self.init(raw: data)
}

func digest(using algorithm: RSA.Algorithm) throws -> Data {
return try raw.digest(using: algorithm)

/// Calculates the digest for current data under a given algorithm.
///
/// - Parameter algorithm: The crypto algorithm used to calculate the data digest.
/// - Returns: A `Data` represents the digest of `self`.
func digest(using algorithm: CryptoAlgorithm) -> Data {
return raw.digest(using: algorithm)
}
}

/// Data Types of RSA related domain.
extension RSA {
struct Crypto {}
/// Data Types of Crypto related domain.
extension Crypto {
/// Represents unencrypted data. The plain data could be encrypted or signed.
struct PlainData: RSAData {
struct PlainData: CryptoData {
let raw: Data

init(raw: Data) { self.raw = raw }
Expand All @@ -70,12 +74,12 @@ extension RSA {
/// - algorithm: The digest algorithm used to encrypt data with `key`.
/// - Returns: The encrypted data representation.
/// - Throws: A `CryptoError` if something wrong happens.
func encrypted(with key: PublicKey, using algorithm: RSA.Algorithm) throws -> EncryptedData {
func encrypted(with key: CryptoPublicKey, using algorithm: CryptoAlgorithm) throws -> EncryptedData {
var error: Unmanaged<CFError>?
guard let data = SecKeyCreateEncryptedData(
key.key, algorithm.encryptionAlgorithm, raw as CFData, &error) else
{
throw CryptoError.RSAFailed(reason: .encryptingError(error?.takeRetainedValue()))
throw CryptoError.algorithmsFailed(reason: .encryptingError(error?.takeRetainedValue()))
}

return EncryptedData(raw: data as Data)
Expand All @@ -88,12 +92,12 @@ extension RSA {
/// - algorithm: The digest algorithm used to sign data with `key`.
/// - Returns: The signed data representation.
/// - Throws: A `CryptoError` if something wrong happens.
func signed(with key: PrivateKey, algorithm: RSA.Algorithm) throws -> SignedData {
func signed(with key: CryptoPrivateKey, algorithm: CryptoAlgorithm) throws -> SignedData {
var error: Unmanaged<CFError>?
guard let data = SecKeyCreateSignature(
key.key, algorithm.signatureAlgorithm, raw as CFData, &error) else
{
throw CryptoError.RSAFailed(reason: .signingError(error?.takeRetainedValue()))
throw CryptoError.algorithmsFailed(reason: .signingError(error?.takeRetainedValue()))
}

return SignedData(raw: data as Data)
Expand All @@ -104,24 +108,47 @@ extension RSA {
/// - Parameters:
/// - key: The public key used to encrypt data.
/// - signature: The signed data created when signing the plain data with paired private key.
/// - algorithm:
/// - algorithm: The digest algorithm used to verify data with `key`.
/// - Returns: The digest algorithm used to verify data with `key`.
/// - Throws: A `CryptoError` if something wrong happens.
func verify(with key: PublicKey, signature: SignedData, algorithm: RSA.Algorithm) throws -> Bool {
var error: Unmanaged<CFError>?
let result = SecKeyVerifySignature(
key.key, algorithm.signatureAlgorithm, raw as CFData, signature.raw as CFData, &error)
func verify(with key: CryptoPublicKey, signature: SignedData, algorithm: CryptoAlgorithm) throws -> Bool {

guard error == nil else {
throw CryptoError.RSAFailed(reason: .verifyingError(error?.takeRetainedValue()))
// EC algorithm does not work when using iOS 10 SecKeyVerifySignature and related SecKeyAlgorithm.
// Maybe it is due to https://forums.developer.apple.com/thread/83136 (ECDSA signature generated by OpenSSL)
// It might be an issue or an implementation mistake. However, raw verify works fine, so we treat them in
// different code path.
if let rsaAlgorithm = algorithm as? RSA.Algorithm {
var error: Unmanaged<CFError>?
let result = SecKeyVerifySignature(
key.key, rsaAlgorithm.signatureAlgorithm, raw as CFData, signature.raw as CFData, &error)

guard error == nil else {
let err = error?.takeRetainedValue()
throw CryptoError.algorithmsFailed(
reason: .verifyingError(err, statusCode: (err as? CustomNSError)?.errorCode))
}

return result
} else if let ecAlgorithm = algorithm as? ECDSA.Algorithm {
let digestData = raw.digest(using: ecAlgorithm)
let digest = [UInt8](digestData)
let signatureBytes: [UInt8] = Array(signature.raw)
let status = SecKeyRawVerify(
key.key, .sigRaw, digest, digest.count, signatureBytes, ecAlgorithm.curve.signatureOctetLength)
if status != 0 {
let statusCode = Int(status)
let error = NSError(domain: CryptoError.errorDomain, code: statusCode, userInfo: nil)
throw CryptoError.algorithmsFailed(reason: .verifyingError(error, statusCode: statusCode))
}
return true
} else {
Log.fatalError("Unsupported algorithm: \(algorithm)")
}

return result
}
}

/// Represents encrypted data. The encrypted data could be decrypted.
struct EncryptedData: RSAData {
struct EncryptedData: CryptoData {
let raw: Data

/// Decrypts the current encrypted data with an RSA private key, using a given algorithm.
Expand All @@ -131,19 +158,19 @@ extension RSA {
/// - algorithm: The digest algorithm used to decrypt data with `key`.
/// - Returns: The plain data representation.
/// - Throws: A `CryptoError` if something wrong happens.
func decrypted(with key: PrivateKey, using algorithm: RSA.Algorithm) throws -> PlainData {
func decrypted(with key: CryptoPrivateKey, using algorithm: CryptoAlgorithm) throws -> PlainData {
var error: Unmanaged<CFError>?
guard let data = SecKeyCreateDecryptedData(
key.key, algorithm.encryptionAlgorithm, raw as CFData, &error) else
{
throw CryptoError.RSAFailed(reason: .decryptingError(error?.takeRetainedValue()))
throw CryptoError.algorithmsFailed(reason: .decryptingError(error?.takeRetainedValue()))
}

return PlainData(raw: data as Data)
}
}

struct SignedData: RSAData {
struct SignedData: CryptoData {
let raw: Data
}
}
Loading

0 comments on commit bd28545

Please sign in to comment.