Skip to content

Commit

Permalink
fix pin auth and add setPin/changePin
Browse files Browse the repository at this point in the history
  • Loading branch information
dangfan committed Jan 16, 2024
1 parent 1428110 commit 7c22ef7
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 12 deletions.
3 changes: 3 additions & 0 deletions example/pcsc_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class CtapCcid extends CtapDevice {
}
rapdu += await _card.transmit(Uint8List.fromList(capdu));
} while (rapdu.length >= 2 && rapdu[rapdu.length - 2] == 0x61);
print('> ${hex.encode(capdu)}');
print('< ${hex.encode(rapdu)}');
return CtapResponse(rapdu[0], rapdu.sublist(1, rapdu.length - 2));
}
}
Expand All @@ -53,6 +55,7 @@ void main() async {
print(ctap.info.versions);
final cp = await ClientPin.create(ctap);
print(await cp.getPinRetries());
print(await cp.changePin('123456', '1234'));

await card.disconnect(Disposition.resetCard);
} finally {
Expand Down
5 changes: 3 additions & 2 deletions lib/src/ctap2/base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,12 @@ class Ctap2 {
return CtapResponse(res.status, parseGetInfoResponse(res.data));
}

Future<CtapResponse<ClientPinResponse>> clientPin(
Future<CtapResponse<ClientPinResponse?>> clientPin(
ClientPinRequest request) async {
final req = makeClientPinRequest(request);
final res = await device.transceive(req);
return CtapResponse(res.status, parseClientPinResponse(res.data));
return CtapResponse(
res.status, res.data.isEmpty ? null : parseClientPinResponse(res.data));
}

/// Make the request to get info from the authenticator.
Expand Down
70 changes: 65 additions & 5 deletions lib/src/ctap2/pin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class PinProtocolV1 extends PinProtocol {
@override
int get version => 1;

final _aes = AesCbc.with128bits(
final _aes = AesCbc.with256bits(
macAlgorithm: MacAlgorithm.empty,
paddingAlgorithm: PaddingAlgorithm.zero);

Expand Down Expand Up @@ -153,7 +153,7 @@ enum ClientPinSubCommand {
getPinToken(0x05),
getPinUvAuthTokenUsingUvWithPermissions(0x06),
getUvRetries(0x07),
getPinUvAuthTokenUsingPinWithPermissions(0x08);
getPinUvAuthTokenUsingPinWithPermissions(0x09);

const ClientPinSubCommand(this.value);

Expand Down Expand Up @@ -225,7 +225,7 @@ class ClientPin {
if (resp.status != 0) {
throw Exception('ClientPin failed.');
}
return _pinProtocol.encapsulate(resp.data.keyAgreement!);
return _pinProtocol.encapsulate(resp.data!.keyAgreement!);
}

/// Get a PIN/UV token from the authenticator.
Expand All @@ -235,13 +235,18 @@ class ClientPin {
/// [permissionsRpId] is the RP ID to which the permissions apply.
Future<List<int>> getPinToken(String pin,
{List<ClientPinPermission>? permissions, String? permissionsRpId}) async {
if (!ClientPin.isSupported(_ctap.info)) {
throw Exception('getPinToken is not supported.');
}

final EncapsulateResult ss = await _getSharedSecret();
final pinHash =
(await Sha256().hash(utf8.encode(pin))).bytes.sublist(0, 16);
final pinHashEnc = await _pinProtocol.encrypt(ss.sharedSecret, pinHash);

int subCmd = ClientPinSubCommand.getPinToken.value;
if (ClientPin.isTokenSupported(_ctap.info)) {
assert(permissions != null);
subCmd =
ClientPinSubCommand.getPinUvAuthTokenUsingPinWithPermissions.value;
}
Expand All @@ -255,14 +260,69 @@ class ClientPin {
rpId: permissionsRpId));

return await _pinProtocol.decrypt(
ss.sharedSecret, resp.data.pinUvAuthToken!);
ss.sharedSecret, resp.data!.pinUvAuthToken!);
}

/// Get the number of PIN retries remaining.
Future<int> getPinRetries() async {
if (!ClientPin.isSupported(_ctap.info)) {
throw Exception('getPinRetries is not supported.');
}

final resp = await _ctap.clientPin(ClientPinRequest(
pinUvAuthProtocol: _pinProtocol.version,
subCommand: ClientPinSubCommand.getPinRetries.value));
return resp.data.pinRetries!;
return resp.data!.pinRetries!;
}

/// Set the [pin] of the authenticator.
///
/// This only works when no PIN is set. To change the PIN when set, use changePin.
Future<bool> setPin(String pin) async {
if (!ClientPin.isSupported(_ctap.info)) {
throw Exception('setPin is not supported.');
}

final EncapsulateResult ss = await _getSharedSecret();
final pinEnc = await _pinProtocol.encrypt(ss.sharedSecret, _padPin(pin));
final pinUvAuthParam =
await _pinProtocol.authenticate(ss.sharedSecret, pinEnc);
final resp = await _ctap.clientPin(ClientPinRequest(
pinUvAuthProtocol: _pinProtocol.version,
subCommand: ClientPinSubCommand.setPin.value,
newPinEnc: pinEnc,
pinUvAuthParam: pinUvAuthParam));
return resp.status == 0;
}

/// Change the PIN of the authenticator.
/// This only works when a PIN is already set. If no PIN is set, use setPin.
Future<bool> changePin(String oldPin, String newPin) async {
if (!ClientPin.isSupported(_ctap.info)) {
throw Exception('changePin is not supported.');
}

final EncapsulateResult ss = await _getSharedSecret();
final pinHash =
(await Sha256().hash(utf8.encode(oldPin))).bytes.sublist(0, 16);
final pinHashEnc = await _pinProtocol.encrypt(ss.sharedSecret, pinHash);
final newPinEnc =
await _pinProtocol.encrypt(ss.sharedSecret, _padPin(newPin));
final pinUvAuthParam = await _pinProtocol.authenticate(
ss.sharedSecret, newPinEnc + pinHashEnc);
final resp = await _ctap.clientPin(ClientPinRequest(
pinUvAuthProtocol: _pinProtocol.version,
subCommand: ClientPinSubCommand.changePin.value,
keyAgreement: ss.coseKey,
pinHashEnc: pinHashEnc,
newPinEnc: newPinEnc,
pinUvAuthParam: pinUvAuthParam));
return resp.status == 0;
}

/// Pad the PIN to 64 bytes.
List<int> _padPin(String pin) {
final pinBytes = utf8.encode(pin);
return pinBytes + List.filled(64 - pinBytes.length, 0);
}
}
11 changes: 6 additions & 5 deletions test/fido2_pin_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import 'package:cryptography/cryptography.dart';
import 'package:elliptic/ecdh.dart';
import 'package:elliptic/elliptic.dart';
import 'package:fido2/fido2.dart';
import 'package:fido2/src/cose.dart';
import 'package:test/test.dart';

import 'fido2_ctap.dart';
Expand All @@ -29,17 +28,19 @@ void main() {
});

test('encrypt', () async {
final key = hex.decode('000102030405060708090a0b0c0d0e0f');
final key = hex.decode(
'000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f');
final plaintext = hex.decode('00112233445566778899aabbccddeeff');
final ciphertext = hex.decode('69c4e0d86a7b0430d8cdb78070b4c55a');
final ciphertext = hex.decode('04a121e92033c921048917754f961b0d');
PinProtocolV1 pinProtocol = PinProtocolV1();
expect(await pinProtocol.encrypt(key, plaintext), equals(ciphertext));
});

test('decrypt', () async {
final key = hex.decode('000102030405060708090a0b0c0d0e0f');
final key = hex.decode(
'000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f');
final plaintext = hex.decode('00112233445566778899aabbccddeeff');
final ciphertext = hex.decode('69c4e0d86a7b0430d8cdb78070b4c55a');
final ciphertext = hex.decode('04a121e92033c921048917754f961b0d');
PinProtocolV1 pinProtocol = PinProtocolV1();
expect(await pinProtocol.decrypt(key, ciphertext), equals(plaintext));
});
Expand Down

0 comments on commit 7c22ef7

Please sign in to comment.