Compare commits

..

No commits in common. "feature/SignalClient-adoption" and "main" have entirely different histories.

6 changed files with 147 additions and 175 deletions

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import <Foundation/Foundation.h>
@ -9,6 +9,11 @@ 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;
@ -24,12 +29,6 @@ NS_ASSUME_NONNULL_BEGIN
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) 2020 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
#import "Curve25519.h"
@ -9,38 +9,65 @@
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);
@interface ECKeyPair (ImplementedInSwift)
+ (Class)concreteSubclass;
- (nullable NSData *)sign:(NSData *)data error:(NSError **)error;
@end
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 */
@implementation ECKeyPair
@dynamic publicKey;
@dynamic privateKey;
+ (BOOL)supportsSecureCoding
{
return YES;
}
+ (Class)classForKeyedUnarchiver
- (void)encodeWithCoder:(NSCoder *)coder
{
return [self concreteSubclass];
[coder encodeBytes:self.publicKey.bytes length:ECCKeyLength forKey:TSECKeyPairPublicKey];
[coder encodeBytes:self.privateKey.bytes length:ECCKeyLength forKey:TSECKeyPairPrivateKey];
}
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder
- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
// 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];
}
NSUInteger returnedLength = 0;
const uint8_t *returnedBuffer = NULL;
- (void)encodeWithCoder:(nonnull NSCoder *)coder
{
OWSFail(@"Implemented by subclass; should never be called directly");
// 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;
}
/**
@ -51,20 +78,44 @@ extern void curve25519_donna(unsigned char *output, const unsigned char *a, cons
privateKeyData:(NSData *)privateKeyData
error:(NSError **)error
{
return [[[ECKeyPair concreteSubclass] alloc] initWithPublicKeyData:publicKeyData
privateKeyData:privateKeyData
error:error];
}
- (instancetype)initFromClassClusterSubclassOnly
{
self = [super init];
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 self;
}
+ (ECKeyPair *)generateKeyPair
{
return [[self concreteSubclass] 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;
}
- (NSData *)throws_sign:(NSData *)data
@ -73,13 +124,20 @@ extern void curve25519_donna(unsigned char *output, const unsigned char *a, cons
OWSRaiseException(NSInvalidArgumentException, @"Missing data.");
}
NSError *error;
NSData *signatureData = [self sign:data error:&error];
NSMutableData *signatureData = [NSMutableData dataWithLength:ECCSignatureLength];
if (!signatureData) {
OWSRaiseException(NSInternalInconsistencyException, @"Message couldn't be signed: %@", error);
OWSFail(@"Could not allocate buffer");
}
return signatureData;
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];
}
@end

View File

@ -1,120 +1,28 @@
//
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 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().
public func ecPublicKey() throws -> ECPublicKey {
return ECPublicKey(self.identityKeyPair.publicKey)
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)
}
// TODO: Rename to privateKey(), rename existing privateKey() method to privateKeyData().
public func ecPrivateKey() throws -> ECPrivateKey {
return ECPrivateKey(self.identityKeyPair.privateKey)
}
func ecPrivateKey() throws -> ECPrivateKey {
guard privateKey.count == ECCKeyLength else {
throw OWSAssertionError("\(logTag) private key has invalid length")
}
@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())
return try ECPrivateKey(keyData: privateKey)
}
}

View File

@ -1,36 +1,32 @@
//
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 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 var keyData: Data {
Data(key.serialize())
}
public let keyData: Data
@objc
public init(keyData: Data) throws {
self.key = try PrivateKey(keyData)
}
guard keyData.count == ECCKeyLength else {
throw OWSAssertionError("\(ECPrivateKey.logTag) key has invalid length")
}
public init(_ key: PrivateKey) {
self.key = key
self.keyData = keyData
}
open override func isEqual(_ object: Any?) -> Bool {
guard let object = object as? ECPrivateKey else {
if let object = object as? ECPrivateKey {
return keyData == object.keyData
} else {
return false
}
// FIXME: compare private keys directly?
return keyData == object.keyData
}
public override var hash: Int {

View File

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

View File

@ -18,11 +18,10 @@ Pod::Spec.new do |spec|
spec.framework = 'Security'
spec.public_header_files = "Classes/*.h"
spec.requires_arc = true
spec.ios.deployment_target = "12.2"
spec.ios.deployment_target = "10.0"
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}'