Commits

Michael Granger committed 855e26d

Finished up initial implementation.

Comments (0)

Files changed (22)

 \.env$
 \.sqlite$
+coverage/
+ChangeLog
+logs/
 # .rvm.gems generated gem export file. Note that any env variable settings will be missing. Append these after using a ';' field separator
 
+simplecov -v0.6.4
 configurability -v1.2.0
 timecop -v0.5.1
-strelka -v0.0.1.pre.314
+strelka -v0.0.1.pre.321
 
-## 0.0.1 [2012-05-17] Michael Granger <ged@FaerieMUD.org>
+== v0.0.1 [2012-09-28] Michael Granger <ged@FaerieMUD.org>
 
 Initial release.
 
+ChangeLog
 History.rdoc
+Manifest.txt
+Procfile
 README.rdoc
 Rakefile
-data/strelka/_auth_token/apps/strelka/_auth_token_app
-data/strelka/_auth_token/templates/layout.tmpl
-data/strelka/_auth_token/templates/top.tmpl
-lib/strelka/_auth_token.rb
-spec/strelka/_auth_token_spec.rb
+example/apps/demo
+example/demo-config.yml
+example/gen-config.rb
+example/templates/authtoken/form.tmpl
+example/templates/authtoken/layout.tmpl
+example/templates/authtoken/success.tmpl
+lib/strelka/authprovider/authtoken.rb
+lib/strelka/scscookie.rb
+spec/lib/helpers.rb
+spec/strelka/authprovider/authtoken_spec.rb
+spec/strelka/scscookie_spec.rb
 # Foreman procfile
-mongrel2: m2sh.rb -c mongrel2.sqlite start
-demo: strelka -l debug -c datas/strelka-authtoken/demo-config.yml start demo
+mongrel2: m2sh.rb -c example/mongrel2.sqlite start
+demo: strelka -D example -l debug -c example/demo-config.yml start demo
 
 #!/usr/bin/env rake
 
+require 'rake/clean'
+
 begin
 	require 'hoe'
 rescue LoadError
 	abort "This Rakefile requires 'hoe' (gem install hoe)"
 end
 
-# Sign gems
+Hoe.plugin :mercurial
 Hoe.plugin :signing
+Hoe.plugin :deveiate
 
-Hoe.spec 'Strelka-AuthToken' do
+Hoe.plugins.delete :rubyforge
+
+hoespec = Hoe.spec 'strelka-authprovider-authtoken' do
 	self.readme_file = 'README.rdoc'
 	self.history_file = 'History.rdoc'
 	self.extra_rdoc_files = FileList[ '*.rdoc' ]
 
-	self.developer 'FIX', 'FIX' # (name, email)
+	self.developer 'Michael Granger', 'ged@FaerieMUD.org'
 
-	self.dependency 'strelka', '~> 0.1'
-	self.dependency 'rspec', '~> 2.7', :developer
+	self.dependency 'configurability', '~> 1.2'
+	self.dependency 'loggability',     '~> 0.5'
+	self.dependency 'strelka',         '~> 0.0'
 
-	self.require_ruby_version( '~> 1.9' )
+	self.dependency 'hoe-deveiate',    '~> 0.1', :developer
+	self.dependency 'simplecov',       '~> 0.6', :developer
+	self.dependency 'timecop',         '~> 0.5', :developer
+
+	self.spec_extras[:licenses] = ["BSD"]
+	self.spec_extras[:rdoc_options] = [
+		'-f', 'fivefish',
+		'-t', 'Strelka AuthToken Auth Provider Plugin',
+		'-w', '4',
+	]
+	self.require_ruby_version( '>=1.9.3' )
+	self.hg_sign_tags = true if self.respond_to?( :hg_sign_tags= )
+	self.check_history_on_release = true if self.respond_to?( :check_history_on_release= )
+
+	self.rdoc_locations << "deveiate:/usr/local/www/public/code/#{remote_rdoc_dir}"
 end
 
+ENV['VERSION'] ||= hoespec.spec.version.to_s
+
+# Ensure the specs pass before checking in
+task 'hg:precheckin' => [ :check_history, :check_manifest, :spec ]
+
+
+desc "Build a coverage report"
+task :coverage do
+	ENV["COVERAGE"] = 'yes'
+	Rake::Task[:spec].invoke
+end
+
+if Rake::Task.task_defined?( '.gemtest' )
+	Rake::Task['.gemtest'].clear
+	task '.gemtest' do
+		$stderr.puts "Not including a .gemtest until I'm confident the test suite is idempotent."
+	end
+end
+

data/strelka-authtoken/apps/demo

-#!/usr/bin/env ruby
-
-require 'strelka'
-
-# An application that demonstrates the 'authtoken' auth provider for Strelka apps.
-class AuthTokenDemo < Strelka::App
-
-	# The Strelka appid
-	ID = 'authtoken-demo'
-
-
-	# Auth plugin
-	plugin :auth
-	auth_provider :authtoken
-	require_auth_for '/restricted'
-
-
-	# Templating plugin
-	plugin :templating
-	layout 'authtoken/layout.tmpl'
-	templates form: 'authtoken/form.tmpl',
-	          success: 'authtoken/success.tmpl'
-
-
-	# Routing plugin
-	plugin :routing
-	get do |req|
-		return :form
-	end
-
-	get 'restricted' do |req|
-		return :success
-	end
-
-end # class AuthTokenDemo
-
-AuthTokenDemo.run if $0 == __FILE__
-

data/strelka-authtoken/demo-config.yml

----
-mongrel2:
-  configdb: mongrel2.sqlite
-
-logging:
-  __default__: debug (color)
-  inversion: info(color)
-

data/strelka-authtoken/gen-config.rb

-# -*- ruby -*-
-# vim: set nosta noet ts=4 sw=4:
-#encoding: utf-8
-
-# The Mongrel config used by the demo app.
-#
-#   m2sh.rb -c mongrel2.sqlite load data/strelka-authtoken/gen-config.rb
-#
-
-require 'strelka'
-require 'mongrel2'
-require 'mongrel2/config/dsl'
-
-Strelka.load_config( 'data/strelka-authtoken/demo-config.yml' )
-
-# samples server
-server 'demo' do
-
-	name         'Strelka AuthToken Demo'
-	default_host 'localhost'
-
-	access_log   '/logs/access.log'
-	error_log    '/logs/error.log'
-	chroot       '/var/mongrel2'
-	pid_file     '/run/mongrel2.pid'
-
-	bind_addr    '127.0.0.1'
-	port         8118
-
-	host 'localhost' do
-		route '/', handler( 'tcp://127.0.0.1:9818', 'authtoken-demo' )
-	end
-
-end
-
-setting "zeromq.threads", 1
-
-mkdir_p 'var'
-mkdir_p 'run'
-mkdir_p 'logs'
-

data/strelka-authtoken/templates/authtoken/form.tmpl

-<?import request ?>
-
-
-<!-- $Id$ -->
-<form action="[?urlencode request.uri ?]" method="get" accept-charset="utf-8">
-	<label for="username">Username</label>
-	<input type="text" name="username" value="" id="username-field">
-
-	<label for="password">Password</label>
-	<input type="password" name="password" value="" id="password-field">
-
-	<p><input type="submit" value="Authenticate &rarr;"></p>
-</form>
-

data/strelka-authtoken/templates/authtoken/layout.tmpl

-<!DOCTYPE html>
-<html lang="en">
-<head>
-	<meta charset="utf-8">
-	<title><?subscribe title || Demo ?></title>
-
-	<style type="text/css" media="screen">
-		html {
-			background: #eee;
-			color: #333;
-			margin: 0;
-			padding: 4em 2em;
-			font-family: sans-serif;
-		}
-	</style>
-</head>
-<body>
-
-	<header>
-		<h1><?subscribe title || Demo ?></h1>
-	</header>
-
-	<section id="body"><?attr body ?></section>
-
-	<footer>
-		<p>Copyright &copy; 2012, Michael Granger</p>
-	</footer>
-
-</body>
-</html>
-

data/strelka-authtoken/templates/authtoken/success.tmpl

-<p><strong>Success!</strong></p>

example/apps/demo

+#!/usr/bin/env ruby
+
+require 'openssl'
+require 'strelka'
+require 'configurability'
+
+
+# An application that demonstrates the 'authtoken' auth provider for Strelka apps.
+class AuthTokenDemo < Strelka::App
+	extend Configurability,
+	       Strelka::MethodUtilities
+
+
+	# Configurability API -- use the 'demo' section of the config
+	config_key :demo
+
+	# Default config -- use a random key and a 'demo:demo' account
+	DEFAULT_KEY = OpenSSL::Random.random_bytes( 16 ).unpack( 'H*' ).first
+	CONFIG_DEFAULTS = {
+		key: DEFAULT_KEY,
+		users: {
+			demo: OpenSSL::HMAC.hexdigest('sha1', DEFAULT_KEY, 'demo'),
+		}
+	}
+
+
+	### Configurability API -- Configure the demo app
+	def self::configure( config=nil )
+		config ||= CONFIG_DEFAULTS
+
+		self.log.debug "Configuring the demo app: %p" % [ config ]
+		@key   = [config[:key]].pack('H*') || [CONFIG_DEFAULTS[:key]].pack('H*')
+		@users = config[:users] || CONFIG_DEFAULTS[:users]
+	end
+
+
+	##
+	# Provide readers for configured users and the key
+	singleton_attr_reader :users, :key
+
+
+	# The Strelka appid
+	ID = 'authtoken-demo'
+
+	default_type 'text/html'
+
+
+	#
+	# Auth
+	#
+	plugin :auth
+	require_auth_for '/restricted'
+
+
+	#
+	# Templating
+	#
+	plugin :templating
+	layout 'authtoken/layout.tmpl'
+	templates form: 'authtoken/form.tmpl',
+	          success: 'authtoken/success.tmpl'
+
+	#
+	# Error-handling
+	#
+	plugin :errors
+
+	# Handle AUTH_REQUIRED errors by showing the authentication form
+	on_status HTTP::AUTH_REQUIRED, :form
+
+
+	#
+	# Parameters
+	#
+	plugin :parameters
+
+	param :username, /^[[:alnum:]]\w+$/
+	param :password, /^[\x20-\x7f]+$/
+
+
+	#
+	# Routing
+	#
+	plugin :routing
+
+	# Base action authenticates if parameters are present, shows the form otherwise
+	get do |req|
+		# If there were malformed credentials, show the login form with a message
+		if !req.params.okay?
+			form = self.template( :form )
+			form.errors = req.params.error_messages
+			return form
+
+		# Try to authenticate if there's a valid username
+		elsif req.params[:username]
+			self.check_authentication( req )
+			return req.redirect( req.header.referer )
+
+		# Otherwise, just show the auth form
+		else
+			return :form
+		end
+	end
+
+
+	# This action requires authentication
+	get 'restricted' do |req|
+		return :success
+	end
+
+
+	#
+	# Utility Methods
+	#
+
+	### Check authentication parameters in the specified +req+ against configured users,
+	### and add an authtoken to the response if authentication succeeds.
+	def check_authentication( req )
+		username = req.params[:username]
+		password = req.params[:password]
+
+		unless hmac = self.class.users[ username ]
+			self.log.error "Auth failure: no such user %p" % [ username ]
+			finish_with( HTTP::AUTH_REQUIRED, "authentication failure" )
+		end
+
+		pw_hmac = OpenSSL::HMAC.hexdigest( 'sha1', self.class.key, password )
+		self.log.debug "  hash of 'demo' is: %p" % [ OpenSSL::HMAC.hexdigest('sha1', self.class.key, 'demo') ]
+
+		unless hmac == pw_hmac
+			self.log.error "Auth failure: password digests don't match: expected %p, got %p" %
+				[ hmac, pw_hmac ]
+			finish_with( HTTP::AUTH_REQUIRED, "authentication failure" )
+		end
+
+		# Tell the auth provider that the user provided valid credentials
+		self.auth_provider.auth_succeeded( req, username )
+
+		return username
+	end
+
+
+	### Syntax sugar to allow returning 'false' while logging a reason for doing so.
+	### Log a message at 'info' level and return false.
+	def log_failure( reason )
+		self.log.warn "Auth failure: %s" % [ reason ]
+		header = "Basic realm=%s" % [ self.class.realm || self.app.conn.app_id ]
+		finish_with( HTTP::AUTH_REQUIRED, "Requires authentication.", www_authenticate: header )
+	end
+
+end # class AuthTokenDemo
+
+Encoding.default_internal = Encoding::UTF_8
+AuthTokenDemo.run if $0 == __FILE__
+

example/demo-config.yml

+---
+mongrel2:
+  configdb: example/mongrel2.sqlite
+
+logging:
+  __default__: debug (color)
+  inversion: info (color)
+
+auth:
+  provider: authtoken
+
+authtoken:
+  cookie_name: auth
+  realm: AuthToken Demo
+
+# Configure the SCS cookie class
+# These are the defaults; they're just here for documentation
+scs:
+  cipher_type: 'aes-128-cbc'
+  digest_type: 'sha1'
+  block_size: 16
+  framing_byte: '|'
+  max_session_age: 3600
+  compression: false
+
+demo:
+  key: 9a07c0a019bfd7450aa6110ac52b4be4
+  users:
+    demo: 64decdcb776fb210103123fc732b5ead77bdcf8b
+
+templates:
+  template_paths:
+    - example/templates
+

example/gen-config.rb

+# -*- ruby -*-
+# vim: set nosta noet ts=4 sw=4:
+#encoding: utf-8
+
+# The Mongrel config used by the demo app.
+#
+#   m2sh.rb -c example/mongrel2.sqlite load example/gen-config.rb
+#
+
+
+# samples server
+server 'demo' do
+
+	name         'Strelka AuthToken Demo'
+	default_host 'localhost'
+
+	chroot       '.'
+	access_log   '/logs/access.log'
+	error_log    '/logs/error.log'
+	pid_file     '/run/mongrel2.pid'
+
+	bind_addr    '127.0.0.1'
+	port         8118
+
+	host 'localhost' do
+		route '/', handler( 'tcp://127.0.0.1:9818', 'authtoken-demo' )
+	end
+
+end
+
+setting "zeromq.threads", 1
+
+mkdir_p 'var'
+mkdir_p 'run'
+mkdir_p 'logs'
+

example/templates/authtoken/form.tmpl

+<?import request ?>
+
+
+<!-- $Id$ -->
+<form action="[?call request.base_uri ?]" method="get" accept-charset="utf-8">
+	<label for="username">Username</label>
+	<input type="text" name="username" value="" id="username-field">
+
+	<label for="password">Password</label>
+	<input type="password" name="password" value="" id="password-field">
+
+	<p><input type="submit" value="Authenticate &rarr;"></p>
+</form>
+

example/templates/authtoken/layout.tmpl

+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="utf-8">
+	<title><?subscribe title || Demo ?></title>
+
+	<style type="text/css" media="screen">
+		html {
+			background: #eee;
+			color: #333;
+			margin: 0;
+			padding: 4em 2em;
+			font-family: sans-serif;
+		}
+	</style>
+</head>
+<body>
+
+	<header>
+		<h1><?subscribe title || Demo ?></h1>
+	</header>
+
+	<section id="body"><?attr body ?></section>
+
+	<footer>
+		<p>Copyright &copy; 2012, Michael Granger</p>
+	</footer>
+
+</body>
+</html>
+

example/templates/authtoken/success.tmpl

+<?import request ?>
+
+<p><strong>Success!</strong></p>
+
+<p>The auth cookie looks like: <tt><?call request.response.cookies.first.inspect ?></tt></p>
+

lib/strelka/authprovider/authtoken.rb

 require 'strelka/app' unless defined?( Strelka::App )
 require 'strelka/authprovider'
 require 'strelka/mixins'
+require 'strelka/scscookie'
+
 
 # AuthToken authentication provider for Strelka applications.
 #
 	config_key :authtoken
 
 
+	# Library version constant
+	VERSION = '0.0.1'
+
+	# Version-control revision constant
+	REVISION = %q$Revision$
+
+
 	# Default configuration
 	CONFIG_DEFAULTS = {
 		cookie_name:  'strelka-authtoken',
 		realm:        nil,
-		users:        [],
 	}.freeze
 
 
 	@cookie_name = CONFIG_DEFAULTS[:cookie_name]
 
 	##
-	# The Hash of users and their SHA1+Base64'ed passwords
-	singleton_attr_accessor :users
-	@users = CONFIG_DEFAULTS[:users]
-
-	##
 	# The authentication realm
 	singleton_attr_accessor :realm
 	@realm = CONFIG_DEFAULTS[:realm]
 
 	### Configurability API -- configure the auth provider instance.
 	def self::configure( config=nil )
-		if config
-			self.log.debug "Configuring AuthToken authprovider: %p" % [ config ]
-			self.cookie_name  = config[:cookie_name]
-			self.realm        = config[:realm]
-			self.users        = config[:users]
-		else
-			self.log.debug "Configuring AuthToken authprovider with default"
-			self.cookie_name  = CONFIG_DEFAULTS[:cookie_name]
-			self.realm        = CONFIG_DEFAULTS[:realm]
-			self.users        = CONFIG_DEFAULTS[:users]
-		end
+		config ||= {}
+
+		self.log.debug "Configuring AuthToken authprovider: %p" % [ config ]
+		self.cookie_name  = config[:cookie_name] || CONFIG_DEFAULTS[:cookie_name]
+		self.realm        = config[:realm]       || CONFIG_DEFAULTS[:realm]
 	end
 
 
 			self.class.realm = self.app.conn.app_id
 		end
 
-		unless self.class.users
-			self.log.warn "No users configured -- using an empty user list"
-			self.class.users = {}
-		end
-
 	end
 
 
 	public
 	######
 
-	# Check the authentication present in +request+ (if any) for validity, returning the
-	# authenticating user's name if authentication succeeds.
+	### Check the authentication present in +request+ (if any) for validity, returning the
+	### authenticating user's name if authentication succeeds.
 	def authenticate( request )
 		Strelka::SCSCookie.rotate_keys
+		return self.check_for_auth_cookie( request )
+	end
 
-		if user = self.check_for_auth_cookie( request )
-			return user
-		else
-			finish_with( HTTP::AUTH_REQUIRED )
-		end
+
+	### Add a authtoken cookie for +username+ to the response for the given +request+ when auth
+	### succeeds.
+	def auth_succeeded( request, username )
+
+		# Add the token to the response
+		# self.log.debug "Adding an SCS cookie for %p to the response." % [ username ]
+		cookie = Strelka::SCSCookie.new( self.class.cookie_name, username )
+		request.response.cookies << cookie
 	end
 
 
 	### Extract credentials from the given request and validate them, either via a
 	### valid authentication token, or from request parameters.
 	def check_for_auth_cookie( request )
+		# self.log.debug "Checking for an auth cookie."
 		cookie = request.cookies[ self.class.cookie_name ] or
 			log_failure "No auth cookie: %s" % [ self.class.cookie_name ]
 
+		# self.log.debug "  upgrading the regular cookie to an SCSCookie."
 		scs_cookie = Strelka::SCSCookie.from_regular_cookie( cookie ) or
 			log_failure "Couldn't upgrade the %s cookie to SCS" % [ self.class.cookie_name]
 
+		# self.log.debug "  setting the %s cookie to %p" % [ self.class.cookie_name, scs_cookie ]
 		request.cookies[ self.class.cookie_name ] = scs_cookie
+
+		# self.log.debug "  returning authenticated username: %p" % [ scs_cookie.value ]
 		return scs_cookie.value
 	end
 

lib/strelka/scscookie.rb

 		digest_type:     'sha1',
 		block_size:      16,
 		framing_byte:    '|',
-		session_max_age: 3600,
+		max_session_age: 3600,
 		compression:     false,
 	}
 
 
+	#
+	# :section: Configuration
+	#
+
 	##
 	# The cipher to use for encrypting the cookie data
-	singleton_attr_accessor :cipher_type
+	singleton_attr_reader :cipher_type
 	@cipher_type = CONFIG_DEFAULTS[:cipher_type]
 
 	##
 	# The digest algorithm to use for the message authentication
-	singleton_attr_accessor :digest_type
+	singleton_attr_reader :digest_type
 	@digest_type = CONFIG_DEFAULTS[:digest_type]
 
 	##
 
 	##
 	# The maximum number of seconds a session is valid for
-	singleton_attr_accessor :session_max_age
-	@session_max_age = CONFIG_DEFAULTS[:session_max_age]
+	singleton_attr_accessor :max_session_age
+	@max_session_age = CONFIG_DEFAULTS[:max_session_age]
 
 	##
 	# If true, compress the payload of the cookie before encypting it
 	singleton_attr_accessor :compression
 	@compression = CONFIG_DEFAULTS[:compression]
 
+
+	### Set the cipher_type to +cipher+, resetting the current keyset if necessary.
+	def self::cipher_type=( cipher )
+		if @cipher_type && cipher != @cipher_type
+			self.log.warn "Cipher changed; resetting keysets."
+			@current_keyset = nil
+			@last_keyset = nil
+		end
+
+		@cipher_type = cipher
+	end
+
+
+	### Set the digest_type to +digest+, resetting the current keyset if necessary.
+	def self::digest_type=( digest )
+		if @digest_type && digest != @digest_type
+			self.log.warn "Digest changed; resetting keysets."
+			@current_keyset = nil
+			@last_keyset = nil
+		end
+
+		@digest_type = digest
+	end
+
+
+	### Configurability API -- configure the class when the config is loaded.
+	def self::configure( config=nil )
+		config ||= {}
+
+		self.cipher_type     = config[:cipher_type]     || CONFIG_DEFAULTS[:cipher_type]
+		self.digest_type     = config[:digest_type]     || CONFIG_DEFAULTS[:digest_type]
+		self.block_size      = config[:block_size]      || CONFIG_DEFAULTS[:block_size]
+		self.framing_byte    = config[:framing_byte]    || CONFIG_DEFAULTS[:framing_byte]
+		self.max_session_age = config[:max_session_age] || CONFIG_DEFAULTS[:max_session_age]
+		self.compression     = config[:compression]     || CONFIG_DEFAULTS[:compression]
+
+		self.log.info "Configured: cipher: %s, digest: %s, blksize: %d, framebyte: %p, maxage: %ds, compression: %s" % [
+			self.cipher_type,
+			self.digest_type,
+			self.block_size,
+			self.framing_byte,
+			self.max_session_age,
+			self.compression
+		]
+
+		raise "The %p cipher is not implemented by your OpenSSL implementation" unless
+			OpenSSL::Cipher.ciphers.include?( self.cipher_type )
+	end
+
+
+	#
+	# :section: Keyset Management
+	#
+
 	##
 	# A KeySet used for creating auth tokens
-	singleton_attr_reader :current_keyset
 	@current_keyset = nil
 
 	##
 	@last_keyset = nil
 
 
+	### Return the current keyset, creating one if necessary.
+	def self::current_keyset
+		@current_keyset ||= self.make_new_keyset( self.max_session_age )
+		return @current_keyset
+	end
+
+
 	### Set the current keyset to +keyset+.
 	def self::current_keyset=( keyset )
-		self.last_keyset = self.current_keyset
-		self.log.info "Activating keyset %p; expires on %s" % [ keyset.tid, keyset.expires ]
+		unless keyset.nil?
+			self.last_keyset = self.current_keyset
+			self.log.info "Activating keyset %s; expires on %s" %
+				[ keyset.tid.unpack('H*').join, keyset.expires ]
+		end
 		@current_keyset = keyset
 	end
 
 
 	### Writer: set the most-recently-expired keyset to +keyset+.
 	def self::last_keyset=( keyset )
-		self.log.info "Rotating keysets: %p expired on %s" % [ keyset.tid, keyset.expires ] unless
-			keyset.nil?
+		self.log.info "Rotating keysets: %s expired on %s" %
+			[ keyset.tid.unpack('H*').join, keyset.expires ] unless keyset.nil?
 		@last_keyset = keyset
 	end
 
 
-	### Configurability API -- configure the class when the config is loaded.
-	def self::configure( config=nil )
-		if config
-			self.cipher_type     = config[:cipher_type]
-			self.digest_type     = config[:digest_type]
-			self.block_size      = config[:block_size]
-			self.framing_byte    = config[:framing_byte]
-			self.session_max_age = config[:session_max_age]
-			self.compression     = config[:compression]
-		else
-			self.cipher_type     = CONFIG_DEFAULTS[:cipher_type]
-			self.digest_type     = CONFIG_DEFAULTS[:digest_type]
-			self.block_size      = CONFIG_DEFAULTS[:block_size]
-			self.framing_byte    = CONFIG_DEFAULTS[:framing_byte]
-			self.session_max_age = CONFIG_DEFAULTS[:session_max_age]
-			self.compression     = CONFIG_DEFAULTS[:compression]
-		end
-
-		self.log.debug "Configured: cipher: %s, digest: %s, blksize: %d, framebyte: %p, maxage: %ds, compression: %s" % [
-			self.cipher_type,
-			self.digest_type,
-			self.block_size,
-			self.framing_byte,
-			self.session_max_age,
-			self.compression
-		]
-
-		# Reset the existing keys in case the cipher has changed.
-		# :FIXME: Remember the cipher so users don't have to auth again when the
-		# server is reloaded.
-		self.current_keyset = self.make_new_keyset( self.session_max_age )
-		self.last_keyset = nil
-
-		raise "The %p cipher is not implemented by your OpenSSL implementation" unless
-			OpenSSL::Cipher.ciphers.include?( self.cipher_type )
+	### Expire the current key and generate a new one if the current keyset is expired.
+	def self::rotate_keys
+		# self.log.debug "Checking for expired keyset: %s" % [ self.current_keyset.expires ]
+		return unless self.current_keyset.expires <= Time.now
+		self.current_keyset = Strelka::SCSCookie.make_new_keyset( self.max_session_age )
 	end
 
 
-	### Expire the current key and generate a new one if the current keyset is expired.
-	def self::rotate_keys
-		self.log.debug "Checking for expired keyset: %s" % [ self.current_keyset.expires ]
-		return unless self.current_keyset.expires <= Time.now
-		self.current_keyset = Strelka::SCSCookie.make_new_keyset( self.session_max_age )
+	### Find the keyset associated with the given +tid+, either the current one
+	### or the most-recently-expired one. Return +nil+ if neither of the
+	### keysets' TIDs match.
+	def self::find_keyset( tid )
+		# self.log.debug "Finding keyset for TID: %s" % [ tid.unpack('H*').join ]
+		return [ self.current_keyset, self.last_keyset ].compact.find {|ks| ks.tid == tid }
 	end
 
 
+	### Create a new keyset with its expiration set to +expires_at+, which can
+	### be a Time object, or an Integer (in which case its treated as the number of
+	### seconds until it expires).
+	def self::make_new_keyset( expires_at )
+		expires_at = Time.now + expires_at unless expires_at.is_a?( Time )
+		# self.log.debug "Making a new keyset that expires on %s" % [ expires_at ]
+
+		ks         = KeySet.new
+		ks.tid     = self.make_new_tid
+		ks.key     = self.make_new_key
+		ks.hkey    = self.make_new_hkey
+		ks.expires = expires_at
+
+		# self.log.debug "  created keyset %s" % [ ks.tid.unpack('H*').join ]
+		return ks
+	end
+
+
+	#
+	# :section: Crypto/Compression/Encoding
+	#
+
+	### Create and return an instance of the configured OpenSSL::Cipher.
+	def self::make_cipher
+		shortname = self.cipher_type
+		return OpenSSL::Cipher.new( shortname )
+	end
+
+
+	### Create and return an instance of the OpenSSL::Digest.
+	def self::make_digest
+		shortname = self.digest_type
+		return OpenSSL::Digest.new( shortname )
+	end
+
+
+	### Make a new key to use for encryption.
+	def self::make_new_key
+		key_size = self.make_cipher.key_len
+		return OpenSSL::Random.random_bytes( key_size )
+	end
+
+
+	### Make a new key to use for the HMAC.
+	def self::make_new_hkey
+		key_size = self.make_digest.size
+		return OpenSSL::Random.random_bytes( key_size )
+	end
+
+
+	### Generate a new transform ID of the specified +size+.
+	def self::make_new_tid
+		size = [ self.block_size, SCS_TID_MAX ].min
+		data = OpenSSL::Random.random_bytes( size )
+		# Shift bytes into visible ASCII:
+		#   http://goo.gl/8QIVE
+		return data.bytes.collect {|byte| (byte % 93) + 33 }.pack( 'C*' )
+	end
+
+
+	### Encrypt the specified +data+ using the specified +key+ and +iv+.
+	def self::encrypt( data, iv, key )
+		cipher = self.make_cipher
+		cipher.encrypt
+		cipher.key = key
+		cipher.iv = iv
+
+		encrypted = cipher.update( data ) << cipher.final
+
+		return encrypted
+	end
+
+
+	### Decrypt the specified +data+ using the given +key+ and +iv+.
+	def self::decrypt( data, iv, key )
+		cipher = self.make_cipher
+		cipher.decrypt
+		cipher.key = key
+		cipher.iv = iv
+
+		decrypted = cipher.update( data ) << cipher.final
+
+		return decrypted
+	end
+
+
+	### Encode the cookie value as Base-64
+	def self::encode( value )
+		return [ value ].pack( 'm' ).chomp
+	end
+
+
+	### Decode the given +data+ using Base-64 and return the decoded value.
+	def self::decode( data )
+		return data.unpack( 'm' ).first
+	end
+
+
+	### Compress the specified +data+ and return it.
+	def self::compress( data )
+		return Zlib::Deflate.deflate( data )
+	end
+
+
+	### Demompress the specified +data+ and return it.
+	def self::decompress( data )
+		return Zlib::Inflate.inflate( data )
+	end
+
+
+	#
+	# :section:
+	#
+
 	### Turn a regular +cookie+ into an SCSCookie.
 	def self::from_regular_cookie( cookie )
-		self.log.debug "Upgrading cookie %p to a %p" % [ cookie, self ]
+		# self.log.debug "Upgrading cookie %p to a %p" % [ cookie, self ]
 
 		# First of all, the inbound scs-cookie-value is broken into its
 		# component fields which MUST be exactly 5, and each at least of the
 		encoded_fields = cookie.value.split( self.framing_byte )
 		self.log.debug "  split into fields: %p" % [ encoded_fields ]
 		unless encoded_fields.length == 5
-			self.log.info "Invalid SCS cookie: expected 5 fields, got %d" % [ encoded_fields.length ]
+			self.log.error "Invalid SCS cookie: expected 5 fields, got %d" % [ encoded_fields.length ]
 			return nil
 		end
+		data, atime, tid, iv, authtag = encoded_fields.collect {|f| f.unpack('m').first }
+		# self.log.debug "  decoded fields: %p" % [[ data, atime, tid, iv, authtag ]]
 
 		# If the cryptographic credentials (encryption and authentication
 		# algorithms and keys identified by TID) are unavailable (step 12.),
 		# stored in RAM, if a server pool node desynchronizes, or in case of a
 		# key compromise that forces the invalidation of all current TID's,
 		# etc.
-		data, atime, tid, iv, authtag = encoded_fields.collect {|f| f.unpack('m').first }
-		self.log.debug "  decoded fields: %p" % [[ data, atime, tid, iv, authtag ]]
 		unless keyset = self.find_keyset( tid )
-			self.log.info "Couldn't find keyset for %p; expired?"
+			self.log.error "Couldn't find keyset for TID %s; expired?" % [ tid.unpack('H*').join ]
 			return nil
 		end
-		self.log.debug "  found keyset for TID: %p" % [ tid ]
 
 		# When a valid key-set is found (step 2.), the AUTHTAG field is decoded
 		# (step 3.) and the (still) encoded DATA, ATIME, TID and IV fields are
 		# carries authentic material; otherwise the SCS cookie is discarded
 		# (step 11.).
 		cookie_authtag = self.make_authtag( data, atime, iv, keyset )
-		self.log.debug "  challenge authtag is: %p" % [ cookie_authtag ]
+		# self.log.debug "  challenge authtag is: %p" % [ cookie_authtag ]
 		unless authtag == cookie_authtag
-			self.log.info "Invalid SCS cookie: authtags don't match: %p vs. %p" %
+			self.log.error "Invalid SCS cookie: authtags don't match: %p vs. %p" %
 				[ authtag, cookie_authtag ]
 			return nil
 		end
 
 		# Then the age of the SCS cookie (as deduced by ATIME field value and
 		# current time provided by the server clock) is decoded and compared to
-		# the maximum time-to-live defined by the session_max_age parameter.
+		# the maximum time-to-live defined by the max_session_age parameter.
 		session_age = Time.now - Time.at( atime.to_i )
-		self.log.debug "  session is %d seconds old" % [ session_age ]
-		if session_age > self.session_max_age
-			self.log.info "Session expired %d seconds ago." % [ session_age - self.session_max_age ]
+		# self.log.debug "  session is %d seconds old" % [ session_age ]
+		if session_age > self.max_session_age
+			self.log.error "Session expired %d seconds ago." % [ session_age - self.max_session_age ]
 			return nil
 		end
 
 		# and decompression.
 		value = self.decrypt( data, iv, keyset.key )
 		value = self.decompress( data ) if self.compression
-		self.log.debug "  decrypted cookie value is: %p" % [ value ]
+		# self.log.debug "  decrypted cookie value is: %p" % [ value ]
 
 		return new( cookie.name, value, cookie.options )
 	end
 
 
-	### Create and return an instance of the configured OpenSSL::Cipher.
-	def self::make_cipher
-		shortname = self.cipher_type
-		return OpenSSL::Cipher.new( shortname )
-	end
-
-
-	### Create and return an instance of the OpenSSL::Digest.
-	def self::make_digest
-		shortname = self.digest_type
-		return OpenSSL::Digest.new( shortname )
-	end
-
-
-	### Encrypt the specified +data+ using the specified +key+ and +iv+.
-	def self::encrypt( data, iv, key )
-		cipher = self.make_cipher
-		cipher.encrypt
-		cipher.key = key
-		cipher.iv = iv
-
-		encrypted = cipher.update( data ) << cipher.final
-
-		return encrypted
-	end
-
-
-	### Decrypt the specified +data+ using the given +key+ and +iv+.
-	def self::decrypt( data, iv, key )
-		cipher = self.make_cipher
-		cipher.decrypt
-		cipher.key = key
-		cipher.iv = iv
-
-		decrypted = cipher.update( data ) << cipher.final
-
-		return decrypted
-	end
-
-
-	### Encode the cookie value as Base-64
-	def self::encode( value )
-		return [ value ].pack( 'm' ).chomp
-	end
-
-
-	### Decode the given +data+ using Base-64 and return the decoded value.
-	def self::decode( data )
-		return data.unpack( 'm' ).first
-	end
-
-
-	### Compress the specified +data+ and return it.
-	def self::compress( data )
-		return Zlib::Deflate.deflate( data )
-	end
-
-
-	### Demompress the specified +data+ and return it.
-	def self::decompress( data )
-		return Zlib::Inflate.inflate( data )
-	end
-
-
-	### Find the keyset associated with the given +tid+, either the current one
-	### or the most-recently-expired one.
-	def self::find_keyset( tid )
-		self.log.debug "Finding keyset for TID: %p" % [ tid ]
-		return [ self.current_keyset, self.last_keyset ].find {|ks| ks.tid == tid }
-	end
-
-
 	### Make an SCS authtag from the specified data, atime, and iv,
 	### plus the tid from the given +keyset+ and hashed with its hkey.
 	def self::make_authtag( data, atime, iv, keyset )
-		self.log.debug "Making authtag for data: %p, atime: %d, iv: %p, keyset: %p" %
-			[ data, atime, iv, keyset ]
+		# self.log.debug "Making authtag for data: %p, atime: %d, iv: %p, keyset: %p" %
+			# [ data, atime, iv, keyset ]
 
 		# AUTHTAG := HMAC(e(DATA)||e(ATIME)||e(TID)||e(IV))
 		hashdata = [ data, atime.to_i, keyset.tid, iv ].
 	end
 
 
-	### Make a new key to use for encryption.
-	def self::make_new_key
-		key_size = self.make_cipher.key_len
-		return OpenSSL::Random.random_bytes( key_size )
-	end
-
-
-	### Make a new key to use for the HMAC.
-	def self::make_new_hkey
-		key_size = self.make_digest.size
-		return OpenSSL::Random.random_bytes( key_size )
-	end
-
-
-	### Generate a new transform ID of the specified +size+.
-	def self::make_new_tid
-		size = [ self.block_size, SCS_TID_MAX ].min
-		data = OpenSSL::Random.random_bytes( size )
-		# Shift bytes into visible ASCII:
-		#   http://goo.gl/8QIVE
-		return data.bytes.collect {|byte| (byte % 93) + 33 }.pack( 'C*' )
-	end
-
-
-	### Create a new keyset with its expiration set to +expires_at+, which can
-	### be a Time object, or an Integer (in which case its treated as the number of
-	### seconds until it expires).
-	def self::make_new_keyset( expires_at )
-		expires_at = Time.now + expires_at unless expires_at.is_a?( Time )
-		self.log.debug "Making a new keyset that expires on %s" % [ expires_at ]
-
-		ks         = KeySet.new
-		ks.tid     = self.make_new_tid
-		ks.key     = self.make_new_key
-		ks.hkey    = self.make_new_hkey
-		ks.expires = expires_at
-
-		return ks
-	end
-
-
 
 	#################################################################
 	###	I N S T A N C E   M E T H O D S
 	#################################################################
 
 	### Set up some additional values used for SCS.
-	def initialize( name, values, options={} ) # :notnew:
+	def initialize( name, value, options={} ) # :notnew:
 		@atime       = Time.now
 		@keyset      = self.class.current_keyset
 		@iv          = OpenSSL::Random.random_bytes( self.class.block_size )
 	### it.
 	def encrypted_data
 		# 3.  DATA := Enc(Comp(plain-text-cookie-value), IV)
-		self.log.debug "Encoding value: %p" % [ self.value ]
 		plain_data = self.value
 		plain_data = self.class.compress( plain_data ) if self.class.compression
 

spec/strelka/authprovider/authtoken_spec.rb

 BEGIN {
 	require 'pathname'
 	basedir = Pathname.new( __FILE__ ).dirname.parent.parent.parent
+
+	strelkalibdir = basedir.parent + 'Strelka/lib'
+
 	$LOAD_PATH.unshift( basedir ) unless $LOAD_PATH.include?( basedir )
+	$LOAD_PATH.unshift( strelkalibdir ) unless $LOAD_PATH.include?( strelkalibdir )
 }
 
 require 'rspec'
 
 require 'spec/lib/helpers'
 
+require 'configurability/behavior'
+
 require 'strelka'
 require 'strelka/scscookie'
 require 'strelka/authprovider/authtoken'
 	before( :each ) do
 		@app = stub( "Strelka::App", :conn => stub("Connection", :app_id => 'test-app') )
 		@provider = Strelka::AuthProvider.create( :authtoken, @app )
-		@config = {
-			:realm => 'Pern',
-			:users => {
-				"lessa" => "8wiomemUvH/+CX8UJv3Yhu+X26k=",
-				"f'lar" => "NSeXAe7J5TTtJUE9epdaE6ojSYk=",
-			}
-		}
+		@config = { :realm => 'Pern' }
 	end
 
 	after( :each ) do
-		described_class.users = {}
 		described_class.realm = nil
 	end
 
 
 
 	#
+	# Shared Examples
+	#
+
+	it_behaves_like "an object with Configurability"
+
+
+	#
 	# Examples
 	#
 
 	it "can be configured via the Configurability API" do
 		described_class.configure( @config )
 		described_class.realm.should == @config[:realm]
-		described_class.users.should == @config[:users]
 	end
 
 
-	context "unconfigured" do
-
-		before( :all ) do
-			described_class.configure( nil )
-		end
-
-		it "rejects a request with no scs cookie and no credential parameters" do
-			req = @request_factory.get( '/admin/console' )
-
-			expect {
-				@provider.authenticate( req )
-			}.to finish_with( HTTP::UNAUTHORIZED, /requires authentication/i ).
-			     and_header( www_authenticate: "AuthToken realm=test-app" )
-		end
-
-		it "rejects a request with an invalid SCS cookie" do
-			req = @request_factory.get( '/admin/console' )
-			req.cookies[ described_class.cookie_name ] = make_auth_cookie( 'lessa' )
-
-			expect {
-				@provider.authenticate( req )
-			}.to finish_with( HTTP::UNAUTHORIZED, /requires authentication/i )
-		end
-
-		it "accepts a request with a valid SCS cookie" do
-			auth_cookie = make_auth_cookie( 'lessa' )
-			req = @request_factory.get( '/admin/console' )
-			req.headers.cookie = auth_cookie.to_s
-
-			@provider.authenticate( req ).should == 'lessa'
-		end
-
+	before( :all ) do
+		described_class.configure( nil )
 	end
 
+	it "rejects a request with no scs cookie" do
+		req = @request_factory.get( '/admin/console' )
 
-	context "configured with at least one user" do
+		expect {
+			@provider.authenticate( req )
+		}.to finish_with( HTTP::UNAUTHORIZED, /requires authentication/i ).
+		     and_header( www_authenticate: "AuthToken realm=test-app" )
+	end
 
-		before( :all ) do
-			described_class.configure( @config )
-		end
+	it "rejects a request with an invalid SCS cookie" do
+		req = @request_factory.get( '/admin/console' )
+		req.cookies[ described_class.cookie_name ] = make_auth_cookie( 'lessa' )
 
-		it "rejects a request with no scs cookie and no credential parameters" do
-			req = @request_factory.get( '/admin/console' )
+		expect {
+			@provider.authenticate( req )
+		}.to finish_with( HTTP::UNAUTHORIZED, /requires authentication/i )
+	end
 
-			expect {
-				@provider.authenticate( req )
-			}.to finish_with( HTTP::UNAUTHORIZED, /requires authentication/i ).
-			     and_header( www_authenticate: "AuthToken realm=test-app" )
-		end
+	it "accepts a request with a valid SCS cookie" do
+		auth_cookie = make_auth_cookie( 'lessa' )
+		req = @request_factory.get( '/admin/console' )
+		req.headers.cookie = auth_cookie.to_s
 
-		it "rejects a request with an invalid SCS cookie" do
-			invalid_cookie = Strelka::Cookie.new( described_class.cookie_name, 'username' )
-			req = @request_factory.get( '/admin/console' )
-			req.cookies[ described_class.cookie_name ] = invalid_cookie
+		@provider.authenticate( req ).should == 'lessa'
+	end
 
-			expect {
-				@provider.authenticate( req )
-			}.to finish_with( HTTP::UNAUTHORIZED, /requires authentication/i )
-		end
+	it "adds an SCSCookie to the request's response when authentication is established" do
+		req = @request_factory.get( '/admin/console' )
+		@provider.auth_succeeded( req, 'a_username' )
 
+		cookie_name = described_class.cookie_name
+		req.response.cookies[ cookie_name ].should be_a( Strelka::SCSCookie )
 	end
 
 end

spec/strelka/scscookie_spec.rb

 	basedir = Pathname( __FILE__ ).dirname.parent.parent
 	libdir = basedir + 'lib'
 
+	strelkalibdir = basedir.parent + 'Strelka/lib'
+
 	$LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
 	$LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
+	$LOAD_PATH.unshift( strelkalibdir ) unless $LOAD_PATH.include?( strelkalibdir )
 }
 
+require 'configurability/behavior'
 require 'loggability/spechelpers'
 require 'openssl'
 require 'timecop'
 
 	before( :all ) do
 		setup_logging()
+		described_class.configure
+	end
+
+	before( :each ) do
+		described_class.current_keyset = nil
+		described_class.last_keyset = nil
 	end
 
 	after( :all ) do
 	end
 
 
+	#
+	# Shared Examples
+	#
+
+	it_behaves_like "an object with Configurability"
+
+
+	#
+	# Examples
+	#
+
+	context "configuration" do
+
+		after( :each ) do
+			# Restore defaults
+			described_class.configure( nil )
+		end
+
+		it "can be configured to use a different cipher" do
+			described_class.configure( cipher_type: 'aes-256-cfb' )
+			described_class.cipher_type.should == 'aes-256-cfb'
+		end
+
+		it "resets its keysets if the configured cipher changes" do
+			initial_keyset = described_class.current_keyset
+			described_class.configure( cipher_type: 'aes-256-cfb' )
+			described_class.current_keyset.should_not be( initial_keyset )
+		end
+
+		it "doesn't reset its keysets if the same cipher configuration is set again" do
+			initial_keyset = described_class.current_keyset
+			described_class.configure( cipher_type: described_class.cipher_type )
+			described_class.current_keyset.should be( initial_keyset )
+		end
+
+		it "resets its keysets if the configured digest changes" do
+			initial_keyset = described_class.current_keyset
+			described_class.configure( digest_type: 'sha512' )
+			described_class.current_keyset.should_not be( initial_keyset )
+		end
+
+		it "doesn't reset its keysets if the same digest configuration is set again" do
+			initial_keyset = described_class.current_keyset
+			described_class.configure( digest_type: described_class.digest_type )
+			described_class.current_keyset.should be( initial_keyset )
+		end
+
+	end
+
+
+	it "rotates its keysets when told to if the current keyset has expired" do
+		initial_keyset = described_class.current_keyset
+		Timecop.freeze( initial_keyset.expires + 1 ) do
+			described_class.rotate_keys
+			described_class.last_keyset.should be( initial_keyset )
+			described_class.current_keyset.should_not be( initial_keyset )
+		end
+	end
+
+
 	it "can be upgraded from a regular cookie" do
-		cookie = Strelka::Cookie.new( 'token', 'a value' )
-		scs_cookie = described_class.from_regular_cookie( cookie )
+		header = described_class.new( 'auth', 'foom' ).to_s
+		cookies = Strelka::Cookie.parse( header )
+
+		scs_cookie = described_class.from_regular_cookie( cookies[:auth] )
 		scs_cookie.should be_a( described_class )
-		scs_cookie.value.should == cookie.value
+		scs_cookie.value.should == 'foom'
 	end
 
 
+	it "can be upgraded from a regular cookie even after its keyset has expired" do
+		initial_keyset = described_class.current_keyset
+
+		Timecop.freeze( initial_keyset.expires - described_class.max_session_age / 3 ) do
+			header = described_class.new( 'auth', 'foom' ).to_s
+
+			Timecop.freeze( initial_keyset.expires + 1 ) do
+				described_class.rotate_keys
+
+				cookies = Strelka::Cookie.parse( header )
+				scs_cookie = described_class.from_regular_cookie( cookies[:auth] )
+				scs_cookie.should be_a( described_class )
+				scs_cookie.value.should == 'foom'
+			end
+		end
+	end
+
+	it "doesn't upgrade a cookie that's missing one of its fields" do
+		header = described_class.new( 'auth', 'foom' ).to_s
+		header.sub!( /=.*?\|/, '=' )
+		cookies = Strelka::Cookie.parse( header )
+
+		described_class.from_regular_cookie( cookies[:auth] ).should be_nil()
+	end
+
+	it "doesn't upgrade a cookie whose keyset has been expired" do
+		header = described_class.new( 'auth', 'foom' ).to_s
+		cookies = Strelka::Cookie.parse( header )
+
+		described_class.current_keyset = nil
+		described_class.last_keyset = nil
+
+		described_class.from_regular_cookie( cookies[:auth] ).should be_nil()
+	end
+
+	it "doesn't upgrade a cookie that has expired" do
+		Timecop.freeze( Time.now ) do
+			initial_keyset = described_class.current_keyset
+			header = described_class.new( 'auth', 'foom' ).to_s
+
+			Timecop.freeze( Time.now + described_class.max_session_age + 1 ) do
+				described_class.rotate_keys
+
+				cookies = Strelka::Cookie.parse( header )
+				described_class.from_regular_cookie( cookies[:auth] ).should be_nil()
+			end
+		end
+	end
+
+
+
 	context "A.1. No Compression" do
 
 		before( :all ) do
 			Timecop.freeze( @time )
 		end
 
-		let( :cookie ) do
-			cookie = described_class.new( 'authcookie', 'a state string',
-			                                key: AES_CBC_128_KEY,
-                                           hkey: HMAC_SHA1_KEY,
-                                            tid: TID,
-                                          atime: ATIME )
-			cookie.instance_variable_set( :@iv, IV )
-			cookie
+		after( :all ) do
+			Timecop.return
 		end
 
-		it "has an encoded data block that matches example from the spec" do
-			cookie.encoded_data.should == "XoYbblL/ap13ye+i6uQULg=="
+		let( :keyset ) do
+			ks         = Strelka::SCSCookie::KeySet.new
+			ks.tid     = TID
+			ks.key     = AES_CBC_128_KEY
+			ks.hkey    = HMAC_SHA1_KEY
+			ks.expires = @time + 3600
+			ks
 		end
 
-		it "has an encoded atime block that matches example from the spec" do
-			cookie.encoded_atime.should == 'MTM0NzI2NTk1NQ=='
+		let( :cookie ) do
+			described_class.new( 'authcookie', 'a state string', iv: IV, keyset: keyset )
 		end
 
-		it "has an encoded TID block that matches example from the spec" do
-			cookie.encoded_tid.should == "dGlk"
+
+		it "can encrypt its payload" do
+			cookie.encrypted_data.should == "^\x86\enR\xFFj\x9Dw\xC9\xEF\xA2\xEA\xE4\x14."
 		end
 
-		it "has an encoded IV block that matches example from the spec" do
-			cookie.encoded_iv.should == 'tL3lJPf2nUSFMN6dtVXJTw=='
-		end
-
-		it "has an encoded authtag block that matches example from the spec" do
-			cookie.encoded_authtag.should == '0xPSM7RDVytACfRuqlkRKucxEOM='
-		end
-
-		it "stringifies as a valid cookie" do
+		it "stringifies as a valid SCS cookie" do
 			cookie.to_s.should == 'authcookie=XoYbblL/ap13ye+i6uQULg==|' +
 				'MTM0NzI2NTk1NQ==|dGlk|tL3lJPf2nUSFMN6dtVXJTw==|0xPSM7RD' +
 				'VytACfRuqlkRKucxEOM='