-
-
Notifications
You must be signed in to change notification settings - Fork 567
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d985988
commit 93b063a
Showing
4 changed files
with
73 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,68 +1,74 @@ | ||
# frozen_string_literal: true | ||
|
||
module RubySaml | ||
# Formats PEM-encoded X.509 certificates and private keys to | ||
# canonical PEM format with 64-char lines and BEGIN/END headers. | ||
# Formats PEM-encoded X.509 certificates and private keys to canonical | ||
# RFC 7468 PEM format, including 64-char lines and BEGIN/END headers. | ||
# | ||
# @api private | ||
module PemFormatter | ||
extend self | ||
|
||
# Formats one or many X.509 certificate(s) to canonical | ||
# PEM format with 64-char lines and BEGIN/END headers. | ||
# Formats one or multiple X.509 certificate(s) to canonical RFC 7468 PEM format. | ||
# | ||
# @param cert [String] The original certificate(s) | ||
# @param multi [true|false] Whether to return multiple keys delimited by newline | ||
# @return [String|nil] The formatted certificate(s) | ||
# @return [String|nil] The formatted certificate(s). Returns nil if the input is nil/blank, | ||
# and returns the original string if it contains non-ASCII characters. | ||
def format_cert(cert, multi: false) | ||
detect_pems(cert, 'CERTIFICATE', multi: multi) do |pem| | ||
format_cert_single(pem) | ||
end | ||
detect_and_format_pems(cert, 'CERTIFICATE', multi: multi) | ||
end | ||
|
||
# Formats one or many private key(s) to canonical PEM format | ||
# with 64-char lines and BEGIN/END headers. | ||
# Formats one or multiple private key(s) to canonical RFC 7468 PEM format. | ||
# | ||
# @param key [String] The original private key(s) | ||
# @param multi [true|false] Whether to return multiple keys delimited by newline | ||
# @return [String|nil] The formatted private key(s) | ||
# @return [String|nil] The formatted private key(s). Returns nil if the input is nil/blank, | ||
# and returns the original string if it contains non-ASCII characters. | ||
def format_private_key(key, multi: false) | ||
detect_pems(key, '(?:RSA|DSA|EC|ECDSA) PRIVATE KEY', multi: multi) do |pem| | ||
format_private_key_single(pem) | ||
end | ||
detect_and_format_pems(key, 'PRIVATE KEY', %w[RSA ECDSA EC DSA], multi: multi) | ||
end | ||
|
||
private | ||
|
||
def detect_pems(str, label, multi: false, &block) | ||
return if str.nil? || str.empty? | ||
return str unless str.ascii_only? | ||
return if str.match?(/\A\s*\z/) | ||
def detect_and_format_pems(str, label, known_prefixes = nil, multi: false) | ||
return str unless str&.ascii_only? | ||
|
||
pems = str.scan(/-{5}\s*BEGIN #{label}\s*-{5}.*?-{5}\s*END #{label}\s*-{5}?/m).map(&block) | ||
return if !str || str.strip.empty? | ||
|
||
# Try to format the original string if no pems were found | ||
return yield(str) if pems.empty? | ||
# Scan the string for PEMs and format each one | ||
pems = str.scan(pem_scan_regexp(label)).map { |pem| format_pem(pem, label, known_prefixes) } | ||
|
||
# Try to format the original string if no PEMs were found | ||
return format_pem(str, label, known_prefixes) if pems.empty? | ||
|
||
multi ? pems.join("\n") : pems.first | ||
end | ||
|
||
def format_cert_single(cert) | ||
format_pem(cert, 'CERTIFICATE') | ||
def pem_scan_regexp(label) | ||
/-{5}\s*BEGIN (?:[A-Z\d] )*#{label}\s*-{5}.*?-{5}\s*END (?:[A-Z\d] )*#{label}\s*-{5}/m | ||
end | ||
|
||
# Given a PEM, a label such as "PRIVATE KEY", and a list of known prefixes | ||
# such as "RSA", "DSA", etc., returns the formatted PEM preserving the known | ||
# prefix if possible. | ||
def format_pem(pem, label, known_prefixes = nil) | ||
detected_prefix = detect_label_prefix(pem, label, known_prefixes) | ||
prefixed_label = "#{detected_prefix}#{label}" | ||
"-----BEGIN #{prefixed_label}-----\n#{format_pem_body(pem)}\n-----END #{prefixed_label}-----" | ||
end | ||
|
||
def format_private_key_single(key) | ||
algo = key.match(/((?:RSA|ECDSA|EC|DSA) )PRIVATE KEY/)&.[](1) | ||
label = "#{algo}PRIVATE KEY" | ||
format_pem(key, label) | ||
# Given a PEM, a label such as "PRIVATE KEY", and a list of known prefixes | ||
# such as "RSA", "DSA", etc., detects and returns the known prefix if it exists. | ||
def detect_label_prefix(pem, label, known_prefixes) | ||
return unless known_prefixes && !known_prefixes.empty? | ||
|
||
pem.match(/((?:#{Array(known_prefixes).join('|')}) )#{label}/)&.[](1) | ||
end | ||
|
||
# Strips all whitespace and the BEGIN/END lines, | ||
# then splits the string into 64-character lines, | ||
# and re-applies BEGIN/END labels | ||
def format_pem(str, label) | ||
str = str.gsub(/\s|-{5}\s*(BEGIN|END) [A-Z\d\s]+-{5}/, '').scan(/.{1,64}/).join("\n") | ||
"-----BEGIN #{label}-----\n#{str}\n-----END #{label}-----" | ||
# Given a PEM, strips all whitespace and the BEGIN/END lines, | ||
# then splits the body into 64-character lines. | ||
def format_pem_body(pem) | ||
pem.gsub(/\s|-{5}\s*(BEGIN|END) [A-Z\d\s]+-{5}/, '').scan(/.{1,64}/).join("\n") | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters