Skip to content

Commit cfed01f

Browse files
bindings for EC crypto (#356)
1 parent 03a527a commit cfed01f

3 files changed

Lines changed: 246 additions & 1 deletion

File tree

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import AwsCCal
2+
3+
import struct Foundation.Data
4+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
5+
// SPDX-License-Identifier: Apache-2.0.
6+
import struct Foundation.Date
7+
import struct Foundation.TimeInterval
8+
9+
public class ECKeyPair {
10+
11+
public enum ECAlgorithm: UInt32 {
12+
case p256 = 0
13+
case p384 = 1
14+
}
15+
16+
public enum ECExportFormat: UInt32 {
17+
case sec1 = 0
18+
case pkcs8 = 1
19+
case spki = 2
20+
}
21+
22+
public typealias ECRawSignature = (r: Data, s: Data)
23+
public typealias ECPublicCoords = (x: Data, y: Data)
24+
25+
public static let maxExportSize = 512
26+
27+
let rawValue: UnsafeMutablePointer<aws_ecc_key_pair>
28+
29+
private init(rawValue: UnsafeMutablePointer<aws_ecc_key_pair>) {
30+
self.rawValue = rawValue
31+
}
32+
33+
/// Generates new ECKeyPair for the specified algo
34+
static func generate(algorithm: ECAlgorithm) throws -> ECKeyPair {
35+
guard
36+
let rawValue = aws_ecc_key_pair_new_generate_random(
37+
allocator.rawValue, aws_ecc_curve_name(algorithm.rawValue))
38+
else {
39+
throw CommonRunTimeError.crtError(.makeFromLastError())
40+
}
41+
return ECKeyPair(rawValue: rawValue)
42+
}
43+
44+
/// Load ECKeyPair from der representation.
45+
/// data must be raw der bytes. i.e. strip base64 if coming from pem
46+
static func fromDer(data: Data) throws -> ECKeyPair {
47+
try data.withUnsafeBytes { dataPointer in
48+
var dataCur = aws_byte_cursor_from_array(dataPointer.baseAddress, data.count)
49+
guard
50+
let rawValue = aws_ecc_key_pair_new_from_asn1(allocator.rawValue, &dataCur)
51+
else {
52+
throw CommonRunTimeError.crtError(.makeFromLastError())
53+
}
54+
return ECKeyPair(rawValue: rawValue)
55+
}
56+
}
57+
58+
/// Decode der ec signature into raw r and s components
59+
static public func decodeDerEcSignature(signature: Data) throws -> ECKeyPair.ECRawSignature {
60+
var rCur = aws_byte_cursor()
61+
var sCur = aws_byte_cursor()
62+
63+
return try signature.withUnsafeBytes { signaturePointer -> ECKeyPair.ECRawSignature in
64+
let signatureCur = aws_byte_cursor_from_array(
65+
signaturePointer.baseAddress,
66+
signature.count
67+
)
68+
69+
guard
70+
aws_ecc_decode_signature_der_to_raw(
71+
allocator.rawValue,
72+
signatureCur,
73+
&rCur,
74+
&sCur
75+
) == AWS_OP_SUCCESS
76+
else {
77+
throw CommonRunTimeError.crtError(.makeFromLastError())
78+
}
79+
80+
let rData = Data(bytes: rCur.ptr, count: rCur.len)
81+
let sData = Data(bytes: sCur.ptr, count: sCur.len)
82+
return ECKeyPair.ECRawSignature(r: rData, s: sData)
83+
}
84+
}
85+
86+
/// Encode raw ec signature into der format
87+
static public func encodeRawECSignature(signature: ECKeyPair.ECRawSignature) throws -> Data {
88+
return try signature.r.withUnsafeBytes { rPointer in
89+
let rCur = aws_byte_cursor_from_array(rPointer.baseAddress, signature.r.count)
90+
return try signature.s.withUnsafeBytes { sPointer in
91+
let sCur = aws_byte_cursor_from_array(sPointer.baseAddress, signature.s.count)
92+
93+
let bufferSize = signature.r.count + signature.s.count + 32
94+
var outData = Data(count: bufferSize)
95+
var newBufferSize = 0
96+
try outData.withUnsafeMutableBytes { outPointer in
97+
var outBuf = aws_byte_buf_from_empty_array(outPointer.baseAddress, bufferSize)
98+
guard
99+
aws_ecc_encode_signature_raw_to_der(
100+
allocator.rawValue,
101+
rCur, sCur, &outBuf) == AWS_OP_SUCCESS
102+
else {
103+
throw CommonRunTimeError.crtError(.makeFromLastError())
104+
}
105+
newBufferSize = outBuf.len
106+
}
107+
outData.count = newBufferSize
108+
return outData
109+
}
110+
}
111+
}
112+
113+
/// Export key pair into specified format
114+
public func exportKey(format: ECKeyPair.ECExportFormat) throws -> Data {
115+
var outData = Data(count: ECKeyPair.maxExportSize)
116+
var newBufferSize = 0
117+
try outData.withUnsafeMutableBytes { outPointer in
118+
var outBuf = aws_byte_buf_from_empty_array(outPointer.baseAddress, ECKeyPair.maxExportSize)
119+
guard
120+
aws_ecc_key_pair_export(rawValue, aws_ecc_key_export_format(format.rawValue), &outBuf)
121+
== AWS_OP_SUCCESS
122+
else {
123+
throw CommonRunTimeError.crtError(.makeFromLastError())
124+
}
125+
newBufferSize = outBuf.len
126+
}
127+
outData.count = newBufferSize
128+
return outData
129+
}
130+
131+
/// Get public coordinates of the key
132+
public func getPublicCoords() throws -> ECKeyPair.ECPublicCoords {
133+
var xCoord = aws_byte_cursor()
134+
var yCoord = aws_byte_cursor()
135+
aws_ecc_key_pair_get_public_key(rawValue, &xCoord, &yCoord)
136+
let xData = Data(bytes: xCoord.ptr, count: xCoord.len)
137+
let yData = Data(bytes: yCoord.ptr, count: yCoord.len)
138+
139+
return ECKeyPair.ECPublicCoords(x: xData, y: yData)
140+
}
141+
142+
/// Sign the data.
143+
/// Note: input is expected to be a digest, ex. sha256
144+
public func sign(digest: Data) throws -> Data {
145+
let bufferSize = aws_ecc_key_pair_signature_length(rawValue)
146+
var outData = Data(count: bufferSize)
147+
var newBufferSize = 0
148+
try digest.withUnsafeBytes { digestPointer in
149+
var digestCur = aws_byte_cursor_from_array(digestPointer.baseAddress, digest.count)
150+
try outData.withUnsafeMutableBytes { outPointer in
151+
var outBuf = aws_byte_buf_from_empty_array(outPointer.baseAddress, bufferSize)
152+
guard aws_ecc_key_pair_sign_message(rawValue, &digestCur, &outBuf) == AWS_OP_SUCCESS else {
153+
throw CommonRunTimeError.crtError(.makeFromLastError())
154+
}
155+
newBufferSize = outBuf.len
156+
}
157+
}
158+
outData.count = newBufferSize
159+
return outData
160+
}
161+
162+
/// Verify signature. returns true for successful verification.
163+
public func verify(digest: Data, signature: Data) -> Bool {
164+
return digest.withUnsafeBytes { digestPointer -> Bool in
165+
var digestCur = aws_byte_cursor_from_array(digestPointer.baseAddress, digest.count)
166+
167+
return signature.withUnsafeBytes { signaturePointer -> Bool in
168+
var signatureCur = aws_byte_cursor_from_array(
169+
signaturePointer.baseAddress, signature.count)
170+
return aws_ecc_key_pair_verify_signature(rawValue, &digestCur, &signatureCur)
171+
== AWS_OP_SUCCESS
172+
}
173+
}
174+
}
175+
176+
deinit {
177+
aws_ecc_key_pair_release(rawValue)
178+
}
179+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0.
3+
import XCTest
4+
import AwsCCommon
5+
6+
@testable import AwsCommonRuntimeKit
7+
8+
class ECTests: XCBaseTestCase {
9+
10+
func testP256() throws {
11+
let input = "Hello"
12+
let sha256 = try input.data(using: .utf8)!.computeSHA256()
13+
14+
let ecKey = try ECKeyPair.generate(algorithm: ECKeyPair.ECAlgorithm.p256)
15+
let signature = try ecKey.sign(digest: sha256)
16+
17+
let raw = try ECKeyPair.decodeDerEcSignature(signature: signature)
18+
XCTAssertEqual(
19+
signature,
20+
try ECKeyPair.encodeRawECSignature(signature: ECKeyPair.ECRawSignature(r: raw.r, s: raw.s)))
21+
22+
XCTAssertTrue(ecKey.verify(digest: sha256, signature: signature))
23+
}
24+
25+
func testP384() throws {
26+
let input = "Hello"
27+
let sha256 = try input.data(using: .utf8)!.computeSHA256()
28+
29+
let ecKey = try ECKeyPair.generate(algorithm: ECKeyPair.ECAlgorithm.p384)
30+
let signature = try ecKey.sign(digest: sha256)
31+
32+
let raw = try ECKeyPair.decodeDerEcSignature(signature: signature)
33+
XCTAssertEqual(
34+
signature,
35+
try ECKeyPair.encodeRawECSignature(signature: ECKeyPair.ECRawSignature(r: raw.r, s: raw.s)))
36+
37+
XCTAssertTrue(ecKey.verify(digest: sha256, signature: signature))
38+
}
39+
40+
func testImport() throws {
41+
let input = "Hello"
42+
let sha256 = try input.data(using: .utf8)!.computeSHA256()
43+
44+
let sec1Key = """
45+
MHcCAQEEIHjt7c+VnkIkN6RW7QgZPFNLb/9AZEhqSYYMtwrlLb3WoAoGCCqGSM49AwEHoUQDQgAEv2F\
46+
jRpMtADMZ4zoZxshV9chEkembgzZnXSUNe+DA8dKqXN/7qTcZjYJHKIi+Rn88zUGqCJo3DWF/X+ufVf\
47+
dU2g==
48+
"""
49+
50+
guard let keyData = Data(base64Encoded: sec1Key) else {
51+
XCTFail("Failed to decode base64 string")
52+
return
53+
}
54+
55+
let ecKey = try ECKeyPair.fromDer(data: keyData)
56+
let signature = try ecKey.sign(digest: sha256)
57+
58+
let raw = try ECKeyPair.decodeDerEcSignature(signature: signature)
59+
XCTAssertEqual(
60+
signature,
61+
try ECKeyPair.encodeRawECSignature(signature: ECKeyPair.ECRawSignature(r: raw.r, s: raw.s)))
62+
63+
XCTAssertTrue(ecKey.verify(digest: sha256, signature: signature))
64+
}
65+
66+
}

0 commit comments

Comments
 (0)