Compare commits

...

7 Commits

Author SHA1 Message Date
Max Radermacher
0af175812b Update deployment target to 12.2 2022-08-31 10:00:08 -07:00
Jordan Rose
459674cfb4 Update for LibSignalClient rename 2022-03-23 12:02:55 -07:00
Jordan Rose
87bfaf0748 Update for 'throws' removal in SignalClient 2020-12-14 15:17:56 -08:00
Jordan Rose
40d77fd50f Hide ECKeyPairImpl from clients of Curve25519Kit 2020-11-06 12:43:30 -08:00
Jordan Rose
7fef1e384b Move NSCoding logic into ECKeyPairImpl as well
ECKeyPair has been pretty much completely hollowed out now, providing
only the minimum necessary logic for a base class (plus preserving a
throwing interface).
2020-11-06 12:43:30 -08:00
Jordan Rose
7653748f2f Make ECKeyPair a class cluster wrapping SignalClient.IdentityKeyPair
Ideally we'd just move ECKeyPair entirely into Swift, but there are
dependencies directly including Curve25519.h and expecting to get the
definition of ECKeyPair that way. To work around this, ECKeyPair now
forwards operations to a subclass ECKeyPairImpl.
2020-11-06 12:43:30 -08:00
Jordan Rose
edcb438b1d Change ECPublic/PrivateKey to wrap a SignalClient.Public/PrivateKey
Step 1 of getting away from APIs that pass around (NS)Data.
2020-11-06 12:43:30 -08:00
6 changed files with 175 additions and 147 deletions

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
#import <Foundation/Foundation.h>
@ -9,11 +9,6 @@ NS_ASSUME_NONNULL_BEGIN
#define ECCKeyLength 32
#define ECCSignatureLength 64
extern NSErrorDomain const Curve25519KitErrorDomain;
typedef NS_ERROR_ENUM(Curve25519KitErrorDomain, Curve25519KitError){
Curve25519KitError_InvalidKeySize = 1
};
@interface ECKeyPair : NSObject <NSSecureCoding>
@property (atomic, readonly) NSData *publicKey;
@ -29,6 +24,12 @@ typedef NS_ERROR_ENUM(Curve25519KitErrorDomain, Curve25519KitError){
privateKeyData:(NSData *)privateKeyData
error:(NSError **)error NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
/**
* A transitionary interface, only to be called from within the implementation of ECKeyPair's class cluster.
*/
- (instancetype)initFromClassClusterSubclassOnly NS_DESIGNATED_INITIALIZER;
@end
#pragma mark -

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
#import "Curve25519.h"
@ -9,65 +9,38 @@
NS_ASSUME_NONNULL_BEGIN
NSErrorDomain const Curve25519KitErrorDomain = @"Curve25519KitErrorDomain";
NSString *const TSECKeyPairPublicKey = @"TSECKeyPairPublicKey";
NSString *const TSECKeyPairPrivateKey = @"TSECKeyPairPrivateKey";
NSString *const TSECKeyPairPreKeyId = @"TSECKeyPairPreKeyId";
extern void curve25519_donna(unsigned char *output, const unsigned char *a, const unsigned char *b);
extern int curve25519_sign(unsigned char *signature_out, /* 64 bytes */
const unsigned char *curve25519_privkey, /* 32 bytes */
const unsigned char *msg,
const unsigned long msg_len,
const unsigned char *random); /* 64 bytes */
@interface ECKeyPair (ImplementedInSwift)
+ (Class)concreteSubclass;
- (nullable NSData *)sign:(NSData *)data error:(NSError **)error;
@end
@implementation ECKeyPair
@dynamic publicKey;
@dynamic privateKey;
+ (BOOL)supportsSecureCoding
{
return YES;
}
- (void)encodeWithCoder:(NSCoder *)coder
+ (Class)classForKeyedUnarchiver
{
[coder encodeBytes:self.publicKey.bytes length:ECCKeyLength forKey:TSECKeyPairPublicKey];
[coder encodeBytes:self.privateKey.bytes length:ECCKeyLength forKey:TSECKeyPairPrivateKey];
return [self concreteSubclass];
}
- (nullable instancetype)initWithCoder:(NSCoder *)coder
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder
{
NSUInteger returnedLength = 0;
const uint8_t *returnedBuffer = NULL;
// FIXME: This should never be called thanks to +classForKeyedUnarchiver above,
// but there's a test in SignalServiceKit that calls it directly.
return [[[ECKeyPair concreteSubclass] alloc] initWithCoder:coder];
}
// De-serialize public key
returnedBuffer = [coder decodeBytesForKey:TSECKeyPairPublicKey returnedLength:&returnedLength];
if (returnedLength != ECCKeyLength) {
OWSFailDebug(@"failure: wrong length for public key.");
return nil;
}
NSData *publicKeyData = [NSData dataWithBytes:returnedBuffer length:returnedLength];
// De-serialize private key
returnedBuffer = [coder decodeBytesForKey:TSECKeyPairPrivateKey returnedLength:&returnedLength];
if (returnedLength != ECCKeyLength) {
OWSFailDebug(@"failure: wrong length for private key.");
return nil;
}
NSData *privateKeyData = [NSData dataWithBytes:returnedBuffer length:returnedLength];
NSError *error;
ECKeyPair *keyPair = [self initWithPublicKeyData:publicKeyData
privateKeyData:privateKeyData
error:&error];
if (error != nil) {
OWSFailDebug(@"error: %@", error);
return nil;
}
return keyPair;
- (void)encodeWithCoder:(nonnull NSCoder *)coder
{
OWSFail(@"Implemented by subclass; should never be called directly");
}
/**
@ -78,44 +51,20 @@ extern int curve25519_sign(unsigned char *signature_out, /* 64 bytes */
privateKeyData:(NSData *)privateKeyData
error:(NSError **)error
{
if (self = [super init]) {
if (publicKeyData.length != ECCKeyLength || privateKeyData.length != ECCKeyLength) {
*error = [NSError errorWithDomain:Curve25519KitErrorDomain
code:Curve25519KitError_InvalidKeySize
userInfo:nil];
return nil;
}
_publicKey = publicKeyData;
_privateKey = privateKeyData;
}
return [[[ECKeyPair concreteSubclass] alloc] initWithPublicKeyData:publicKeyData
privateKeyData:privateKeyData
error:error];
}
- (instancetype)initFromClassClusterSubclassOnly
{
self = [super init];
return self;
}
+ (ECKeyPair *)generateKeyPair
{
// Generate key pair as described in
// https://code.google.com/p/curve25519-donna/
NSMutableData *privateKey = [[Randomness generateRandomBytes:ECCKeyLength] mutableCopy];
uint8_t *privateKeyBytes = privateKey.mutableBytes;
privateKeyBytes[0] &= 248;
privateKeyBytes[31] &= 127;
privateKeyBytes[31] |= 64;
static const uint8_t basepoint[ECCKeyLength] = { 9 };
NSMutableData *publicKey = [NSMutableData dataWithLength:ECCKeyLength];
if (!publicKey) {
OWSFail(@"Could not allocate buffer");
}
curve25519_donna(publicKey.mutableBytes, privateKey.mutableBytes, basepoint);
ECKeyPair *keyPair = [[ECKeyPair alloc] initWithPublicKeyData:[publicKey copy]
privateKeyData:[privateKey copy]
error:nil];
OWSAssert(keyPair != nil);
return keyPair;
return [[self concreteSubclass] generateKeyPair];
}
- (NSData *)throws_sign:(NSData *)data
@ -124,20 +73,13 @@ extern int curve25519_sign(unsigned char *signature_out, /* 64 bytes */
OWSRaiseException(NSInvalidArgumentException, @"Missing data.");
}
NSMutableData *signatureData = [NSMutableData dataWithLength:ECCSignatureLength];
NSError *error;
NSData *signatureData = [self sign:data error:&error];
if (!signatureData) {
OWSFail(@"Could not allocate buffer");
OWSRaiseException(NSInternalInconsistencyException, @"Message couldn't be signed: %@", error);
}
NSData *randomBytes = [Randomness generateRandomBytes:64];
if (curve25519_sign(
signatureData.mutableBytes, self.privateKey.bytes, [data bytes], [data length], [randomBytes bytes])
== -1) {
OWSRaiseException(NSInternalInconsistencyException, @"Message couldn't be signed.");
}
return [signatureData copy];
return signatureData;
}
@end

View File

@ -1,28 +1,120 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
//
import Foundation
import SignalCoreKit
import LibSignalClient
// Work around Swift's lack of factory initializers.
// See https://bugs.swift.org/browse/SR-5255.
public protocol ECKeyPairFromIdentityKeyPair {}
public extension ECKeyPairFromIdentityKeyPair {
init(_ keyPair: IdentityKeyPair) {
self = ECKeyPairImpl(keyPair) as! Self
}
}
extension ECKeyPair: ECKeyPairFromIdentityKeyPair {}
// TODO: Eventually we should define ECKeyPair entirely in Swift as a wrapper around IdentityKeyPair,
// but doing that right now would break clients that are importing Curve25519.h and nothing else.
// For now, just provide the API we'd like to have in the future via its subclass.
extension ECKeyPair {
public var identityKeyPair: IdentityKeyPair {
(self as! ECKeyPairImpl).storedKeyPair
}
public extension ECKeyPair {
// TODO: Rename to publicKey(), rename existing publicKey() method to publicKeyData().
func ecPublicKey() throws -> ECPublicKey {
guard publicKey.count == ECCKeyLength else {
throw OWSAssertionError("\(logTag) public key has invalid length")
}
// NOTE: we don't use ECPublicKey(serializedKeyData:) since the
// key data should not have a type byte.
return try ECPublicKey(keyData: publicKey)
public func ecPublicKey() throws -> ECPublicKey {
return ECPublicKey(self.identityKeyPair.publicKey)
}
// TODO: Rename to privateKey(), rename existing privateKey() method to privateKeyData().
func ecPrivateKey() throws -> ECPrivateKey {
guard privateKey.count == ECCKeyLength else {
throw OWSAssertionError("\(logTag) private key has invalid length")
}
public func ecPrivateKey() throws -> ECPrivateKey {
return ECPrivateKey(self.identityKeyPair.privateKey)
}
return try ECPrivateKey(keyData: privateKey)
@objc private class var concreteSubclass: ECKeyPair.Type {
return ECKeyPairImpl.self
}
}
/// A transitionary class. Do not use directly; continue using ECKeyPair instead.
private class ECKeyPairImpl: ECKeyPair {
private static let TSECKeyPairPublicKey = "TSECKeyPairPublicKey"
private static let TSECKeyPairPrivateKey = "TSECKeyPairPrivateKey"
let storedKeyPair: IdentityKeyPair
init(_ keyPair: IdentityKeyPair) {
storedKeyPair = keyPair
super.init(fromClassClusterSubclassOnly: ())
}
override convenience init(publicKeyData: Data, privateKeyData: Data) throws {
// Go through ECPublicKey to handle the public key data without a type byte.
let publicKey = try ECPublicKey(keyData: publicKeyData).key
let privateKey = try PrivateKey(privateKeyData)
self.init(IdentityKeyPair(publicKey: publicKey, privateKey: privateKey))
}
required convenience init?(coder: NSCoder) {
var returnedLength = 0
let publicKeyBuffer = coder.decodeBytes(forKey: Self.TSECKeyPairPublicKey, returnedLength: &returnedLength)
guard returnedLength == ECCKeyLength else {
owsFailDebug("failure: wrong length for public key.")
return nil
}
let publicKeyData = Data(bytes: publicKeyBuffer!, count: returnedLength)
let privateKeyBuffer = coder.decodeBytes(forKey: Self.TSECKeyPairPrivateKey, returnedLength: &returnedLength)
guard returnedLength == ECCKeyLength else {
owsFailDebug("failure: wrong length for private key.")
return nil
}
let privateKeyData = Data(bytes: privateKeyBuffer!, count: returnedLength)
do {
try self.init(publicKeyData: publicKeyData, privateKeyData: privateKeyData)
} catch {
owsFailDebug("error: \(error)")
return nil
}
}
override func encode(with coder: NSCoder) {
// Go through ECPublicKey to drop the type byte.
self.identityKeyPair.publicKey.keyBytes.withUnsafeBufferPointer {
coder.encodeBytes($0.baseAddress, length: $0.count, forKey: Self.TSECKeyPairPublicKey)
}
self.identityKeyPair.privateKey.serialize().withUnsafeBufferPointer {
coder.encodeBytes($0.baseAddress, length: $0.count, forKey: Self.TSECKeyPairPrivateKey)
}
}
override class var supportsSecureCoding: Bool {
return true
}
override var classForCoder: AnyClass {
return ECKeyPair.self
}
@objc private class func generateKeyPair() -> ECKeyPair {
return ECKeyPairImpl(IdentityKeyPair.generate())
}
@objc private func sign(_ data: Data) throws -> Data {
return Data(identityKeyPair.privateKey.generateSignature(message: data))
}
override var publicKey: Data {
return Data(identityKeyPair.publicKey.keyBytes)
}
override var privateKey: Data {
return Data(identityKeyPair.privateKey.serialize())
}
}

View File

@ -1,32 +1,36 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
//
import Foundation
import SignalCoreKit
import LibSignalClient
// See:
// https://github.com/signalapp/libsignal-protocol-java/blob/87fae0f98332e98a32bbb82515428b4edeb4181f/java/src/main/java/org/whispersystems/libsignal/ecc/ECPrivateKey.java
@objc public class ECPrivateKey: NSObject {
public let key: PrivateKey
@objc
public let keyData: Data
public var keyData: Data {
Data(key.serialize())
}
@objc
public init(keyData: Data) throws {
guard keyData.count == ECCKeyLength else {
throw OWSAssertionError("\(ECPrivateKey.logTag) key has invalid length")
}
self.key = try PrivateKey(keyData)
}
self.keyData = keyData
public init(_ key: PrivateKey) {
self.key = key
}
open override func isEqual(_ object: Any?) -> Bool {
if let object = object as? ECPrivateKey {
return keyData == object.keyData
} else {
guard let object = object as? ECPrivateKey else {
return false
}
// FIXME: compare private keys directly?
return keyData == object.keyData
}
public override var hash: Int {

View File

@ -1,13 +1,10 @@
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
//
import Foundation
import SignalCoreKit
public enum ECKeyError: Error {
case assertionError(description: String)
}
import LibSignalClient
// See:
// https://github.com/signalapp/libsignal-protocol-java/blob/87fae0f98332e98a32bbb82515428b4edeb4181f/java/src/main/java/org/whispersystems/libsignal/ecc/DjbECPublicKey.java
@ -16,46 +13,37 @@ public enum ECKeyError: Error {
@objc
public static let keyTypeDJB: UInt8 = 0x05
public let key: PublicKey
@objc
public let keyData: Data
public var keyData: Data {
return Data(key.keyBytes)
}
@objc
public init(keyData: Data) throws {
guard keyData.count == ECCKeyLength else {
throw ECKeyError.assertionError(description: "\(ECPublicKey.logTag) key has invalid length")
}
self.key = try PublicKey([ECPublicKey.keyTypeDJB] + keyData)
}
self.keyData = keyData
public init(_ key: PublicKey) {
self.key = key
}
// https://github.com/signalapp/libsignal-protocol-java/blob/master/java/src/main/java/org/whispersystems/libsignal/ecc/Curve.java#L30
@objc
public init(serializedKeyData: Data) throws {
let parser = OWSDataParser(data: serializedKeyData)
let typeByte = try parser.nextByte(name: "type byte")
guard typeByte == ECPublicKey.keyTypeDJB else {
throw ECKeyError.assertionError(description: "\(ECPublicKey.logTag) key data has invalid type byte")
}
let keyData = try parser.remainder(name: "key data")
guard keyData.count == ECCKeyLength else {
throw ECKeyError.assertionError(description: "\(ECPublicKey.logTag) key has invalid length")
}
self.keyData = keyData
self.key = try PublicKey(serializedKeyData)
}
@objc public var serialized: Data {
return Data([ECPublicKey.keyTypeDJB] + keyData)
return Data(key.serialize())
}
open override func isEqual(_ object: Any?) -> Bool {
if let object = object as? ECPublicKey {
return keyData == object.keyData
} else {
guard let object = object as? ECPublicKey else {
return false
}
return key == object.key
}
public override var hash: Int {

View File

@ -18,10 +18,11 @@ Pod::Spec.new do |spec|
spec.framework = 'Security'
spec.public_header_files = "Classes/*.h"
spec.requires_arc = true
spec.ios.deployment_target = "10.0"
spec.ios.deployment_target = "12.2"
spec.dependency 'CocoaLumberjack'
spec.dependency 'SignalCoreKit'
spec.dependency 'LibSignalClient', '>= 0.15.0'
spec.test_spec 'Tests' do |test_spec|
test_spec.source_files = 'BuildTests/BuildTestsTests/**/*.{h,m,swift}'