x509_cert_store 1.2.1 copy "x509_cert_store: ^1.2.1" to clipboard
x509_cert_store: ^1.2.1 copied to clipboard

A Flutter plugin for Windows and macOS desktop applications that enables adding X.509 certificates to the local certificate store with trust settings support.

example/lib/main.dart

// main.dart
import 'dart:convert';
import 'dart:io';

import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:x509_cert_store/x509_cert_store.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'X509 Certificate Store Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const CertificateManagerPage(),
    );
  }
}

class CertificateManagerPage extends StatefulWidget {
  const CertificateManagerPage({super.key});

  @override
  State<CertificateManagerPage> createState() => _CertificateManagerPageState();
}

class _CertificateManagerPageState extends State<CertificateManagerPage> {
  final x509CertStorePlugin = X509CertStore();
  final TextEditingController _certificateController = TextEditingController();

  X509StoreName _selectedStore = X509StoreName.root;
  X509AddType _selectedAddType = X509AddType.addNew;
  bool _setTrusted = false;

  String _statusMessage = '';
  bool _isSuccess = false;
  bool _isLoading = false;
  String? _loadedFilename;
  String _errorCode = '';

  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();

  @override
  void dispose() {
    _certificateController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('X509 Certificate Manager'),
        backgroundColor: Theme.of(context).colorScheme.primaryContainer,
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: Form(
          key: _formKey,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // Certificate Storage Location
              const Text(
                "Certificate Store Location:",
                style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
              ),
              const SizedBox(height: 8),
              Wrap(
                spacing: 8.0,
                children: [
                  FilterChip(
                    label: const Text('ROOT Store'),
                    selected: _selectedStore == X509StoreName.root,
                    showCheckmark: false,
                    onSelected: (selected) {
                      if (selected) {
                        setState(() {
                          _selectedStore = X509StoreName.root;
                        });
                      }
                    },
                    avatar: Icon(
                      Icons.security,
                      size: 18,
                      color: _selectedStore == X509StoreName.root
                          ? Theme.of(context).colorScheme.onSecondaryContainer
                          : null,
                    ),
                  ),
                  FilterChip(
                    label: const Text('MY Store (Personal)'),
                    selected: _selectedStore == X509StoreName.my,
                    showCheckmark: false,
                    onSelected: (selected) {
                      if (selected) {
                        setState(() {
                          _selectedStore = X509StoreName.my;
                        });
                      }
                    },
                    avatar: Icon(
                      Icons.person,
                      size: 18,
                      color: _selectedStore == X509StoreName.my
                          ? Theme.of(context).colorScheme.onSecondaryContainer
                          : null,
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 16),

              // Certificate Addition Type
              const Text(
                "Certificate Addition Type:",
                style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
              ),
              const SizedBox(height: 8),
              Wrap(
                spacing: 8.0,
                runSpacing: 4.0,
                children: [
                  FilterChip(
                    label: const Text('Add New'),
                    selected: _selectedAddType == X509AddType.addNew,
                    showCheckmark: false,
                    onSelected: (selected) {
                      if (selected) {
                        setState(() {
                          _selectedAddType = X509AddType.addNew;
                        });
                      }
                    },
                    avatar: Icon(
                      Icons.new_label,
                      size: 18,
                      color: _selectedAddType == X509AddType.addNew
                          ? Theme.of(context).colorScheme.onSecondaryContainer
                          : null,
                    ),
                    tooltip: 'Only if not exists',
                  ),
                  FilterChip(
                    label: const Text('Add Newer'),
                    selected: _selectedAddType == X509AddType.addNewer,
                    showCheckmark: false,
                    onSelected: (selected) {
                      if (selected) {
                        setState(() {
                          _selectedAddType = X509AddType.addNewer;
                        });
                      }
                    },
                    avatar: Icon(
                      Icons.upgrade,
                      size: 18,
                      color: _selectedAddType == X509AddType.addNewer
                          ? Theme.of(context).colorScheme.onSecondaryContainer
                          : null,
                    ),
                    tooltip: 'Only if newer',
                  ),
                  FilterChip(
                    label: const Text('Replace'),
                    selected:
                        _selectedAddType == X509AddType.addReplaceExisting,
                    showCheckmark: false,
                    onSelected: (selected) {
                      if (selected) {
                        setState(() {
                          _selectedAddType = X509AddType.addReplaceExisting;
                        });
                      }
                    },
                    avatar: Icon(
                      Icons.published_with_changes,
                      size: 18,
                      color: _selectedAddType == X509AddType.addReplaceExisting
                          ? Theme.of(context).colorScheme.onSecondaryContainer
                          : null,
                    ),
                    tooltip: 'Overwrite existing',
                  ),
                ],
              ),
              const SizedBox(height: 16),

              // Trust Settings Section
              const Text(
                "Trust Settings:",
                style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
              ),
              const SizedBox(height: 8),
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Row(
                        children: [
                          Icon(
                            _setTrusted ? Icons.verified : Icons.security,
                            color: _setTrusted ? Colors.green : Colors.orange,
                            size: 20,
                          ),
                          const SizedBox(width: 8),
                          Text(
                            _setTrusted
                                ? 'Set as trusted certificate'
                                : 'Add certificate without trust settings',
                            style: const TextStyle(fontWeight: FontWeight.w500),
                          ),
                        ],
                      ),
                      const SizedBox(height: 8),
                      Text(
                        _setTrusted
                            ? 'The certificate will be added and marked as trusted for SSL, S/MIME, and code signing.'
                            : 'The certificate will be added to the store but won\'t be automatically trusted.',
                        style: TextStyle(
                          color: Theme.of(context).colorScheme.onSurfaceVariant,
                          fontSize: 12,
                        ),
                      ),
                      const SizedBox(height: 12),
                      Wrap(
                        spacing: 8.0,
                        children: [
                          FilterChip(
                            label: const Text('No Trust'),
                            selected: !_setTrusted,
                            showCheckmark: false,
                            onSelected: (selected) {
                              if (selected) {
                                setState(() {
                                  _setTrusted = false;
                                });
                              }
                            },
                            avatar: Icon(
                              Icons.security,
                              size: 18,
                              color: !_setTrusted
                                  ? Theme.of(context)
                                      .colorScheme
                                      .onSecondaryContainer
                                  : null,
                            ),
                          ),
                          FilterChip(
                            label: const Text('Set Trusted'),
                            selected: _setTrusted,
                            showCheckmark: false,
                            onSelected: (selected) {
                              if (selected) {
                                setState(() {
                                  _setTrusted = true;
                                });
                              }
                            },
                            avatar: Icon(
                              Icons.verified,
                              size: 18,
                              color: _setTrusted
                                  ? Theme.of(context)
                                      .colorScheme
                                      .onSecondaryContainer
                                  : null,
                            ),
                          ),
                        ],
                      ),
                      if (_setTrusted && _selectedStore == X509StoreName.root)
                        Container(
                          margin: const EdgeInsets.only(top: 8),
                          padding: const EdgeInsets.all(8),
                          decoration: BoxDecoration(
                            color: Colors.orange.withValues(alpha: 0.1),
                            borderRadius: BorderRadius.circular(4),
                            border: Border.all(
                                color: Colors.orange.withValues(alpha: 0.3)),
                          ),
                          child: Row(
                            children: [
                              const Icon(Icons.admin_panel_settings,
                                  size: 16, color: Colors.orange),
                              const SizedBox(width: 8),
                              Expanded(
                                child: Text(
                                  'Admin privileges required for system-wide trust settings',
                                  style: TextStyle(
                                    fontSize: 12,
                                    color: Colors.orange.shade700,
                                  ),
                                ),
                              ),
                            ],
                          ),
                        ),
                    ],
                  ),
                ),
              ),
              const SizedBox(height: 24),

              // Certificate Content Section
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  const Text(
                    "Certificate Content (Base64):",
                    style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
                  ),
                  Row(
                    children: [
                      if (_loadedFilename != null)
                        Chip(
                          label: Text(_loadedFilename!),
                          deleteIcon: const Icon(Icons.close, size: 16),
                          onDeleted: () {
                            setState(() {
                              _loadedFilename = null;
                            });
                          },
                        ),
                      const SizedBox(width: 8),
                      ElevatedButton.icon(
                        icon: const Icon(Icons.upload_file),
                        label: const Text('Load from File'),
                        onPressed: _loadCertificateFromFile,
                      ),
                      const SizedBox(width: 8),
                      IconButton(
                        icon: const Icon(Icons.paste),
                        tooltip: 'Paste from clipboard',
                        onPressed: _pasteFromClipboard,
                      ),
                      IconButton(
                        icon: const Icon(Icons.copy),
                        tooltip: 'Copy to clipboard',
                        onPressed: () {
                          if (_certificateController.text.isNotEmpty) {
                            Clipboard.setData(ClipboardData(
                                text: _certificateController.text));
                            ScaffoldMessenger.of(context).showSnackBar(
                              const SnackBar(
                                  content: Text('Copied to clipboard')),
                            );
                          }
                        },
                      ),
                      IconButton(
                        icon: const Icon(Icons.clear),
                        tooltip: 'Clear content',
                        onPressed: () {
                          setState(() {
                            _certificateController.clear();
                            _loadedFilename = null;
                          });
                        },
                      ),
                    ],
                  ),
                ],
              ),
              const SizedBox(height: 8),
              TextFormField(
                controller: _certificateController,
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  hintText: 'Paste base64 encoded certificate here...',
                ),
                maxLines: 6,
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please enter certificate content';
                  }
                  try {
                    base64Decode(value.replaceAll(RegExp(r'\s+'), ''));
                    return null;
                  } catch (e) {
                    return 'Invalid base64 format';
                  }
                },
              ),
              const SizedBox(height: 16),

              // Action Buttons
              Center(
                child: _isLoading
                    ? const CircularProgressIndicator()
                    : ElevatedButton.icon(
                        icon:
                            Icon(_setTrusted ? Icons.verified : Icons.security),
                        label: Text(_setTrusted
                            ? 'Add Trusted Certificate to Store'
                            : 'Add Certificate to Store'),
                        style: ElevatedButton.styleFrom(
                          padding: const EdgeInsets.symmetric(
                              horizontal: 24, vertical: 12),
                          textStyle: const TextStyle(fontSize: 16),
                          backgroundColor:
                              _setTrusted ? Colors.green.shade600 : null,
                          foregroundColor: _setTrusted ? Colors.white : null,
                        ),
                        onPressed: _addCertificate,
                      ),
              ),
              const SizedBox(height: 24),

              // Status Section
              if (_statusMessage.isNotEmpty)
                Container(
                  padding: const EdgeInsets.all(16),
                  decoration: BoxDecoration(
                    color: _isSuccess
                        ? Colors.green.withValues(alpha: 0.1)
                        : Colors.red.withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(8),
                    border: Border.all(
                      color: _isSuccess ? Colors.green : Colors.red,
                    ),
                  ),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Row(
                        children: [
                          Icon(
                            _isSuccess ? Icons.check_circle : Icons.error,
                            color: _isSuccess ? Colors.green : Colors.red,
                          ),
                          const SizedBox(width: 8),
                          Text(
                            _isSuccess ? "Success" : "Error",
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                              fontSize: 18,
                              color: _isSuccess ? Colors.green : Colors.red,
                            ),
                          ),
                        ],
                      ),
                      const SizedBox(height: 8),
                      Text(_statusMessage),
                      if (!_isSuccess && _errorCode.isNotEmpty)
                        Padding(
                          padding: const EdgeInsets.only(top: 8),
                          child: Text(
                            "Error Code: $_errorCode",
                            style: const TextStyle(
                              fontStyle: FontStyle.italic,
                            ),
                          ),
                        ),
                    ],
                  ),
                ),

              // Sample certificate section
              const SizedBox(height: 24),
              ExpansionTile(
                title: const Text("Sample Certificate"),
                subtitle: const Text("Use this sample certificate for testing"),
                expandedCrossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Padding(
                    padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                    child: Text(
                      "This is a sample self-signed certificate for testing purposes only.",
                      style: TextStyle(fontStyle: FontStyle.italic),
                    ),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: OutlinedButton.icon(
                      icon: const Icon(Icons.content_copy),
                      label: const Text("Copy Sample Certificate"),
                      onPressed: () {
                        _certificateController.text = _getSampleCertificate();
                        ScaffoldMessenger.of(context).showSnackBar(
                          const SnackBar(
                              content: Text('Sample certificate loaded')),
                        );
                      },
                    ),
                  ),
                ],
              ),

              // Debug Info (for developers)
              if (_isSuccess == false && _errorCode.isNotEmpty)
                ExpansionTile(
                  title: const Text("Debug Information"),
                  children: [
                    Padding(
                      padding: const EdgeInsets.all(16.0),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text("Error Code: $_errorCode"),
                          const SizedBox(height: 8),
                          const Text("Common Error Codes:"),
                          const SizedBox(height: 4),
                          const Text("• 1223: User canceled the operation"),
                          const Text(
                              "• 2148081669: Certificate already exists in the store"),
                        ],
                      ),
                    ),
                  ],
                ),
            ],
          ),
        ),
      ),
    );
  }

  Future<void> _pasteFromClipboard() async {
    final data = await Clipboard.getData(Clipboard.kTextPlain);
    if (data != null && data.text != null) {
      setState(() {
        _certificateController.text = data.text!;
        _loadedFilename = null;
      });
    }
  }

  String _getSampleCertificate() {
    return "MIIDPzCCAiegAwIBAgIUTaCSxPYnAxNeYiWCTKJhTEuLlrUwDQYJKoZIhvcNAQELBQAwGDEWMBQGA1UEAwwNbXljb21wYW55LmNvbTAeFw0yNTA0MTYxMzAyNThaFw0yNjA0MTYxMzAyNThaMBgxFjAUBgNVBAMMDW15Y29tcGFueS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC/FBH6gVh0bd3s1j1sJ5VvNVBPCTlX8pyvE5TzSyY2wbu8FB+qwjZVUqhkJM4eTmlyn5oR1ZJrzxBxX3t2h2Mqd+EePZ6d1c1yb/FhnvxgUQINUI1PBnQfqHq//5e0NS2OHk3nLiGM01iLPL71E8PAjZnKxtjdQfGoxBvF5DnUtzk0ZmrUdpHSuJA2jzru0D70Yqi6BxLX+P9dDIhR/+Ym4CBewmh4bsBl0Cq9DzR1uajs860U7Y9nFK4JGQOPsQPkgNNXDaXF9OJiQx+dxuKGUcdTqmwy4bsiwxTLhRZQxTaG7oFTgepB+jvCMU4eAE+FXSETJneQkB+KjdJnRhEZAgMBAAGjgYAwfjAdBgNVHQ4EFgQUMR37QYIjV14yf5K1M21NwL6zANEwHwYDVR0jBBgwFoAUMR37QYIjV14yf5K1M21NwL6zANEwDwYDVR0TAQH/BAUwAwEB/zArBgNVHREEJDAigg1teWNvbXBhbnkuY29tghF3d3cubXljb21wYW55LmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAYMBzjiPhLwuJUhJwM9mzGL6FP81Gyk7wenMZQF1UC54Lw/mZCzCWJW4fC398j4OVxHfU/aenkAVs0s9xu7q/Z+iol6iVAen6yHIM6RyzrKBLZfXU/15lH5wTjM+EUUEutzbxS80Kb2hBO/e3ITqq0qcbHLD0S6aM67KYbpQ/g6MbNNuMB6Uw0aC2EVknfP8JqSjMfY0w8n/y8Bsz1JQZa/zLsrQr95i1FaSayB94AB9yWrf2XBqS9BbX9BXr2YqKST3lLzUMbsvUpN9IDtRULFeD3LcNfMXTMYHInggf7trTkj2YRVKr6acLhggyqFi1I/feXuXtENOOvq0RcLK1bg==";
  }

  Future<void> _loadCertificateFromFile() async {
    try {
      FilePickerResult? result = await FilePicker.platform.pickFiles(
        type: FileType.custom,
        allowedExtensions: ['crt', 'pem', 'cer', 'der'],
      );

      if (result != null) {
        File file = File(result.files.single.path!);
        Uint8List bytes = await file.readAsBytes();
        String base64Cert = base64Encode(bytes);

        setState(() {
          _certificateController.text = base64Cert;
          _loadedFilename = result.files.single.name;
        });
      }
    } catch (e) {
      setState(() {
        _statusMessage = 'Error loading certificate file: $e';
        _isSuccess = false;
        _errorCode = 'FILE_READ_ERROR';
      });
    }
  }

  void _handleCertificateResult(X509ResValue result) {
    setState(() {
      _isLoading = false;
      _isSuccess = result.isOk;
      _statusMessage = result.msg;
      _errorCode = result.code;

      // Add additional context to common error codes
      if (result.hasError(X509ErrorCode.alreadyExist)) {
        _statusMessage =
            "Certificate already exists in the store. To replace it, use the 'Replace' mode.";
      } else if (result.hasError(X509ErrorCode.canceled)) {
        _statusMessage = "User canceled the certificate addition process.";
      }
    });
  }

  Future<void> _addCertificate() async {
    if (!_formKey.currentState!.validate()) {
      return;
    }

    setState(() {
      _isLoading = true;
      _statusMessage = '';
    });

    try {
      final certificateBase64 =
          _certificateController.text.replaceAll(RegExp(r'\s+'), '');

      final result = await x509CertStorePlugin.addCertificate(
        storeName: _selectedStore,
        certificateBase64: certificateBase64,
        addType: _selectedAddType,
        setTrusted: _setTrusted,
      );

      _handleCertificateResult(result);
    } catch (e) {
      setState(() {
        _isLoading = false;
        _isSuccess = false;
        _statusMessage = "Exception during addCertificate: $e";
        _errorCode = 'EXCEPTION';
      });
    }
  }
}
1
likes
160
points
303
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for Windows and macOS desktop applications that enables adding X.509 certificates to the local certificate store with trust settings support.

Repository (GitHub)
View/report issues

Topics

#x509 #keychain #certificate #add-certificate

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on x509_cert_store