Skip to content
API Documentation
BastionGuard™
BastionGuard::Activation Namespace Reference

Namespaces

namespace  anonymous_namespace{activation.cpp}

Classes

struct  ActivationStatus

Functions

std::string activation_engine_id ()
std::string machine_id ()
std::string motherboard_serial ()
std::string activation_key_path ()
ActivationStatus validate_key (const std::string &raw_key)
ActivationStatus validate_installed_key ()
bool save_key_if_valid (const std::string &raw_key, std::string &error_message)

Function Documentation

◆ activation_engine_id()

std::string BastionGuard::Activation::activation_engine_id ( )

Definition at line 675 of file activation.cpp.

675 {
676 return std::string(kActivationEngineId);
677}

References activation_engine_id().

Referenced by activation_engine_id(), and validate_key().

◆ activation_key_path()

std::string BastionGuard::Activation::activation_key_path ( )

Definition at line 710 of file activation.cpp.

710 {
711 const std::string system_path = system_activation_key_path();
712 std::error_code ec;
713 if (std::filesystem::exists(system_path, ec) && !ec) {
714 return system_path;
715 }
716
717 if (is_running_as_root()) {
718 return system_path;
719 }
720
721 return user_activation_key_path();
722}

References activation_key_path().

Referenced by activation_key_path().

◆ machine_id()

std::string BastionGuard::Activation::machine_id ( )

Definition at line 679 of file activation.cpp.

679 {
680 const std::string raw_machine_id = read_first_existing_file({
681 "/etc/machine-id",
682 "/var/lib/dbus/machine-id"
683 });
684
685 const std::string cleaned = compact_hex_machine_id(raw_machine_id);
686 if (cleaned.size() == 32) {
687 // Use the real Linux machine-id exactly as requested: this is the
688 // identifier shown in About → Activation and stored inside keys.
689 return cleaned;
690 }
691
692 // Fallback only for unusual systems where /etc/machine-id is unavailable
693 // or malformed. This keeps activation usable without changing the public UI.
694 return legacy_application_machine_id();
695}

References machine_id().

Referenced by machine_id(), and validate_key().

◆ motherboard_serial()

std::string BastionGuard::Activation::motherboard_serial ( )

Definition at line 697 of file activation.cpp.

697 {
698 const std::string serial = read_first_valid_serial({
699 "/sys/class/dmi/id/board_serial",
700 "/sys/devices/virtual/dmi/id/board_serial",
701 "/sys/class/dmi/id/product_serial",
702 "/sys/devices/virtual/dmi/id/product_serial",
703 "/sys/class/dmi/id/product_uuid",
704 "/sys/devices/virtual/dmi/id/product_uuid"
705 });
706
707 return serial;
708}

References motherboard_serial().

Referenced by motherboard_serial(), and validate_key().

◆ save_key_if_valid()

bool BastionGuard::Activation::save_key_if_valid ( const std::string & raw_key,
std::string & error_message )

Definition at line 974 of file activation.cpp.

974 {
975 const ActivationStatus status = validate_key(raw_key);
976 if (!status.active) {
977 error_message = status.message;
978 return false;
979 }
980
981 const std::string target_path = preferred_activation_key_path_for_save();
982 const std::string dir = std::filesystem::path(target_path).parent_path().string();
983
984 std::error_code ec;
985 std::filesystem::create_directories(dir, ec);
986 if (ec) {
987 error_message = "Cannot create activation directory '" + dir + "': " + ec.message();
988 return false;
989 }
990
991 std::ofstream output(target_path, std::ios::binary | std::ios::trunc);
992 if (!output.is_open()) {
993 error_message = "Cannot write activation key file: " + target_path;
994 if (!is_running_as_root() && target_path.rfind("/etc/", 0) == 0) {
995 error_message += " Run the application as root/pkexec.";
996 }
997 return false;
998 }
999
1000 output << extract_activation_key(raw_key) << "\n";
1001 output.close();
1002
1003 if (!output) {
1004 error_message = "Activation key file was opened but could not be written completely: " + target_path;
1005 return false;
1006 }
1007
1008 std::error_code verify_ec;
1009 if (!std::filesystem::exists(target_path, verify_ec) || verify_ec) {
1010 error_message = "Activation key was not created at: " + target_path;
1011 if (verify_ec) error_message += " (" + verify_ec.message() + ")";
1012 return false;
1013 }
1014
1015 const ActivationStatus saved_status = validate_key(read_file_all(target_path));
1016 if (!saved_status.active) {
1017 error_message = "Activation key was written but could not be read back as valid: " + saved_status.message;
1018 return false;
1019 }
1020
1021 error_message.clear();
1022 return true;
1023}
std::string extract_activation_key(const std::string &raw)
ActivationStatus validate_key(const std::string &raw_key)

References BastionGuard::Activation::ActivationStatus::active, BastionGuard::Activation::ActivationStatus::message, save_key_if_valid(), and validate_key().

Referenced by save_key_if_valid().

◆ validate_installed_key()

ActivationStatus BastionGuard::Activation::validate_installed_key ( )

Definition at line 950 of file activation.cpp.

950 {
951 const std::string system_path = system_activation_key_path();
952 const std::string user_path = user_activation_key_path();
953
954 std::error_code ec;
955 if (std::filesystem::exists(system_path, ec) && !ec) {
956 ActivationStatus system_status = validate_key(read_file_all(system_path));
957 if (system_status.active) {
958 return system_status;
959 }
960
961 if (std::filesystem::exists(user_path, ec) && !ec) {
962 ActivationStatus user_status = validate_key(read_file_all(user_path));
963 if (user_status.active) {
964 return user_status;
965 }
966 }
967
968 return system_status;
969 }
970
971 return validate_key(read_file_all(user_path));
972}

References BastionGuard::Activation::ActivationStatus::active, validate_installed_key(), and validate_key().

Referenced by validate_installed_key().

◆ validate_key()

ActivationStatus BastionGuard::Activation::validate_key ( const std::string & raw_key)

Definition at line 724 of file activation.cpp.

724 {
725 ActivationStatus status;
726 status.machine_id = machine_id();
728
729 const std::string key = extract_activation_key(raw_key);
730 if (key.empty()) {
731 status.message = "No activation key installed.";
732 return status;
733 }
734
735 const auto apply_common_field_checks = [&](const std::string& product,
736 const std::string& key_machine_raw,
737 const std::string& key_motherboard_raw,
738 const std::string& owner,
739 const std::string& edition,
740 const std::string& expires) -> bool {
741 if (product != kProductId) {
742 status.message = "Activation key is for a different product.";
743 return false;
744 }
745
746 if (key_machine_raw.empty()) {
747 status.message = "Activation key does not contain a Machine ID.";
748 return false;
749 }
750
751 if (key_motherboard_raw.empty()) {
752 status.message = "Activation key does not contain a motherboard serial.";
753 return false;
754 }
755
756 if (status.motherboard_serial.empty()) {
757 status.message = "Motherboard serial could not be read on this computer.";
758 return false;
759 }
760
761 const std::string key_machine = compact_hex_machine_id(key_machine_raw);
762 const std::string current_machine = compact_hex_machine_id(status.machine_id);
763 const std::string legacy_machine = compact_hex_machine_id(legacy_application_machine_id());
764
765 if (key_machine != current_machine && key_machine != legacy_machine) {
766 status.message = "Activation key is for a different machine-id.";
767 return false;
768 }
769
770 const std::string key_motherboard = canonical_serial(key_motherboard_raw);
771 const std::string current_motherboard = canonical_serial(status.motherboard_serial);
772
773 if (key_motherboard != current_motherboard) {
774 status.message = "Activation key is for a different motherboard serial.";
775 return false;
776 }
777
778 status.owner = owner.empty() ? "Donation supporter" : owner;
779 status.edition = edition.empty() ? "Donation" : edition;
780 status.expires = expires.empty() ? "never" : lowercase(expires);
781
782 if (!is_valid_expiry(status.expires)) {
783 status.expired = true;
784 status.message = "Activation key contains an invalid expiry date.";
785 return false;
786 }
787
788 if (has_expired(status.expires)) {
789 status.expired = true;
790 status.message = "Activation key has expired.";
791 return false;
792 }
793
794 status.active = true;
795 status.message = "Activation is valid.";
796 return true;
797 };
798
799 // Preferred clean key format: BGRD3. This avoids SHA-based signing and uses
800 // a simple owner-only donation code calculated from product, engine,
801 // /etc/machine-id, motherboard serial, edition, expiry, and the private
802 // owner secret. The visible owner name is informational and does not affect
803 // the code, which avoids UTF-8/locale copy-paste mismatches.
804 if (key.find(std::string(kKeyPrefixV3)) != std::string::npos) {
805 const auto fields = parse_key_block(key);
806 const std::string code = normalize_activation_code(field_or_empty(fields, "CODE"));
807 const std::string product = trim(field_or_empty(fields, "PRODUCT"));
808 const std::string engine = normalize_engine_id(field_or_empty(fields, "ENGINE"));
809 const std::string machine = compact_hex_machine_id(field_or_empty(fields, "MACHINE"));
810 const std::string motherboard = canonical_serial(field_or_empty(fields, "MOTHERBOARD"));
811 const std::string owner = trim(field_or_empty(fields, "OWNER"));
812 const std::string edition = trim(field_or_empty(fields, "EDITION"));
813 const std::string expires = lowercase(trim(field_or_empty(fields, "EXPIRES")));
814
815 if (field_or_empty(fields, "FORMAT") != std::string(kKeyPrefixV3)) {
816 status.message = "Invalid BGRD3 activation key: BGRD3 format line is missing.";
817 return status;
818 }
819
820 if (code.size() != 25) {
821 status.message = "Invalid BGRD3 activation key: CODE must contain 25 letters/numbers.";
822 return status;
823 }
824
825 if (engine.empty()) {
826 status.message = "Invalid BGRD3 activation key: ENGINE field is missing.";
827 return status;
828 }
829
830 const std::string current_engine = normalize_engine_id(activation_engine_id());
831 if (normalize_engine_id(engine) != current_engine) {
832 status.message = "Activation engine mismatch. App engine is " + current_engine +
833 " but key engine is " + engine +
834 ". Rebuild and reinstall the app and generate the keygen from the same package.";
835 return status;
836 }
837
838 const std::string canonical_payload = code_payload_v3(fields);
839 const std::string expected_code = normalize_activation_code(generate_activation_code_v3(canonical_payload));
840 if (code != expected_code) {
841 status.message = "BGRD3 activation code is not valid. The key was not saved. Generate the key again with the matching keygen and copy the whole BEGIN/END block.";
842 return status;
843 }
844
845 apply_common_field_checks(product, machine, motherboard, owner, edition, expires);
846 return status;
847 }
848
849 // Legacy clean key format: multiline BGRD2 block. The key explicitly
850 // carries the activation engine so mismatched app/keygen builds are
851 // diagnosed before signature validation.
852 if (key.find(std::string(kKeyPrefixV2)) != std::string::npos) {
853 const auto fields = parse_key_block(key);
854 const std::string signature = uppercase(trim(field_or_empty(fields, "SIGNATURE")));
855 const std::string product = trim(field_or_empty(fields, "PRODUCT"));
856 const std::string engine = normalize_engine_id(field_or_empty(fields, "ENGINE"));
857 const std::string machine = compact_hex_machine_id(field_or_empty(fields, "MACHINE"));
858 const std::string motherboard = canonical_serial(field_or_empty(fields, "MOTHERBOARD"));
859 const std::string owner = trim(field_or_empty(fields, "OWNER"));
860 const std::string edition = trim(field_or_empty(fields, "EDITION"));
861 const std::string expires = lowercase(trim(field_or_empty(fields, "EXPIRES")));
862
863 if (signature.size() != 40) {
864 status.message = "Invalid BGRD2 activation key: SIGNATURE must contain 40 hexadecimal characters.";
865 return status;
866 }
867 for (unsigned char c : signature) {
868 if (std::isxdigit(c) == 0) {
869 status.message = "Invalid BGRD2 activation key: SIGNATURE contains a non-hexadecimal character.";
870 return status;
871 }
872 }
873
874 if (engine.empty()) {
875 status.message = "Invalid BGRD2 activation key: ENGINE field is missing.";
876 return status;
877 }
878
879 const std::string current_engine = normalize_engine_id(activation_engine_id());
880 if (normalize_engine_id(engine) != current_engine) {
881 status.message = "Activation engine mismatch. App engine is " + current_engine +
882 " but key engine is " + engine +
883 ". Rebuild and reinstall the app and generate the keygen from the same package.";
884 return status;
885 }
886
887 const std::string canonical_payload = canonical_v2_payload(fields);
888 const std::string expected_signature = sign_payload_v2(canonical_payload);
889 if (signature != expected_signature) {
890 status.message = "BGRD2 activation key signature is not valid. The key was not saved. Generate the key again with the matching keygen and copy the whole BEGIN/END block.";
891 return status;
892 }
893
894 apply_common_field_checks(product, machine, motherboard, owner, edition, expires);
895 return status;
896 }
897
898 // Legacy BGRD1 one-line key support.
899 const std::string prefix = std::string(kKeyPrefixV1) + ".";
900 if (key.rfind(prefix, 0) != 0) {
901 status.message = "Invalid activation key prefix. Paste a BGRD3 activation block generated by the matching keygen.";
902 return status;
903 }
904
905 const auto first_dot = key.find('.', prefix.size());
906 if (first_dot == std::string::npos) {
907 status.message = "Malformed legacy activation key.";
908 return status;
909 }
910
911 const std::string payload_b64 = key.substr(prefix.size(), first_dot - prefix.size());
912 const std::string signature = uppercase(key.substr(first_dot + 1));
913
914 if (signature.size() != 40) {
915 status.message = "Invalid legacy activation key signature length.";
916 return status;
917 }
918
919 const auto decoded_payload = base64url_decode(payload_b64);
920 if (!decoded_payload.has_value()) {
921 status.message = "Legacy activation key payload is not valid base64url.";
922 return status;
923 }
924
925 const std::string expected_signature = sign_payload_v1(*decoded_payload);
926 if (signature != expected_signature) {
927 status.message = "Legacy activation key signature is not valid. Generate a new BGRD3 key with the matching keygen.";
928 return status;
929 }
930
931 const auto fields = parse_payload(*decoded_payload);
932 const auto product_it = fields.find("product");
933 const auto machine_it = fields.find("machine");
934 const auto motherboard_it = fields.find("motherboard");
935 const auto owner_it = fields.find("owner");
936 const auto edition_it = fields.find("edition");
937 const auto expires_it = fields.find("expires");
938
939 apply_common_field_checks(
940 product_it != fields.end() ? product_it->second : std::string{},
941 machine_it != fields.end() ? machine_it->second : std::string{},
942 motherboard_it != fields.end() ? motherboard_it->second : std::string{},
943 owner_it != fields.end() ? owner_it->second : std::string{},
944 edition_it != fields.end() ? edition_it->second : std::string{},
945 expires_it != fields.end() ? expires_it->second : std::string{}
946 );
947 return status;
948}
std::string code_payload_v3(const std::map< std::string, std::string > &fields)
std::map< std::string, std::string > parse_key_block(const std::string &key)
std::map< std::string, std::string > parse_payload(const std::string &payload)
std::string generate_activation_code_v3(const std::string &canonical_payload)
std::string canonical_v2_payload(const std::map< std::string, std::string > &fields)
std::string normalize_engine_id(const std::string &value)
std::optional< std::string > base64url_decode(const std::string &input)
std::string sign_payload_v2(const std::string &canonical_payload)
std::string field_or_empty(const std::map< std::string, std::string > &fields, const std::string &name)
std::string sign_payload_v1(const std::string &payload)
std::string compact_hex_machine_id(const std::string &raw)
std::string motherboard_serial()
std::string activation_engine_id()

References activation_engine_id(), BastionGuard::Activation::ActivationStatus::active, BastionGuard::Activation::ActivationStatus::edition, BastionGuard::Activation::ActivationStatus::expired, BastionGuard::Activation::ActivationStatus::expires, BastionGuard::Activation::ActivationStatus::machine_id, machine_id(), BastionGuard::Activation::ActivationStatus::message, BastionGuard::Activation::ActivationStatus::motherboard_serial, motherboard_serial(), BastionGuard::Activation::ActivationStatus::owner, and validate_key().

Referenced by save_key_if_valid(), validate_installed_key(), and validate_key().