Skip to content

Commit

Permalink
Release 2.2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
leif-ibsen committed Feb 1, 2022
1 parent bc3392f commit e0ce0bf
Show file tree
Hide file tree
Showing 66 changed files with 2,365 additions and 855 deletions.
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.3
// swift-tools-version:5.5
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand All @@ -14,7 +14,7 @@ let package = Package(
dependencies: [
// Dependencies declare other packages that this package depends on.
.package(url: "https://github.com/leif-ibsen/ASN1", from: "2.0.0"),
.package(url: "https://github.com/leif-ibsen/BigInt", from: "1.2.6"),
.package(url: "https://github.com/leif-ibsen/BigInt", from: "1.2.11"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
Expand Down
179 changes: 123 additions & 56 deletions README.md

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions Sources/SwiftECC/Base64.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
/// Base64 exists to provide a namespace. It contains static functions for Base64 encoding and decoding.
///
public struct Base64 {

static let linesize = 76

static let base64chars = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
"Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f",
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
Expand All @@ -31,7 +30,7 @@ public struct Base64 {
/// - pem: The PEM header- and footer string
/// - Returns: The Base64 PEM encoding of *input*
public static func pemEncode(_ input: Bytes, _ pem: String) -> String {
return "-----BEGIN " + pem + "-----\n" + encode(input) + "\n-----END " + pem + "-----"
return "-----BEGIN " + pem + "-----\n" + encode(input, 64) + "\n-----END " + pem + "-----"
}

/// PEM decodes a string
Expand All @@ -56,8 +55,9 @@ public struct Base64 {
///
/// - Parameters:
/// - input: Bytes to encode
/// - linesize: Number of characters per line - 76 is default
/// - Returns: The Base64 encoding of *input*
public static func encode(_ input: Bytes) -> String {
public static func encode(_ input: Bytes, _ linesize: Int = 76) -> String {
var base64 = ""
var i = 0
var k = 0
Expand Down
12 changes: 8 additions & 4 deletions Sources/SwiftECC/Cipher/MD.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@
// Created by Leif Ibsen on 05/01/2020.
//

//
// Message digest interface to the SHA2 MD's
//
enum MessageDigestAlgorithm: String {
///
/// Message digest algorithms
///
public enum MessageDigestAlgorithm: CaseIterable {
/// SHA2 224
case SHA2_224
/// SHA2 256
case SHA2_256
/// SHA2 384
case SHA2_384
/// SHA2 512
case SHA2_512
}

Expand Down
16 changes: 12 additions & 4 deletions Sources/SwiftECC/Domain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,16 @@ public class Domain: CustomStringConvertible, Equatable {
/// - cofactor: The cofactor
/// - oid: An optional domain OID
/// - Returns: The domain
/// - Throws: A *domainParameter* exception if 4 * a^3 + 27 * b^2 = 0
/// - Throws: A *domainParameter* exception if 4 * a^3 + 27 * b^2 = 0 or the generator point is not on the curve
public static func instance(name: String, p: BInt, a: BInt, b: BInt, gx: BInt, gy: BInt, order: BInt, cofactor: Int, oid: ASN1ObjectIdentifier? = nil) throws -> Domain {
if (4 * a * a * a + 27 * b * b) % p == BInt.ZERO {
throw ECException.domainParameter
}
return Domain(DomainP(name, p, a, b, gx, gy, order, cofactor, oid))
let domain = Domain(DomainP(name, p, a, b, gx, gy, order, cofactor, oid))
if !domain.contains(domain.g) {
throw ECException.domainParameter
}
return domain
}

/// Constructs a characteristic 2 domain
Expand All @@ -257,12 +261,16 @@ public class Domain: CustomStringConvertible, Equatable {
/// - cofactor: The cofactor
/// - oid: An optional domain OID
/// - Returns: The domain
/// - Throws: A *domainParameter* exception if b = 0
/// - Throws: A *domainParameter* exception if b = 0 or the generator point is not on the curve
public static func instance(name: String, rp: RP, a: BInt, b: BInt, gx: BInt, gy: BInt, order: BInt, cofactor: Int, oid: ASN1ObjectIdentifier? = nil) throws -> Domain {
if b.isZero {
throw ECException.domainParameter
}
return Domain(Domain2(name, rp, a, b, gx, gy, order, cofactor, oid))
let domain = Domain(Domain2(name, rp, a, b, gx, gy, order, cofactor, oid))
if !domain.contains(domain.g) {
throw ECException.domainParameter
}
return domain
}


Expand Down
5 changes: 5 additions & 0 deletions Sources/SwiftECC/Exception.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public enum ECException: Error, CustomStringConvertible {
return "Unknown domain OID"
case .notOnCurve:
return "Point not on curve"
case .keyAgreementParameter:
return "Invalid key agreement parameter"
}
}

Expand All @@ -60,6 +62,9 @@ public enum ECException: Error, CustomStringConvertible {
/// Point to encode does not lie on the domain curve
case encodePoint

/// Invalid key agreement parameter
case keyAgreementParameter

/// Not enough input to decrypt
case notEnoughInput

Expand Down
46 changes: 46 additions & 0 deletions Sources/SwiftECC/PrivateKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,50 @@ public class ECPrivateKey: CustomStringConvertible {
public func decrypt(msg: Data, cipher: AESCipher, mode: BlockMode = .GCM) throws -> Data {
return try Data(self.decrypt(msg: Bytes(msg), cipher: cipher, mode: mode))
}

/// Constructs a shared secret key using Diffie-Hellman key agreement - please refer [SEC 1] section 3.3.1
///
/// - Parameters:
/// - pubKey: The other party's public key
/// - length: The required length of the shared secret
/// - md: The message digest algorithm to use
/// - sharedInfo: Information shared with the other party
/// - cofactor: Use cofactor version - *false* is default
/// - Returns: A byte array which is the shared secret key
/// - Throws: An exception if *this* and *pubKey* do not belong to the same domain or *length* is negative
public func keyAgreement(pubKey: ECPublicKey, length: Int, md: MessageDigestAlgorithm, sharedInfo: Bytes, cofactor: Bool = false) throws -> Bytes {
if self.domain != pubKey.domain {
throw ECException.keyAgreementParameter
}
let mda = MessageDigest(md)
if length >= mda.digestLength * 0xffffffff || length < 0 {
throw ECException.keyAgreementParameter
}
var Z = try self.domain.multiplyPoint(pubKey.w, (cofactor ? self.domain.cofactor : 1) * self.s).x.asMagnitudeBytes()
Z = self.domain.align(Z)

// [SEC 1] - section 3.6.1

var k: Bytes = []
var counter: Bytes = [0, 0, 0, 1]
let n = length == 0 ? 0 : (length - 1) / mda.digestLength + 1
for _ in 0 ..< n {
mda.update(Z)
mda.update(counter)
mda.update(sharedInfo)
k += mda.digest()
counter[3] &+= 1
if counter[3] == 0 {
counter[2] &+= 1
if counter[2] == 0 {
counter[1] &+= 1
if counter[1] == 0 {
counter[0] &+= 1
}
}
}
}
return Bytes(k[0 ..< length])
}

}
4 changes: 2 additions & 2 deletions Sources/SwiftECC/PublicKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ public class ECPublicKey: CustomStringConvertible {
/// - Parameters:
/// - domain: The domain the key belongs to
/// - w: The public key value - a curve point
/// - Throws: An exception if *w* is not on the curve
/// - Throws: An exception if *w* is not on the curve or is infinity
public init(domain: Domain, w: Point) throws {
if !domain.contains(w) {
if !domain.contains(w) || w.infinity {
throw ECException.publicKeyParameter
}
self.domain = domain
Expand Down
2 changes: 1 addition & 1 deletion Tests/SwiftECCTests/Base64Test.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// Base64Test.swift
// AECTests
// SwiftECCTests
//
// Created by Leif Ibsen on 06/01/2020.
//
Expand Down
2 changes: 1 addition & 1 deletion Tests/SwiftECCTests/CipherTest.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// CipherTest.swift
// AECTests
// SwiftECCTests
//
// Created by Leif Ibsen on 04/02/2020.
//
Expand Down
193 changes: 193 additions & 0 deletions Tests/SwiftECCTests/CryptoKitTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
//
// CryptoKitTest.swift
// SwiftECCTests
//
// Created by Leif Ibsen on 27/01/2022.
//

import XCTest
import CryptoKit

// Test compatibility with Swift CryptoKit
class CryptoKitTest: XCTestCase {

static let message = "The quick brown fox jumps over the lazy dog!".data(using: .utf8)!

// CryptoKit signs, SwiftECC verifies
func doTest256A() throws {
let domain = Domain.instance(curve: .EC256r1)
let ckPrivKey = P256.Signing.PrivateKey()
let eccPubKey = try ECPublicKey(pem: ckPrivKey.publicKey.pemRepresentation)
let ckSignature = try ckPrivKey.signature(for: CryptoKitTest.message)
let rs = ckSignature.withUnsafeBytes({return Array($0)})
let eccSignature = ECSignature(domain: domain, r: Bytes(rs[0 ..< 32]), s: Bytes(rs[32 ..< 64]))
XCTAssertTrue(eccPubKey.verify(signature: eccSignature, msg: CryptoKitTest.message))
}

// SwiftECC signs, CryptoKit verifies
func doTest256B() throws {
let domain = Domain.instance(curve: .EC256r1)
let (eccPubKey, eccPrivKey) = domain.makeKeyPair()
let eccSignature = eccPrivKey.sign(msg: CryptoKitTest.message)
let ckSignature = try P256.Signing.ECDSASignature(rawRepresentation: eccSignature.r + eccSignature.s)
let ckPubKey = try P256.Signing.PublicKey(pemRepresentation: eccPubKey.pem)
XCTAssertTrue(ckPubKey.isValidSignature(ckSignature, for: CryptoKitTest.message))
}

// CryptoKit signs, SwiftECC verifies
func doTest384A() throws {
let domain = Domain.instance(curve: .EC384r1)
let ckPrivKey = P384.Signing.PrivateKey()
let eccPubKey = try ECPublicKey(pem: ckPrivKey.publicKey.pemRepresentation)
let ckSignature = try ckPrivKey.signature(for: CryptoKitTest.message)
let rs = ckSignature.withUnsafeBytes({return Array($0)})
let eccSignature = ECSignature(domain: domain, r: Bytes(rs[0 ..< 48]), s: Bytes(rs[48 ..< 96]))
XCTAssertTrue(eccPubKey.verify(signature: eccSignature, msg: CryptoKitTest.message))
}

// SwiftECC signs, CryptoKit verifies
func doTest384B() throws {
let domain = Domain.instance(curve: .EC384r1)
let (eccPubKey, eccPrivKey) = domain.makeKeyPair()
let signature = eccPrivKey.sign(msg: CryptoKitTest.message)
let ckSignature = try P384.Signing.ECDSASignature(rawRepresentation: signature.r + signature.s)
let ckPubKey = try P384.Signing.PublicKey(pemRepresentation: eccPubKey.pem)
XCTAssertTrue(ckPubKey.isValidSignature(ckSignature, for: CryptoKitTest.message))
}

// CryptoKit signs, SwiftECC verifies
func doTest521A() throws {
let domain = Domain.instance(curve: .EC521r1)
let ckPrivKey = P521.Signing.PrivateKey()
let eccPubKey = try ECPublicKey(pem: ckPrivKey.publicKey.pemRepresentation)
let ckSignature = try ckPrivKey.signature(for: CryptoKitTest.message)
let rs = ckSignature.withUnsafeBytes({return Array($0)})
let eccSignature = ECSignature(domain: domain, r: Bytes(rs[0 ..< 66]), s: Bytes(rs[66 ..< 132]))
XCTAssertTrue(eccPubKey.verify(signature: eccSignature, msg: CryptoKitTest.message))
}

// SwiftECC signs, CryptoKit verifies
func doTest521B() throws {
let domain = Domain.instance(curve: .EC521r1)
let (eccPubKey, eccPrivKey) = domain.makeKeyPair()
let eccSignature = eccPrivKey.sign(msg: CryptoKitTest.message)
let ckSignature = try P521.Signing.ECDSASignature(rawRepresentation: eccSignature.r + eccSignature.s)
let ckPubKey = try P521.Signing.PublicKey(pemRepresentation: eccPubKey.pem)
XCTAssertTrue(ckPubKey.isValidSignature(ckSignature, for: CryptoKitTest.message))
}

func doECDH256(_ info: Bytes, _ length: Int) throws {
let domain = Domain.instance(curve: .EC256r1)
let (eccPubKey, eccPrivKey) = domain.makeKeyPair()
let ckPrivKey = P256.KeyAgreement.PrivateKey()
let ckPubKey = ckPrivKey.publicKey

// CryptoKit public key converted to SwiftECC format
let eccPubKey2 = try ECPublicKey(pem: ckPubKey.pemRepresentation)

// SwiftECC public key converted to CryptoKit format
let ckPubKey2 = try P256.KeyAgreement.PublicKey(pemRepresentation: eccPubKey.pem)

// Secret computed with CryptoKit keys
let secret1 = try ckPrivKey.sharedSecretFromKeyAgreement(with: ckPubKey2).x963DerivedSymmetricKey(using: SHA256.self, sharedInfo: info, outputByteCount: length).withUnsafeBytes({return Array($0)})

// Secret computed with SwiftECC keys
let secret2 = try eccPrivKey.keyAgreement(pubKey: eccPubKey2, length: length, md: .SHA2_256, sharedInfo: info)
XCTAssertEqual(secret1, secret2)
}

func doECDH384(_ info: Bytes, _ length: Int) throws {
let domain = Domain.instance(curve: .EC384r1)
let (eccPubKey, eccPrivKey) = domain.makeKeyPair()
let ckPrivKey = P384.KeyAgreement.PrivateKey()
let ckPubKey = ckPrivKey.publicKey

// CryptoKit public key converted to SwiftECC format
let eccPubKey2 = try ECPublicKey(pem: ckPubKey.pemRepresentation)

// SwiftECC public key converted to CryptoKit format
let ckPubKey2 = try P384.KeyAgreement.PublicKey(pemRepresentation: eccPubKey.pem)

// Secret computed with CryptoKit keys
let secret1 = try ckPrivKey.sharedSecretFromKeyAgreement(with: ckPubKey2).x963DerivedSymmetricKey(using: SHA384.self, sharedInfo: info, outputByteCount: length).withUnsafeBytes({return Array($0)})

// Secret computed with SwiftECC keys
let secret2 = try eccPrivKey.keyAgreement(pubKey: eccPubKey2, length: length, md: .SHA2_384, sharedInfo: info)
XCTAssertEqual(secret1, secret2)
}

func doECDH521(_ info: Bytes, _ length: Int) throws {
let domain = Domain.instance(curve: .EC521r1)
let (eccPubKey, eccPrivKey) = domain.makeKeyPair()
let ckPrivKey = P521.KeyAgreement.PrivateKey()
let ckPubKey = ckPrivKey.publicKey

// CryptoKit public key converted to SwiftECC format
let eccPubKey2 = try ECPublicKey(pem: ckPubKey.pemRepresentation)

// SwiftECC public key converted to CryptoKit format
let ckPubKey2 = try P521.KeyAgreement.PublicKey(pemRepresentation: eccPubKey.pem)

// Secret computed with CryptoKit keys
let secret1 = try ckPrivKey.sharedSecretFromKeyAgreement(with: ckPubKey2).x963DerivedSymmetricKey(using: SHA512.self, sharedInfo: info, outputByteCount: length).withUnsafeBytes({return Array($0)})

// Secret computed with SwiftECC keys
let secret2 = try eccPrivKey.keyAgreement(pubKey: eccPubKey2, length: length, md: .SHA2_512, sharedInfo: info)
XCTAssertEqual(secret1, secret2)
}

func doECDH(_ info: Bytes, _ length: Int) throws {
try doECDH256(info, length)
try doECDH384(info, length)
try doECDH521(info, length)
}

func testECDH() throws {
try doECDH([], 1000)
try doECDH([], 32)
try doECDH([], 1)
try doECDH([], 0)
try doECDH([1, 2, 3], 1000)
try doECDH([1, 2, 3], 32)
try doECDH([1, 2, 3], 1)
try doECDH([1, 2, 3], 0)
try doECDH(Bytes(repeating: 1, count: 1000), 1000)
try doECDH(Bytes(repeating: 1, count: 1000), 32)
try doECDH(Bytes(repeating: 1, count: 1000), 1)
try doECDH(Bytes(repeating: 1, count: 1000), 0)
}

func testSignature() throws {
for _ in 0 ..< 10 {
try doTest256A()
try doTest256B()
try doTest384A()
try doTest384B()
try doTest521A()
try doTest521B()
}
}

func testConversion() throws {
let d256 = Domain.instance(curve: .EC256r1)
let (pub256, _) = d256.makeKeyPair()
let ckPubKey256 = try P256.KeyAgreement.PublicKey(pemRepresentation: pub256.pem)
let pub1 = try ECPublicKey(pem: ckPubKey256.pemRepresentation)
XCTAssertEqual(pub256.domain, pub1.domain)
XCTAssertEqual(pub256.w, pub1.w)

let d384 = Domain.instance(curve: .EC384r1)
let (pub384, _) = d384.makeKeyPair()
let ckPubKey384 = try P384.KeyAgreement.PublicKey(pemRepresentation: pub384.pem)
let pub2 = try ECPublicKey(pem: ckPubKey384.pemRepresentation)
XCTAssertEqual(pub384.domain, pub2.domain)
XCTAssertEqual(pub384.w, pub2.w)

let d521 = Domain.instance(curve: .EC521r1)
let (pub521, _) = d521.makeKeyPair()
let ckPubKey521 = try P521.KeyAgreement.PublicKey(pemRepresentation: pub521.pem)
let pub3 = try ECPublicKey(pem: ckPubKey521.pemRepresentation)
XCTAssertEqual(pub521.domain, pub3.domain)
XCTAssertEqual(pub521.w, pub3.w)
}
}
Loading

0 comments on commit e0ce0bf

Please sign in to comment.