smart-id-ruby-client 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/README.md +38 -1
  4. data/certificates/demo/anchors/TEST_of_EE_Certification_Centre_Root_CA.pem.crt +24 -0
  5. data/certificates/demo/anchors/TEST_of_SK_ID_Solutions_ROOT_G1E.pem.crt +17 -0
  6. data/certificates/demo/anchors/TEST_of_SK_ID_Solutions_ROOT_G1R.pem.crt +33 -0
  7. data/certificates/demo/intermediates/TEST_of_SK_ID_Solutions_EID-NQ_2021E.pem.crt +22 -0
  8. data/certificates/demo/intermediates/TEST_of_SK_ID_Solutions_EID-Q_2024E.pem.crt +23 -0
  9. data/certificates/production/anchors/EE_Certification_Centre_Root_CA.pem.crt +24 -0
  10. data/certificates/production/anchors/SK_ID_Solutions_ROOT_G1E.pem.crt +17 -0
  11. data/certificates/production/anchors/SK_ID_Solutions_ROOT_G1R.pem.crt +33 -0
  12. data/certificates/production/intermediates/EID-SK_2016.pem.crt +39 -0
  13. data/certificates/production/intermediates/NQ-SK_2016.pem.crt +37 -0
  14. data/certificates/production/intermediates/SK_ID_Solutions_EID-NQ_2021E.pem.crt +21 -0
  15. data/certificates/production/intermediates/SK_ID_Solutions_EID-NQ_2021R.pem.crt +38 -0
  16. data/certificates/production/intermediates/SK_ID_Solutions_EID-Q_2024E.pem.crt +22 -0
  17. data/certificates/production/intermediates/SK_ID_Solutions_EID-Q_2024R.pem.crt +39 -0
  18. data/lib/smart_id_ruby/configuration.rb +2 -0
  19. data/lib/smart_id_ruby/errors/network_timeout_error.rb +17 -0
  20. data/lib/smart_id_ruby/errors.rb +1 -0
  21. data/lib/smart_id_ruby/rest/connector.rb +48 -18
  22. data/lib/smart_id_ruby/validation/certificate_validator.rb +6 -2
  23. data/lib/smart_id_ruby/validation/signature_response_validator.rb +23 -3
  24. data/lib/smart_id_ruby/validation/signature_value_validator.rb +5 -2
  25. data/lib/smart_id_ruby/validation/trusted_ca_cert_store.rb +126 -0
  26. data/lib/smart_id_ruby/version.rb +1 -1
  27. metadata +17 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b91db6fa6f6c9adfc7ba1bc61e178f26b203d3a87517986ebb7528810fa4b506
4
- data.tar.gz: 13f4360d2baf919d80c2d471383a914befc0bc25faddddfd713dee5c9b14c215
3
+ metadata.gz: f2557ad1f4f1f45074dbab16c745a82531b13e05f286493c069c6c4503be6eac
4
+ data.tar.gz: 0b997d4e9d17560ad2162b8ca5771505ac907124f8f18cdbbcfa9e9da47e551d
5
5
  SHA512:
6
- metadata.gz: 28df6c16e3c0d265de6f09f7b438e1b506dbbd03de0bfbd342a0516c1569019c640fe00a675ecb29515d3d5570984f67e9c929ad2f47c17568f390bf89495c3d
7
- data.tar.gz: f9225bed6e6cfdf290fa1da005a62a158c06667afe787f21cee437a17d370e9d13ed1ad1e09a8008b9840a0074c256213127abea6bc1589b383cc4c7ef4df9cb
6
+ metadata.gz: 26869b9813b1169874a51c635daf5a0d060f07afafe94ae25b659c27e6a073e58b486259e5478e7fee680786d63f6f8a35ea17c6a921315a0ad34743922d124a
7
+ data.tar.gz: a14a8f2f2a347950fe714e12b0e053c80297b2fe4813d12f57427613219f772a5847bfcb142d2096d1c60e5c7ec371c400c5f1d9742f8fd2a5a372b13ecd4171
data/CHANGELOG.md CHANGED
@@ -1,3 +1,34 @@
1
+ ## [0.2.0] - 2026-09-14
2
+
3
+ - **Bundled SK ID Solutions CA certificates**, matching what `smart-id-java-client` ships,
4
+ so trust-chain validation works without any setup. Signer certificates chain to SK's own
5
+ eID CAs, which are not present in any operating system CA bundle — before this, chain
6
+ validation failed for every real Smart-ID certificate. New entry points:
7
+ `TrustedCaCertStore.production` (live CAs), `TrustedCaCertStore.demo` (sid.demo.sk.ee),
8
+ `.default` (follows configuration), plus `.from_directory`, `.from_pkcs12` and
9
+ `.from_certificates` for supplying your own.
10
+ - **`CertificateValidator` now defaults to the bundled store** for the configured
11
+ environment instead of the system CA store alone. Pass `trusted_ca_cert_store: nil` for
12
+ the old behaviour. This is the default `SignatureResponseValidator` uses, so signature
13
+ responses validate out of the box.
14
+ - New configuration key `trusted_ca_environment` (`:production` by default, `:demo` for the
15
+ demo service).
16
+ - **Transport errors no longer crash the client.** `Rest::Connector` read
17
+ `error.response[:status]` unconditionally, but a `Faraday::TimeoutError` — which is a
18
+ `Faraday::ServerError` — carries no response, so a long-poll session status request that
19
+ timed out raised `NoMethodError: undefined method '[]' for nil` instead of something
20
+ catchable. Timeouts now raise the new `Errors::NetworkTimeoutError` (distinct from
21
+ `SessionTimeoutError`, which means the user did not respond), and a dropped connection
22
+ raises `Errors::ResponseError` rather than leaking a raw Faraday exception.
23
+ - **ADVANCED signature certificates are checked properly.** The certificate purpose
24
+ validation returned early for ADVANCED, so any Non-Repudiation certificate from any CA in
25
+ the trust store was accepted. It now requires the non-qualified Smart-ID policy OIDs
26
+ (`1.3.6.1.4.1.10015.17.1`, `0.4.0.2042.1.1`), matching `smart-id-java-client`.
27
+ - **`SignatureValueValidator` no longer rewrites its own errors.** A blanket
28
+ `rescue StandardError` swallowed the `RequestSetupError`s raised for missing parameters
29
+ and reported them as "Signature value validation failed"; gem errors are re-raised as-is.
30
+ Found by giving the class its first specs.
31
+
1
32
  ## [Unreleased]
2
33
 
3
34
  - Implemented trust-chain certificate validation parity with `TrustedCaCertStore` and `CertificateValidator`, and wired chain validation into signature/certificate-choice/authentication certificate validation flows.
data/README.md CHANGED
@@ -384,7 +384,44 @@ client.configured_connection = Faraday.new(url: client.host_url) do |f|
384
384
  end
385
385
  ```
386
386
 
387
- ### Trusting custom CA certificates
387
+ ### Trusting Smart-ID signer certificates
388
+
389
+ Smart-ID signer and authentication certificates chain to SK ID Solutions' own eID CAs,
390
+ which are **not** in any operating system CA bundle — that store holds TLS roots. The gem
391
+ therefore ships SK's CA certificates for both environments, and the validators use them by
392
+ default, so nothing needs configuring in production:
393
+
394
+ ```ruby
395
+ SmartIdRuby::Validation::SignatureResponseValidator.new.validate(session_status, "QUALIFIED")
396
+ ```
397
+
398
+ Against the demo service (`sid.demo.sk.ee`), point the validators at the demo CAs:
399
+
400
+ ```ruby
401
+ SmartIdRuby.configure do |config|
402
+ config.trusted_ca_environment = :demo # :production (default) or :demo
403
+ end
404
+ ```
405
+
406
+ The two sets are never merged: a demo certificate is rejected by the production store and
407
+ vice versa. To supply your own CA certificates instead — an internal truststore, or a CA
408
+ SK adds before this gem is updated:
409
+
410
+ ```ruby
411
+ store = SmartIdRuby::Validation::TrustedCaCertStore.from_pkcs12("truststore.p12", "changeit")
412
+ # or .from_directory("/etc/smart-id/ca") -- 'anchors/' + 'intermediates/', or a flat directory
413
+ # or .from_certificates([cert, ...]) -- self-signed ones become the trust anchors
414
+
415
+ validator = SmartIdRuby::Validation::CertificateValidator.new(trusted_ca_cert_store: store)
416
+ SmartIdRuby::Validation::SignatureResponseValidator.new(certificate_validator: validator)
417
+ ```
418
+
419
+ The bundled certificates are in `certificates/production` and `certificates/demo`
420
+ (`anchors/` are the self-signed roots, `intermediates/` the issuing CAs).
421
+
422
+ ### Trusting custom TLS CA certificates
423
+
424
+ This is about the TLS connection to the Smart-ID API, not about signer certificates.
388
425
 
389
426
  ```ruby
390
427
  cert_store = OpenSSL::X509::Store.new
@@ -0,0 +1,24 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIEEzCCAvugAwIBAgIQc/jtqiMEFERMtVvsSsH7sjANBgkqhkiG9w0BAQUFADB9
3
+ MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1
4
+ czEwMC4GA1UEAwwnVEVTVCBvZiBFRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBSb290
5
+ IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwIhgPMjAxMDEwMDcxMjM0NTZa
6
+ GA8yMDMwMTIxNzIzNTk1OVowfTELMAkGA1UEBhMCRUUxIjAgBgNVBAoMGUFTIFNl
7
+ cnRpZml0c2VlcmltaXNrZXNrdXMxMDAuBgNVBAMMJ1RFU1Qgb2YgRUUgQ2VydGlm
8
+ aWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYGCSqGSIb3DQEJARYJcGtpQHNrLmVl
9
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1gGpqCtDmNNEHUjC8LXq
10
+ xRdC1kpjDgkzOTxQynzDxw/xCjy5hhyG3xX4RPrW9Z6k5ZNTNS+xzrZgQ9m5U6uM
11
+ ywYpx3F3DVgbdQLd8DsLmuVOz02k/TwoRt1uP6xtV9qG0HsGvN81q3HvPR/zKtA7
12
+ MmNZuwuDFQwsguKgDR2Jfk44eKmLfyzvh+Xe6Cr5+zRnsVYwMA9bgBaOZMv1TwTT
13
+ VNi9H1ltK32Z+IhUX8W5f2qVP33R1wWCKapK1qTX/baXFsBJj++F8I8R6+gSyC3D
14
+ kV5N/pOlWPzZYx+kHRkRe/oddURA9InJwojbnsH+zJOa2VrNKakNv2HnuYCIonzu
15
+ pwIDAQABo4GKMIGHMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G
16
+ A1UdDgQWBBS1NAqdpS8QxechDr7EsWVHGwN2/jBFBgNVHSUEPjA8BggrBgEFBQcD
17
+ AgYIKwYBBQUHAwEGCCsGAQUFBwMDBggrBgEFBQcDBAYIKwYBBQUHAwgGCCsGAQUF
18
+ BwMJMA0GCSqGSIb3DQEBBQUAA4IBAQAj72VtxIw6p5lqeNmWoQ48j8HnUBM+6mI0
19
+ I+VkQr0EfQhfmQ5KFaZwnIqxWrEPaxRjYwV0xKa1AixVpFOb1j+XuVmgf7khxXTy
20
+ Bmd8JRLwl7teCkD1SDnU/yHmwY7MV9FbFBd+5XK4teHVvEVRsJ1oFwgcxVhyoviR
21
+ SnbIPaOvk+0nxKClrlS6NW5TWZ+yG55z8OCESHaL6JcimkLFjRjSsQDWIEtDvP4S
22
+ tH3vIMUPPiKdiNkGjVLSdChwkW3z+m0EvAjyD9rnGCmjeEm5diLFu7VMNVqupsbZ
23
+ SfDzzBLc5+6TqgQTOG7GaZk2diMkn03iLdHGFrh8ML+mXG9SjEPI
24
+ -----END CERTIFICATE-----
@@ -0,0 +1,17 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIICxDCCAiagAwIBAgIQGjWemJjC5ORg6CkyNQ5DzTAKBggqhkjOPQQDBDBuMQsw
3
+ CQYDVQQGEwJFRTEbMBkGA1UECgwSU0sgSUQgU29sdXRpb25zIEFTMRcwFQYDVQRh
4
+ DA5OVFJFRS0xMDc0NzAxMzEpMCcGA1UEAwwgVEVTVCBvZiBTSyBJRCBTb2x1dGlv
5
+ bnMgUk9PVCBHMUUwHhcNMjEwNzA5MTA0NzE0WhcNNDEwNzA5MTA0NzE0WjBuMQsw
6
+ CQYDVQQGEwJFRTEbMBkGA1UECgwSU0sgSUQgU29sdXRpb25zIEFTMRcwFQYDVQRh
7
+ DA5OVFJFRS0xMDc0NzAxMzEpMCcGA1UEAwwgVEVTVCBvZiBTSyBJRCBTb2x1dGlv
8
+ bnMgUk9PVCBHMUUwgZswEAYHKoZIzj0CAQYFK4EEACMDgYYABACGx6ye24WAORL1
9
+ 8N0SquoI3TTJ3dd2EcZLs+wZY0XWYzPa0S4o8BKZQTCDbXz9O2x94hpdAjZ4S3Q2
10
+ N7DAvQ0FfAHmM2JotR4UnYvxYv4JxJHpoRvrQoXOXdqO/wMymiPKTXHPFQz6nxxa
11
+ ORjy8xsrQeIdrTLj3c+HDVBRA5yE/IXed6NjMGEwDwYDVR0TAQH/BAUwAwEB/zAO
12
+ BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFOIc3mPcvviEfgE7LkuAseF/1fHmMB8G
13
+ A1UdIwQYMBaAFOIc3mPcvviEfgE7LkuAseF/1fHmMAoGCCqGSM49BAMEA4GLADCB
14
+ hwJBNDZ3R6qmJqL5bQf01oT369DEGcLhr2vA00nRZSqeaaLMfq+RQW8aYl0njfIZ
15
+ JAC6q6IJklpH5IyYrcZ29tcBrxECQgFH5aw8ZORororrLDPl1yY2RgsCO1SFoDh5
16
+ eMEaKVtRKNSG1jLzfgiZJOdtIj/h/l/4oDc5DrDDY6kbAnl4M5pDKw==
17
+ -----END CERTIFICATE-----
@@ -0,0 +1,33 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ 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
33
+ -----END CERTIFICATE-----
@@ -0,0 +1,22 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDpTCCAwagAwIBAgIQTiO7d7Wr6Flg+BPaeYgVHDAKBggqhkjOPQQDAzBuMQsw
3
+ CQYDVQQGEwJFRTEbMBkGA1UECgwSU0sgSUQgU29sdXRpb25zIEFTMRcwFQYDVQRh
4
+ DA5OVFJFRS0xMDc0NzAxMzEpMCcGA1UEAwwgVEVTVCBvZiBTSyBJRCBTb2x1dGlv
5
+ bnMgUk9PVCBHMUUwHhcNMjEwNzIxMTIzMjI2WhcNMzYwNzIxMTIzMjI2WjByMQsw
6
+ CQYDVQQGEwJFRTEbMBkGA1UECgwSU0sgSUQgU29sdXRpb25zIEFTMRcwFQYDVQRh
7
+ DA5OVFJFRS0xMDc0NzAxMzEtMCsGA1UEAwwkVEVTVCBvZiBTSyBJRCBTb2x1dGlv
8
+ bnMgRUlELU5RIDIwMjFFMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEBn6bE+DVXUwO
9
+ 8gYWoA6tu2gb4ou3Gk55ge6jYehcxehS5RO3GaknTrc2YrLcq6nwrcBoIrkVlDOd
10
+ Bfub4oea3zL7VlA/ADQ8PTYexu+0zxk1TEtsj0KHH9lh8f7FR1awo4IBYzCCAV8w
11
+ HwYDVR0jBBgwFoAU4hzeY9y++IR+ATsuS4Cx4X/V8eYwHQYDVR0OBBYEFLNZ0LWq
12
+ a/mBsLQHo63DzpXv8Y5GMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/
13
+ AgEAMGwGCCsGAQUFBwEBBGAwXjAiBggrBgEFBQcwAYYWaHR0cDovL2RlbW8uc2su
14
+ ZWUvb2NzcDA4BggrBgEFBQcwAoYsaHR0cDovL2Muc2suZWUvVEVTVF9TS19ST09U
15
+ X0cxXzIwMjFFLmRlci5jcnQwOQYDVR0fBDIwMDAuoCygKoYoaHR0cDovL2Muc2su
16
+ ZWUvVEVTVF9TS19ST09UX0cxXzIwMjFFLmNybDBQBgNVHSAESTBHMEUGBFUdIAAw
17
+ PTA7BggrBgEFBQcCARYvaHR0cHM6Ly93d3cuc2tpZHNvbHV0aW9ucy5ldS9lbi9y
18
+ ZXBvc2l0b3J5L0NQUy8wCgYIKoZIzj0EAwMDgYwAMIGIAkIBsJ6X9zwyHP3b28br
19
+ WIsid0vqWxOzPFU4GFTH/AqXW71V9WLNBJHsbuBg2VNi4k7CKUW7MpRqL8UI8QX7
20
+ /X7jFxMCQgF+IPUDMXMsV99sgqo/Y6VkZYqiakayHkvECkJCncUfmpqVYUlcAxeZ
21
+ zRlYIOz3F5AvYJTrtMP0TR3yASD1GtYs4A==
22
+ -----END CERTIFICATE-----
@@ -0,0 +1,23 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDxzCCAymgAwIBAgIUIJ92Wg42THMIC1QSOpWpxv3+22AwCgYIKoZIzj0EAwMw
3
+ bjELMAkGA1UEBhMCRUUxGzAZBgNVBAoMElNLIElEIFNvbHV0aW9ucyBBUzEXMBUG
4
+ A1UEYQwOTlRSRUUtMTA3NDcwMTMxKTAnBgNVBAMMIFRFU1Qgb2YgU0sgSUQgU29s
5
+ dXRpb25zIFJPT1QgRzFFMB4XDTI0MDYwMzEzMDEyMloXDTM5MDUzMTEzMDEyMVow
6
+ cTEsMCoGA1UEAwwjVEVTVCBvZiBTSyBJRCBTb2x1dGlvbnMgRUlELVEgMjAyNEUx
7
+ FzAVBgNVBGEMDk5UUkVFLTEwNzQ3MDEzMRswGQYDVQQKDBJTSyBJRCBTb2x1dGlv
8
+ bnMgQVMxCzAJBgNVBAYTAkVFMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE9tnu4Hr6
9
+ oZ3virQ52FkQ8zgSnRLjSpbr7y6hjaI5ZtvFTssL3aOgvULxOvV5x+HtOmcGVfmh
10
+ vy9YtoJENq/E3pFFOkofrkX3O/RVLdtPpiVahYa89HCgqoEVDln5ILMWo4IBgzCC
11
+ AX8wEgYDVR0TAQH/BAgwBgEB/wIBADAfBgNVHSMEGDAWgBTiHN5j3L74hH4BOy5L
12
+ gLHhf9Xx5jBsBggrBgEFBQcBAQRgMF4wOAYIKwYBBQUHMAKGLGh0dHA6Ly9jLnNr
13
+ LmVlL1RFU1RfU0tfUk9PVF9HMV8yMDIxRS5kZXIuY3J0MCIGCCsGAQUFBzABhhZo
14
+ dHRwOi8vZGVtby5zay5lZS9vY3NwMHAGA1UdIARpMGcwBgYEVR0gADBdBgNVHSAw
15
+ VjBUBggrBgEFBQcCARZIaHR0cHM6Ly93d3cuc2tpZHNvbHV0aW9ucy5ldS9yZXNv
16
+ dXJjZXMvY2VydGlmaWNhdGlvbi1wcmFjdGljZS1zdGF0ZW1lbnQvMDkGA1UdHwQy
17
+ MDAwLqAsoCqGKGh0dHA6Ly9jLnNrLmVlL1RFU1RfU0tfUk9PVF9HMV8yMDIxRS5j
18
+ cmwwHQYDVR0OBBYEFLAkFxmI42b4zShYZXtNFNiSZk9rMA4GA1UdDwEB/wQEAwIB
19
+ BjAKBggqhkjOPQQDAwOBiwAwgYcCQXIdNKdyvEhtB+48QZEXi2dgXiAjYD7O0D4f
20
+ 4Y2KPajqrRcwd9KEYr/yFjK0JWYHqRFN47tMdYhisy7aFySEWmKcAkIBUbTJeSbo
21
+ XAKBT9+j2zQduKv8Eqb/AIQybcVXyP23w+1ujNkcQZMkok41nGOH2YNRP7aGsCZa
22
+ 7Wy8pf2lw6EcfyU=
23
+ -----END CERTIFICATE-----
@@ -0,0 +1,24 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1
3
+ MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1
4
+ czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG
5
+ CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy
6
+ MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl
7
+ ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS
8
+ b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB
9
+ AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy
10
+ euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO
11
+ bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw
12
+ WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d
13
+ MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE
14
+ 1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD
15
+ VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/
16
+ zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB
17
+ BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF
18
+ BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV
19
+ v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG
20
+ E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u
21
+ uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW
22
+ iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v
23
+ GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0=
24
+ -----END CERTIFICATE-----
@@ -0,0 +1,17 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIICtDCCAhagAwIBAgIQSmaL1ubiC3FhWulCIhJ3+zAKBggqhkjOPQQDBDBmMQsw
3
+ CQYDVQQGEwJFRTEbMBkGA1UECgwSU0sgSUQgU29sdXRpb25zIEFTMRcwFQYDVQRh
4
+ DA5OVFJFRS0xMDc0NzAxMzEhMB8GA1UEAwwYU0sgSUQgU29sdXRpb25zIFJPT1Qg
5
+ RzFFMB4XDTIxMTAwNDExNDUwNloXDTQxMTAwNDExNDUwNlowZjELMAkGA1UEBhMC
6
+ RUUxGzAZBgNVBAoMElNLIElEIFNvbHV0aW9ucyBBUzEXMBUGA1UEYQwOTlRSRUUt
7
+ MTA3NDcwMTMxITAfBgNVBAMMGFNLIElEIFNvbHV0aW9ucyBST09UIEcxRTCBmzAQ
8
+ BgcqhkjOPQIBBgUrgQQAIwOBhgAEAN+zN0QNRH0pL6ozIEqFSBiqASoVlcgoi2+e
9
+ pQ6fU/gJ5ZbySnNtjcfxqqi3sW+XHK3IXN2kwMImzWx/P7S4ZhRfAJNJi6qPmyyD
10
+ nXIE17jUuqVBcPZwpap3cqQND3FA3/wgs3r/dnBHyQD4M2ihf3j4IDy/ANl6IiI2
11
+ D4Z50ik4QUfYo2MwYTAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd
12
+ BgNVHQ4EFgQUhnRPOus48rCn7u25hZudgwlFMWswHwYDVR0jBBgwFoAUhnRPOus4
13
+ 8rCn7u25hZudgwlFMWswCgYIKoZIzj0EAwQDgYsAMIGHAkF+pYLDdJjsHc7hvW0d
14
+ lIPk6riZKr3PDe6+3t3Paq+Udb4OGw3yzJoxCKQbnLULtlN6weO6wupBn91X+wX6
15
+ QbCy/gJCAK/JtP1UsWLkmxCannuKGOIH8CgcKIXdW/z7/TQLfrygdJMgZ5TjzLMH
16
+ XoX3gKSTNpCuZEeamEYBzd8HKLDnKYWV
17
+ -----END CERTIFICATE-----
@@ -0,0 +1,33 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ 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
32
+ 5fcX5tJZaD3vy9mYJAJ5d/vhcGOqHbI3s2W27+c=
33
+ -----END CERTIFICATE-----
@@ -0,0 +1,39 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIG4jCCBcqgAwIBAgIQO4A6a2nBKoxXxVAFMRvE2jANBgkqhkiG9w0BAQwFADB1
3
+ MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1
4
+ czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG
5
+ CSqGSIb3DQEJARYJcGtpQHNrLmVlMCAXDTE2MDgzMDA5MjEwOVoYDzIwMzAxMjE3
6
+ MjM1OTU5WjBgMQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVy
7
+ aW1pc2tlc2t1czEXMBUGA1UEYQwOTlRSRUUtMTA3NDcwMTMxFDASBgNVBAMMC0VJ
8
+ RC1TSyAyMDE2MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7XWFN0j
9
+ 1CFoGIuVe9xRezEnA0Tk3vmvIpvURX+y7Z5DJsfub2mtpSLtbhXjAeynq9QV78zj
10
+ gQ73pNVGh+GQ6oPG7HF8KIlZuIYsf1+gBxPxNiLa0+sCWxa6p4HQbgdgYRVGod4I
11
+ Qbib9KbOki3wjCG5WiWh1SP9qcuTZVY+9zawkSMf65Px/Y4ChjtNFtY66MEvsPCh
12
+ lHHfsBNiUbtZ68jJNYCECjtkm0vxz2iiSXB2WRIv3/hTrRgMJ2CNMyFjRQoGQlpH
13
+ 010+fcisObKeyPwA8kI22Oto9MzLw7KsY524OD3B1L5MExYxHD916XIEHT/9gBP2
14
+ Zn8qZu/BllKdSIapOIJW9ZEw+3w5UOU6LT3tTSbAzeQAnD3eCABPifYwHYC0lmKs
15
+ PpQJqtx0Q3Jbm3BGReYiZ9KuK36nF/G78YjhM+yioERr2B/cKf31j0W/GuGvyHak
16
+ bokwy7nsbL30sTuRLR70Oqi5UBMy4e8J2CduR3R3NJw5UqpScJIchngsLAx+WsyC
17
+ 0w38AmMewMBcnlp/QbakKo52HrsYRR1m+NhCVDBy45Lzl8I0/OGd9Ikdg1h7T7SI
18
+ guZVpyzys8E0yfrcS5YMEd9hMqVPr7rszXCzbxyw0tVIk8QLMw/lI+XE1Oi7Skgz
19
+ A2i5Vpa6i2K0ard6GPHzRqGPTkjc5Z4DzZMCAwEAAaOCAn8wggJ7MB8GA1UdIwQY
20
+ MBaAFBLyWj7qVhy/zQas8fElyalL1BSZMB0GA1UdDgQWBBScCagHhww9rC6H/KCu
21
+ 0vtlSYgo+zAOBgNVHQ8BAf8EBAMCAQYwgcQGA1UdIASBvDCBuTA8BgcEAIvsQAEC
22
+ MDEwLwYIKwYBBQUHAgEWI2h0dHBzOi8vd3d3LnNrLmVlL3JlcG9zaXRvb3JpdW0v
23
+ Q1BTMDwGBwQAi+xAAQAwMTAvBggrBgEFBQcCARYjaHR0cHM6Ly93d3cuc2suZWUv
24
+ cmVwb3NpdG9vcml1bS9DUFMwOwYGBACPegECMDEwLwYIKwYBBQUHAgEWI2h0dHBz
25
+ Oi8vd3d3LnNrLmVlL3JlcG9zaXRvb3JpdW0vQ1BTMBIGA1UdEwEB/wQIMAYBAf8C
26
+ AQAwJwYDVR0lBCAwHgYIKwYBBQUHAwkGCCsGAQUFBwMCBggrBgEFBQcDBDB8Bggr
27
+ BgEFBQcBAQRwMG4wIAYIKwYBBQUHMAGGFGh0dHA6Ly9vY3NwLnNrLmVlL0NBMEoG
28
+ CCsGAQUFBzAChj5odHRwOi8vd3d3LnNrLmVlL2NlcnRzL0VFX0NlcnRpZmljYXRp
29
+ b25fQ2VudHJlX1Jvb3RfQ0EuZGVyLmNydDBBBgNVHR4EOjA4oTYwBIICIiIwCocI
30
+ AAAAAAAAAAAwIocgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwJQYI
31
+ KwYBBQUHAQMEGTAXMBUGCCsGAQUFBwsCMAkGBwQAi+xJAQEwPQYDVR0fBDYwNDAy
32
+ oDCgLoYsaHR0cDovL3d3dy5zay5lZS9yZXBvc2l0b3J5L2NybHMvZWVjY3JjYS5j
33
+ cmwwDQYJKoZIhvcNAQEMBQADggEBAKSIoud5DSfhDU6yp+VrXYL40wi5zFTf19ha
34
+ /kO/zzLxZ1hf45VJmSyukMWaWXEqhaLWBZuw5kP78mQ0HyaRUennN0hom/pEiBz6
35
+ cuz9oc+xlmPAZM25ZoaLqa4upP2/+NCWoRTzYkIdc9MEECs5RMBUmyT1G4s8J6n8
36
+ L2M2yYadBMvPGJS3yXxYdc/b3a2foiw3kKa/q1tXAHXZCsuxFVYxXdZt3AwInYHe
37
+ mCVKjZg8BaRpvIEXd3AgJwt+9bpV/x0/MouRPNRv0jjWIx1sAlL94hO74WZDMFbZ
38
+ VaV6gpG77X2P3dPHKFIRWzjtSQJX4C5n1uvQBxO4ABoMswq0lq0=
39
+ -----END CERTIFICATE-----
@@ -0,0 +1,37 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIGYjCCBUqgAwIBAgIQV6nz7KIvDihXxU71YTbgWjANBgkqhkiG9w0BAQwFADB1
3
+ MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1
4
+ czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG
5
+ CSqGSIb3DQEJARYJcGtpQHNrLmVlMCAXDTE2MDgzMDA5MTYzN1oYDzIwMzAxMjE3
6
+ MjM1OTU5WjBfMQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVy
7
+ aW1pc2tlc2t1czEXMBUGA1UEYQwOTlRSRUUtMTA3NDcwMTMxEzARBgNVBAMMCk5R
8
+ LVNLIDIwMTYwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDdkRRNDxfO
9
+ 6oKU9GDrGLNQc41PA+pqDKCEcDhSw1bnkC/nDumg4PawQk8xklyDHr2ShrsFrTo5
10
+ wps5UcgxxTMqb98bmMxQYghqxu5NqqpaZopbbSj+qDYUzrZkXIlVe+HFpUt5ce9W
11
+ NpEmeenVAlt4ZaN1/srDfv3NSMmcF2r9XiUIIhDavxQ+QgPy3CrgT0Ja3yw/PLpF
12
+ /ajCNQWaGWJHYkgNVzrnrKhKYDhgorc3lSqGfTfhW2Xf5klvBZokPfbhD26csnPe
13
+ JjQQQJ2Loot3Z9/QPzfY/Qnqp5hjkvfqjKksX2wAt/UB+Hk4sRG+6Nqa3b+gxqMc
14
+ ih1eI/I93Ii6OC7LijhN2k0R9L5+ArgQXhlAQYZGeCAC/unHmpCkiUQrEJq27kst
15
+ mzoENnwQnF3mhq81KQGZul/Guw1fsQOolALESEWG6dTP1szaLeba4LYN707b9puR
16
+ OVXk1WLoau131KZnIdc/+Ktu2ni4SVL3+qKbJ7+oqIfiFAqlSuCPTKssdFC49m7V
17
+ G4bXnrYeA5svUQjCvpANmzXqRs6DmdctKPuXUj+W/gnQNoLOvIEkK30TD/RKd4eh
18
+ uzzYj9qirhqBDFg+Ipqh9OByK7aY6f9KZ6qKmKttcPb4R7arBtuQoBlqadcXoGig
19
+ o/kr/iXVRabWfGVM73iQo36RZrklrSu5awIDAQABo4ICADCCAfwwHwYDVR0jBBgw
20
+ FoAUEvJaPupWHL/NBqzx8SXJqUvUFJkwHQYDVR0OBBYEFHq3hV+h88xBt67p6gZR
21
+ CuD5AsisMA4GA1UdDwEB/wQEAwIBBjBGBgNVHSAEPzA9MDsGBgQAj3oBATAxMC8G
22
+ CCsGAQUFBwIBFiNodHRwczovL3d3dy5zay5lZS9yZXBvc2l0b29yaXVtL0NQUzAS
23
+ BgNVHRMBAf8ECDAGAQH/AgEAMCcGA1UdJQQgMB4GCCsGAQUFBwMJBggrBgEFBQcD
24
+ AgYIKwYBBQUHAwQwfAYIKwYBBQUHAQEEcDBuMCAGCCsGAQUFBzABhhRodHRwOi8v
25
+ b2NzcC5zay5lZS9DQTBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5zay5lZS9jZXJ0
26
+ cy9FRV9DZXJ0aWZpY2F0aW9uX0NlbnRyZV9Sb290X0NBLmRlci5jcnQwQQYDVR0e
27
+ BDowOKE2MASCAiIiMAqHCAAAAAAAAAAAMCKHIAAAAAAAAAAAAAAAAAAAAAAAAAAA
28
+ AAAAAAAAAAAAAAAAMCUGCCsGAQUFBwEDBBkwFzAVBggrBgEFBQcLAjAJBgcEAIvs
29
+ SQEBMD0GA1UdHwQ2MDQwMqAwoC6GLGh0dHA6Ly93d3cuc2suZWUvcmVwb3NpdG9y
30
+ eS9jcmxzL2VlY2NyY2EuY3JsMA0GCSqGSIb3DQEBDAUAA4IBAQCu4HLsEBBpKmXw
31
+ agXpFkmEGqTOC/eYWrtwVZNnz/MB+z8c6TyxwW2cDmNwMwMfojXT447rQ/xlai/5
32
+ gjGkRwRE8P5W90h/JkO3rUWG4asrvPAwmkIiUHsHIHDVHCsSmhLNEgPdM4zP88/L
33
+ EmV89ZIvUWGjzZYcgBljYeAlK4dCy4/7U14JW9FCwvFjFOyfDcpoYwxbV7Jkbhsw
34
+ 9J8uzzxjspGCvoq5izeTGuRV+WtV+yy6W/UOnpmYOJ6jxzUoYq6fnQGU+J9CLVxj
35
+ jE8Jj25fuWSU3BvPs8cms7RzvvuZvPgQWm8IZt6L5P6EHOzeER3m3nftERhG2OXE
36
+ +MHJ+onc
37
+ -----END CERTIFICATE-----
@@ -0,0 +1,21 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDjDCCAu2gAwIBAgIQZS4pkx05/qxhWvBEMBMpRzAKBggqhkjOPQQDAzBmMQsw
3
+ CQYDVQQGEwJFRTEbMBkGA1UECgwSU0sgSUQgU29sdXRpb25zIEFTMRcwFQYDVQRh
4
+ DA5OVFJFRS0xMDc0NzAxMzEhMB8GA1UEAwwYU0sgSUQgU29sdXRpb25zIFJPT1Qg
5
+ RzFFMB4XDTIxMTAwNDEyMTUwMFoXDTM2MTAwNDEyMTUwMFowajELMAkGA1UEBhMC
6
+ RUUxGzAZBgNVBAoMElNLIElEIFNvbHV0aW9ucyBBUzEXMBUGA1UEYQwOTlRSRUUt
7
+ MTA3NDcwMTMxJTAjBgNVBAMMHFNLIElEIFNvbHV0aW9ucyBFSUQtTlEgMjAyMUUw
8
+ djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQAI69a/9UtQp22Spxh1cNsK3ptIAszE8uM
9
+ owS7q3YS7qB5UGD1up0dUOt9cjstQfuddxP+ZQ0GtrWeYx2rFFqxm6YZsxjc0G9T
10
+ QsEgnlfuHMije5UEiK1nU3rjZKM4ThijggFaMIIBVjAfBgNVHSMEGDAWgBSGdE86
11
+ 6zjysKfu7bmFm52DCUUxazAdBgNVHQ4EFgQUZ9DnDB060b7OhLGcCNHtTBjxwh0w
12
+ DgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwbQYIKwYBBQUHAQEE
13
+ YTBfMCAGCCsGAQUFBzABhhRodHRwOi8vb2NzcC5zay5lZS9DQTA7BggrBgEFBQcw
14
+ AoYvaHR0cDovL2Muc2suZWUvU0tfSURfU29sdXRpb25zX1JPT1RfRzFFLmRlci5j
15
+ cnQwLwYDVR0fBCgwJjAkoCKgIIYeaHR0cDovL2Muc2suZWUvU0tfUk9PVF9HMUUu
16
+ Y3JsMFAGA1UdIARJMEcwRQYEVR0gADA9MDsGCCsGAQUFBwIBFi9odHRwczovL3d3
17
+ dy5za2lkc29sdXRpb25zLmV1L2VuL3JlcG9zaXRvcnkvQ1BTLzAKBggqhkjOPQQD
18
+ AwOBjAAwgYgCQgE+TW4aSpdM96cUQIi35M/8slEZbTjNFVQs5cA5MdamR22uYwV2
19
+ +xDvBLTNfNsyQEAhqP8ROdlDaW+nxPlMTwroggJCARskIsMSmr0+g7AvmsscKexv
20
+ 5tpN1OPIscXC6g/bt0YKHz+R7hAdV5YU3DE9G8vqBP+046tr1iIpTxbgce7gSTEc
21
+ -----END CERTIFICATE-----
@@ -0,0 +1,38 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIGtjCCBJ6gAwIBAgIQC10GXSSPYY9hWvJauHbJ3zANBgkqhkiG9w0BAQwFADBm
3
+ MQswCQYDVQQGEwJFRTEbMBkGA1UECgwSU0sgSUQgU29sdXRpb25zIEFTMRcwFQYD
4
+ VQRhDA5OVFJFRS0xMDc0NzAxMzEhMB8GA1UEAwwYU0sgSUQgU29sdXRpb25zIFJP
5
+ T1QgRzFSMB4XDTIxMTAwNDEyMjM1NFoXDTM2MTAwNDEyMjM1NFowajELMAkGA1UE
6
+ BhMCRUUxGzAZBgNVBAoMElNLIElEIFNvbHV0aW9ucyBBUzEXMBUGA1UEYQwOTlRS
7
+ RUUtMTA3NDcwMTMxJTAjBgNVBAMMHFNLIElEIFNvbHV0aW9ucyBFSUQtTlEgMjAy
8
+ MVIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCyTmudLFCfMoKAwggc
9
+ rOzGvWgcftry9nXwyVO52GuTWGvVgeAeG/rrUAP7AyYmPWbZPQaSjevCNC+vihEu
10
+ zbWlQkxoBrajF5Dd0dJoAJ4jDruyWdeKXPaj3sRBvo54adT6y3d/RVxVLC2X4bOS
11
+ 5tiZUbdhpMcEP7uLJg2HfKBHLegtcv34kj0kRtpwvDdYfV5u1KfW4R1XwAolcNZt
12
+ pxn2tXzB/v32O+wjnLmNvtz8GAoCLwrlE+Jo0kJdTjSFG82SRA4NNaqQoIwIRH2D
13
+ rlIqVoIyfzTf69uwai0PAqa1BJO1hFWrqC586GS5LPwnntUx81hlzBnmKNCaMMZp
14
+ eK65BI+lSQriH+H1MFccgQ2XBaMJ/P6nUTRoPw3ol/HHRPC/Tzd9/IMMYWTuUriF
15
+ upbrrAzfLHtVZCIQZgYI5Qe5LA+OBuW/AFy3kKR/2xJ5rhx4dQYWfZAMRLs5apzn
16
+ lkkDAMnOCp2p/6jB3xQMEqdI/FUMP9yafz4cd+JkNcY8jvstPoDYhmv6zekvNY4y
17
+ RC3bx4u/m/ld4lMr74LB/Azt8MK/Ai52YzibBkUzAfZ+DiQj03nmVAJxBI2z+MYx
18
+ 0O1m/svX94ebK6gWd/9+u5dT1iMcrFf3saNbZExjEBstcX7ZjsiEONdHV2WGIKyu
19
+ S7vIfgL9j4ZfDBc/fp71IbeEHwIDAQABo4IBWjCCAVYwHwYDVR0jBBgwFoAUlQ23
20
+ ZBjCpptmdtj8/JpaJLwo1s0wHQYDVR0OBBYEFHcBdj3W1Vvu3W740O8iDH8terlV
21
+ MA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMG0GCCsGAQUFBwEB
22
+ BGEwXzAgBggrBgEFBQcwAYYUaHR0cDovL29jc3Auc2suZWUvQ0EwOwYIKwYBBQUH
23
+ MAKGL2h0dHA6Ly9jLnNrLmVlL1NLX0lEX1NvbHV0aW9uc19ST09UX0cxUi5kZXIu
24
+ Y3J0MC8GA1UdHwQoMCYwJKAioCCGHmh0dHA6Ly9jLnNrLmVlL1NLX1JPT1RfRzFS
25
+ LmNybDBQBgNVHSAESTBHMEUGBFUdIAAwPTA7BggrBgEFBQcCARYvaHR0cHM6Ly93
26
+ d3cuc2tpZHNvbHV0aW9ucy5ldS9lbi9yZXBvc2l0b3J5L0NQUy8wDQYJKoZIhvcN
27
+ AQEMBQADggIBAKsjYNumlITS5Hmmr3UyWw0x1qgQP2LSsYP1YPL30my8Y/5IEPYR
28
+ 2v+uKYSGMP9wjRNKXEqnvGhBxB+TjGvIG78s12JdsXYDfE0FpnWUowGZeuX7B+CF
29
+ mCxtBM+D1VUfOuGqX1RQJNcFAgOfTjlTLLVEq/iFIXIOEljWctPzdmTgmUTQJ780
30
+ AlTw7Nx5GRLFvWv3ra+X2d8f939LI/RAgL64nm9zXvz6GgpSb8o0MeOM3ohvMBqf
31
+ 8pJi0SroiUVqO71cCbiQNPXj2NIGAQCGjwTFCIxo9GoeDcCmYvvGtk9+4EQ4RGEM
32
+ e7gjA3oNRxUh+tnretJoAw7NCfQAHZaN83/h+vvCfEv+UdHe+kVhO+n9r+1+dS/w
33
+ aN8CPgrBIpPeKxpfD9ke9zNTnIjlFWPXAsaTeVWR/ppzh/LvdFENCod2J5ySmtgy
34
+ kO4LAqDCqiCrAc2a/e2ZxF8WsXZzBJZ1sLGW6FlxRvDYwGgk1clL+KWtWQSgd6KP
35
+ csD2MZecis3hSBmpG2JNysXepJU+GLGamoXXshvNGaP+Wkl2EjXYuimTGR0EZ/Ju
36
+ 0BvtK3oWuUkTbWxTWWtEQPp9B4d+60v5fnO4mL4JgfT4jd/ALXWhU3yPwq7xQiN0
37
+ D2s+/yfSZKH4EY97+yGCD18kVSMyP932mZNXN0739ChlQdqP8h+VUWWq
38
+ -----END CERTIFICATE-----
@@ -0,0 +1,22 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDrzCCAxCgAwIBAgIUJFmkBcQvQ0QOhvceUs8st3JaFI4wCgYIKoZIzj0EAwMw
3
+ ZjELMAkGA1UEBhMCRUUxGzAZBgNVBAoMElNLIElEIFNvbHV0aW9ucyBBUzEXMBUG
4
+ A1UEYQwOTlRSRUUtMTA3NDcwMTMxITAfBgNVBAMMGFNLIElEIFNvbHV0aW9ucyBS
5
+ T09UIEcxRTAeFw0yNDA3MDIwODM0NThaFw0zOTA2MjkwODM0NTdaMGkxJDAiBgNV
6
+ BAMMG1NLIElEIFNvbHV0aW9ucyBFSUQtUSAyMDI0RTEXMBUGA1UEYQwOTlRSRUUt
7
+ MTA3NDcwMTMxGzAZBgNVBAoMElNLIElEIFNvbHV0aW9ucyBBUzELMAkGA1UEBhMC
8
+ RUUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASS0qdCTcqwsTWm/TSe1ZTHV8+92muF
9
+ 2Lr8s8og8kF8lVAk/b9BEIch0SlB99gWbjZcXEGVY5pn3TnxbOIB4gAfbcrkToAG
10
+ HZ6CnQHy0Cu1yGx26SgxGiIT7G6aWKgVjRyjggF6MIIBdjASBgNVHRMBAf8ECDAG
11
+ AQH/AgEAMB8GA1UdIwQYMBaAFIZ0TzrrOPKwp+7tuYWbnYMJRTFrMG0GCCsGAQUF
12
+ BwEBBGEwXzA7BggrBgEFBQcwAoYvaHR0cDovL2Muc2suZWUvU0tfSURfU29sdXRp
13
+ b25zX1JPT1RfRzFFLmRlci5jcnQwIAYIKwYBBQUHMAGGFGh0dHA6Ly9vY3NwLnNr
14
+ LmVlL0NBMHAGA1UdIARpMGcwBgYEVR0gADBdBgNVHSAwVjBUBggrBgEFBQcCARZI
15
+ aHR0cHM6Ly93d3cuc2tpZHNvbHV0aW9ucy5ldS9yZXNvdXJjZXMvY2VydGlmaWNh
16
+ dGlvbi1wcmFjdGljZS1zdGF0ZW1lbnQvMC8GA1UdHwQoMCYwJKAioCCGHmh0dHA6
17
+ Ly9jLnNrLmVlL1NLX1JPT1RfRzFFLmNybDAdBgNVHQ4EFgQUdkUHZ+4lf+4gTZws
18
+ qVexnp+H1TkwDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA4GMADCBiAJCAIBx
19
+ MfYdVnp1WBtpFerhlPritsebecWtmgE59KIirv/bsIPBIFecs6TyvLY00l0XVD0K
20
+ oEuPQ+OxjPbNX5e2GmWoAkIBjZTfQqK4XPWV0QIBMr2GIFCTWOVs4MY/jMMBmGnZ
21
+ 283eQ+PTJhyz2kA+rEdlhlMGwz1RGIuh2gbUwvB11yoHhnc=
22
+ -----END CERTIFICATE-----
@@ -0,0 +1,39 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIG2TCCBMGgAwIBAgIUXMy/z4HaMHIhVHiAuftKBIfHqJwwDQYJKoZIhvcNAQEM
3
+ BQAwZjELMAkGA1UEBhMCRUUxGzAZBgNVBAoMElNLIElEIFNvbHV0aW9ucyBBUzEX
4
+ MBUGA1UEYQwOTlRSRUUtMTA3NDcwMTMxITAfBgNVBAMMGFNLIElEIFNvbHV0aW9u
5
+ cyBST09UIEcxUjAeFw0yNDA3MDIwODQwMjRaFw0zOTA2MjkwODQwMjNaMGkxJDAi
6
+ BgNVBAMMG1NLIElEIFNvbHV0aW9ucyBFSUQtUSAyMDI0UjEXMBUGA1UEYQwOTlRS
7
+ RUUtMTA3NDcwMTMxGzAZBgNVBAoMElNLIElEIFNvbHV0aW9ucyBBUzELMAkGA1UE
8
+ BhMCRUUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDakmc2loLVmkcK
9
+ O+Cc2ntz5axekITqL10C416W2uNeBVax+L/UWvf1Yw9tFhygUXlMsgLoy6G7gQb5
10
+ C6SOmKjulFd0WeicY7QSbaTI+MjUH2qsEz4syipPcnLj2S6JF/vZgWmtSlehAvFa
11
+ cyoOvKCOAMdw1v/LxiaqJL8gN/RTZ3JuihQsz19TqICPAByharQOffCPoJ/keHCi
12
+ y7hjLGAnaL7KWGZy4y+PH5qSTCNNB0HcpTNst7GzlfB6xriXyeqcNFAuQ/5697Yp
13
+ SQ2H8zae9kBLAjcpAUzzhY75C5Fe0XpS1VIrGdIy8hgHqh+9Tw2ZlSlBbO1o/Pzx
14
+ qrjHUITp+p4LAYI/SN3U1sRu4msi3y0vLb4AFwpzyJTiI7BGqwy1JgvEO/ojdgBo
15
+ SVsdzrzrhS/hXSc3aBrqfndr/tg+Ylj3e5N8xPD8d8jAjplz+crrZFlJh/YBMtN+
16
+ /xck5xrgcQoly2Tx5qqq6i1mq70m90J4X6Hz7Wl9wVRYkVc0TExtkdYHh/wMmAKx
17
+ R7/UnM7b9rCq9aliu4xy+9Lp4JsfZiR33a6+VrVoDsaSgpmNRPve17qfWHlGj8LG
18
+ dz0lvWPiOBXJzbmgt3ImCGUuxnwlI0HeK/T1KWuCsVNCOROYq7zayi9+odukfoaI
19
+ F24SbApJbUF/spXIfNwFkF/FqylGOwIDAQABo4IBejCCAXYwEgYDVR0TAQH/BAgw
20
+ BgEB/wIBADAfBgNVHSMEGDAWgBSVDbdkGMKmm2Z22Pz8mlokvCjWzTBtBggrBgEF
21
+ BQcBAQRhMF8wOwYIKwYBBQUHMAKGL2h0dHA6Ly9jLnNrLmVlL1NLX0lEX1NvbHV0
22
+ aW9uc19ST09UX0cxUi5kZXIuY3J0MCAGCCsGAQUFBzABhhRodHRwOi8vb2NzcC5z
23
+ ay5lZS9DQTBwBgNVHSAEaTBnMAYGBFUdIAAwXQYDVR0gMFYwVAYIKwYBBQUHAgEW
24
+ SGh0dHBzOi8vd3d3LnNraWRzb2x1dGlvbnMuZXUvcmVzb3VyY2VzL2NlcnRpZmlj
25
+ YXRpb24tcHJhY3RpY2Utc3RhdGVtZW50LzAvBgNVHR8EKDAmMCSgIqAghh5odHRw
26
+ Oi8vYy5zay5lZS9TS19ST09UX0cxUi5jcmwwHQYDVR0OBBYEFFPw5zS5YAJCy5pf
27
+ 1c9Jkx8cDZ9XMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwFAAOCAgEACNJH
28
+ FWFKDPGm/PKZJVY+ZN1R2IgRqCxxCx2A+2rLb2JV0YtL0OBFgt0Vy2WA7AiEF4dq
29
+ fugIO73c4Nq6wCzB3dUCg6XD6eKqUdJ+/GpuebbALe4ntk7ka6clSjNpNroBpH0N
30
+ CZGucZVBbKeWNqx/W1dh/De5Lx0u+JbkA1UXf4ne9ZXj2BrCH5PznDIXdx7ta7xg
31
+ gSuIzJRfa6SN2cjvxczuS4ExTM1/H9ijT3qHoehovJ8Ae80kaceLB6OGh3nMp0rp
32
+ bY4ra0iZiU/ZpMxEJruDpq5c4klY76YJO9AkU7PztiW44zL5PWsMDKxfBz5XEwdB
33
+ 6kZ3pSP5QdRJ55/ZMJ23dH/SXVrGGo+zKI6MaDleYRvejfyR9Jq7CP4UnrcQ4Tiy
34
+ KssLxWHa4ah/AUn0dXRxVHs+87zAUWx1nASVdJCU+/pektlUjFCf21i5el6zo3w7
35
+ dBob803T0cg1MZ2IXkJmJfpzcBSh8/ewT2lQR/M2SzBDMjwE9SzN/bR8CCfQuG67
36
+ UwIXrRz/IvfzHXUkG+uexK83bqeLhdqGqUp77T7H5VtQ9HYlYD/lN/wiMaCpVdEu
37
+ TUYnNxRZs36k2ld20ZTBLELxOmCj1Oc1ylQJ3CnPOs1OXoeoHCm9c6YlV9o4AHGc
38
+ Oy4HKnqjncdBkQ3RNde0zgG4jMeSLgVHxCm1eTI=
39
+ -----END CERTIFICATE-----
@@ -16,6 +16,8 @@ module SmartIdRuby
16
16
  truststore_path: nil,
17
17
  truststore_type: nil,
18
18
  truststore_password: nil,
19
+ # Which bundled SK CA set the response validators trust: :production or :demo.
20
+ trusted_ca_environment: :production,
19
21
  tls_config: nil,
20
22
  network_connection_config: nil,
21
23
  configured_connection: nil
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartIdRuby
4
+ module Errors
5
+ # Raised when a request to the Smart-ID API times out at the transport level.
6
+ #
7
+ # Distinct from SessionTimeoutError, which means the *user* did not respond in time and
8
+ # is reported by the API as a TIMEOUT end result. This one says nothing about the
9
+ # session — a long-poll session status request that times out is expected, and the
10
+ # caller should poll again.
11
+ class NetworkTimeoutError < Error
12
+ def initialize(message = "Request to Smart-ID API timed out")
13
+ super
14
+ end
15
+ end
16
+ end
17
+ end
@@ -11,6 +11,7 @@ require_relative "errors/unsupported_client_api_version_error"
11
11
  require_relative "errors/request_validation_error"
12
12
  require_relative "errors/request_setup_error"
13
13
  require_relative "errors/response_error"
14
+ require_relative "errors/network_timeout_error"
14
15
  require_relative "errors/unprocessable_response_error"
15
16
  require_relative "errors/document_unusable_error"
16
17
  require_relative "errors/certificate_level_mismatch_error"
@@ -216,13 +216,8 @@ module SmartIdRuby
216
216
  rescue Faraday::ResourceNotFound
217
217
  logger.warn("Session or resource not found for path #{path}")
218
218
  raise(not_found_error || SmartIdRuby::Errors::UserAccountNotFoundError)
219
- rescue Faraday::UnauthorizedError, Faraday::ForbiddenError => e
220
- logger.warn("Request is unauthorized for path #{path}: #{e.message}")
221
- raise SmartIdRuby::Errors::RelyingPartyAccountConfigurationError, e.message
222
- rescue Faraday::ClientError => e
223
- handle_client_error(e)
224
- rescue Faraday::ServerError => e
225
- handle_server_error(e)
219
+ rescue Faraday::Error => e
220
+ handle_faraday_error(e, path)
226
221
  end
227
222
 
228
223
  def post(path, body:)
@@ -231,16 +226,25 @@ module SmartIdRuby
231
226
  rescue Faraday::ResourceNotFound
232
227
  logger.warn("User account not found for path #{path}")
233
228
  raise SmartIdRuby::Errors::UserAccountNotFoundError
234
- rescue Faraday::UnauthorizedError, Faraday::ForbiddenError => e
235
- logger.warn("No permission to issue request for path #{path}: #{e.message}")
236
- raise SmartIdRuby::Errors::RelyingPartyAccountConfigurationError, e.message
237
229
  rescue Faraday::BadRequestError => e
238
230
  logger.warn("Request is invalid for path #{path}: #{e.message}")
239
231
  raise SmartIdRuby::Errors::RequestValidationError, e.message
240
- rescue Faraday::ClientError => e
241
- handle_client_error(e)
242
- rescue Faraday::ServerError => e
243
- handle_server_error(e)
232
+ rescue Faraday::Error => e
233
+ handle_faraday_error(e, path)
234
+ end
235
+
236
+ # Shared tail of the rescue chains above. Faraday::TimeoutError has to be matched
237
+ # before Faraday::ServerError, which it inherits from.
238
+ def handle_faraday_error(error, path)
239
+ case error
240
+ when Faraday::UnauthorizedError, Faraday::ForbiddenError
241
+ logger.warn("Request is unauthorized for path #{path}: #{error.message}")
242
+ raise SmartIdRuby::Errors::RelyingPartyAccountConfigurationError, error.message
243
+ when Faraday::TimeoutError then handle_timeout_error(error, path)
244
+ when Faraday::ClientError then handle_client_error(error)
245
+ when Faraday::ServerError then handle_server_error(error)
246
+ else raise_without_response(error)
247
+ end
244
248
  end
245
249
 
246
250
  def perform_request(method, path, query: nil, body: nil)
@@ -283,9 +287,32 @@ module SmartIdRuby
283
287
  raise SmartIdRuby::Errors::ResponseError, "Failed to parse Smart-ID response body: #{e.message}"
284
288
  end
285
289
 
290
+ def handle_timeout_error(error, path)
291
+ logger.warn("Request to #{path} timed out: #{error.message}")
292
+ raise SmartIdRuby::Errors::NetworkTimeoutError
293
+ end
294
+
295
+ # Faraday errors raised by the adapter rather than by a response — a timeout or a
296
+ # dropped connection — carry no response hash, so reading error.response[:status]
297
+ # would raise NoMethodError and mask the real failure.
298
+ def error_response(error)
299
+ response = error.response
300
+ response.is_a?(Hash) ? response : nil
301
+ end
302
+
303
+ def raise_without_response(error)
304
+ logger.warn("Request failed without a response: #{error.class}: #{error.message}")
305
+ raise SmartIdRuby::Errors::NetworkTimeoutError if error.is_a?(Faraday::TimeoutError)
306
+
307
+ raise SmartIdRuby::Errors::ResponseError, error.message
308
+ end
309
+
286
310
  def handle_client_error(error)
287
- status = error.response[:status].to_i
288
- logger.debug("Client error response status=#{status}, body=#{truncate_for_log(error.response[:body])}")
311
+ response = error_response(error)
312
+ return raise_without_response(error) if response.nil?
313
+
314
+ status = response[:status].to_i
315
+ logger.debug("Client error response status=#{status}, body=#{truncate_for_log(response[:body])}")
289
316
  case status
290
317
  when 471
291
318
  logger.warn("No suitable account of requested type found, but user has some other accounts")
@@ -303,8 +330,11 @@ module SmartIdRuby
303
330
  end
304
331
 
305
332
  def handle_server_error(error)
306
- status = error.response[:status].to_i
307
- logger.debug("Server error response status=#{status}, body=#{truncate_for_log(error.response[:body])}")
333
+ response = error_response(error)
334
+ return raise_without_response(error) if response.nil?
335
+
336
+ status = response[:status].to_i
337
+ logger.debug("Server error response status=#{status}, body=#{truncate_for_log(response[:body])}")
308
338
  if status == 580
309
339
  logger.warn("Server is under maintenance, retry later")
310
340
  raise SmartIdRuby::Errors::ServerMaintenanceError
@@ -8,8 +8,12 @@ module SmartIdRuby
8
8
  module Validation
9
9
  # Validates X.509 certificate validity period and trust chain.
10
10
  class CertificateValidator
11
- def initialize(trusted_ca_cert_store: nil, use_system_store: true)
12
- @trusted_ca_cert_store = trusted_ca_cert_store
11
+ # Defaults to the CA certificates bundled with the gem for the configured
12
+ # environment (see TrustedCaCertStore). Pass `trusted_ca_cert_store: nil` explicitly
13
+ # to validate against the system CA store alone — which does not contain SK's eID
14
+ # CAs, so every Smart-ID certificate will fail to chain.
15
+ def initialize(trusted_ca_cert_store: :default, use_system_store: true)
16
+ @trusted_ca_cert_store = trusted_ca_cert_store == :default ? TrustedCaCertStore.default : trusted_ca_cert_store
13
17
  @use_system_store = use_system_store
14
18
  end
15
19
 
@@ -15,6 +15,8 @@ module SmartIdRuby
15
15
  QC_STATEMENTS_EXTENSION_OID = "1.3.6.1.5.5.7.1.3"
16
16
  QC_TYPE_STATEMENT_OID = "0.4.0.1862.1.6"
17
17
  QUALIFIED_ELECTRONIC_SIGNATURE_OID = "0.4.0.1862.1.6.1"
18
+ QUALIFIED_CERTIFICATE_POLICY_OIDS = ["1.3.6.1.4.1.10015.17.2", "0.4.0.194112.1.2"].freeze
19
+ NON_QUALIFIED_CERTIFICATE_POLICY_OIDS = ["1.3.6.1.4.1.10015.17.1", "0.4.0.2042.1.1"].freeze
18
20
  SUPPORTED_HASH_ALGORITHMS = {
19
21
  "SHA-256" => 32,
20
22
  "SHA-384" => 48,
@@ -119,21 +121,39 @@ module SmartIdRuby
119
121
  unless key_usage.match?(/Non[- ]Repudiation/i)
120
122
  raise SmartIdRuby::Errors::UnprocessableResponseError, "Certificate does not have Non-Repudiation set in 'KeyUsage' extension"
121
123
  end
122
- return if certificate_level == "ADVANCED"
124
+ return validate_non_qualified_certificate(certificate) if certificate_level == "ADVANCED"
123
125
 
126
+ validate_qualified_certificate(certificate)
127
+ end
128
+
129
+ def validate_qualified_certificate(certificate)
124
130
  policy_oids = extract_certificate_policy_oids(certificate)
125
131
  if policy_oids.empty?
126
132
  raise SmartIdRuby::Errors::UnprocessableResponseError, "Certificate does not have certificate policy OIDs"
127
133
  end
128
134
 
129
- required = ["1.3.6.1.4.1.10015.17.2", "0.4.0.194112.1.2"]
130
- unless (required - policy_oids).empty?
135
+ unless (QUALIFIED_CERTIFICATE_POLICY_OIDS - policy_oids).empty?
131
136
  raise SmartIdRuby::Errors::UnprocessableResponseError,
132
137
  "Certificate does not contain required qualified certificate policy OIDs"
133
138
  end
134
139
  validate_certificate_can_be_used_for_qualified_electronic_signature(certificate)
135
140
  end
136
141
 
142
+ # An ADVANCED signature certificate must still be a Smart-ID one: without this check
143
+ # any Non-Repudiation certificate from any CA in the trust store would be accepted.
144
+ def validate_non_qualified_certificate(certificate)
145
+ policy_oids = extract_certificate_policy_oids(certificate)
146
+ if policy_oids.empty?
147
+ raise SmartIdRuby::Errors::UnprocessableResponseError,
148
+ "Certificate does not have certificate policy OIDs and is not a non-qualified Smart-ID certificate"
149
+ end
150
+
151
+ return if (NON_QUALIFIED_CERTIFICATE_POLICY_OIDS - policy_oids).empty?
152
+
153
+ raise SmartIdRuby::Errors::UnprocessableResponseError,
154
+ "Certificate is not a non-qualified Smart-ID certificate"
155
+ end
156
+
137
157
  def validate_certificate_can_be_used_for_qualified_electronic_signature(certificate)
138
158
  extension = certificate.extensions.find do |ext|
139
159
  ["qcStatements", QC_STATEMENTS_EXTENSION_OID].include?(ext.oid)
@@ -27,10 +27,13 @@ module SmartIdRuby
27
27
 
28
28
  raise SmartIdRuby::Errors::UnprocessableResponseError,
29
29
  "Provided signature value does not match the calculated signature value"
30
+ # Gem errors are already precise — re-raise them before the catch-all below, or the
31
+ # "Parameter 'x' is not provided" errors from validate_inputs get rewritten into a
32
+ # generic validation failure.
33
+ rescue SmartIdRuby::Errors::Error
34
+ raise
30
35
  rescue OpenSSL::PKey::PKeyError, ArgumentError
31
36
  raise SmartIdRuby::Errors::UnprocessableResponseError, "Invalid signature algorithm parameters were provided"
32
- rescue SmartIdRuby::Errors::UnprocessableResponseError
33
- raise
34
37
  rescue StandardError
35
38
  raise SmartIdRuby::Errors::UnprocessableResponseError, "Signature value validation failed"
36
39
  end
@@ -1,9 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "openssl"
4
+
3
5
  module SmartIdRuby
4
6
  module Validation
5
7
  # Container for trust anchors and CA certificates used in validation.
8
+ #
9
+ # The gem ships SK ID Solutions' CA certificates for both environments, so the common
10
+ # cases need no setup:
11
+ #
12
+ # SmartIdRuby::Validation::TrustedCaCertStore.production
13
+ # SmartIdRuby::Validation::TrustedCaCertStore.demo
14
+ #
15
+ # `default` follows `SmartIdRuby.configuration.trusted_ca_environment` (`:production`
16
+ # unless configured otherwise) and is what the validators use when none is given.
17
+ #
18
+ # Signer certificates chain to SK's own eID CAs, which are not in any operating system
19
+ # CA bundle — that store holds TLS roots. Without one of these stores, chain validation
20
+ # of a signature response fails for every real certificate, in both environments.
6
21
  class TrustedCaCertStore
22
+ ENVIRONMENTS = %i[production demo].freeze
23
+ CERTIFICATE_ROOT = File.expand_path("../../../certificates", __dir__).freeze
24
+
7
25
  attr_reader :trust_anchors, :trusted_ca_certificates, :ocsp_enabled
8
26
 
9
27
  def initialize(trust_anchors:, trusted_ca_certificates:, ocsp_enabled: false)
@@ -15,6 +33,114 @@ module SmartIdRuby
15
33
  def ocsp_enabled?
16
34
  ocsp_enabled
17
35
  end
36
+
37
+ def empty?
38
+ trust_anchors.empty? && trusted_ca_certificates.empty?
39
+ end
40
+
41
+ class << self
42
+ # The bundled store for the configured environment.
43
+ def default(ocsp_enabled: false)
44
+ for_environment(configured_environment, ocsp_enabled: ocsp_enabled)
45
+ end
46
+
47
+ # SK's live CAs: ROOT G1E / G1R and EE Certification Centre Root CA as anchors,
48
+ # EID-Q 2024E/R, EID-NQ 2021E/R, EID-SK 2016 and NQ-SK 2016 as intermediates.
49
+ def production(ocsp_enabled: false)
50
+ for_environment(:production, ocsp_enabled: ocsp_enabled)
51
+ end
52
+
53
+ # SK's demo CAs, for sid.demo.sk.ee. Never trust these in production — they are a
54
+ # separate hierarchy and are published for testing.
55
+ def demo(ocsp_enabled: false)
56
+ for_environment(:demo, ocsp_enabled: ocsp_enabled)
57
+ end
58
+
59
+ def for_environment(environment, ocsp_enabled: false)
60
+ name = environment.to_s.to_sym
61
+ unless ENVIRONMENTS.include?(name)
62
+ raise SmartIdRuby::Errors::RequestSetupError,
63
+ "Unsupported trusted CA environment: #{environment.inspect}. " \
64
+ "Supported values are #{ENVIRONMENTS.map(&:inspect).join(", ")}"
65
+ end
66
+
67
+ anchors, intermediates = bundled_certificates(name)
68
+ new(trust_anchors: anchors, trusted_ca_certificates: intermediates, ocsp_enabled: ocsp_enabled)
69
+ end
70
+
71
+ # Build a store from a directory. Either lay it out as the bundled ones —
72
+ # `anchors/` and `intermediates/` subdirectories — or point at a flat directory of
73
+ # PEM/DER certificates, in which case self-signed ones become the anchors.
74
+ def from_directory(path, ocsp_enabled: false)
75
+ anchors_dir = File.join(path, "anchors")
76
+ intermediates_dir = File.join(path, "intermediates")
77
+
78
+ if Dir.exist?(anchors_dir) || Dir.exist?(intermediates_dir)
79
+ new(trust_anchors: read_directory(anchors_dir),
80
+ trusted_ca_certificates: read_directory(intermediates_dir),
81
+ ocsp_enabled: ocsp_enabled)
82
+ else
83
+ from_certificates(read_directory(path), ocsp_enabled: ocsp_enabled)
84
+ end
85
+ end
86
+
87
+ # Build a store from a PKCS#12 truststore (the shape SK distributes).
88
+ def from_pkcs12(path, password, ocsp_enabled: false)
89
+ p12 = OpenSSL::PKCS12.new(File.binread(path), password)
90
+ from_certificates((Array(p12.ca_certs) + [p12.certificate]).compact, ocsp_enabled: ocsp_enabled)
91
+ rescue OpenSSL::PKCS12::PKCS12Error => e
92
+ raise SmartIdRuby::Errors::RequestSetupError, "Failed to read PKCS#12 truststore '#{path}': #{e.message}"
93
+ end
94
+
95
+ # Partition a flat list: self-signed certificates are anchors, the rest
96
+ # intermediates.
97
+ def from_certificates(certificates, ocsp_enabled: false)
98
+ anchors, intermediates = Array(certificates).compact.partition { |cert| self_signed?(cert) }
99
+ new(trust_anchors: anchors, trusted_ca_certificates: intermediates, ocsp_enabled: ocsp_enabled)
100
+ end
101
+
102
+ def self_signed?(certificate)
103
+ certificate.issuer == certificate.subject
104
+ end
105
+
106
+ private
107
+
108
+ def configured_environment
109
+ configured = SmartIdRuby.configuration.trusted_ca_environment if SmartIdRuby.respond_to?(:configuration)
110
+ configured.nil? ? :production : configured.to_s.to_sym
111
+ rescue NoMethodError
112
+ :production
113
+ end
114
+
115
+ def bundled_certificates(environment)
116
+ @bundled_certificates ||= {}
117
+ @bundled_certificates[environment] ||= load_bundle(File.join(CERTIFICATE_ROOT, environment.to_s))
118
+ end
119
+
120
+ def load_bundle(base)
121
+ anchors = read_directory(File.join(base, "anchors"))
122
+ if anchors.empty?
123
+ raise SmartIdRuby::Errors::RequestSetupError,
124
+ "No bundled trust anchors found under #{base}. The gem package looks incomplete."
125
+ end
126
+
127
+ [anchors.freeze, read_directory(File.join(base, "intermediates")).freeze]
128
+ end
129
+
130
+ def read_directory(path)
131
+ return [] unless Dir.exist?(path)
132
+
133
+ Dir.glob(File.join(path, "*")).sort.filter_map { |file| read_certificate(file) }
134
+ end
135
+
136
+ def read_certificate(path)
137
+ return nil unless File.file?(path)
138
+
139
+ OpenSSL::X509::Certificate.new(File.binread(path))
140
+ rescue OpenSSL::X509::CertificateError => e
141
+ raise SmartIdRuby::Errors::RequestSetupError, "Failed to read certificate '#{path}': #{e.message}"
142
+ end
143
+ end
18
144
  end
19
145
  end
20
146
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SmartIdRuby
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart-id-ruby-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergei Tsõganov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-04-07 00:00:00.000000000 Z
11
+ date: 2026-09-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64
@@ -66,6 +66,20 @@ files:
66
66
  - LICENSE.txt
67
67
  - README.md
68
68
  - Rakefile
69
+ - certificates/demo/anchors/TEST_of_EE_Certification_Centre_Root_CA.pem.crt
70
+ - certificates/demo/anchors/TEST_of_SK_ID_Solutions_ROOT_G1E.pem.crt
71
+ - certificates/demo/anchors/TEST_of_SK_ID_Solutions_ROOT_G1R.pem.crt
72
+ - certificates/demo/intermediates/TEST_of_SK_ID_Solutions_EID-NQ_2021E.pem.crt
73
+ - certificates/demo/intermediates/TEST_of_SK_ID_Solutions_EID-Q_2024E.pem.crt
74
+ - certificates/production/anchors/EE_Certification_Centre_Root_CA.pem.crt
75
+ - certificates/production/anchors/SK_ID_Solutions_ROOT_G1E.pem.crt
76
+ - certificates/production/anchors/SK_ID_Solutions_ROOT_G1R.pem.crt
77
+ - certificates/production/intermediates/EID-SK_2016.pem.crt
78
+ - certificates/production/intermediates/NQ-SK_2016.pem.crt
79
+ - certificates/production/intermediates/SK_ID_Solutions_EID-NQ_2021E.pem.crt
80
+ - certificates/production/intermediates/SK_ID_Solutions_EID-NQ_2021R.pem.crt
81
+ - certificates/production/intermediates/SK_ID_Solutions_EID-Q_2024E.pem.crt
82
+ - certificates/production/intermediates/SK_ID_Solutions_EID-Q_2024R.pem.crt
69
83
  - lib/smart-id-ruby-client.rb
70
84
  - lib/smart_id_ruby.rb
71
85
  - lib/smart_id_ruby/callback_url.rb
@@ -79,6 +93,7 @@ files:
79
93
  - lib/smart_id_ruby/errors/document_unusable_error.rb
80
94
  - lib/smart_id_ruby/errors/error.rb
81
95
  - lib/smart_id_ruby/errors/expected_linked_session_error.rb
96
+ - lib/smart_id_ruby/errors/network_timeout_error.rb
82
97
  - lib/smart_id_ruby/errors/no_suitable_account_of_requested_type_found_error.rb
83
98
  - lib/smart_id_ruby/errors/person_should_view_smart_id_portal_error.rb
84
99
  - lib/smart_id_ruby/errors/protocol_failure_error.rb