src/main/scala/auth/WebIdClaim.scala
author Henry Story <henry.story@bblfish.net>
Thu, 24 Nov 2011 13:53:25 +0100
branchwebid
changeset 140 9993d3594e1a
parent 129 0e0d7212e63c
child 142 d1d551188b0f
permissions -rw-r--r--
updated to latest spec ( http://webid.info/spec ) which simplifies dramatically the code - it is not accidental. Also upgraded to spec2 which seems to work.
     1 /*
     2  * Copyright (c) 2011 Henry Story (bblfish.net)
     3  * under the MIT licence defined
     4  *    http://www.opensource.org/licenses/mit-license.html
     5  *
     6  * Permission is hereby granted, free of charge, to any person obtaining a copy of
     7  * this software and associated documentation files (the "Software"), to deal in the
     8  * Software without restriction, including without limitation the rights to use, copy,
     9  * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
    10  * and to permit persons to whom the Software is furnished to do so, subject to the
    11  * following conditions:
    12  *
    13  * The above copyright notice and this permission notice shall be included in all
    14  * copies or substantial portions of the Software.
    15  *
    16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    17  * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    18  * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    19  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    20  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    21  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    22  */
    23 
    24 package org.w3.readwriteweb.auth
    25 
    26 import java.security.PublicKey
    27 import org.w3.readwriteweb.WebCache
    28 import java.security.interfaces.RSAPublicKey
    29 import com.hp.hpl.jena.query.{QueryExecutionFactory, QueryExecution, QuerySolutionMap, QueryFactory}
    30 import java.net.URL
    31 import com.hp.hpl.jena.datatypes.xsd.XSDDatatype
    32 
    33 /**
    34  * @author hjs
    35  * @created: 13/10/2011
    36  */
    37 
    38 object WebIDClaim {
    39     final val cert: String = "http://www.w3.org/ns/auth/cert#"
    40 
    41     val askQuery = QueryFactory.create("""
    42       PREFIX : <http://www.w3.org/ns/auth/cert#>
    43       ASK {
    44           ?webid :key [ :modulus ?m ;
    45                         :exponent ?e ].
    46       }""")
    47 
    48      def hex(bytes: Array[Byte]): String = bytes.map("%02X" format _).mkString.stripPrefix("00")
    49 
    50 }
    51 
    52 /**
    53  * A claim that the user identified by the WebId controls the public key
    54  *
    55  * @author bblfish
    56  * @created 30/03/2011
    57  */
    58 class WebIDClaim(val webId: String, val key: PublicKey) {
    59 
    60 	lazy val principal = new WebIdPrincipal(webId)
    61 
    62 	var tests: List[Verification] = List()
    63 
    64   /** the tests have been done and are still valid - the idea is perhaps after a time tests would
    65    * have to be done again? Eg: the claim is cached and re-used after a while */
    66   private var valid = false
    67 
    68   def verified(implicit cache: WebCache): Boolean = {
    69     if (!valid) tests = verify(cache)
    70     tests.contains(verifiedWebID)
    71   }
    72   
    73   private def verify(implicit cache: WebCache): List[Verification] = {
    74     import org.w3.readwriteweb.util.wrapValidation
    75 
    76     import WebIDClaim._
    77     try {
    78       return if (!webId.startsWith("http:") && !webId.startsWith("https:")) {
    79         //todo: ftp, and ftps should also be doable, though content negotiations is then lacking
    80         unsupportedProtocol::Nil
    81       } else if (!key.isInstanceOf[RSAPublicKey]) {
    82         certificateKeyTypeNotSupported::Nil
    83       } else {
    84         val res = for {
    85           model <- cache.resource(new URL(webId)).get() failMap {
    86             t => new ProfileError("error fetching profile", t)
    87           }
    88         } yield {
    89           val rsakey = key.asInstanceOf[RSAPublicKey]
    90           val initialBinding = new QuerySolutionMap();
    91           initialBinding.add("webid",model.createResource(webId))
    92           initialBinding.add("m",model.createTypedLiteral( hex(rsakey.getModulus.toByteArray), XSDDatatype.XSDhexBinary))
    93           initialBinding.add("e",model.createTypedLiteral( rsakey.getPublicExponent.toString, XSDDatatype.XSDinteger ))
    94           val qe: QueryExecution = QueryExecutionFactory.create(WebIDClaim.askQuery, model,initialBinding)
    95           try {
    96             if (qe.execAsk()) verifiedWebID
    97             else noMatchingKey
    98           } finally {
    99             qe.close()
   100           }
   101         }
   102         res.either match {
   103           case Right(tests) => tests::Nil
   104           case Left(profileErr) => profileErr::Nil
   105         }
   106       }
   107     } finally {
   108       valid = true
   109     }
   110 
   111 
   112   }
   113 
   114 	def canEqual(other: Any) = other.isInstanceOf[WebIDClaim]
   115 
   116 	override
   117 	def equals(other: Any): Boolean =
   118 		other match {
   119 			case that: WebIDClaim => (that eq this) || (that.canEqual(this) && webId == that.webId && key == that.key)
   120 			case _ => false
   121 		}
   122 
   123 	override
   124 	lazy val hashCode: Int = 41 * (
   125 		41 * (
   126 			41 + (if (webId != null) webId.hashCode else 0)
   127 			) + (if (key != null) key.hashCode else 0)
   128 		)
   129 
   130 }
   131 
   132 class ProfileError(msg: String,  t: Throwable) extends Verification(profileOkTst,failed,msg, Some(t))
   133 class KeyProblem(msg: String) extends Verification(profileWellFormedKeyTst,failed,msg)
   134 
   135 object keyDoesNotMatch extends Verification(null,null,null) //this test will be forgotten
   136 
   137 object verifiedWebID extends Verification(webidClaimTst, passed, "WebId verified")
   138 object noMatchingKey extends Verification(webidClaimTst, failed, "No keys in profile matches key in cert")
   139 object unsupportedProtocol extends Verification(webidClaimTst,untested,"WebID protocol not supported" )
   140 
   141 object certificateKeyTypeNotSupported extends Verification(pubkeyTypeTst,failed,"The certificate key type is not supported. We only support RSA")
   142 
   143