Test suite now works with WebID auth. Creates Profile, adds authorization, and only user can write to file to add a friend for example. webid
authorHenry Story <henry.story@bblfish.net>
Sun, 30 Oct 2011 20:42:26 +0100
branchwebid
changeset 107 3bb89aaaab51
parent 103 c0bf9b280888
child 111 5497f1957ad0
child 119 956b66216b0c
Test suite now works with WebID auth. Creates Profile, adds authorization, and only user can write to file to add a friend for example.
This required digging deep into TLS to understand what is going on. As it happens the Java client at present will only send certificate on renegotiation when it is NEEDed by the server. So the server now looks at HTTP header and if the client is Java then it sets client certificate request to NEED.
Also discovered that with Netty (perhaps with Jetty too) the default Content-Location header sent by server have weird host names (http://[localhost:8234]:8234/... so before trying to fix that I now have RWW send the correct header - but only for GET... to look into.
The test code now sets the correct properties so that this does not need to be known by the tester.
The certificates used for testing are signed by the certificate the server is running on.
A new SpyInputStream is very useful for debugging - it helps to look at what the server is receiving.
Added X509CertSigner class that creates takes a public key and webid and creates a certificate out of those. Using internal sun classes to reduce dependencies on bouncycastle. Those classes are GPL so they need to be extracted into a jar, and included on machines that are not running sun jvms.
bin/rwsbt
bin/rwsbt.sh
keys/KEYSTORE.jks
project/build.scala
src/main/scala/ReadWriteWebMain.scala
src/main/scala/WebCache.scala
src/main/scala/auth/Authz.scala
src/main/scala/auth/X509Cert.scala
src/main/scala/netty/SslLoginTest.scala
src/main/scala/plan.scala
src/main/scala/util/SpyInputStream.scala
src/test/resources/KEYSTORE.jks
src/test/scala/auth/CreateWebIDSpec.scala
src/test/scala/auth/SecureReadWriteWebSpec.scala
src/test/scala/auth/secure_specs.scala
test_www/.meta.n3
test_www/foaf.n3.protect.n3
--- a/bin/rwsbt	Fri Oct 28 00:35:16 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,25 +0,0 @@
-#!/bin/bash 
-
-KS=keys/KEYSTORE.jks
-while [ $# -gt 0 ] 
-do 
- case $1 in 
-  -d) PROPS="$PROPS -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005"
-   ;; 
-  -n) PROPS="$PROPS -Dnetty.ssl.keyStoreType=JKS -Dnetty.ssl.keyStore=$KS -Dnetty.ssl.keyStorePassword=secret" 
-   ;;
-  -j) PROPS="$PROPS -Djetty.ssl.keyStoreType=JKS -Djetty.ssl.keyStore=$KS -Djetty.ssl.keyStorePassword=secret"
-   ;;
-  -sslUnsafe) PROPS="$PROPS -Dsun.security.ssl.allowUnsafeRenegotiation=true"
-   ;; # see: http://download.oracle.com/javase/7/docs/technotes/guides/security/jsse/JSSERefGuide.html#workarounds
-  -sslLegacy) PROPS="$PROPS -Dsun.security.ssl.allowLegacyHelloMessages=true"
-   ;;
-  *) echo the arguments to use are -d
-   ;;
-  esac
-  shift 1
- done
-
-
-export SBT_PROPS=$PROPS
-xsbt 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/rwsbt.sh	Sun Oct 30 20:42:26 2011 +0100
@@ -0,0 +1,27 @@
+#!/bin/bash 
+
+KS=src/test/resources/KEYSTORE.jks
+while [ $# -gt 0 ] 
+do 
+ case $1 in 
+  -d) PROPS="$PROPS -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005"
+   ;; 
+  -n) PROPS="$PROPS -Dnetty.ssl.keyStoreType=JKS -Dnetty.ssl.keyStore=$KS -Dnetty.ssl.keyStorePassword=secret" 
+   ;;
+  -j) PROPS="$PROPS -Djetty.ssl.keyStoreType=JKS -Djetty.ssl.keyStore=$KS -Djetty.ssl.keyStorePassword=secret"
+   ;;
+  -sslUnsafe) PROPS="$PROPS -Dsun.security.ssl.allowUnsafeRenegotiation=true"
+   ;; # see: http://download.oracle.com/javase/7/docs/technotes/guides/security/jsse/JSSERefGuide.html#workarounds
+  -sslLegacy) PROPS="$PROPS -Dsun.security.ssl.allowLegacyHelloMessages=true"
+   ;;
+  -sslDebug) PROPS="$PROPS -Djavax.net.debug=all"
+   ;; # see http://download.oracle.com/javase/1,5,0/docs/guide/security/jsse/ReadDebug.html
+  *) echo the arguments to use are -d
+   ;;
+  esac
+  shift 1
+ done
+
+
+export SBT_PROPS=$PROPS
+xsbt 
Binary file keys/KEYSTORE.jks has changed
--- a/project/build.scala	Fri Oct 28 00:35:16 2011 +0200
+++ b/project/build.scala	Sun Oct 30 20:42:26 2011 +0100
@@ -23,14 +23,18 @@
   val arq = "com.hp.hpl.jena" % "arq" % "2.8.8"
   val grizzled = "org.clapper" %% "grizzled-scala" % "1.0.8" % "test"
   val scalaz = "org.scalaz" %% "scalaz-core" % "6.0.2"
-  val jsslutils = "org.jsslutils" % "jsslutils" % "1.0.7"
+
   val argot =  "org.clapper" %% "argot" % "0.3.5"
   val guava =  "com.google.guava" % "guava" % "10.0.1"
+//  val restlet = "org.restlet.dev" % "org.restlet" % "2.1-SNAPSHOT"
+//  val restlet_ssl = "org.restlet.dev" % "org.restlet.ext.ssl" % "2.1-SNAPSHOT"
+  val jsslutils = "org.jsslutils" % "jsslutils" % "1.0.5"
 }
 
 // some usefull repositories
 object Resolvers {
   val novus = "repo.novus snaps" at "http://repo.novus.com/snapshots/"
+  val mavenLocal = "Local Maven Repository" at "file://" + (Path.userHome / ".m2" / "repository").absolutePath
 }
 
 // general build settings
@@ -75,6 +79,7 @@
 
   val projectSettings =
     Seq(
+      resolvers += mavenLocal,
       resolvers += ScalaToolsReleases,
       resolvers += ScalaToolsSnapshots,
       libraryDependencies += specs,
@@ -97,6 +102,8 @@
       jarName in assembly := "read-write-web.jar"
     )
 
+
+
   lazy val project = Project(
     id = "read-write-web",
     base = file("."),
--- a/src/main/scala/ReadWriteWebMain.scala	Fri Oct 28 00:35:16 2011 +0200
+++ b/src/main/scala/ReadWriteWebMain.scala	Sun Oct 30 20:42:26 2011 +0100
@@ -10,10 +10,7 @@
 
 import org.clapper.argot._
 import ArgotConverters._
-import unfiltered.request.HttpRequest
-import unfiltered.Cycle
 import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
-import unfiltered.filter.Plan
 
 trait ReadWriteWebArgs {
   val logger: Logger = LoggerFactory.getLogger(this.getClass)
--- a/src/main/scala/WebCache.scala	Fri Oct 28 00:35:16 2011 +0200
+++ b/src/main/scala/WebCache.scala	Sun Oct 30 20:42:26 2011 +0100
@@ -46,7 +46,6 @@
   def sanityCheck() = true  //cache dire exists? But is this needed for functioning?
 
   def resource(u : URL) = new org.w3.readwriteweb.Resource {
-
     def get() = {
       // note we prefer rdf/xml and turtle over html, as html does not always contain rdfa, and we prefer those over n3,
       // as we don't have a full n3 parser. Better would be to have a list of available parsers for whatever rdf framework is
@@ -63,10 +62,10 @@
             case None => RDFXML  // it would be better to try to do a bit of guessing in this case by looking at content
           }
           val loc = headers("Content-Location").headOption match {
-            case Some(loc) => new URL(u,loc)
+            case Some(loc) =>  new URL(u,loc)
             case None => new URL(u.getProtocol,u.getAuthority,u.getPort,u.getPath)
           }
-          res>>{ in=>modelFromInputStream(in,loc,encoding) }
+          res>>{ in=> modelFromInputStream(in,loc,encoding) }
 
         }
       })
--- a/src/main/scala/auth/Authz.scala	Fri Oct 28 00:35:16 2011 +0200
+++ b/src/main/scala/auth/Authz.scala	Sun Oct 30 20:42:26 2011 +0100
@@ -171,12 +171,14 @@
         if (agentsAllowed.size > 0) {
           if (agentsAllowed.exists( pair =>  pair._2 == foafAgent )) true
           else subj() match {
-            case Some(s) => agentsAllowed.exists{ 
-              p =>  s.getPrincipals(classOf[WebIdPrincipal]).
-                exists(id=> {
-                val ps = if (p._1 != null) p._1.toString else null;
-                ps == id.webid
-              })
+            case Some(s) => {
+              agentsAllowed.exists{
+                p =>  s.getPrincipals(classOf[WebIdPrincipal]).
+                  exists(id=> {
+                  val ps = if (p._1 != null) p._1.toString else null;
+                  ps == id.webid
+                })
+              }
             }
             case None => false
           }
--- a/src/main/scala/auth/X509Cert.scala	Fri Oct 28 00:35:16 2011 +0200
+++ b/src/main/scala/auth/X509Cert.scala	Sun Oct 30 20:42:26 2011 +0100
@@ -24,17 +24,35 @@
 package org.w3.readwriteweb.auth
 
 import javax.servlet.http.HttpServletRequest
-import unfiltered.request.HttpRequest
 import unfiltered.netty.ReceivedMessage
-import java.security.cert.{X509Certificate}
-import java.security.cert.Certificate
 import java.util.Date
 import java.math.BigInteger
-import java.security.{SecureRandom, KeyPair}
 import java.net.URL
+import unfiltered.request.{UserAgent, HttpRequest}
+import java.security.cert.{X509Certificate, Certificate}
+import java.security._
+import interfaces.RSAPublicKey
+import unfiltered.util.IO
 import sun.security.x509._
 
-object X509Cert {
+
+object X509CertSigner {
+
+  def apply(keyStoreLoc: URL, keyStoreType: String, password: String, alias: String) = {
+    val keystore =  KeyStore.getInstance(keyStoreType)
+
+    IO.use(keyStoreLoc.openStream()) { in =>
+      keystore.load(in, password.toCharArray)
+    }
+    val privateKey = keystore.getKey(alias, password.toCharArray).asInstanceOf[PrivateKey]
+    val certificate = keystore.getCertificate(alias).asInstanceOf[X509Certificate]
+    //one could verify that indeed this is the private key corresponding to the public key in the cert.
+
+    new X509CertSigner(certificate,privateKey)
+  }
+}
+
+class X509CertSigner(signingCert: X509Certificate, signingKey: PrivateKey ) {
   val WebID_DN="""O=FOAF+SSL, OU=The Community of Self Signers, CN=Not a Certification Authority"""
 
   /**
@@ -43,41 +61,84 @@
    * have these classes don't need to download the code. It should be easy in scala to create a build that can decide
    * if these need to be added to the classpath. I think the code just looks better than bouncycastle too.
    *
+   * WARNING THIS IS   in construction
+   *
    * Create a self-signed X.509 Certificate
-   * @param issuerDN the X.509 Distinguished Name, eg "CN=Test, L=London, C=GB"
+   * @param subjectDN the X.509 Distinguished Name, eg "CN=Test, L=London, C=GB"
    * @param pair the KeyPair
    * @param days how many days from now the Certificate is valid for
    * @param algorithm the signing algorithm, eg "SHA1withRSA"
    */
-    def generate_self_signed(issuerDN: String,
-                 pair: KeyPair,
+    def generate(subjectDN: String,
+                 subjectKey: RSAPublicKey,
                  days: Int,
-                 webId: URL,
-                 algorithm: String="SHA1withRSA"): X509Certificate = {
+                 webId: URL): X509Certificate = {   //todo: the algorithm should be deduced from private key in part
+
+
       var info = new X509CertInfo
-      val from = new Date
+      val from = new Date(System.currentTimeMillis()-10*1000*60) //start 10 minutes ago, to avoid network trouble
       val to = new Date(from.getTime + days*24*60*60*1000) 
       val interval = new CertificateValidity(from, to)
-      val sn = new BigInteger(64, new SecureRandom)
-      val owner = new X500Name(issuerDN)
+      val serialNumber = new BigInteger(64, new SecureRandom)
+      val subjectXN = new X500Name(subjectDN)
+      val issuerXN = new X500Name(signingCert.getSubjectDN.toString)
+
       info.set(X509CertInfo.VALIDITY, interval)
-      info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn))
-      info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(owner))
-      info.set(X509CertInfo.ISSUER, new CertificateIssuerName(owner))
-      info.set(X509CertInfo.KEY, new CertificateX509Key(pair.getPublic))
+      info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(serialNumber))
+      info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(subjectXN))
+      info.set(X509CertInfo.ISSUER, new CertificateIssuerName(issuerXN))
+      info.set(X509CertInfo.KEY, new CertificateX509Key(subjectKey))
       info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3))
+
+      //
+      //extensions
+      //
       val extensions = new CertificateExtensions();
-      val san = new SubjectAlternativeNameExtension(new GeneralNames().add(new GeneralName(new URIName(webId.toExternalForm))))
+
+      val san = new SubjectAlternativeNameExtension(true, new GeneralNames().add(new GeneralName(new URIName(webId.toExternalForm))))
       extensions.set(san.getName,san)
+
+      val basicCstrExt = new BasicConstraintsExtension(false,1)
+      extensions.set(basicCstrExt.getName,basicCstrExt)
+
+      { import KeyUsageExtension._
+        val keyUsage = new KeyUsageExtension()
+        List(DIGITAL_SIGNATURE, NON_REPUDIATION, KEY_ENCIPHERMENT, KEY_AGREEMENT, KEY_CERTSIGN).foreach {
+           usage => keyUsage.set(usage,true)
+        }
+        extensions.set(keyUsage.getName,keyUsage)
+      }
+
+      { import NetscapeCertTypeExtension._
+      val netscapeExt = new NetscapeCertTypeExtension()
+       List(SSL_CLIENT, S_MIME) foreach { ext => netscapeExt.set(ext,true) }
+        extensions.set(netscapeExt.getName, new NetscapeCertTypeExtension(false,netscapeExt.getValue))
+      }
+      
+      val subjectKeyExt = new SubjectKeyIdentifierExtension(new KeyIdentifier(subjectKey).getIdentifier)
+      extensions.set(subjectKeyExt.getName,subjectKeyExt)
+      
       info.set(X509CertInfo.EXTENSIONS,extensions)
-      val algo = new AlgorithmId(AlgorithmId.md5WithRSAEncryption_oid)
+
+      val algo = signingCert.getPublicKey.getAlgorithm match {
+        case "DSA" =>  new AlgorithmId(AlgorithmId.sha1WithDSA_oid )
+        case "RSA" =>  new AlgorithmId(AlgorithmId.sha1WithRSAEncryption_oid)
+        case _ => throw new RuntimeException("Don't know how to sign with this type of key")  
+      }
+
       info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algo))
-      var cert = new X509CertImpl(info)
-      cert.sign(pair.getPrivate, algorithm)
-      val sigAlgo = cert.get(X509CertImpl.SIG_ALG).asInstanceOf[AlgorithmId]
+
+      // Sign the cert to identify the algorithm that's used.
+      val tmpCert = new X509CertImpl(info)
+      tmpCert.sign(signingKey,algo.getName)
+
+      //update the algorithm and re-sign
+      val sigAlgo = tmpCert.get(X509CertImpl.SIG_ALG).asInstanceOf[AlgorithmId]
       info.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, sigAlgo)
-      cert = new X509CertImpl(info)
-      cert.sign(pair.getPrivate, algorithm)
+      val cert = new X509CertImpl(info)
+      cert.sign(signingKey,algo.getName)
+      
+      cert.verify(signingCert.getPublicKey)
       return cert
     }
 
@@ -117,7 +178,11 @@
         case e => {
           // request a certificate from the user
           sslh.setEnableRenegotiation(true)
-          sslh.getEngine.setWantClientAuth(true)
+          r match {
+            case UserAgent(agent) if needAuth(agent) => sslh.getEngine.setNeedClientAuth(true)
+            case _ => sslh.getEngine.setWantClientAuth(true)  
+          }
+          
           val future = sslh.handshake()
           future.await(30000) //that's certainly way too long.
           if (future.isDone) {
@@ -137,5 +202,18 @@
     }
 
   }
+
+ /**
+  *  Some agents do not send client certificates unless required. This is a problem for them, as it ends up breaking the
+  *  connection for those agents if the client does not have a certificate...
+  *
+  *  It would be useful if this could be updated by server from time to  time from a file on the internet,
+  *  so that changes to browsers could update server behavior
+  *
+  */
+  def needAuth(agent: String): Boolean = {
+    agent.contains("Java")
+  }
+  
 }
 
--- a/src/main/scala/netty/SslLoginTest.scala	Fri Oct 28 00:35:16 2011 +0200
+++ b/src/main/scala/netty/SslLoginTest.scala	Sun Oct 30 20:42:26 2011 +0100
@@ -58,7 +58,8 @@
        req.underlying.context.getPipeline.get(classOf[org.jboss.netty.handler.ssl.SslHandler])  match {
           case sslh: SslHandler => {
             sslh.setEnableRenegotiation(true)
-            sslh.getEngine.setWantClientAuth(true)
+            sslh.getEngine.setNeedClientAuth(true)
+//            sslh.getEngine.setWantClientAuth(true)
             val future = sslh.handshake()
             future.await(5000)
             val res = if (future.isDone) {
--- a/src/main/scala/plan.scala	Fri Oct 28 00:35:16 2011 +0200
+++ b/src/main/scala/plan.scala	Sun Oct 30 20:42:26 2011 +0100
@@ -16,12 +16,10 @@
               QueryTypeConstruct => CONSTRUCT,
               QueryTypeDescribe => DESCRIBE}
 
-import scalaz.{Resource=>SzResource,_}
-import unfiltered.filter.Plan
-import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
+import scalaz.{Resource=>SzResource}
 import unfiltered.request._
+import unfiltered.Cycle
 import unfiltered.response._
-import unfiltered.Cycle
 
 //object ReadWriteWeb {
 //
@@ -77,7 +75,7 @@
    */
   def rwwIntent  =  (req: HttpRequest[Req]) => {
 
-          val Authoritative(uri, representation) = req
+          val Authoritative(uri: URL, representation: Representation) = req
           val r: Resource = rm.resource(uri)
           val res: ResponseFunction[Res] = req match {
             case GET(_) if representation == HTMLRepr => {
@@ -93,10 +91,11 @@
                   case _ => Lang.default
                 }
               } yield {
-                req match {
+                val res = req match {
                   case GET(_) => Ok ~> ViaSPARQL ~> ContentType(lang.contentType) ~> ResponseModel(model, uri, lang)
                   case HEAD(_) => Ok ~> ViaSPARQL ~> ContentType(lang.contentType)
                 }
+                res ~> ContentLocation( uri.toString ) // without this netty (perhaps jetty too?) sends very weird headers, breaking tests
               }
             case PUT(_) & RequestLang(lang) if representation == DirectoryRepr => {
               for {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/scala/util/SpyInputStream.scala	Sun Oct 30 20:42:26 2011 +0100
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2011 Henry Story (bblfish.net)
+ * under the MIT licence defined at
+ *    http://www.opensource.org/licenses/mit-license.html
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of 
+ * this software and associated documentation files (the "Software"), to deal in the
+ * Software without restriction, including without limitation the rights to use, copy,
+ * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so, subject to the
+ * following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
+ * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+ * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package org.w3.readwriteweb.util
+
+import java.io.{IOException, OutputStream, InputStream}
+
+
+/**
+ * Wrap an inputstream and write everything that comes in here
+ * @author hjs
+ * @created: 30/10/2011
+ */
+
+class SpyInputStream(val in: InputStream, val out: OutputStream) extends InputStream {
+  var stopOut = false
+  
+  def read() ={
+
+    val i = try {
+       in.read()
+   } catch {
+      case ioe: IOException => {
+        out.close()
+        stopOut=true
+        throw ioe;
+      }
+    }
+    if (!stopOut) try {
+      out.write(i)
+    } catch {
+      case ioe: IOException => {
+        stopOut = true
+      }
+    }
+    i
+  }
+}
\ No newline at end of file
Binary file src/test/resources/KEYSTORE.jks has changed
--- a/src/test/scala/auth/CreateWebIDSpec.scala	Fri Oct 28 00:35:16 2011 +0200
+++ b/src/test/scala/auth/CreateWebIDSpec.scala	Sun Oct 30 20:42:26 2011 +0100
@@ -26,16 +26,14 @@
 import org.w3.readwriteweb.utiltest._
 
 import dispatch._
-import java.io.File
-import org.apache.http.conn.scheme.Scheme
-import java.lang.String
-import java.security.cert.{CertificateFactory, X509Certificate}
+import java.security.cert.X509Certificate
 import java.security._
 import interfaces.RSAPublicKey
-import org.w3.readwriteweb.{RDFXML, TURTLE}
 import java.net.{Socket, URL}
-import scala.collection.{mutable, immutable}
+import scala.collection.mutable
 import javax.net.ssl._
+import java.io.File
+import org.w3.readwriteweb.{Post, RDFXML, TURTLE}
 
 
 /**
@@ -86,21 +84,6 @@
   lazy val lambdaMeta = new File(lambdaDir,".meta.n3")
 
 
- val keyManager = new FlexiKeyManager();
-
-  val  sslContext = javax.net.ssl.SSLContext.getInstance("TLS");
-  sslContext.init(Array(keyManager.asInstanceOf[KeyManager]), Array[TrustManager](new X509TrustManager {
-    def checkClientTrusted(chain: Array[X509Certificate], authType: String) {}
-    def checkServerTrusted(chain: Array[X509Certificate], authType: String) {}
-    def getAcceptedIssuers = Array[X509Certificate]()
-  }),null); // we are not trying to test our trust of localhost server
-  {
-   import org.apache.http.conn.ssl._
-   val sf = new SSLSocketFactory(sslContext,SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
-   val  scheme = new Scheme("https", 443, sf);
-   Http.client.getConnectionManager.getSchemeRegistry.register(scheme)
-  }
-
 
   val foaf = """
        @prefix foaf: <http://xmlns.com/foaf/0.1/> .
@@ -109,17 +92,19 @@
        <> a foaf:PersonalProfileDocument;
           foaf:primaryTopic :me .
 
-       :jl a foaf:Person;
+       :jL a foaf:Person;
            foaf:name "Joe Lambda"@en .
   """
-  
+
+  val webID = new URL(webidProfile.secure.to_uri + "#jL")
+
+
   val updatePk = """
-       PREFIX foaf: <http://xmlns.com/foaf/0.1/>
        PREFIX cert: <http://www.w3.org/ns/auth/cert#>
        PREFIX rsa: <http://www.w3.org/ns/auth/rsa#>
        PREFIX : <#>
        INSERT DATA {
-         :j1 foaf:key [ rsa:modulus "%s"^^cert:hex;
+         :jL cert:key [ rsa:modulus "%s"^^cert:hex;
                         rsa:public_exponent "%s"^^cert:int ] .
        }
   """
@@ -128,21 +113,11 @@
        PREFIX foaf: <http://xmlns.com/foaf/0.1/>
        PREFIX : <#>
        INSERT DATA {
-          :j1 foaf:knows <%s> .
+          :jL foaf:knows <%s> .
        }
   """
 
-  val rsagen = KeyPairGenerator.getInstance("RSA")
-  rsagen.initialize(512)
-  val rsaKP = rsagen.generateKeyPair()
-  val certFct = CertificateFactory.getInstance("X.509")
-  val webID = new URL(webidProfile.secure.to_uri + "#me")
-  val testCert = X509Cert.generate_self_signed("CN=JoeLambda, OU=DIG, O=W3C", rsaKP, 1, webID)
 
-  val testCertPk = testCert.getPublicKey.asInstanceOf[RSAPublicKey]
-
-  keyManager.addClientCert("JoeLambda",Array(testCert),rsaKP.getPrivate)
-  
   "PUTing nothing on /people/" should {
        "return a 201" in {
          val httpCode = Http(peopleDirUri.secure.put(TURTLE, "") get_statusCode)
@@ -175,17 +150,47 @@
      }
    }
 
+
    "POSTing public key into the /people/Lambda/Joe profile" should {
-     "return a 200" in {
-       val updateQ = updatePk.format(
-                     testCertPk.getModulus.toString(16),
-                     testCertPk.getPublicExponent()
+
+     "first create signed WebID certificate and add it to local SSL keystore" in {
+       val keystore = getClass.getClassLoader.getResource("KEYSTORE.jks")
+       val signer = X509CertSigner(
+         keystore,
+         "JKS",
+         "secret",
+         "localhost"
        )
-       System.out.println(updateQ)
+
+       val rsagen = KeyPairGenerator.getInstance("RSA")
+       rsagen.initialize(512)
+       val rsaKP = rsagen.generateKeyPair()
+
+
+       val testCert = signer.generate("CN=JoeLambda, OU=DIG, O=W3C", rsaKP.getPublic.asInstanceOf[RSAPublicKey], 1, webID)
+       
+       testCert mustNotBe null
+
+       testKeyManager.addClientCert("JoeLambda",Array(testCert),rsaKP.getPrivate)
+    }
+
+     "return a 200 when POSTing relations to profile" in {
+       val joeCert = testKeyManager.getCertificateChain("JoeLambda")
+
+       joeCert mustNotBe null
+
+       val joeKey = joeCert(0).getPublicKey.asInstanceOf[RSAPublicKey]
+
+       val updateQStr = updatePk.format(
+                     joeKey.getModulus.toString(16),
+                     joeKey.getPublicExponent()
+       )
+
        val httpCode = Http(
-         webidProfile.secure.postSPARQL(updateQ) get_statusCode )
+         webidProfile.secure.postSPARQL(updateQStr) get_statusCode )
         httpCode must_== 200
      }
+
      "create 3 more relations" in {
        val model = Http(webidProfile.secure as_model(baseURI(webidProfile.secure), RDFXML))
        model.size() must_== 7
@@ -201,7 +206,7 @@
   :a1 a acl:Authorization;
      acl:accessTo <Joe>;
      acl:mode acl:Write;
-     acl:agent <%s> .
+     acl:agent <%s>, <http://bblfish.net/people/henry/card#me> .
 
   :allRead a acl:Authorization;
      acl:accessTo <Joe>;
@@ -221,7 +226,7 @@
     }
     
     "everybody can still read the profile" in {
-      keyManager.setId(null)
+      testKeyManager.setId(null)
       val model = Http(webidProfile.secure as_model(baseURI(webidProfile.secure), RDFXML))
       model.size() must_== 7
     }
@@ -232,26 +237,38 @@
     }
 
     "access it as the user - allow him to add a friend" in {
-      keyManager.setId("JoeLambda")
-//      val scon =webidProfile.secure.to_uri.toURL.openConnection().asInstanceOf[HttpsURLConnection]
-//      scon.setSSLSocketFactory(sslContext.getSocketFactory)
-//      scon.setRequestProperty("Content-Type",TURTLE.contentType)
-//      scon.setRequestMethod("POST")
-//      val msg = updateFriend.format("http://bblfish.net/#hjs").getBytes("UTF-8")
-//      scon.setRequestProperty("Content-Length",msg.length.toString)
-//      scon.setDoOutput(true)
-//      scon.setDoInput(true)
-//
-//      val out = scon.getOutputStream
-//      out.write(msg)
-//      out.flush()
-//      out.close()
-//      scon.connect()
-//
-//      val httpCode = scon.getResponseCode
+      testKeyManager.setId("JoeLambda")
 
-      val httpCode = Http( webidProfile.secure.put(TURTLE, updateFriend.format("http://bblfish.net/#hjs")) get_statusCode )
-      httpCode must_== 201
+/*    The code below was very useful to help me debug this.
+      Sometimes it helps to get back to basics. So I will leave this here.
+
+      val scon =webidProfile.secure.to_uri.toURL.openConnection().asInstanceOf[HttpsURLConnection]
+      scon.setSSLSocketFactory(sslContext.getSocketFactory)
+      scon.setRequestProperty("Content-Type",Post.SPARQL)
+      scon.setRequestProperty("User-Agent" , "Java/1.7.0")
+      scon.setRequestMethod("POST")
+      val msg = updateFriend.format("http://bblfish.net/#hjs").getBytes("UTF-8")
+      scon.setRequestProperty("Content-Length",msg.length.toString)
+      scon.setDoOutput(true)
+      scon.setDoInput(true)
+
+      val out = scon.getOutputStream
+      out.write(msg)
+      out.flush()
+      out.close()
+      scon.connect()
+
+      val httpCode = scon.getResponseCode
+*/
+
+      val req =webidProfile.secure.PUT <:< Map("User-Agent" -> "Java/1.7.0","Content-Type"->Post.SPARQL)
+      val req2 = req.copy(
+              method="POST",
+              body=Some(new RefStringEntity(updateFriend.format(webID.toExternalForm),Post.SPARQL,"UTF-8"))
+            )
+
+      val httpCode = Http( req2 get_statusCode )
+      httpCode must_== 200
     }
 
     "and so have one more relation in the foaf" in {
--- a/src/test/scala/auth/SecureReadWriteWebSpec.scala	Fri Oct 28 00:35:16 2011 +0200
+++ b/src/test/scala/auth/SecureReadWriteWebSpec.scala	Sun Oct 30 20:42:26 2011 +0100
@@ -31,8 +31,12 @@
  */
 
 object SecureReadWriteWebSpec extends Specification {
+  try {
   "The Secure Read Write Web".isSpecifiedBy(
      CreateWebIDSpec
    )
+  } catch {
+    case e => e.printStackTrace(System.out)
+  }
 
 }
\ No newline at end of file
--- a/src/test/scala/auth/secure_specs.scala	Fri Oct 28 00:35:16 2011 +0200
+++ b/src/test/scala/auth/secure_specs.scala	Sun Oct 30 20:42:26 2011 +0100
@@ -26,12 +26,15 @@
 import unfiltered.spec.netty.Started
 import org.specs.Specification
 import unfiltered.netty.{ReceivedMessage, ServerErrorResponse, cycle}
-import org.w3.readwriteweb.auth.RDFAuthZ
 import java.io.File
 import org.w3.readwriteweb._
 import grizzled.file.GrizzledFile._
 
-import org.specs.specification.BeforeAfter
+import java.security.cert.X509Certificate
+import org.apache.http.conn.scheme.Scheme
+import dispatch.Http
+import org.apache.http.client.HttpClient
+import javax.net.ssl.{SSLContext, X509TrustManager, KeyManager}
 
 /**
  * @author hjs
@@ -42,9 +45,21 @@
 trait SecureServed extends Started {
   import org.w3.readwriteweb.netty._
 
+  //todo: replace this with non property method of setting this.
+  System.setProperty("netty.ssl.keyStore",getClass.getClassLoader.getResource("KEYSTORE.jks").getFile)
+  System.setProperty("netty.ssl.keyStoreType","JKS")
+  System.setProperty("netty.ssl.keyStorePassword","secret")
+
   def setup: (Https => Https)
   lazy val server = setup( KeyAuth_Https(port) )
 
+
+}
+
+object AcceptAllTrustManager extends X509TrustManager {
+      def checkClientTrusted(chain: Array[X509Certificate], authType: String) {}
+      def checkServerTrusted(chain: Array[X509Certificate], authType: String) {}
+      def getAcceptedIssuers = Array[X509Certificate]()
 }
 
 /**
@@ -55,7 +70,40 @@
 
   def resourceManager: ResourceManager
 
+  /**
+   * Inject flexible behavior into the client ssl so that it does not
+   * break on every localhost problem. It returns a key manager which can be used
+   * to allow the client to take on various guises
+   */
+  def flexi(client: HttpClient, km: KeyManager): SSLContext = {
+
+    val  sslContext = javax.net.ssl.SSLContext.getInstance("TLS");
+  
+    sslContext.init(Array(km.asInstanceOf[KeyManager]), Array(AcceptAllTrustManager),null); // we are not trying to test our trust of localhost server
+
+    import org.apache.http.conn.ssl._
+    val sf = new SSLSocketFactory(sslContext, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER)
+    val scheme = new Scheme("https", 443, sf);
+    client.getConnectionManager.getSchemeRegistry.register(scheme)
+
+    sslContext
+  }
+
+
+
   val webCache = new WebCache()
+  val serverSslContext = javax.net.ssl.SSLContext.getInstance("TLS");
+
+
+
+  flexi(webCache.http.client, new FlexiKeyManager)
+
+
+  val testKeyManager = new FlexiKeyManager();
+  val sslContext = flexi(Http.client,testKeyManager)
+  
+
+
 
   val rww = new cycle.Plan  with cycle.ThreadPool with ServerErrorResponse with ReadWriteWeb[ReceivedMessage,HttpResponse] {
     val rm = resourceManager
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test_www/.meta.n3	Sun Oct 30 20:42:26 2011 +0100
@@ -0,0 +1,14 @@
+@prefix acl: <http://www.w3.org/ns/auth/acl#> .
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix : <#> .
+
+:a1 a acl:Authorization;
+   acl:accessTo <foaf.n3>;
+   acl:mode acl:Write;
+   acl:agent <http://bblfish.net/people/henry/card#me> .
+
+    
+:readAll a acl:Authorization;
+   acl:accessTo <foaf.n3>;
+   acl:mode acl:Read;
+   acl:agentClass foaf:Agent .
--- a/test_www/foaf.n3.protect.n3	Fri Oct 28 00:35:16 2011 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,8 +0,0 @@
-@prefix acl: <http://www.w3.org/ns/auth/acl#> .
-@prefix : <#> .
-
-:a1 a acl:Authorization;
-   acl:accessTo <foaf.n3>;
-   acl:mode acl:Read;
-   acl:agent <http://bblfish.net/people/henry/card#me> .
-