NemID
NemID is a Ruby gem integrating the NemID JavaScript client with Ruby applications.
Note
NemID is expected to be phased out on October 31st, 2023 1. The repository is therefore archived.
Installation
Add this line to your application's Gemfile:
gem 'nemid'
And then execute:
$ bundle install
Or install it yourself as:
$ gem install nemid
Configuration
This gem can be configured through the following block:
NemID.configure do |config|
# NemID has two URLs to call the PID-CPR service. The env configuration determines
# which should be used. Production URL (PR) is used when the value is 'production' or
# 'staging'. Other cases will use the pre-production (PP) URL.
# Defaults to rails environment (if defined), to RACK_ENV, or to 'development'
config.env = "production"
# Your OCES Certificate in PEM format.
config.oces_cert = "abc"
# Your Private Key in PEM format.
config.private_key = "def"
# Your SPID
config.spid = "ghi"
end
Check the end of this readme to know how to get your OCES certificate and Private Key in PEM format.
Usage
This gem implements the following modules:
-
Authentication:
generate client initialization parameters and response handling. -
PIDCPR:
match PID to a CPR number. Match and translation are available only to selected service providers. -
OCSP:
use this if you want to manually perform an OCSP request.
Authentication::Params
Generate client initialization parameters. See here if you do not know how to get your certificate and private key in pem format.
nemid = NemID::Authentication::Params.new(
cert: 'your_voces_certificate_in_pem_format',
key: 'your_private_key_in_pem_format',
)
nemid.client_initialization_parameters # ruby hash with signed parameters
Authentication::Response
Parse and validate NemID response, then export user information from certificate. As of this version, it is only possible to export the PID (or RID).
response = NemID::Authentication::Response.new(base64_str) # Base64 string from NemID
# or
response = NemID::Authentication::Response.new(xml_str) # XML string from NemID
# First, validate NemID response, as stated in NemID Documentation
begin
response.validate_response
rescue NemID::Errors::ResponseValidationError => e
puts e # Developer-friendly message, example: Signature is invalid.
end
# Note that response.validate_response raises exceptions instead of returning true or false, the exceptions are
# raised according to the order that the methods are invoked. The following methods perform the same validations
# and do not raise exceptions:
response.valid_signature? # true
response.valid_certificate_chain? # true
response.user_certificate_expired? # false
response.user_certificate_revoked? # false
# If response is valid, then proceed to extract user information:
# Extract PID or RID
response.extract_pid_or_rid # "PID:9208-2002-2-316380231171"
# Has PID?
response.has_pid? # true
# Extract PID
response.extract_pid # "9208-2002-2-316380231171"
# Has RID?
response.has_rid? # false
# Extract RID
response.extract_rid # nil
PIDCPR
Match a PID to a CPR number.
pid_cpr = NemID::PIDCPR.new(
cert: 'your_voces_certificate_in_pem_format',
key: 'your_private_key_in_pem_format',
spid: 'your_service_provider_id'
)
pid_cpr.match(pid: '9208-2002-2-316380231171', cpr: '2205943423')
# Expected result - success
{:cpr=>"2205943423", :pid=>"9208-2002-2-316380231171", :id=>nil, :redir_url=>nil, :status_code=>"0", :status_text_dk=>"OK", :status_text_uk=>"OK"}
# Expected result - failure ( status_code and status_text varies on error occuring )
{:cpr=>"123", :pid=>"9208-2002-2-316380231171", :id=>nil, :redir_url=>nil, :status_code=>"1", :status_text_dk=>"CPR svarer ikke til PID", :status_text_uk=>"CPR does not match PID"}
# To complete:
# - how is the error going to be handled? raising a class error?
OCSP
An OCSP request is performed when the .validate_response
or .user_certificate_revoked?
methods are invoked. If you wish to perform the OCSP request yourself, to catch specific OCSP errors, you can do it this way:
user_certificate = OpenSSL::X509::Certificate.new(raw) # User's certificate
issuer = OpenSSL::X509::Certificate.new(raw) # The issuer of the user's certificate (Normally the intermediate certificate)
ca = OpenSSL::X509::Certificate.new(raw) # Certificate Authority
ocsp = NemID::OCSP
ocsp.request(
subject: user_certificate,
issuer: issuer,
ca: ca
) # Returns +true+ if the certificate status is revoked or unknown, +false+ if the certificate status is OK.
# Catching OCSP errors:
# This implementation raises the following OCSP errors, be sure to catch them so the execution of your program is not interrupted.
begin
ocsp.request(
subject: user_certificate,
issuer: issuer,
ca: ca
)
rescue NemID::OCSP::Error
# Catches all OCSP errors
rescue NemID::OCSP::InvalidSignatureError => e
# If you get a failure here you may be missing the intermediate certificates.
puts e # Response is not signed by a trusted certificate
rescue NemID::OCSP::NoStatusError => e
# Means that we could not extract the status information for the certificate from the basic response
puts e # basic_response does not have the status for the certificate
rescue NemID::OCSP::InvalidUpdateError => e
# A status issued in the future must be rejected.
puts e # this_update is in the future or next_update time has passed
rescue NemID::OCSP::NonceError => e
# Adding a nonce to the request protects against replay attacks but not all CA process the nonce.
# Therefore, this implementation only checks if the both nonces are present and not equal
puts e # Nonces both present and not equal
end
Exporting Certificate and Private Key
To be able to export the certificate and the key, you will be prompted the password that NemID used to encrypt the p12 archive. It should have been sent to you together with the p12 file.
Exporting the certificate:
# Replace <filename.p12> with the file name of the p12 that NemID sent to you
$ openssl pkcs12 -in <filename.p12> -clcerts -nokeys | openssl x509 -out cert.cer
Exporting the key:
# Replace <filename.p12> with the file name of the p12 that NemID sent to you
$ openssl pkcs12 -in <filename.p12> -nocerts -nodes | openssl pkcs8 -nocrypt -out private_key.key
After you export both files, you will need to manually replace the newlines with \n
, in both.
If everything went well, you should have two one-line strings that look like this (but longer):
-----BEGIN CERTIFICATE-----\nMIIGETCCBPmgAwIBAgIEX(a-lot-of-alphanumeric-characters-and-\n)wIBAgIE\n-----END CERTIFICATE-----\n
and
-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8fX6t4Hkhxl+FM=\n-----END PRIVATE KEY-----\n
Notice the trailing \n, it is very important that you include it.
If you get an error like (nested asn1 error)
, it means that you have done something wrong when editing the file. Try exporting again and carefully replace the newlines with \n.
Keep these files private to you, use environment variables!
Development
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and tags, and push the .gem
file to rubygems.org.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/davideluque/nemid. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the NemID project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.