Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

extras: Add EC toolbox abstractions and OPRF(P-384, SHA-384) VOPRF API #292

Merged
merged 13 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from 9 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
13 changes: 12 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,18 @@ let package = Package(
],
swiftSettings: swiftSettings
),
.testTarget(name: "_CryptoExtrasTests", dependencies: ["_CryptoExtras"]),
.testTarget(
name: "_CryptoExtrasTests",
dependencies: ["_CryptoExtras"],
resources: [
.copy("ECToolbox/H2CVectors/P256_XMD-SHA-256_SSWU_RO_.json"),
.copy("ECToolbox/H2CVectors/P384_XMD-SHA-384_SSWU_RO_.json"),
.copy("OPRFs/OPRFVectors/OPRFVectors-VOPRFDraft8.json"),
.copy("OPRFs/OPRFVectors/OPRFVectors-VOPRFDraft19.json"),
.copy("OPRFs/OPRFVectors/OPRFVectors-edgecases.json"),
],
swiftSettings: swiftSettings
),
.testTarget(name: "CryptoBoringWrapperTests", dependencies: ["CryptoBoringWrapper"]),
],
cxxLanguageStandard: .cxx11
Expand Down
14 changes: 14 additions & 0 deletions Sources/CCryptoBoringSSLShims/include/CCryptoBoringSSLShims.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,20 @@ int CCryptoBoringSSLShims_EVP_PKEY_decrypt(EVP_PKEY_CTX *ctx, void *out,
size_t *out_len, const void *in,
size_t in_len);

int CCryptoBoringSSLShims_EC_hash_to_curve_p256_xmd_sha256_sswu(const EC_GROUP *group, EC_POINT *out,
const void *dst, size_t dst_len,
const void *msg, size_t msg_len);

int CCryptoBoringSSLShims_EC_hash_to_curve_p384_xmd_sha384_sswu(const EC_GROUP *group, EC_POINT *out,
const void *dst, size_t dst_len,
const void *msg, size_t msg_len);

size_t CCryptoBoringSSLShims_EC_POINT_point2oct(const EC_GROUP *group,
const EC_POINT *point,
point_conversion_form_t form,
void *buf, size_t max_out,
BN_CTX *ctx);

#if defined(__cplusplus)
}
#endif // defined(__cplusplus)
Expand Down
20 changes: 20 additions & 0 deletions Sources/CCryptoBoringSSLShims/shims.c
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,23 @@ int CCryptoBoringSSLShims_EVP_PKEY_decrypt(EVP_PKEY_CTX *ctx, void *out,
size_t in_len) {
return CCryptoBoringSSL_EVP_PKEY_decrypt(ctx, out, out_len, in, in_len);
}

int CCryptoBoringSSLShims_EC_hash_to_curve_p256_xmd_sha256_sswu(const EC_GROUP *group, EC_POINT *out,
const void *dst, size_t dst_len,
const void *msg, size_t msg_len) {
return CCryptoBoringSSL_EC_hash_to_curve_p256_xmd_sha256_sswu(group, out, dst, dst_len, msg, msg_len);
}

int CCryptoBoringSSLShims_EC_hash_to_curve_p384_xmd_sha384_sswu(const EC_GROUP *group, EC_POINT *out,
const void *dst, size_t dst_len,
const void *msg, size_t msg_len) {
return CCryptoBoringSSL_EC_hash_to_curve_p384_xmd_sha384_sswu(group, out, dst, dst_len, msg, msg_len);
}

size_t CCryptoBoringSSLShims_EC_POINT_point2oct(const EC_GROUP *group,
const EC_POINT *point,
point_conversion_form_t form,
void *buf, size_t max_out,
BN_CTX *ctx) {
return CCryptoBoringSSL_EC_POINT_point2oct(group, point, form, buf, max_out, ctx);
}
24 changes: 23 additions & 1 deletion Sources/CryptoBoringWrapper/EC/EllipticCurve.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
/// A wrapper around BoringSSL's EC_GROUP object that handles reference counting and
/// liveness.
@usableFromInline
package class BoringSSLEllipticCurveGroup {
package final class BoringSSLEllipticCurveGroup {
/* private but usableFromInline */ @usableFromInline var _group: OpaquePointer

@usableFromInline
Expand Down Expand Up @@ -72,6 +72,14 @@ extension BoringSSLEllipticCurveGroup {
return try! ArbitraryPrecisionInteger(copying: baseOrder)
}

@usableFromInline
package var generator: EllipticCurvePoint { get throws {
Copy link
Contributor

Choose a reason for hiding this comment

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

Might be worth not squishing this in, get throws { can be on its own line.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, I moved this onto its own line.

Aside: I have a task to add the swift-format job to this repo soon, which will squash all these inconsistencies away.

guard let generatorPtr = CCryptoBoringSSL_EC_GROUP_get0_generator(self._group) else {
throw CryptoBoringWrapperError.internalBoringSSLError()
}
return try EllipticCurvePoint(copying: generatorPtr, on: self)
}}

/// An elliptic curve can be represented in a Weierstrass form: `y² = x³ + ax + b`. This
/// property provides the values of a and b on the curve.
@usableFromInline
Expand Down Expand Up @@ -102,6 +110,20 @@ extension BoringSSLEllipticCurveGroup {
case p384
case p521
}

@usableFromInline
var curveName: CurveName? {
switch CCryptoBoringSSL_EC_GROUP_get_curve_name(self._group) {
case NID_X9_62_prime256v1:
return .p256
case NID_secp384r1:
return .p384
case NID_secp521r1:
return .p521
default:
return nil
}
}
}

extension BoringSSLEllipticCurveGroup.CurveName {
Expand Down
243 changes: 232 additions & 11 deletions Sources/CryptoBoringWrapper/EC/EllipticCurvePoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,41 +12,262 @@
//
//===----------------------------------------------------------------------===//
@_implementationOnly import CCryptoBoringSSL
@_implementationOnly import CCryptoBoringSSLShims
import struct Foundation.Data
import protocol Foundation.ContiguousBytes

/// A wrapper around BoringSSL's EC_POINT with some lifetime management.
@usableFromInline
package class EllipticCurvePoint {
package final class EllipticCurvePoint {
/* private but @usableFromInline */ @usableFromInline var _basePoint: OpaquePointer

@usableFromInline
package init(multiplying scalar: ArbitraryPrecisionInteger, on group: BoringSSLEllipticCurveGroup) throws {
package init(copying pointer: OpaquePointer, on group: BoringSSLEllipticCurveGroup) throws {
self._basePoint = try group.withUnsafeGroupPointer { groupPtr in
guard let basePoint = CCryptoBoringSSL_EC_POINT_new(groupPtr) else {
guard let pointPtr = CCryptoBoringSSL_EC_POINT_dup(pointer, groupPtr) else {
throw CryptoBoringWrapperError.internalBoringSSLError()
}
return basePoint
return pointPtr
}
}

@usableFromInline
package convenience init(copying other: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws {
try self.init(copying: other._basePoint, on: group)
}

@usableFromInline
package init(multiplying scalar: ArbitraryPrecisionInteger, on group: BoringSSLEllipticCurveGroup) throws {
self._basePoint = try scalar.withUnsafeBignumPointer { scalarPtr in
try group.withUnsafeGroupPointer { groupPtr in
guard
let pointPtr = CCryptoBoringSSL_EC_POINT_new(groupPtr),
CCryptoBoringSSL_EC_POINT_mul(groupPtr, pointPtr, scalarPtr, nil, nil, nil) == 1
Copy link
Contributor

Choose a reason for hiding this comment

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

Not a new issue, but this leaks the pointPtr if the mul fails.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've addressed this by factoring out the EC_POINT_new into a designated init(_pointAtInfinityOn group:) and made all these convenience initialisers.

else { throw CryptoBoringWrapperError.internalBoringSSLError() }
return pointPtr
}
}
}

deinit {
CCryptoBoringSSL_EC_POINT_free(self._basePoint)
}

@usableFromInline
package func multiply(by rhs: ArbitraryPrecisionInteger, on group: BoringSSLEllipticCurveGroup) throws {
try self.withPointPointer { selfPtr in
try rhs.withUnsafeBignumPointer { rhsPtr in
try group.withUnsafeGroupPointer { groupPtr in
guard CCryptoBoringSSL_EC_POINT_mul(groupPtr, selfPtr, nil, selfPtr, rhsPtr, nil) != 0 else {
throw CryptoBoringWrapperError.internalBoringSSLError()
}
}
}
}
}

try group.withUnsafeGroupPointer { groupPtr in
try scalar.withUnsafeBignumPointer { bigNumPtr in
guard CCryptoBoringSSL_EC_POINT_mul(groupPtr, self._basePoint, bigNumPtr, nil, nil, nil) != 0 else {
@usableFromInline
package convenience init(multiplying lhs: EllipticCurvePoint, by rhs: ArbitraryPrecisionInteger, on group: BoringSSLEllipticCurveGroup) throws {
try self.init(copying: lhs, on: group)
try self.multiply(by: rhs, on: group)
}

@usableFromInline
package func multiplying(by rhs: ArbitraryPrecisionInteger, on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
try EllipticCurvePoint(multiplying: self, by: rhs, on: group)
}

@usableFromInline
package static func multiplying(_ lhs: EllipticCurvePoint, by rhs: ArbitraryPrecisionInteger, on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
try EllipticCurvePoint(multiplying: lhs, by: rhs, on: group)
}

@usableFromInline
package func add(_ rhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws {
try self.withPointPointer { selfPtr in
try group.withUnsafeGroupPointer { groupPtr in
try rhs.withPointPointer { rhsPtr in
guard CCryptoBoringSSL_EC_POINT_add(groupPtr, selfPtr, selfPtr, rhsPtr, nil) != 0 else {
throw CryptoBoringWrapperError.internalBoringSSLError()
}
}
}
}
}

@usableFromInline
package convenience init(adding lhs: EllipticCurvePoint, _ rhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup ) throws {
try self.init(copying: lhs, on: group)
try self.add(rhs, on: group)
}

@usableFromInline
package func adding(_ rhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
try EllipticCurvePoint(adding: self, rhs, on: group)
}

@usableFromInline
package static func adding(_ lhs: EllipticCurvePoint, _ rhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
try EllipticCurvePoint(adding: lhs, rhs, on: group)
}

@usableFromInline
package func invert(on group: BoringSSLEllipticCurveGroup) throws {
try self.withPointPointer { selfPtr in
try group.withUnsafeGroupPointer { groupPtr in
guard CCryptoBoringSSL_EC_POINT_invert(groupPtr, selfPtr, nil) != 0 else {
throw CryptoBoringWrapperError.internalBoringSSLError()
}
}
}
}

package init(copying pointer: OpaquePointer, on group: BoringSSLEllipticCurveGroup) throws {
@usableFromInline
package convenience init(inverting point: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws {
try self.init(copying: point, on: group)
try self.invert(on: group)
}

@usableFromInline
package func inverting(on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
try EllipticCurvePoint(inverting: self, on: group)
}

@usableFromInline
package static func inverting(_ point: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
try EllipticCurvePoint(inverting: point, on: group)
}

@usableFromInline
package func subtract(_ rhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws {
try self.add(rhs.inverting(on: group), on: group)
}

@usableFromInline
package convenience init(subtracting rhs: EllipticCurvePoint, from lhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws {
try self.init(copying: lhs, on: group)
try self.subtract(rhs, on: group)
}

@usableFromInline
package func subtracting(_ rhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
try EllipticCurvePoint(subtracting: rhs, from: self, on: group)
}

@usableFromInline
package static func subtracting(_ rhs: EllipticCurvePoint, from lhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) throws -> EllipticCurvePoint {
try EllipticCurvePoint(subtracting: rhs, from: lhs, on: group)
}

@usableFromInline
package init<MessageBytes: ContiguousBytes, DSTBytes: ContiguousBytes>(hashing msg: MessageBytes, to group: BoringSSLEllipticCurveGroup, domainSeparationTag: DSTBytes) throws {
let hashToCurveFunction = switch group.curveName {
case .p256: CCryptoBoringSSLShims_EC_hash_to_curve_p256_xmd_sha256_sswu
case .p384: CCryptoBoringSSLShims_EC_hash_to_curve_p384_xmd_sha384_sswu
case .p521: throw CryptoBoringWrapperError.invalidParameter // BoringSSL doesn't have a hash_to_curve API for P521.
case .none: throw CryptoBoringWrapperError.internalBoringSSLError()
}

self._basePoint = try msg.withUnsafeBytes { msgPtr in
try group.withUnsafeGroupPointer { groupPtr in
try domainSeparationTag.withUnsafeBytes { dstPtr in
guard
let pointPtr = CCryptoBoringSSL_EC_POINT_new(groupPtr),
hashToCurveFunction(
Copy link
Contributor

Choose a reason for hiding this comment

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

If this fails, we leak the point.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

groupPtr,
pointPtr,
dstPtr.baseAddress,
dstPtr.count,
msgPtr.baseAddress,
msgPtr.count
) == 1
else { throw CryptoBoringWrapperError.internalBoringSSLError() }
return pointPtr
}
}
}
}

@usableFromInline
package func isEqual(to rhs: EllipticCurvePoint, on group: BoringSSLEllipticCurveGroup) -> Bool {
self.withPointPointer { selfPtr in
group.withUnsafeGroupPointer { groupPtr in
rhs.withPointPointer { rhsPtr in
switch CCryptoBoringSSL_EC_POINT_cmp(groupPtr, selfPtr, rhsPtr, nil) {
case 0: return true
case 1: return false
default: fatalError("Unexpected error testing EC point equality")
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't know that we need to be this strict. 0 is true, everything else is false. Happy to have an assertion for the other codes, but probably shouldn't fatal error.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think that's not quite right since the docs say that EC_POINT_cmp can return an error:

EC_POINT_cmp returns 1 if the points are not equal, 0 if they are, or -1 on error.

I think this can happen if you provide a point on a different group for an equality test, for instance.

This function isn't throwing so the best we can do is fatal error, which seems to be in line with what we do elsewhere?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure we need the error to be this loud, though. Perhaps we drop this to an assertion and then return false?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After further consideration, given we allow folks to pass in the group, we shouldn't even assert, but just return false if comparing two points from different groups.

I've updated the code to clear the BoringSSL error and return false in this scenario.

}
}
}
}
}

@usableFromInline
package init<Bytes: ContiguousBytes>(x962Representation bytes: Bytes, on group: BoringSSLEllipticCurveGroup) throws {
self._basePoint = try group.withUnsafeGroupPointer { groupPtr in
guard let basePoint = CCryptoBoringSSL_EC_POINT_dup(pointer, groupPtr) else {
guard let basePoint = CCryptoBoringSSL_EC_POINT_new(groupPtr) else {
throw CryptoBoringWrapperError.internalBoringSSLError()
}
return basePoint
}

guard group.withUnsafeGroupPointer({ groupPtr in
bytes.withUnsafeBytes { dataPtr in
CCryptoBoringSSL_EC_POINT_oct2point(
Lukasa marked this conversation as resolved.
Show resolved Hide resolved
groupPtr,
self._basePoint,
dataPtr.baseAddress,
dataPtr.count,
nil
)
}
}) == 1 else {
throw CryptoBoringWrapperError.invalidParameter
}
}

deinit {
CCryptoBoringSSL_EC_POINT_free(self._basePoint)
@usableFromInline
package func x962RepresentationByteCount(compressed: Bool, on group: BoringSSLEllipticCurveGroup) throws -> Int {
let numBytesNeeded = group.withUnsafeGroupPointer { groupPtr in
CCryptoBoringSSL_EC_POINT_point2oct(
groupPtr,
self._basePoint,
compressed ? POINT_CONVERSION_COMPRESSED : POINT_CONVERSION_UNCOMPRESSED,
nil,
0,
nil
)
}
guard numBytesNeeded != 0 else {
throw CryptoBoringWrapperError.internalBoringSSLError()
}
return numBytesNeeded
}

@usableFromInline
package func x962Representation(compressed: Bool, on group: BoringSSLEllipticCurveGroup) throws -> Data {
let numBytesNeeded = try self.x962RepresentationByteCount(compressed: compressed, on: group)

var buf = Data(repeating: 0, count: numBytesNeeded)

let numBytesWritten = group.withUnsafeGroupPointer { groupPtr in
buf.withUnsafeMutableBytes { bufPtr in
CCryptoBoringSSLShims_EC_POINT_point2oct(
groupPtr,
self._basePoint,
compressed ? POINT_CONVERSION_COMPRESSED : POINT_CONVERSION_UNCOMPRESSED,
bufPtr.baseAddress,
numBytesNeeded,
nil
)
}
}
guard numBytesWritten == numBytesNeeded else {
throw CryptoBoringWrapperError.internalBoringSSLError()
}

return buf
}
}

Expand Down
Loading
Loading