Commits (13)
......@@ -31,6 +31,8 @@ pages:
- apt-get update && apt-get install -y puppet-strings puppet
- puppet strings generate
- mv doc public
only:
- master
artifacts:
paths:
- public
......@@ -4,12 +4,22 @@ All notable changes to this project will be documented in this file. The format
is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this
project adheres to [Semantic Versioning](http://semver.org).
## [2.1.0](https://gitlab.com/shared-puppet-modules-group/tor/-/tags/2.1.0) (2020-05-10)
### Fixed
- Migrate functions to Puppet 4.x syntax
[Full Changelog](https://gitlab.com/shared-puppet-modules-group/tor/-/compare/2.0.1...2.1.0)
## [2.0.1](https://gitlab.com/shared-puppet-modules-group/tor/-/tags/2.0.1) (2020-05-07)
### Fixed
- Fix upstream apt pinning by adding a negative pin
[Full Changelog](https://gitlab.com/shared-puppet-modules-group/tor/-/compare/2.0.0...2.0.1)
## [2.0.0](https://gitlab.com/shared-puppet-modules-group/tor/-/tags/2.0.0) (2020-04-27)
This module has been extensively re-written for the 2.0.0 version. Even though
......
# @summary
# Generates or loads an RSA private key for v2 onion service.
#
# It will also store the onion address under /tmp/my_secret_key.hostname. If
# /tmp/my_secret_key.key exists, but not the hostname file. Then the function
# will be loaded and the onion address will be generated from it.
#
# The private key either exists under the supplied path/key_identifier or is
# being generated on the fly and stored under that path for the next
# execution.
#
# @example
# res = tor::generate_onion_key('/tmp','my_secret_key')
# notice "Onion Address: ${res[0]}"
# notice "Private Key: ${res[1]}"
#
Puppet::Functions.create_function(:'tor::generate_onion_key') do
#
# @param path
# A path (on the puppet master) to load and store the private key from.
#
# @param secret_key
# An identifier, which will be used as a filename in the location.
#
# @return [Array]
# The onion address and the private key content.
#
dispatch :default_impl do
repeated_param 'Any', :args
end
def default_impl(*args)
location = args.shift
identifier = args.shift
raise(Puppet::ParseError, "tor::generate_onion_key(): requires 2 arguments") unless [location,identifier].all?{|i| !i.nil? }
raise(Puppet::ParseError, "tor::generate_onion_key(): requires location (#{location}) to be a directory") unless File.directory?(location)
path = File.join(location,identifier)
private_key = if File.exists?(kf="#{path}.key")
pk = OpenSSL::PKey::RSA.new(File.read(kf))
raise(Puppet::ParseError, "tor::generate_onion_key(): key in path #{kf} must have a length of 1024bit") unless (pk.n.num_bytes * 8) == 1024
pk
else
# 1024 is hardcoded by tor
pk = OpenSSL::PKey::RSA.generate(1024)
File.open(kf,'w'){|f| f << pk.to_s }
pk
end
onion_address = if File.exists?(hf="#{path}.hostname")
File.read(hf)
else
oa = call_function('tor::onion_address', private_key)
File.open(hf,'w'){|f| f << oa.to_s }
oa
end
[ onion_address, private_key.to_s ]
end
end
# @summary
# Generates a v2 onion address from a 1024-bit RSA private key.
#
# @example
# tor::onion_address("-----BEGIN RSA PRIVATE KEY-----
# MII....
# -----END RSA PRIVATE KEY-----")
#
# @return [String]
# The onion address for that key, *without* the .onion suffix.
#
require 'base32'
module Puppet::Parser::Functions
newfunction(:onion_address, :type => :rvalue, :doc => <<-EOS
Generates an onion address from a 1024-bit RSA private key.
Example:
Puppet::Functions.create_function(:'tor::onion_address') do
dispatch :default_impl do
repeated_param 'Any', :args
end
onion_address("-----BEGIN RSA PRIVATE KEY-----
MII....
-----END RSA PRIVATE KEY-----")
Returns the onionadress for that key, *without* the .onion suffix.
EOS
) do |args|
def default_impl(*args)
key = args.shift
raise(Puppet::ParseError, "onion_address(): requires 1 argument") unless key && args.empty?
raise(Puppet::ParseError, "tor::onion_address(): requires 1 argument") unless key && args.empty?
private_key = key.is_a?(OpenSSL::PKey::RSA) ? key : OpenSSL::PKey::RSA.new(key)
# the onion address are a base32 encoded string of the first half of the sha1 over the
# der format of the public key
# the onion address are a base32 encoded string of the first half of the
# sha1 over the der format of the public key
# https://trac.torproject.org/projects/tor/wiki/doc/HiddenServiceNames#Howare.onionnamescreated
# We can skip the first 22 bits of the der format as they are ignored by tor
# https://timtaubert.de/blog/2014/11/using-the-webcrypto-api-to-generate-onion-names-for-tor-hidden-services/
# https://gitweb.torproject.org/torspec.git/tree/rend-spec.txt#n525
# Except for Ruby 1.8.7 where the first 22 are not present at all
start = RUBY_VERSION.to_f < 1.9 ? 0 : 22
public_key_der = private_key.public_key.to_der
Base32.encode(Digest::SHA1.digest(public_key_der[start..-1]))[0..15].downcase
Base32.encode(Digest::SHA1.digest(public_key_der[22..-1]))[0..15].downcase
end
end
module Puppet::Parser::Functions
newfunction(:generate_onion_key, :type => :rvalue, :doc => <<-EOS
Generates or loads a rsa private key for an onion service, returns they onion
onion address and the private key content.
Requires a location to load and store the private key, as well an identifier, which will be used as a filename in the location.
Example:
res = generate_onion_key('/tmp','my_secret_key')
notice "Onion Address: \${res[0]"
notice "Priavte Key: \${res[1]"
It will also store the onion address under /tmp/my_secret_key.hostname.
If /tmp/my_secret_key.key exists, but not the hostname file. Then the function will be loaded and the onion address will be generated from it.
EOS
) do |args|
location = args.shift
identifier = args.shift
raise(Puppet::ParseError, "generate_onion_key(): requires 2 arguments") unless [location,identifier].all?{|i| !i.nil? }
raise(Puppet::ParseError, "generate_onion_key(): requires location (#{location}) to be a directory") unless File.directory?(location)
path = File.join(location,identifier)
private_key = if File.exists?(kf="#{path}.key")
pk = OpenSSL::PKey::RSA.new(File.read(kf))
raise(Puppet::ParseError, "generate_onion_key(): key in path #{kf} must have a length of 1024bit") unless (pk.n.num_bytes * 8) == 1024
pk
else
# 1024 is hardcoded by tor
pk = OpenSSL::PKey::RSA.generate(1024)
File.open(kf,'w'){|f| f << pk.to_s }
pk
end
onion_address = if File.exists?(hf="#{path}.hostname")
File.read(hf)
else
oa = function_onion_address([private_key])
File.open(hf,'w'){|f| f << oa.to_s }
oa
end
[ onion_address, private_key.to_s ]
end
end
......@@ -67,11 +67,11 @@ define tor::daemon::onion_service(
fail('Either private_key OR (private_key_name AND private_key_store_path) must be set, but not all three of them')
}
if $private_key_store_path and $private_key_name {
$tmp = generate_onion_key($private_key_store_path,$private_key_name)
$tmp = tor::generate_onion_key($private_key_store_path,$private_key_name)
$os_hostname = $tmp[0]
$real_private_key = $tmp[1]
} else {
$os_hostname = onion_address($private_key)
$os_hostname = tor::onion_address($private_key)
$real_private_key = $private_key
}
include ::tor::daemon::params
......
{
"name": "smash-tor",
"version": "2.0.1",
"version": "2.1.0",
"author": "SMASH",
"summary": "Installs, configures and manages Tor",
"license": "GPL-3.0",
......
require 'spec_helper'
require 'fileutils'
describe 'generate_onion_key' do
describe 'tor::generate_onion_key' do
before(:all) do
@tmp_path = File.expand_path(File.join(File.dirname(__FILE__),'..','fixtures','tmp'))
@test_path = File.join(@tmp_path,'test.key')
......@@ -44,7 +44,7 @@ znq+qT/KbJlwy/27X/auCAzD5rJ9VVzyWiu8nnwICS8=
FileUtils.rm_rf(@tmp_path) if File.exists?(@tmp_path)
end
let(:return_value) {
scope.function_generate_onion_key([@tmp_path,'test'])
call_function('tor::generate_onion_key', @tmp_path, 'test')
}
context 'without an existing key' do
it 'returns an onion address and a key ' do
......@@ -54,13 +54,13 @@ znq+qT/KbJlwy/27X/auCAzD5rJ9VVzyWiu8nnwICS8=
expect(return_value.last).to be_eql(File.read(File.join(@tmp_path,'test.key')))
end
it 'returns a proper onion address' do
expect(return_value.first).to be_eql(scope.function_onion_address([File.read(File.join(@tmp_path,'test.key'))]))
expect(return_value.first).to be_eql(call_function('tor::onion_address', File.read(File.join(@tmp_path,'test.key'))))
end
it 'does not recreate a key once created' do
expect(scope.function_generate_onion_key([@tmp_path,'test'])).to be_eql(scope.function_generate_onion_key([@tmp_path,'test']))
expect(call_function('tor::generate_onion_key', @tmp_path, 'test')).to be_eql(call_function('tor::generate_onion_key', @tmp_path, 'test'))
end
it 'creates to different keys for different names' do
expect(scope.function_generate_onion_key([@tmp_path,'test']).first).to_not be_eql(scope.function_generate_onion_key([@tmp_path,'test2']))
expect(call_function('tor::generate_onion_key', @tmp_path, 'test').first).to_not be_eql(call_function('tor::generate_onion_key', @tmp_path, 'test2'))
end
end
context 'with an existing key' do
......
require 'spec_helper'
describe 'onion_address' do
describe 'tor::onion_address' do
describe 'signature validation' do
it { is_expected.not_to eq(nil) }
it { is_expected.to run.with_params().and_raise_error(Puppet::ParseError, /requires 1 argument/) }
......