PIV: More slot information and ability to generate pubkey only

This commit is contained in:
Dain Nilsson 2024-02-07 14:48:51 +01:00
parent fd288c947b
commit 0a6eedcc99
No known key found for this signature in database
GPG Key ID: F04367096FBA95E8
18 changed files with 427 additions and 349 deletions

View File

@ -75,6 +75,7 @@ class InvalidPinException(RpcException):
@unique
class GENERATE_TYPE(str, Enum):
PUBLIC_KEY = "publicKey"
CSR = "csr"
CERTIFICATE = "certificate"
@ -304,7 +305,13 @@ def _get_cert_info(cert):
not_before = cert.not_valid_before
not_after = cert.not_valid_after
try:
key_type = KEY_TYPE.from_public_key(cert.public_key())
except ValueError:
key_type = None
return dict(
key_type=key_type,
subject=cert.subject.rfc4514_string(),
issuer=cert.issuer.rfc4514_string(),
serial=hex(cert.serial_number)[2:],
@ -348,7 +355,7 @@ class SlotsNode(RpcNode):
f"{int(slot):02x}": dict(
slot=int(slot),
name=slot.name,
has_key=metadata is not None if self._has_metadata else None,
metadata=asdict(metadata) if metadata else None,
cert_info=_get_cert_info(cert),
)
for slot, (metadata, cert) in self._slots.items()
@ -450,6 +457,9 @@ class SlotNode(RpcNode):
public_key = self.session.generate_key(
self.slot, key_type, pin_policy, touch_policy
)
public_key_pem = public_key.public_bytes(
encoding=Encoding.PEM, format=PublicFormat.SubjectPublicKeyInfo
).decode()
if pin_policy != PIN_POLICY.NEVER:
# TODO: Check if verified?
@ -459,7 +469,9 @@ class SlotNode(RpcNode):
if touch_policy in (TOUCH_POLICY.ALWAYS, TOUCH_POLICY.CACHED):
signal("touch")
if generate_type == GENERATE_TYPE.CSR:
if generate_type == GENERATE_TYPE.PUBLIC_KEY:
result = public_key_pem
elif generate_type == GENERATE_TYPE.CSR:
csr = generate_csr(self.session, self.slot, public_key, subject)
result = csr.public_bytes(encoding=Encoding.PEM).decode()
elif generate_type == GENERATE_TYPE.CERTIFICATE:
@ -479,13 +491,8 @@ class SlotNode(RpcNode):
self.session.put_certificate(self.slot, cert)
self.session.put_object(OBJECT_ID.CHUID, generate_chuid())
else:
raise ValueError("Unsupported GENERATE_TYPE")
raise ValueError(f"Unsupported GENERATE_TYPE: {generate_type}")
self._refresh()
return dict(
public_key=public_key.public_bytes(
encoding=Encoding.PEM, format=PublicFormat.SubjectPublicKeyInfo
).decode(),
result=result,
)
return dict(public_key=public_key_pem, result=result)

View File

@ -334,6 +334,12 @@ class _DesktopPivSlotsNotifier extends PivSlotsNotifier {
});
final (type, subject, validFrom, validTo) = parameters.when(
publicKey: () => (
GenerateType.publicKey,
null,
null,
null,
),
certificate: (subject, validFrom, validTo) => (
GenerateType.certificate,
subject,

View File

@ -79,6 +79,7 @@
"s_show_secret_key": null,
"s_hide_secret_key": null,
"s_private_key": null,
"s_public_key": null,
"s_invalid_length": "Ungültige Länge",
"l_invalid_format_allowed_chars": null,
"@l_invalid_format_allowed_chars": {
@ -460,6 +461,7 @@
"s_csr": null,
"s_subject": null,
"l_export_csr_file": null,
"l_export_public_key_file": null,
"l_select_import_file": null,
"l_export_certificate": null,
"l_export_certificate_file": null,

View File

@ -79,6 +79,7 @@
"s_show_secret_key": "Show secret key",
"s_hide_secret_key": "Hide secret key",
"s_private_key": "Private key",
"s_public_key": "Public key",
"s_invalid_length": "Invalid length",
"l_invalid_format_allowed_chars": "Invalid format, allowed characters: {characters}",
"@l_invalid_format_allowed_chars": {
@ -460,6 +461,7 @@
"s_csr": "CSR",
"s_subject": "Subject",
"l_export_csr_file": "Save CSR to file",
"l_export_public_key_file": "Save public key to file",
"l_select_import_file": "Select file to import",
"l_export_certificate": "Export certificate",
"l_export_certificate_file": "Export certificate to file",

View File

@ -79,6 +79,7 @@
"s_show_secret_key": null,
"s_hide_secret_key": null,
"s_private_key": "Clé privée",
"s_public_key": null,
"s_invalid_length": "Longueur invalide",
"l_invalid_format_allowed_chars": null,
"@l_invalid_format_allowed_chars": {
@ -460,6 +461,7 @@
"s_csr": "CSR",
"s_subject": "Sujet",
"l_export_csr_file": "Sauvegarder le CSR vers un fichier",
"l_export_public_key_file": null,
"l_select_import_file": "Sélectionnez un fichier à importer",
"l_export_certificate": "Exporter le certificat",
"l_export_certificate_file": "Exporter le certificat vers un fichier",

View File

@ -79,6 +79,7 @@
"s_show_secret_key": null,
"s_hide_secret_key": null,
"s_private_key": "秘密鍵",
"s_public_key": null,
"s_invalid_length": "無効な長さです",
"l_invalid_format_allowed_chars": null,
"@l_invalid_format_allowed_chars": {
@ -460,6 +461,7 @@
"s_csr": "CSR",
"s_subject": "サブジェクト",
"l_export_csr_file": "CSRをファイルに保存",
"l_export_public_key_file": null,
"l_select_import_file": "インポートするファイルの選択",
"l_export_certificate": "証明書をエクスポートする",
"l_export_certificate_file": "証明書をファイルにエクスポートする",

View File

@ -79,6 +79,7 @@
"s_show_secret_key": "Pokaż tajny klucz",
"s_hide_secret_key": "Ukryj tajny klucz",
"s_private_key": "Klucz prywatny",
"s_public_key": null,
"s_invalid_length": "Nieprawidłowa długość",
"l_invalid_format_allowed_chars": "Nieprawidłowy format, dozwolone znaki: {characters}",
"@l_invalid_format_allowed_chars": {
@ -460,6 +461,7 @@
"s_csr": "CSR",
"s_subject": "Temat",
"l_export_csr_file": "Zapisz CSR do pliku",
"l_export_public_key_file": null,
"l_select_import_file": "Wybierz plik do zaimportowania",
"l_export_certificate": "Eksportuj certyfikat",
"l_export_certificate_file": "Eksportuj certyfikat do pliku",

View File

@ -56,11 +56,14 @@ const appListItem9c = Key('$_prefix.9c.applistitem');
const appListItem9d = Key('$_prefix.9d.applistitem');
const appListItem9e = Key('$_prefix.9e.applistitem');
// CertInfo body keys
// SlotMetadata body keys
const slotMetadataKeyType = Key('$_prefix.slotMetadata.keyType');
const certInfoSubjectKey = Key('$_prefix.certInfo.subject');
const certInfoIssuerKey = Key('$_prefix.certInfo.issuer');
const certInfoSerialKey = Key('$_prefix.certInfo.serial');
const certInfoFingerprintKey = Key('$_prefix.certInfo.fingerprint');
const certInfoValidFromKey = Key('$_prefix.certInfo.validFrom');
const certInfoValidToKey = Key('$_prefix.certInfo.validTo');
// CertInfo body keys
const certInfoKeyType = Key('$_prefix.certInfo.keyType');
const certInfoSubject = Key('$_prefix.certInfo.subject');
const certInfoIssuer = Key('$_prefix.certInfo.issuer');
const certInfoSerial = Key('$_prefix.certInfo.serial');
const certInfoFingerprint = Key('$_prefix.certInfo.fingerprint');
const certInfoValidFrom = Key('$_prefix.certInfo.validFrom');
const certInfoValidTo = Key('$_prefix.certInfo.validTo');

View File

@ -28,12 +28,13 @@ const defaultKeyType = KeyType.eccp256;
const defaultGenerateType = GenerateType.certificate;
enum GenerateType {
// TODO: Add "publicKey"? Needed for X25519
publicKey,
certificate,
csr;
String getDisplayName(AppLocalizations l10n) {
return switch (this) {
GenerateType.publicKey => l10n.s_public_key,
GenerateType.certificate => l10n.s_certificate,
GenerateType.csr => l10n.s_csr,
};
@ -247,6 +248,7 @@ class PivState with _$PivState {
@freezed
class CertInfo with _$CertInfo {
factory CertInfo({
required KeyType? keyType,
required String subject,
required String issuer,
required String serial,
@ -263,7 +265,7 @@ class CertInfo with _$CertInfo {
class PivSlot with _$PivSlot {
factory PivSlot({
required SlotId slot,
bool? hasKey,
SlotMetadata? metadata,
CertInfo? certInfo,
}) = _PivSlot;
@ -286,11 +288,14 @@ class PivExamineResult with _$PivExamineResult {
@freezed
class PivGenerateParameters with _$PivGenerateParameters {
factory PivGenerateParameters.publicKey() = _GeneratePublicKey;
factory PivGenerateParameters.certificate({
required String subject,
required DateTime validFrom,
required DateTime validTo,
}) = _GenerateCertificate;
factory PivGenerateParameters.csr({
required String subject,
}) = _GenerateCsr;
@ -301,7 +306,7 @@ class PivGenerateResult with _$PivGenerateResult {
factory PivGenerateResult({
required GenerateType generateType,
required String publicKey,
required String result,
String? result,
}) = _PivGenerateResult;
factory PivGenerateResult.fromJson(Map<String, dynamic> json) =>

View File

@ -1431,6 +1431,7 @@ CertInfo _$CertInfoFromJson(Map<String, dynamic> json) {
/// @nodoc
mixin _$CertInfo {
KeyType? get keyType => throw _privateConstructorUsedError;
String get subject => throw _privateConstructorUsedError;
String get issuer => throw _privateConstructorUsedError;
String get serial => throw _privateConstructorUsedError;
@ -1450,7 +1451,8 @@ abstract class $CertInfoCopyWith<$Res> {
_$CertInfoCopyWithImpl<$Res, CertInfo>;
@useResult
$Res call(
{String subject,
{KeyType? keyType,
String subject,
String issuer,
String serial,
String notValidBefore,
@ -1471,6 +1473,7 @@ class _$CertInfoCopyWithImpl<$Res, $Val extends CertInfo>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? keyType = freezed,
Object? subject = null,
Object? issuer = null,
Object? serial = null,
@ -1479,6 +1482,10 @@ class _$CertInfoCopyWithImpl<$Res, $Val extends CertInfo>
Object? fingerprint = null,
}) {
return _then(_value.copyWith(
keyType: freezed == keyType
? _value.keyType
: keyType // ignore: cast_nullable_to_non_nullable
as KeyType?,
subject: null == subject
? _value.subject
: subject // ignore: cast_nullable_to_non_nullable
@ -1516,7 +1523,8 @@ abstract class _$$CertInfoImplCopyWith<$Res>
@override
@useResult
$Res call(
{String subject,
{KeyType? keyType,
String subject,
String issuer,
String serial,
String notValidBefore,
@ -1535,6 +1543,7 @@ class __$$CertInfoImplCopyWithImpl<$Res>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? keyType = freezed,
Object? subject = null,
Object? issuer = null,
Object? serial = null,
@ -1543,6 +1552,10 @@ class __$$CertInfoImplCopyWithImpl<$Res>
Object? fingerprint = null,
}) {
return _then(_$CertInfoImpl(
keyType: freezed == keyType
? _value.keyType
: keyType // ignore: cast_nullable_to_non_nullable
as KeyType?,
subject: null == subject
? _value.subject
: subject // ignore: cast_nullable_to_non_nullable
@ -1575,7 +1588,8 @@ class __$$CertInfoImplCopyWithImpl<$Res>
@JsonSerializable()
class _$CertInfoImpl implements _CertInfo {
_$CertInfoImpl(
{required this.subject,
{required this.keyType,
required this.subject,
required this.issuer,
required this.serial,
required this.notValidBefore,
@ -1585,6 +1599,8 @@ class _$CertInfoImpl implements _CertInfo {
factory _$CertInfoImpl.fromJson(Map<String, dynamic> json) =>
_$$CertInfoImplFromJson(json);
@override
final KeyType? keyType;
@override
final String subject;
@override
@ -1600,7 +1616,7 @@ class _$CertInfoImpl implements _CertInfo {
@override
String toString() {
return 'CertInfo(subject: $subject, issuer: $issuer, serial: $serial, notValidBefore: $notValidBefore, notValidAfter: $notValidAfter, fingerprint: $fingerprint)';
return 'CertInfo(keyType: $keyType, subject: $subject, issuer: $issuer, serial: $serial, notValidBefore: $notValidBefore, notValidAfter: $notValidAfter, fingerprint: $fingerprint)';
}
@override
@ -1608,6 +1624,7 @@ class _$CertInfoImpl implements _CertInfo {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$CertInfoImpl &&
(identical(other.keyType, keyType) || other.keyType == keyType) &&
(identical(other.subject, subject) || other.subject == subject) &&
(identical(other.issuer, issuer) || other.issuer == issuer) &&
(identical(other.serial, serial) || other.serial == serial) &&
@ -1621,7 +1638,7 @@ class _$CertInfoImpl implements _CertInfo {
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, subject, issuer, serial,
int get hashCode => Object.hash(runtimeType, keyType, subject, issuer, serial,
notValidBefore, notValidAfter, fingerprint);
@JsonKey(ignore: true)
@ -1640,7 +1657,8 @@ class _$CertInfoImpl implements _CertInfo {
abstract class _CertInfo implements CertInfo {
factory _CertInfo(
{required final String subject,
{required final KeyType? keyType,
required final String subject,
required final String issuer,
required final String serial,
required final String notValidBefore,
@ -1650,6 +1668,8 @@ abstract class _CertInfo implements CertInfo {
factory _CertInfo.fromJson(Map<String, dynamic> json) =
_$CertInfoImpl.fromJson;
@override
KeyType? get keyType;
@override
String get subject;
@override
@ -1675,7 +1695,7 @@ PivSlot _$PivSlotFromJson(Map<String, dynamic> json) {
/// @nodoc
mixin _$PivSlot {
SlotId get slot => throw _privateConstructorUsedError;
bool? get hasKey => throw _privateConstructorUsedError;
SlotMetadata? get metadata => throw _privateConstructorUsedError;
CertInfo? get certInfo => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@ -1688,8 +1708,9 @@ abstract class $PivSlotCopyWith<$Res> {
factory $PivSlotCopyWith(PivSlot value, $Res Function(PivSlot) then) =
_$PivSlotCopyWithImpl<$Res, PivSlot>;
@useResult
$Res call({SlotId slot, bool? hasKey, CertInfo? certInfo});
$Res call({SlotId slot, SlotMetadata? metadata, CertInfo? certInfo});
$SlotMetadataCopyWith<$Res>? get metadata;
$CertInfoCopyWith<$Res>? get certInfo;
}
@ -1707,7 +1728,7 @@ class _$PivSlotCopyWithImpl<$Res, $Val extends PivSlot>
@override
$Res call({
Object? slot = null,
Object? hasKey = freezed,
Object? metadata = freezed,
Object? certInfo = freezed,
}) {
return _then(_value.copyWith(
@ -1715,10 +1736,10 @@ class _$PivSlotCopyWithImpl<$Res, $Val extends PivSlot>
? _value.slot
: slot // ignore: cast_nullable_to_non_nullable
as SlotId,
hasKey: freezed == hasKey
? _value.hasKey
: hasKey // ignore: cast_nullable_to_non_nullable
as bool?,
metadata: freezed == metadata
? _value.metadata
: metadata // ignore: cast_nullable_to_non_nullable
as SlotMetadata?,
certInfo: freezed == certInfo
? _value.certInfo
: certInfo // ignore: cast_nullable_to_non_nullable
@ -1726,6 +1747,18 @@ class _$PivSlotCopyWithImpl<$Res, $Val extends PivSlot>
) as $Val);
}
@override
@pragma('vm:prefer-inline')
$SlotMetadataCopyWith<$Res>? get metadata {
if (_value.metadata == null) {
return null;
}
return $SlotMetadataCopyWith<$Res>(_value.metadata!, (value) {
return _then(_value.copyWith(metadata: value) as $Val);
});
}
@override
@pragma('vm:prefer-inline')
$CertInfoCopyWith<$Res>? get certInfo {
@ -1746,8 +1779,10 @@ abstract class _$$PivSlotImplCopyWith<$Res> implements $PivSlotCopyWith<$Res> {
__$$PivSlotImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({SlotId slot, bool? hasKey, CertInfo? certInfo});
$Res call({SlotId slot, SlotMetadata? metadata, CertInfo? certInfo});
@override
$SlotMetadataCopyWith<$Res>? get metadata;
@override
$CertInfoCopyWith<$Res>? get certInfo;
}
@ -1764,7 +1799,7 @@ class __$$PivSlotImplCopyWithImpl<$Res>
@override
$Res call({
Object? slot = null,
Object? hasKey = freezed,
Object? metadata = freezed,
Object? certInfo = freezed,
}) {
return _then(_$PivSlotImpl(
@ -1772,10 +1807,10 @@ class __$$PivSlotImplCopyWithImpl<$Res>
? _value.slot
: slot // ignore: cast_nullable_to_non_nullable
as SlotId,
hasKey: freezed == hasKey
? _value.hasKey
: hasKey // ignore: cast_nullable_to_non_nullable
as bool?,
metadata: freezed == metadata
? _value.metadata
: metadata // ignore: cast_nullable_to_non_nullable
as SlotMetadata?,
certInfo: freezed == certInfo
? _value.certInfo
: certInfo // ignore: cast_nullable_to_non_nullable
@ -1787,7 +1822,7 @@ class __$$PivSlotImplCopyWithImpl<$Res>
/// @nodoc
@JsonSerializable()
class _$PivSlotImpl implements _PivSlot {
_$PivSlotImpl({required this.slot, this.hasKey, this.certInfo});
_$PivSlotImpl({required this.slot, this.metadata, this.certInfo});
factory _$PivSlotImpl.fromJson(Map<String, dynamic> json) =>
_$$PivSlotImplFromJson(json);
@ -1795,13 +1830,13 @@ class _$PivSlotImpl implements _PivSlot {
@override
final SlotId slot;
@override
final bool? hasKey;
final SlotMetadata? metadata;
@override
final CertInfo? certInfo;
@override
String toString() {
return 'PivSlot(slot: $slot, hasKey: $hasKey, certInfo: $certInfo)';
return 'PivSlot(slot: $slot, metadata: $metadata, certInfo: $certInfo)';
}
@override
@ -1810,14 +1845,15 @@ class _$PivSlotImpl implements _PivSlot {
(other.runtimeType == runtimeType &&
other is _$PivSlotImpl &&
(identical(other.slot, slot) || other.slot == slot) &&
(identical(other.hasKey, hasKey) || other.hasKey == hasKey) &&
(identical(other.metadata, metadata) ||
other.metadata == metadata) &&
(identical(other.certInfo, certInfo) ||
other.certInfo == certInfo));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, slot, hasKey, certInfo);
int get hashCode => Object.hash(runtimeType, slot, metadata, certInfo);
@JsonKey(ignore: true)
@override
@ -1836,7 +1872,7 @@ class _$PivSlotImpl implements _PivSlot {
abstract class _PivSlot implements PivSlot {
factory _PivSlot(
{required final SlotId slot,
final bool? hasKey,
final SlotMetadata? metadata,
final CertInfo? certInfo}) = _$PivSlotImpl;
factory _PivSlot.fromJson(Map<String, dynamic> json) = _$PivSlotImpl.fromJson;
@ -1844,7 +1880,7 @@ abstract class _PivSlot implements PivSlot {
@override
SlotId get slot;
@override
bool? get hasKey;
SlotMetadata? get metadata;
@override
CertInfo? get certInfo;
@override
@ -2253,9 +2289,9 @@ abstract class _InvalidPassword implements PivExamineResult {
/// @nodoc
mixin _$PivGenerateParameters {
String get subject => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() publicKey,
required TResult Function(
String subject, DateTime validFrom, DateTime validTo)
certificate,
@ -2264,6 +2300,7 @@ mixin _$PivGenerateParameters {
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? publicKey,
TResult? Function(String subject, DateTime validFrom, DateTime validTo)?
certificate,
TResult? Function(String subject)? csr,
@ -2271,6 +2308,7 @@ mixin _$PivGenerateParameters {
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? publicKey,
TResult Function(String subject, DateTime validFrom, DateTime validTo)?
certificate,
TResult Function(String subject)? csr,
@ -2279,27 +2317,26 @@ mixin _$PivGenerateParameters {
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_GeneratePublicKey value) publicKey,
required TResult Function(_GenerateCertificate value) certificate,
required TResult Function(_GenerateCsr value) csr,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_GeneratePublicKey value)? publicKey,
TResult? Function(_GenerateCertificate value)? certificate,
TResult? Function(_GenerateCsr value)? csr,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_GeneratePublicKey value)? publicKey,
TResult Function(_GenerateCertificate value)? certificate,
TResult Function(_GenerateCsr value)? csr,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$PivGenerateParametersCopyWith<PivGenerateParameters> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
@ -2307,8 +2344,6 @@ abstract class $PivGenerateParametersCopyWith<$Res> {
factory $PivGenerateParametersCopyWith(PivGenerateParameters value,
$Res Function(PivGenerateParameters) then) =
_$PivGenerateParametersCopyWithImpl<$Res, PivGenerateParameters>;
@useResult
$Res call({String subject});
}
/// @nodoc
@ -2321,28 +2356,125 @@ class _$PivGenerateParametersCopyWithImpl<$Res,
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? subject = null,
}) {
return _then(_value.copyWith(
subject: null == subject
? _value.subject
: subject // ignore: cast_nullable_to_non_nullable
as String,
) as $Val);
}
}
/// @nodoc
abstract class _$$GenerateCertificateImplCopyWith<$Res>
implements $PivGenerateParametersCopyWith<$Res> {
abstract class _$$GeneratePublicKeyImplCopyWith<$Res> {
factory _$$GeneratePublicKeyImplCopyWith(_$GeneratePublicKeyImpl value,
$Res Function(_$GeneratePublicKeyImpl) then) =
__$$GeneratePublicKeyImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$GeneratePublicKeyImplCopyWithImpl<$Res>
extends _$PivGenerateParametersCopyWithImpl<$Res, _$GeneratePublicKeyImpl>
implements _$$GeneratePublicKeyImplCopyWith<$Res> {
__$$GeneratePublicKeyImplCopyWithImpl(_$GeneratePublicKeyImpl _value,
$Res Function(_$GeneratePublicKeyImpl) _then)
: super(_value, _then);
}
/// @nodoc
class _$GeneratePublicKeyImpl implements _GeneratePublicKey {
_$GeneratePublicKeyImpl();
@override
String toString() {
return 'PivGenerateParameters.publicKey()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$GeneratePublicKeyImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() publicKey,
required TResult Function(
String subject, DateTime validFrom, DateTime validTo)
certificate,
required TResult Function(String subject) csr,
}) {
return publicKey();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? publicKey,
TResult? Function(String subject, DateTime validFrom, DateTime validTo)?
certificate,
TResult? Function(String subject)? csr,
}) {
return publicKey?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? publicKey,
TResult Function(String subject, DateTime validFrom, DateTime validTo)?
certificate,
TResult Function(String subject)? csr,
required TResult orElse(),
}) {
if (publicKey != null) {
return publicKey();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_GeneratePublicKey value) publicKey,
required TResult Function(_GenerateCertificate value) certificate,
required TResult Function(_GenerateCsr value) csr,
}) {
return publicKey(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_GeneratePublicKey value)? publicKey,
TResult? Function(_GenerateCertificate value)? certificate,
TResult? Function(_GenerateCsr value)? csr,
}) {
return publicKey?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_GeneratePublicKey value)? publicKey,
TResult Function(_GenerateCertificate value)? certificate,
TResult Function(_GenerateCsr value)? csr,
required TResult orElse(),
}) {
if (publicKey != null) {
return publicKey(this);
}
return orElse();
}
}
abstract class _GeneratePublicKey implements PivGenerateParameters {
factory _GeneratePublicKey() = _$GeneratePublicKeyImpl;
}
/// @nodoc
abstract class _$$GenerateCertificateImplCopyWith<$Res> {
factory _$$GenerateCertificateImplCopyWith(_$GenerateCertificateImpl value,
$Res Function(_$GenerateCertificateImpl) then) =
__$$GenerateCertificateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String subject, DateTime validFrom, DateTime validTo});
}
@ -2421,6 +2553,7 @@ class _$GenerateCertificateImpl implements _GenerateCertificate {
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() publicKey,
required TResult Function(
String subject, DateTime validFrom, DateTime validTo)
certificate,
@ -2432,6 +2565,7 @@ class _$GenerateCertificateImpl implements _GenerateCertificate {
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? publicKey,
TResult? Function(String subject, DateTime validFrom, DateTime validTo)?
certificate,
TResult? Function(String subject)? csr,
@ -2442,6 +2576,7 @@ class _$GenerateCertificateImpl implements _GenerateCertificate {
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? publicKey,
TResult Function(String subject, DateTime validFrom, DateTime validTo)?
certificate,
TResult Function(String subject)? csr,
@ -2456,6 +2591,7 @@ class _$GenerateCertificateImpl implements _GenerateCertificate {
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_GeneratePublicKey value) publicKey,
required TResult Function(_GenerateCertificate value) certificate,
required TResult Function(_GenerateCsr value) csr,
}) {
@ -2465,6 +2601,7 @@ class _$GenerateCertificateImpl implements _GenerateCertificate {
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_GeneratePublicKey value)? publicKey,
TResult? Function(_GenerateCertificate value)? certificate,
TResult? Function(_GenerateCsr value)? csr,
}) {
@ -2474,6 +2611,7 @@ class _$GenerateCertificateImpl implements _GenerateCertificate {
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_GeneratePublicKey value)? publicKey,
TResult Function(_GenerateCertificate value)? certificate,
TResult Function(_GenerateCsr value)? csr,
required TResult orElse(),
@ -2491,23 +2629,19 @@ abstract class _GenerateCertificate implements PivGenerateParameters {
required final DateTime validFrom,
required final DateTime validTo}) = _$GenerateCertificateImpl;
@override
String get subject;
DateTime get validFrom;
DateTime get validTo;
@override
@JsonKey(ignore: true)
_$$GenerateCertificateImplCopyWith<_$GenerateCertificateImpl> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class _$$GenerateCsrImplCopyWith<$Res>
implements $PivGenerateParametersCopyWith<$Res> {
abstract class _$$GenerateCsrImplCopyWith<$Res> {
factory _$$GenerateCsrImplCopyWith(
_$GenerateCsrImpl value, $Res Function(_$GenerateCsrImpl) then) =
__$$GenerateCsrImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String subject});
}
@ -2567,6 +2701,7 @@ class _$GenerateCsrImpl implements _GenerateCsr {
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() publicKey,
required TResult Function(
String subject, DateTime validFrom, DateTime validTo)
certificate,
@ -2578,6 +2713,7 @@ class _$GenerateCsrImpl implements _GenerateCsr {
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? publicKey,
TResult? Function(String subject, DateTime validFrom, DateTime validTo)?
certificate,
TResult? Function(String subject)? csr,
@ -2588,6 +2724,7 @@ class _$GenerateCsrImpl implements _GenerateCsr {
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? publicKey,
TResult Function(String subject, DateTime validFrom, DateTime validTo)?
certificate,
TResult Function(String subject)? csr,
@ -2602,6 +2739,7 @@ class _$GenerateCsrImpl implements _GenerateCsr {
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_GeneratePublicKey value) publicKey,
required TResult Function(_GenerateCertificate value) certificate,
required TResult Function(_GenerateCsr value) csr,
}) {
@ -2611,6 +2749,7 @@ class _$GenerateCsrImpl implements _GenerateCsr {
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_GeneratePublicKey value)? publicKey,
TResult? Function(_GenerateCertificate value)? certificate,
TResult? Function(_GenerateCsr value)? csr,
}) {
@ -2620,6 +2759,7 @@ class _$GenerateCsrImpl implements _GenerateCsr {
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_GeneratePublicKey value)? publicKey,
TResult Function(_GenerateCertificate value)? certificate,
TResult Function(_GenerateCsr value)? csr,
required TResult orElse(),
@ -2634,9 +2774,7 @@ class _$GenerateCsrImpl implements _GenerateCsr {
abstract class _GenerateCsr implements PivGenerateParameters {
factory _GenerateCsr({required final String subject}) = _$GenerateCsrImpl;
@override
String get subject;
@override
@JsonKey(ignore: true)
_$$GenerateCsrImplCopyWith<_$GenerateCsrImpl> get copyWith =>
throw _privateConstructorUsedError;
@ -2650,7 +2788,7 @@ PivGenerateResult _$PivGenerateResultFromJson(Map<String, dynamic> json) {
mixin _$PivGenerateResult {
GenerateType get generateType => throw _privateConstructorUsedError;
String get publicKey => throw _privateConstructorUsedError;
String get result => throw _privateConstructorUsedError;
String? get result => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
@ -2664,7 +2802,7 @@ abstract class $PivGenerateResultCopyWith<$Res> {
PivGenerateResult value, $Res Function(PivGenerateResult) then) =
_$PivGenerateResultCopyWithImpl<$Res, PivGenerateResult>;
@useResult
$Res call({GenerateType generateType, String publicKey, String result});
$Res call({GenerateType generateType, String publicKey, String? result});
}
/// @nodoc
@ -2682,7 +2820,7 @@ class _$PivGenerateResultCopyWithImpl<$Res, $Val extends PivGenerateResult>
$Res call({
Object? generateType = null,
Object? publicKey = null,
Object? result = null,
Object? result = freezed,
}) {
return _then(_value.copyWith(
generateType: null == generateType
@ -2693,10 +2831,10 @@ class _$PivGenerateResultCopyWithImpl<$Res, $Val extends PivGenerateResult>
? _value.publicKey
: publicKey // ignore: cast_nullable_to_non_nullable
as String,
result: null == result
result: freezed == result
? _value.result
: result // ignore: cast_nullable_to_non_nullable
as String,
as String?,
) as $Val);
}
}
@ -2709,7 +2847,7 @@ abstract class _$$PivGenerateResultImplCopyWith<$Res>
__$$PivGenerateResultImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({GenerateType generateType, String publicKey, String result});
$Res call({GenerateType generateType, String publicKey, String? result});
}
/// @nodoc
@ -2725,7 +2863,7 @@ class __$$PivGenerateResultImplCopyWithImpl<$Res>
$Res call({
Object? generateType = null,
Object? publicKey = null,
Object? result = null,
Object? result = freezed,
}) {
return _then(_$PivGenerateResultImpl(
generateType: null == generateType
@ -2736,10 +2874,10 @@ class __$$PivGenerateResultImplCopyWithImpl<$Res>
? _value.publicKey
: publicKey // ignore: cast_nullable_to_non_nullable
as String,
result: null == result
result: freezed == result
? _value.result
: result // ignore: cast_nullable_to_non_nullable
as String,
as String?,
));
}
}
@ -2748,9 +2886,7 @@ class __$$PivGenerateResultImplCopyWithImpl<$Res>
@JsonSerializable()
class _$PivGenerateResultImpl implements _PivGenerateResult {
_$PivGenerateResultImpl(
{required this.generateType,
required this.publicKey,
required this.result});
{required this.generateType, required this.publicKey, this.result});
factory _$PivGenerateResultImpl.fromJson(Map<String, dynamic> json) =>
_$$PivGenerateResultImplFromJson(json);
@ -2760,7 +2896,7 @@ class _$PivGenerateResultImpl implements _PivGenerateResult {
@override
final String publicKey;
@override
final String result;
final String? result;
@override
String toString() {
@ -2802,7 +2938,7 @@ abstract class _PivGenerateResult implements PivGenerateResult {
factory _PivGenerateResult(
{required final GenerateType generateType,
required final String publicKey,
required final String result}) = _$PivGenerateResultImpl;
final String? result}) = _$PivGenerateResultImpl;
factory _PivGenerateResult.fromJson(Map<String, dynamic> json) =
_$PivGenerateResultImpl.fromJson;
@ -2812,7 +2948,7 @@ abstract class _PivGenerateResult implements PivGenerateResult {
@override
String get publicKey;
@override
String get result;
String? get result;
@override
@JsonKey(ignore: true)
_$$PivGenerateResultImplCopyWith<_$PivGenerateResultImpl> get copyWith =>

View File

@ -133,6 +133,7 @@ Map<String, dynamic> _$$PivStateImplToJson(_$PivStateImpl instance) =>
_$CertInfoImpl _$$CertInfoImplFromJson(Map<String, dynamic> json) =>
_$CertInfoImpl(
keyType: $enumDecodeNullable(_$KeyTypeEnumMap, json['key_type']),
subject: json['subject'] as String,
issuer: json['issuer'] as String,
serial: json['serial'] as String,
@ -143,6 +144,7 @@ _$CertInfoImpl _$$CertInfoImplFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> _$$CertInfoImplToJson(_$CertInfoImpl instance) =>
<String, dynamic>{
'key_type': _$KeyTypeEnumMap[instance.keyType],
'subject': instance.subject,
'issuer': instance.issuer,
'serial': instance.serial,
@ -154,7 +156,9 @@ Map<String, dynamic> _$$CertInfoImplToJson(_$CertInfoImpl instance) =>
_$PivSlotImpl _$$PivSlotImplFromJson(Map<String, dynamic> json) =>
_$PivSlotImpl(
slot: SlotId.fromJson(json['slot'] as int),
hasKey: json['has_key'] as bool?,
metadata: json['metadata'] == null
? null
: SlotMetadata.fromJson(json['metadata'] as Map<String, dynamic>),
certInfo: json['cert_info'] == null
? null
: CertInfo.fromJson(json['cert_info'] as Map<String, dynamic>),
@ -163,7 +167,7 @@ _$PivSlotImpl _$$PivSlotImplFromJson(Map<String, dynamic> json) =>
Map<String, dynamic> _$$PivSlotImplToJson(_$PivSlotImpl instance) =>
<String, dynamic>{
'slot': _$SlotIdEnumMap[instance.slot]!,
'has_key': instance.hasKey,
'metadata': instance.metadata,
'cert_info': instance.certInfo,
};
@ -209,7 +213,7 @@ _$PivGenerateResultImpl _$$PivGenerateResultImplFromJson(
_$PivGenerateResultImpl(
generateType: $enumDecode(_$GenerateTypeEnumMap, json['generate_type']),
publicKey: json['public_key'] as String,
result: json['result'] as String,
result: json['result'] as String?,
);
Map<String, dynamic> _$$PivGenerateResultImplToJson(
@ -221,6 +225,7 @@ Map<String, dynamic> _$$PivGenerateResultImplToJson(
};
const _$GenerateTypeEnumMap = {
GenerateType.publicKey: 'publicKey',
GenerateType.certificate: 'certificate',
GenerateType.csr: 'csr',
};

View File

@ -123,21 +123,33 @@ class PivActions extends ConsumerWidget {
),
);
switch (result?.generateType) {
case GenerateType.csr:
if (result != null) {
final (fileExt, title, data) = switch (result.generateType) {
GenerateType.publicKey => (
'pem',
l10n.l_export_public_key_file,
result.publicKey,
),
GenerateType.csr => (
'csr',
l10n.l_export_csr_file,
result.result,
),
_ => (null, null, null),
};
if (fileExt != null) {
final filePath = await FilePicker.platform.saveFile(
dialogTitle: l10n.l_export_csr_file,
allowedExtensions: ['csr'],
dialogTitle: title,
allowedExtensions: [fileExt],
type: FileType.custom,
lockParentWindow: true,
);
if (filePath != null) {
final file = File(filePath);
await file.writeAsString(result!.result, flush: true);
await file.writeAsString(data!, flush: true);
}
break;
default:
break;
}
}
return result != null;
@ -250,165 +262,6 @@ class PivActions extends ConsumerWidget {
}
}
Widget registerPivActions(
DevicePath devicePath,
PivState pivState, {
required WidgetRef ref,
required Widget Function(BuildContext context) builder,
Map<Type, Action<Intent>> actions = const {},
}) {
final hasFeature = ref.watch(featureProvider);
return Actions(
actions: {
if (hasFeature(features.slotsGenerate))
GenerateIntent:
CallbackAction<GenerateIntent>(onInvoke: (intent) async {
final withContext = ref.read(withContextProvider);
if (!pivState.protectedKey &&
!await withContext(
(context) => _authIfNeeded(context, devicePath, pivState))) {
return false;
}
// TODO: Avoid asking for PIN if not needed?
final verified = await withContext((context) async =>
await showBlurDialog(
context: context,
builder: (context) => PinDialog(devicePath))) ??
false;
if (!verified) {
return false;
}
return await withContext((context) async {
final l10n = AppLocalizations.of(context)!;
final PivGenerateResult? result = await showBlurDialog(
context: context,
builder: (context) => GenerateKeyDialog(
devicePath,
pivState,
intent.slot,
),
);
switch (result?.generateType) {
case GenerateType.csr:
final filePath = await FilePicker.platform.saveFile(
dialogTitle: l10n.l_export_csr_file,
allowedExtensions: ['csr'],
type: FileType.custom,
lockParentWindow: true,
);
if (filePath != null) {
final file = File(filePath);
await file.writeAsString(result!.result, flush: true);
}
break;
default:
break;
}
return result != null;
});
}),
if (hasFeature(features.slotsImport))
ImportIntent: CallbackAction<ImportIntent>(onInvoke: (intent) async {
final withContext = ref.read(withContextProvider);
if (!await withContext(
(context) => _authIfNeeded(context, devicePath, pivState))) {
return false;
}
final picked = await withContext(
(context) async {
final l10n = AppLocalizations.of(context)!;
return await FilePicker.platform.pickFiles(
allowedExtensions: ['pem', 'der', 'pfx', 'p12', 'key', 'crt'],
type: FileType.custom,
allowMultiple: false,
lockParentWindow: true,
dialogTitle: l10n.l_select_import_file);
},
);
if (picked == null || picked.files.isEmpty) {
return false;
}
return await withContext((context) async =>
await showBlurDialog(
context: context,
builder: (context) => ImportFileDialog(
devicePath,
pivState,
intent.slot,
File(picked.paths.first!),
),
) ??
false);
}),
if (hasFeature(features.slotsExport))
ExportIntent: CallbackAction<ExportIntent>(onInvoke: (intent) async {
final (_, cert) = await ref
.read(pivSlotsProvider(devicePath).notifier)
.read(intent.slot.slot);
if (cert == null) {
return false;
}
final withContext = ref.read(withContextProvider);
final filePath = await withContext((context) async {
final l10n = AppLocalizations.of(context)!;
return await FilePicker.platform.saveFile(
dialogTitle: l10n.l_export_certificate_file,
allowedExtensions: ['pem'],
type: FileType.custom,
lockParentWindow: true,
);
});
if (filePath == null) {
return false;
}
final file = File(filePath);
await file.writeAsString(cert, flush: true);
await withContext((context) async {
final l10n = AppLocalizations.of(context)!;
showMessage(context, l10n.l_certificate_exported);
});
return true;
}),
if (hasFeature(features.slotsDelete))
DeleteIntent<PivSlot>:
CallbackAction<DeleteIntent<PivSlot>>(onInvoke: (intent) async {
final withContext = ref.read(withContextProvider);
if (!await withContext(
(context) => _authIfNeeded(context, devicePath, pivState))) {
return false;
}
final bool? deleted = await withContext((context) async =>
await showBlurDialog(
context: context,
builder: (context) => DeleteCertificateDialog(
devicePath,
intent.target,
),
) ??
false);
return deleted;
}),
...actions,
},
child: Builder(builder: builder),
);
}
List<ActionItem> buildSlotActions(PivSlot slot, AppLocalizations l10n) {
final hasCert = slot.certInfo != null;
return [

View File

@ -22,13 +22,13 @@ import 'package:intl/intl.dart';
import '../../app/message.dart';
import '../../app/state.dart';
import '../../widgets/tooltip_if_truncated.dart';
import '../keys.dart';
import '../keys.dart' as keys;
import '../models.dart';
class CertInfoTable extends ConsumerWidget {
final CertInfo certInfo;
class _InfoTable extends ConsumerWidget {
final Map<String, (String, Key)> values;
const CertInfoTable(this.certInfo, {super.key});
const _InfoTable(this.values);
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -38,70 +38,96 @@ class CertInfoTable extends ConsumerWidget {
final subtitleStyle = textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
);
final dateFormat =
DateFormat.yMMMEd(ref.watch(currentLocaleProvider).toString());
final clipboard = ref.watch(clipboardProvider);
final withContext = ref.watch(withContextProvider);
Widget header(String title) => Text(
title,
textAlign: TextAlign.right,
);
Widget body(String title, String value, Key key) => GestureDetector(
onDoubleTap: () async {
await clipboard.setText(value);
if (!clipboard.platformGivesFeedback()) {
await withContext((context) async {
showMessage(context, l10n.p_target_copied_clipboard(title));
});
}
},
child: TooltipIfTruncated(
key: key,
text: value,
style: subtitleStyle,
tooltip: value.replaceAllMapped(
RegExp(r',([A-Z]+)='), (match) => '\n${match[1]}='),
),
);
return Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
header(l10n.s_subject),
header(l10n.s_issuer),
header(l10n.s_serial),
header(l10n.s_certificate_fingerprint),
header(l10n.s_valid_from),
header(l10n.s_valid_to),
],
children: values.keys
.map((title) => Text(
title,
textAlign: TextAlign.right,
))
.toList(),
),
const SizedBox(width: 8),
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
body(l10n.s_subject, certInfo.subject, certInfoSubjectKey),
body(l10n.s_issuer, certInfo.issuer, certInfoIssuerKey),
body(l10n.s_serial, certInfo.serial, certInfoSerialKey),
body(l10n.s_certificate_fingerprint, certInfo.fingerprint,
certInfoFingerprintKey),
body(
l10n.s_valid_from,
dateFormat.format(DateTime.parse(certInfo.notValidBefore)),
certInfoValidFromKey),
body(
l10n.s_valid_to,
dateFormat.format(DateTime.parse(certInfo.notValidAfter)),
certInfoValidToKey),
],
children: values.entries.map((e) {
final title = e.key;
final (value, key) = e.value;
return GestureDetector(
onDoubleTap: () async {
await clipboard.setText(value);
if (!clipboard.platformGivesFeedback()) {
await withContext((context) async {
showMessage(
context, l10n.p_target_copied_clipboard(title));
});
}
},
child: TooltipIfTruncated(
key: key,
text: value,
style: subtitleStyle,
tooltip: value.replaceAllMapped(
RegExp(r',([A-Z]+)='), (match) => '\n${match[1]}='),
),
);
}).toList(),
),
),
],
);
}
}
class CertInfoTable extends ConsumerWidget {
final CertInfo? certInfo;
final SlotMetadata? metadata;
const CertInfoTable(this.certInfo, this.metadata, {super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!;
final dateFormat =
DateFormat.yMMMEd(ref.watch(currentLocaleProvider).toString());
final certInfo = this.certInfo;
final metadata = this.metadata;
return _InfoTable({
if (metadata != null)
l10n.s_private_key: (
metadata.keyType.getDisplayName(l10n),
keys.slotMetadataKeyType
),
if (certInfo != null) ...{
l10n.s_public_key: (
certInfo.keyType?.getDisplayName(l10n) ?? l10n.s_unknown_type,
keys.certInfoKeyType
),
l10n.s_subject: (certInfo.subject, keys.certInfoSubject),
l10n.s_issuer: (certInfo.issuer, keys.certInfoIssuer),
l10n.s_serial: (certInfo.serial, keys.certInfoSerial),
l10n.s_certificate_fingerprint: (
certInfo.fingerprint,
keys.certInfoFingerprint
),
l10n.s_valid_from: (
dateFormat.format(DateTime.parse(certInfo.notValidBefore)),
keys.certInfoValidFrom
),
l10n.s_valid_to: (
dateFormat.format(DateTime.parse(certInfo.notValidAfter)),
keys.certInfoValidTo
),
},
});
}
}

View File

@ -65,13 +65,14 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
_validToMax = DateTime.utc(now.year + 10, now.month, now.day);
}
List<KeyType> _getSupportedKeyTypes() => [
KeyType.rsa1024,
List<KeyType> _getSupportedKeyTypes(bool isFips) => [
if (!isFips) KeyType.rsa1024,
KeyType.rsa2048,
if (widget.pivState.version.isAtLeast(5, 7)) ...[
KeyType.rsa3072,
KeyType.rsa4096,
KeyType.ed25519,
if (!isFips) KeyType.x25519,
],
KeyType.eccp256,
KeyType.eccp384,
@ -86,15 +87,20 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
color: Theme.of(context).colorScheme.onSurfaceVariant,
);
final isFips =
ref.watch(currentDeviceDataProvider).valueOrNull?.info.isFips ?? false;
final canSave = !_generating &&
(!_invalidSubject || _generateType == GenerateType.publicKey);
return ResponsiveDialog(
allowCancel: !_generating,
title: Text(l10n.s_generate_key),
actions: [
TextButton(
key: keys.saveButton,
onPressed: _generating || _invalidSubject
? null
: () async {
onPressed: canSave
? () async {
if (!await confirmOverwrite(
context,
widget.pivSlot,
@ -111,11 +117,12 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
final pivNotifier =
ref.read(pivSlotsProvider(widget.devicePath).notifier);
if (!await pivNotifier.validateRfc4514(_subject)) {
if (!(_generateType == GenerateType.publicKey ||
await pivNotifier.validateRfc4514(_subject))) {
setState(() {
_generating = false;
_invalidSubject = true;
});
_invalidSubject = true;
return;
}
@ -123,6 +130,8 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
widget.pivSlot.slot,
_keyType,
parameters: switch (_generateType) {
GenerateType.publicKey =>
PivGenerateParameters.publicKey(),
GenerateType.certificate =>
PivGenerateParameters.certificate(
subject: _subject,
@ -142,7 +151,8 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
);
},
);
},
}
: null,
child: Text(l10n.s_save),
),
],
@ -169,7 +179,7 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
: null,
),
textInputAction: TextInputAction.next,
enabled: !_generating,
enabled: !_generating && _generateType != GenerateType.publicKey,
onChanged: (value) {
setState(() {
_invalidSubject = value.isEmpty;
@ -192,7 +202,7 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
runSpacing: 8.0,
children: [
ChoiceFilterChip<KeyType>(
items: _getSupportedKeyTypes(),
items: _getSupportedKeyTypes(isFips),
value: _keyType,
selected: _keyType != defaultKeyType,
itemBuilder: (value) => Text(value.getDisplayName(l10n)),
@ -201,6 +211,9 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
: (value) {
setState(() {
_keyType = value;
if (value == KeyType.x25519) {
_generateType = GenerateType.publicKey;
}
});
},
),
@ -209,7 +222,7 @@ class _GenerateKeyDialogState extends ConsumerState<GenerateKeyDialog> {
value: _generateType,
selected: _generateType != defaultGenerateType,
itemBuilder: (value) => Text(value.getDisplayName(l10n)),
onChanged: _generating
onChanged: _generating || _keyType == KeyType.x25519
? null
: (value) {
setState(() {

View File

@ -256,7 +256,7 @@ class _ImportFileDialogState extends ConsumerState<ImportFileDialog> {
),
SizedBox(
height: 120, // Needed for layout, adapt if text sizes changes
child: CertInfoTable(certInfo),
child: CertInfoTable(certInfo, null),
),
]
]

View File

@ -68,7 +68,7 @@ Future<bool> confirmOverwrite(
required bool writeCert,
}) async {
final overwritesCert = writeCert && pivSlot.certInfo != null;
final overwritesKey = writeKey ? pivSlot.hasKey : false;
final overwritesKey = writeKey ? pivSlot.metadata != null : false;
if (overwritesCert || overwritesKey != false) {
return await showBlurDialog(
context: context,

View File

@ -128,14 +128,19 @@ class _PivScreenState extends ConsumerState<PivScreen> {
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
selected.certInfo != null
? CertInfoTable(selected.certInfo!)
: Text(
l10n.l_no_certificate,
softWrap: true,
textAlign: TextAlign.center,
style: subtitleStyle,
),
if (selected.certInfo != null ||
selected.metadata != null) ...[
CertInfoTable(selected.certInfo,
selected.metadata),
const SizedBox(height: 16),
],
if (selected.certInfo == null)
Text(
l10n.l_no_certificate,
softWrap: true,
textAlign: TextAlign.center,
style: subtitleStyle,
),
],
),
),
@ -225,7 +230,7 @@ class _CertificateListItem extends ConsumerWidget {
subtitle: certInfo != null
// Simplify subtitle by stripping "CN=", etc.
? certInfo.subject.replaceAll(RegExp(r'[A-Z]+='), ' ').trimLeft()
: pivSlot.hasKey == true
: pivSlot.metadata != null
? l10n.l_key_no_certificate
: l10n.l_no_certificate,
trailing: expanded

View File

@ -59,6 +59,7 @@ class SlotDialog extends ConsumerWidget {
}
final certInfo = slotData.certInfo;
final metadata = slotData.metadata;
return PivActions(
devicePath: node.path,
pivState: pivState,
@ -79,26 +80,34 @@ class SlotDialog extends ConsumerWidget {
softWrap: true,
textAlign: TextAlign.center,
),
if (certInfo != null) ...[
Padding(
padding: const EdgeInsets.all(16),
child: CertInfoTable(certInfo),
Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
if (certInfo != null || metadata != null) ...[
Padding(
padding: const EdgeInsets.only(bottom: 16),
child: CertInfoTable(certInfo, metadata),
),
],
if (certInfo == null) ...[
Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Text(
l10n.l_no_certificate,
softWrap: true,
textAlign: TextAlign.center,
style: subtitleStyle,
),
),
],
],
),
] else ...[
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Text(
l10n.l_no_certificate,
softWrap: true,
textAlign: TextAlign.center,
style: subtitleStyle,
),
),
const SizedBox(height: 16),
],
),
],
),
),
const SizedBox(height: 16),
ActionListSection.fromMenuActions(
context,
l10n.s_actions,