Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 179 additions & 0 deletions Source/AwsCommonRuntimeKit/crt/EC.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import AwsCCal

import struct Foundation.Data
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0.
import struct Foundation.Date
import struct Foundation.TimeInterval

public class ECKeyPair {

public enum ECAlgorithm: UInt32 {
case p256 = 0
case p384 = 1
}

public enum ECExportFormat: UInt32 {
case sec1 = 0
case pkcs8 = 1
case spki = 2
}

public typealias ECRawSignature = (r: Data, s: Data)
public typealias ECPublicCoords = (x: Data, y: Data)

public static let maxExportSize = 512

let rawValue: UnsafeMutablePointer<aws_ecc_key_pair>

private init(rawValue: UnsafeMutablePointer<aws_ecc_key_pair>) {
self.rawValue = rawValue
}

/// Generates new ECKeyPair for the specified algo
static func generate(algorithm: ECAlgorithm) throws -> ECKeyPair {
guard
let rawValue = aws_ecc_key_pair_new_generate_random(
allocator.rawValue, aws_ecc_curve_name(algorithm.rawValue))
else {
throw CommonRunTimeError.crtError(.makeFromLastError())
}
return ECKeyPair(rawValue: rawValue)
}

/// Load ECKeyPair from der representation.
/// data must be raw der bytes. i.e. strip base64 if coming from pem
static func fromDer(data: Data) throws -> ECKeyPair {
try data.withUnsafeBytes { dataPointer in
var dataCur = aws_byte_cursor_from_array(dataPointer.baseAddress, data.count)
guard
let rawValue = aws_ecc_key_pair_new_from_asn1(allocator.rawValue, &dataCur)
else {
throw CommonRunTimeError.crtError(.makeFromLastError())
}
return ECKeyPair(rawValue: rawValue)
}
}

/// Decode der ec signature into raw r and s components
static public func decodeDerEcSignature(signature: Data) throws -> ECKeyPair.ECRawSignature {
var rCur = aws_byte_cursor()
var sCur = aws_byte_cursor()

return try signature.withUnsafeBytes { signaturePointer -> ECKeyPair.ECRawSignature in
let signatureCur = aws_byte_cursor_from_array(
signaturePointer.baseAddress,
signature.count
)

guard
aws_ecc_decode_signature_der_to_raw(
allocator.rawValue,
signatureCur,
&rCur,
&sCur
) == AWS_OP_SUCCESS
else {
throw CommonRunTimeError.crtError(.makeFromLastError())
}

let rData = Data(bytes: rCur.ptr, count: rCur.len)
let sData = Data(bytes: sCur.ptr, count: sCur.len)
return ECKeyPair.ECRawSignature(r: rData, s: sData)
}
}

/// Encode raw ec signature into der format
static public func encodeRawECSignature(signature: ECKeyPair.ECRawSignature) throws -> Data {
return try signature.r.withUnsafeBytes { rPointer in
let rCur = aws_byte_cursor_from_array(rPointer.baseAddress, signature.r.count)
return try signature.s.withUnsafeBytes { sPointer in
let sCur = aws_byte_cursor_from_array(sPointer.baseAddress, signature.s.count)

let bufferSize = signature.r.count + signature.s.count + 32
var outData = Data(count: bufferSize)
var newBufferSize = 0
try outData.withUnsafeMutableBytes { outPointer in
var outBuf = aws_byte_buf_from_empty_array(outPointer.baseAddress, bufferSize)
guard
aws_ecc_encode_signature_raw_to_der(
allocator.rawValue,
rCur, sCur, &outBuf) == AWS_OP_SUCCESS
else {
throw CommonRunTimeError.crtError(.makeFromLastError())
}
newBufferSize = outBuf.len
}
outData.count = newBufferSize
return outData
}
}
}

/// Export key pair into specified format
public func exportKey(format: ECKeyPair.ECExportFormat) throws -> Data {
var outData = Data(count: ECKeyPair.maxExportSize)
var newBufferSize = 0
try outData.withUnsafeMutableBytes { outPointer in
var outBuf = aws_byte_buf_from_empty_array(outPointer.baseAddress, ECKeyPair.maxExportSize)
guard
aws_ecc_key_pair_export(rawValue, aws_ecc_key_export_format(format.rawValue), &outBuf)
== AWS_OP_SUCCESS
else {
throw CommonRunTimeError.crtError(.makeFromLastError())
}
newBufferSize = outBuf.len
}
outData.count = newBufferSize
return outData
}

/// Get public coordinates of the key
public func getPublicCoords() throws -> ECKeyPair.ECPublicCoords {
var xCoord = aws_byte_cursor()
var yCoord = aws_byte_cursor()
aws_ecc_key_pair_get_public_key(rawValue, &xCoord, &yCoord)
let xData = Data(bytes: xCoord.ptr, count: xCoord.len)
let yData = Data(bytes: yCoord.ptr, count: yCoord.len)

return ECKeyPair.ECPublicCoords(x: xData, y: yData)
}

/// Sign the data.
/// Note: input is expected to be a digest, ex. sha256
public func sign(digest: Data) throws -> Data {
let bufferSize = aws_ecc_key_pair_signature_length(rawValue)
var outData = Data(count: bufferSize)
var newBufferSize = 0
try digest.withUnsafeBytes { digestPointer in
var digestCur = aws_byte_cursor_from_array(digestPointer.baseAddress, digest.count)
try outData.withUnsafeMutableBytes { outPointer in
var outBuf = aws_byte_buf_from_empty_array(outPointer.baseAddress, bufferSize)
guard aws_ecc_key_pair_sign_message(rawValue, &digestCur, &outBuf) == AWS_OP_SUCCESS else {
throw CommonRunTimeError.crtError(.makeFromLastError())
}
newBufferSize = outBuf.len
}
}
outData.count = newBufferSize
return outData
}

/// Verify signature. returns true for successful verification.
public func verify(digest: Data, signature: Data) -> Bool {
return digest.withUnsafeBytes { digestPointer -> Bool in
var digestCur = aws_byte_cursor_from_array(digestPointer.baseAddress, digest.count)

return signature.withUnsafeBytes { signaturePointer -> Bool in
var signatureCur = aws_byte_cursor_from_array(
signaturePointer.baseAddress, signature.count)
return aws_ecc_key_pair_verify_signature(rawValue, &digestCur, &signatureCur)
== AWS_OP_SUCCESS
}
}
}

deinit {
aws_ecc_key_pair_release(rawValue)
}
}
66 changes: 66 additions & 0 deletions Test/AwsCommonRuntimeKitTests/crt/ECTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0.
import XCTest
import AwsCCommon

@testable import AwsCommonRuntimeKit

class ECTests: XCBaseTestCase {

func testP256() throws {
let input = "Hello"
let sha256 = try input.data(using: .utf8)!.computeSHA256()

let ecKey = try ECKeyPair.generate(algorithm: ECKeyPair.ECAlgorithm.p256)
let signature = try ecKey.sign(digest: sha256)

let raw = try ECKeyPair.decodeDerEcSignature(signature: signature)
XCTAssertEqual(
signature,
try ECKeyPair.encodeRawECSignature(signature: ECKeyPair.ECRawSignature(r: raw.r, s: raw.s)))

XCTAssertTrue(ecKey.verify(digest: sha256, signature: signature))
}

func testP384() throws {
let input = "Hello"
let sha256 = try input.data(using: .utf8)!.computeSHA256()

let ecKey = try ECKeyPair.generate(algorithm: ECKeyPair.ECAlgorithm.p384)
let signature = try ecKey.sign(digest: sha256)

let raw = try ECKeyPair.decodeDerEcSignature(signature: signature)
XCTAssertEqual(
signature,
try ECKeyPair.encodeRawECSignature(signature: ECKeyPair.ECRawSignature(r: raw.r, s: raw.s)))

XCTAssertTrue(ecKey.verify(digest: sha256, signature: signature))
}

func testImport() throws {
let input = "Hello"
let sha256 = try input.data(using: .utf8)!.computeSHA256()

let sec1Key = """
MHcCAQEEIHjt7c+VnkIkN6RW7QgZPFNLb/9AZEhqSYYMtwrlLb3WoAoGCCqGSM49AwEHoUQDQgAEv2F\
jRpMtADMZ4zoZxshV9chEkembgzZnXSUNe+DA8dKqXN/7qTcZjYJHKIi+Rn88zUGqCJo3DWF/X+ufVf\
dU2g==
"""

guard let keyData = Data(base64Encoded: sec1Key) else {
XCTFail("Failed to decode base64 string")
return
}

let ecKey = try ECKeyPair.fromDer(data: keyData)
let signature = try ecKey.sign(digest: sha256)

let raw = try ECKeyPair.decodeDerEcSignature(signature: signature)
XCTAssertEqual(
signature,
try ECKeyPair.encodeRawECSignature(signature: ECKeyPair.ECRawSignature(r: raw.r, s: raw.s)))

XCTAssertTrue(ecKey.verify(digest: sha256, signature: signature))
}

}
2 changes: 1 addition & 1 deletion aws-common-runtime/aws-c-cal
Submodule aws-c-cal updated 44 files
+38 −10 .github/workflows/ci.yml
+32 −0 .github/workflows/codecov.yml
+2 −2 .github/workflows/handle-stale-discussions.yml
+1 −1 .github/workflows/stale_issue.yml
+30 −2 CMakeLists.txt
+7 −0 builder.json
+35 −7 include/aws/cal/ecc.h
+29 −0 include/aws/cal/hash.h
+55 −0 include/aws/cal/hkdf.h
+34 −0 include/aws/cal/hmac.h
+43 −0 include/aws/cal/private/der.h
+8 −2 include/aws/cal/private/ecc.h
+2 −0 include/aws/cal/private/opensslcrypto_common.h
+24 −4 source/darwin/commoncrypto_hmac.c
+8 −2 source/darwin/commoncrypto_platform_init.c
+0 −4 source/darwin/commoncrypto_sha1.c
+1 −4 source/darwin/commoncrypto_sha256.c
+72 −0 source/darwin/commoncrypto_sha512.c
+24 −52 source/darwin/securityframework_ecc.c
+73 −12 source/der.c
+824 −57 source/ecc.c
+19 −0 source/hash.c
+61 −0 source/hkdf.c
+40 −0 source/hmac.c
+11 −1 source/shared/ed25519.c
+41 −0 source/shared/lccrypto_common.c
+149 −0 source/shared/ref_hkdf.c
+1 −42 source/unix/openssl_platform_init.c
+12 −5 source/unix/openssl_rsa.c
+75 −57 source/unix/opensslcrypto_ecc.c
+30 −25 source/unix/opensslcrypto_hash.c
+40 −5 source/unix/opensslcrypto_hmac.c
+4 −25 source/windows/bcrypt_ecc.c
+47 −12 source/windows/bcrypt_hash.c
+65 −6 source/windows/bcrypt_hmac.c
+8 −24 source/windows/bcrypt_platform_init.c
+38 −0 tests/CMakeLists.txt
+50 −2 tests/der_test.c
+198 −14 tests/ecc_test.c
+0 −1 tests/ed25519_test.c
+128 −0 tests/hkdf_test.c
+31 −0 tests/rsa_test.c
+354 −0 tests/sha512_hmac_test.c
+300 −0 tests/sha512_test.c