Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
168 changes: 168 additions & 0 deletions Source/AwsCommonRuntimeKit/crt/EC.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
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
}

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)
}

static func fromDer(data: Data) throws -> ECKeyPair {
try data.withUnsafeBytes { bufferPointer in
var byteCursor = aws_byte_cursor_from_array(bufferPointer.baseAddress, data.count)
guard
let rawValue = aws_ecc_key_pair_new_from_asn1(allocator.rawValue, &byteCursor)
else {
throw CommonRunTimeError.crtError(.makeFromLastError())
}
return ECKeyPair(rawValue: rawValue)
}
}

/// Decodes 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 signatureCursor = aws_byte_cursor_from_array(
signaturePointer.baseAddress,
signature.count
)

guard
aws_ecc_decode_signature_der_to_raw(
allocator.rawValue,
signatureCursor,
&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 rCursor = aws_byte_cursor_from_array(rPointer.baseAddress, signature.r.count)
return try signature.s.withUnsafeBytes { sPointer in
let sCursor = aws_byte_cursor_from_array(sPointer.baseAddress, signature.s.count)

let bufferSize = signature.r.count + signature.s.count + 32
var bufferData = Data(count: bufferSize)
var newBufferSize = 0
try bufferData.withUnsafeMutableBytes { bufferPointer in
var buffer = aws_byte_buf_from_empty_array(bufferPointer.baseAddress, bufferSize)
guard
aws_ecc_encode_signature_raw_to_der(
allocator.rawValue,
rCursor, sCursor, &buffer) == AWS_OP_SUCCESS
else {
throw CommonRunTimeError.crtError(.makeFromLastError())
}
newBufferSize = buffer.len
}
bufferData.count = newBufferSize
return bufferData
}
}
}

public func exportKey(format: ECKeyPair.ECExportFormat) throws -> Data {
var bufferData = Data(count: ECKeyPair.maxExportSize)
try bufferData.withUnsafeMutableBytes { bufferPointer in
var buffer = aws_byte_buf_from_empty_array(bufferPointer.baseAddress, ECKeyPair.maxExportSize)
guard
aws_ecc_key_pair_export(rawValue, aws_ecc_key_export_format(format.rawValue), &buffer)
== AWS_OP_SUCCESS
else {
throw CommonRunTimeError.crtError(.makeFromLastError())
}
}
return bufferData
}

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)
}

public func sign(digest: Data) throws -> Data {
let bufferSize = aws_ecc_key_pair_signature_length(rawValue)
var bufferData = Data(count: bufferSize)
var newBufferSize = 0
try digest.withUnsafeBytes { bufferPointer in
var byteCursor = aws_byte_cursor_from_array(bufferPointer.baseAddress, digest.count)
try bufferData.withUnsafeMutableBytes { bufferPointer in
var buffer = aws_byte_buf_from_empty_array(bufferPointer.baseAddress, bufferSize)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trivial: for readability, maybe rename bufferPointer so it's not the same from this two different disclosure?

The first one is for digest and the second one is for the buffer to return, right?

Oh, just like the verify functions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, went through the code and updated a bunch of places (original was based on hash impl naming, which we should probably refactor at some point as well)

guard aws_ecc_key_pair_sign_message(rawValue, &byteCursor, &buffer) == AWS_OP_SUCCESS else {
throw CommonRunTimeError.crtError(.makeFromLastError())
}
newBufferSize = buffer.len
}
}
bufferData.count = newBufferSize
return bufferData
}

public func verify(digest: Data, signature: Data) -> Bool {
return digest.withUnsafeBytes { digestPointer -> Bool in
var digestCursor = aws_byte_cursor_from_array(digestPointer.baseAddress, digest.count)

return signature.withUnsafeBytes { signaturePointer -> Bool in
var signatureCursor = aws_byte_cursor_from_array(
signaturePointer.baseAddress, signature.count)
return aws_ecc_key_pair_verify_signature(rawValue, &digestCursor, &signatureCursor)
== 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
+20 −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
Loading