Skip to content

Commit

Permalink
Added support for 3072 and 4096 RSA keys.
Browse files Browse the repository at this point in the history
  • Loading branch information
jensutbult committed May 7, 2024
1 parent 4d92423 commit b48194f
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
typedef NS_ENUM(NSUInteger, YKFPIVKeyType) {
YKFPIVKeyTypeRSA1024 = 0x06,
YKFPIVKeyTypeRSA2048 = 0x07,
YKFPIVKeyTypeRSA3072 = 0x05,
YKFPIVKeyTypeRSA4096 = 0x16,
YKFPIVKeyTypeECCP256 = 0x11,
YKFPIVKeyTypeECCP384 = 0x14,
YKFPIVKeyTypeUnknown = 0x00
Expand Down
10 changes: 10 additions & 0 deletions YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVKeyType.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ YKFPIVKeyType YKFPIVKeyTypeFromKey(SecKeyRef key) {
if (size == 2048) {
return YKFPIVKeyTypeRSA2048;
}
if (size == 3072) {
return YKFPIVKeyTypeRSA3072;
}
if (size == 4096) {
return YKFPIVKeyTypeRSA4096;
}
}
if ([type isEqual:(__bridge NSString*)kSecAttrKeyTypeEC]) {
if (size == 256) {
Expand All @@ -43,6 +49,10 @@ int YKFPIVSizeFromKeyType(YKFPIVKeyType keyType) {
return 1024 / 8;
case YKFPIVKeyTypeRSA2048:
return 2048 / 8;
case YKFPIVKeyTypeRSA3072:
return 3072 / 8;
case YKFPIVKeyTypeRSA4096:
return 4096 / 8;
default:
return 0;
}
Expand Down
19 changes: 18 additions & 1 deletion YubiKit/YubiKit/Connections/Shared/Sessions/PIV/YKFPIVSession.m
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@ - (void)decryptWithKeyInSlot:(YKFPIVSlot)slot algorithm:(SecKeyAlgorithm)algorit
case 2048 / 8:
keyType = YKFPIVKeyTypeRSA2048;
break;
case 3072 / 8:
keyType = YKFPIVKeyTypeRSA3072;
break;
case 4096 / 8:
keyType = YKFPIVKeyTypeRSA4096;
break;
default:
completion(nil, [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVFErrorCodeInvalidCipherTextLength userInfo:@{NSLocalizedDescriptionKey: @"Invalid lenght of cipher text."}]);
return;
Expand Down Expand Up @@ -259,6 +265,7 @@ - (void)generateKeyInSlot:(YKFPIVSlot)slot type:(YKFPIVKeyType)type pinPolicy:(Y
NSError *error = [self checkKeySupport:type pinPolicy:pinPolicy touchPolicy:touchPolicy generateKey:YES];
if (error) {
completion(nil, error);
return;
}
NSMutableData *data = [NSMutableData dataWithBytes:&type length:1];
YKFTLVRecord *tlv = [[YKFTLVRecord alloc] initWithTag:YKFPIVTagGenAlgorithm value:data];
Expand All @@ -276,7 +283,7 @@ - (void)generateKeyInSlot:(YKFPIVSlot)slot type:(YKFPIVKeyType)type pinPolicy:(Y
(id)kSecAttrKeyClass: (id)kSecAttrKeyClassPublic};
CFDictionaryRef attributesRef = (__bridge CFDictionaryRef)attributes;
publicKey = SecKeyCreateWithData(cfDataRef, attributesRef, &cfError);
} else if (type == YKFPIVKeyTypeRSA1024 || type == YKFPIVKeyTypeRSA2048) {
} else if (type == YKFPIVKeyTypeRSA1024 || type == YKFPIVKeyTypeRSA2048 || type == YKFPIVKeyTypeRSA3072 || type == YKFPIVKeyTypeRSA4096) {
NSMutableData *modulusData = [NSMutableData dataWithBytes:&(UInt8 *){0x00} length:1];
[modulusData appendData:[records ykfTLVRecordWithTag:(UInt64)0x81].value];
NSData *exponentData = [records ykfTLVRecordWithTag:(UInt64)0x82].value;
Expand Down Expand Up @@ -329,6 +336,14 @@ - (NSError * _Nullable)checkKeySupport:(YKFPIVKeyType)keyType pinPolicy:(YKFPIVP
}
}

if (generateKey && (keyType == YKFPIVKeyTypeRSA3072 || keyType == YKFPIVKeyTypeRSA4096)) {
YKFVersion *from = [[YKFVersion alloc] initWithString:@"5.7.0"];
NSComparisonResult fromComparision = [from compare:self.version];
if (fromComparision == NSOrderedDescending) {
errorMessage = @"RSA 3072 and 4096 key generation";
}
}

if (errorMessage) {
return [[NSError alloc] initWithDomain:YKFPIVErrorDomain code:YKFPIVFErrorCodeUnsupportedOperation userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"%@ not supported by this YubiKey.", errorMessage]}];
}
Expand All @@ -354,6 +369,8 @@ - (void)putKey:(SecKeyRef)key inSlot:(YKFPIVSlot)slot pinPolicy:(YKFPIVPinPolicy
switch (keyType) {
case YKFPIVKeyTypeRSA1024:
case YKFPIVKeyTypeRSA2048:
case YKFPIVKeyTypeRSA3072:
case YKFPIVKeyTypeRSA4096:
{
NSArray<YKFTLVRecord*> *records = [YKFTLVRecord sequenceOfRecordsFromData:[YKFTLVRecord recordFromData:data].value];
NSData *primeOne = records[4].value;
Expand Down
52 changes: 49 additions & 3 deletions YubiKitTests/Tests/PIVTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,40 @@ class PIVTests: XCTestCase {
}
}

func testGenerate3072RSAKey() throws {
runYubiKitTest(timeout: 100.0) { connection, completion in
connection.authenticatedPivTestSession { session in
session.generateKey(in: .signature, type: .RSA3072, pinPolicy: .always, touchPolicy: .cached) { publicKey, error in
guard error == nil else { XCTFail("\(error!)"); completion(); return }
XCTAssertNotNil(publicKey);
let attributes = SecKeyCopyAttributes(publicKey!) as! [String: Any]
XCTAssert(attributes[kSecAttrKeySizeInBits as String] as! Int == 3072)
XCTAssert(attributes[kSecAttrKeyType as String] as! String == kSecAttrKeyTypeRSA as String)
XCTAssert(attributes[kSecAttrKeyClass as String] as! String == kSecAttrKeyClassPublic as String)
print("✅ Generated 3072 RSA key")
completion()
}
}
}
}

func testGenerate4096RSAKey() throws {
runYubiKitTest(timeout: 200.0) { connection, completion in
connection.authenticatedPivTestSession { session in
session.generateKey(in: .signature, type: .RSA4096, pinPolicy: .always, touchPolicy: .cached) { publicKey, error in
guard error == nil else { XCTFail("\(error!)"); completion(); return }
XCTAssertNotNil(publicKey);
let attributes = SecKeyCopyAttributes(publicKey!) as! [String: Any]
XCTAssert(attributes[kSecAttrKeySizeInBits as String] as! Int == 4096)
XCTAssert(attributes[kSecAttrKeyType as String] as! String == kSecAttrKeyTypeRSA as String)
XCTAssert(attributes[kSecAttrKeyClass as String] as! String == kSecAttrKeyClassPublic as String)
print("✅ Generated 4096 RSA key")
completion()
}
}
}
}

func testGenerateECCP384Key() throws {
runYubiKitTest { connection, completion in
connection.authenticatedPivTestSession { session in
Expand Down Expand Up @@ -786,9 +820,21 @@ extension YKFConnectionProtocol {
func authenticatedPivTestSession(completion: @escaping (_ session: YKFPIVSession) -> Void) {
self.pivTestSession { session in
let defaultManagementKey = Data([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08])
session.authenticate(withManagementKey: defaultManagementKey, type: .tripleDES()) { error in
guard error == nil else { XCTAssertTrue(false, "🔴 Failed to authenticate PIV application"); return }
completion(session)

let authBlock: (YKFPIVManagementKeyType)->(Void) = { keyType in
session.authenticate(withManagementKey: defaultManagementKey, type: keyType) { error in
guard error == nil else { XCTAssertTrue(false, "🔴 Failed to authenticate PIV application"); return }
completion(session)
}
}

if session.features.metadata.isSupported(bySession: session) {
session.getManagementKeyMetadata { metadata, error in
guard let metadata else { XCTFail("🔴 Failed to get management key metadata: \(error!)"); return }
authBlock(metadata.keyType)
}
} else {
authBlock(.tripleDES())
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions YubiKitTests/Tests/Utilities/XCTestCase+Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import XCTest

extension XCTestCase {
func runYubiKitTest(completion: @escaping (_ connection: YKFConnectionProtocol, _ completion: @escaping () -> Void) -> Void) {
func runYubiKitTest(timeout: TimeInterval = 30.0, completion: @escaping (_ connection: YKFConnectionProtocol, _ completion: @escaping () -> Void) -> Void) {
let connectionExpectation = expectation(description: "Get a YubiKey Connection")
let connection = YubiKeyConnection()
connection.connection { connection in
Expand All @@ -35,7 +35,7 @@ extension XCTestCase {
}
completion(connection, testCompletion)
}
waitForExpectations(timeout: 30.0) { error in
waitForExpectations(timeout: timeout) { error in
// If we get an error then the expectation has timed out and we need to stop all connections
if error != nil {
if YubiKitDeviceCapabilities.supportsMFIAccessoryKey {
Expand Down

0 comments on commit b48194f

Please sign in to comment.