diff --git a/.gitignore b/.gitignore
index d4d6dee0fd1e80f6a2cfde6ee2ab1c82f47dc67a..2350421419f0ea7f2c9d18599a8ccde81e16df15 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+.DS_Store
 .idea/*
 .vscode/*
 /cli
diff --git a/cmd/rekor-cli/app/pflags.go b/cmd/rekor-cli/app/pflags.go
index cfd8acaaaa4974b3606667da419fcf0e18188a77..f70113931613d12179577d8c0e5d5380ad5f6a7f 100644
--- a/cmd/rekor-cli/app/pflags.go
+++ b/cmd/rekor-cli/app/pflags.go
@@ -34,6 +34,7 @@ import (
 	"github.com/spf13/viper"
 
 	"github.com/sigstore/rekor/pkg/generated/models"
+	jar_v001 "github.com/sigstore/rekor/pkg/types/jar/v0.0.1"
 	rekord_v001 "github.com/sigstore/rekor/pkg/types/rekord/v0.0.1"
 	rpm_v001 "github.com/sigstore/rekor/pkg/types/rpm/v0.0.1"
 )
@@ -131,7 +132,7 @@ func validateArtifactPFlags(uuidValid, indexValid bool) error {
 		if signature == "" && typeStr == "rekord" {
 			return errors.New("--signature is required when --artifact is used")
 		}
-		if publicKey == "" {
+		if publicKey == "" && typeStr != "jar" {
 			return errors.New("--public-key is required when --artifact is used")
 		}
 	}
@@ -139,6 +140,71 @@ func validateArtifactPFlags(uuidValid, indexValid bool) error {
 	return nil
 }
 
+func CreateJarFromPFlags() (models.ProposedEntry, error) {
+	//TODO: how to select version of item to create
+	returnVal := models.Jar{}
+	re := new(jar_v001.V001Entry)
+
+	jar := viper.GetString("entry")
+	if jar != "" {
+		var jarBytes []byte
+		jarURL, err := url.Parse(jar)
+		if err == nil && jarURL.IsAbs() {
+			/* #nosec G107 */
+			jarResp, err := http.Get(jar)
+			if err != nil {
+				return nil, fmt.Errorf("error fetching 'jar': %w", err)
+			}
+			defer jarResp.Body.Close()
+			jarBytes, err = ioutil.ReadAll(jarResp.Body)
+			if err != nil {
+				return nil, fmt.Errorf("error fetching 'jar': %w", err)
+			}
+		} else {
+			jarBytes, err = ioutil.ReadFile(filepath.Clean(jar))
+			if err != nil {
+				return nil, fmt.Errorf("error processing 'jar' file: %w", err)
+			}
+		}
+		if err := json.Unmarshal(jarBytes, &returnVal); err != nil {
+			return nil, fmt.Errorf("error parsing jar file: %w", err)
+		}
+	} else {
+		// we will need only the artifact; public-key & signature are embedded in JAR
+		re.JARModel = models.JarV001Schema{}
+		re.JARModel.Archive = &models.JarV001SchemaArchive{}
+
+		artifact := viper.GetString("artifact")
+		dataURL, err := url.Parse(artifact)
+		if err == nil && dataURL.IsAbs() {
+			re.JARModel.Archive.URL = strfmt.URI(artifact)
+		} else {
+			artifactBytes, err := ioutil.ReadFile(filepath.Clean(artifact))
+			if err != nil {
+				return nil, fmt.Errorf("error reading artifact file: %w", err)
+			}
+
+			//TODO: ensure this is a valid JAR file; look for META-INF/MANIFEST.MF?
+			re.JARModel.Archive.Content = strfmt.Base64(artifactBytes)
+		}
+
+		if err := re.Validate(); err != nil {
+			return nil, err
+		}
+
+		if re.HasExternalEntities() {
+			if err := re.FetchExternalEntities(context.Background()); err != nil {
+				return nil, fmt.Errorf("error retrieving external entities: %v", err)
+			}
+		}
+
+		returnVal.APIVersion = swag.String(re.APIVersion())
+		returnVal.Spec = re.JARModel
+	}
+
+	return &returnVal, nil
+}
+
 func CreateRpmFromPFlags() (models.ProposedEntry, error) {
 	//TODO: how to select version of item to create
 	returnVal := models.Rpm{}
@@ -358,6 +424,7 @@ func (t *typeFlag) Set(s string) error {
 	set := map[string]struct{}{
 		"rekord": {},
 		"rpm":    {},
+		"jar":    {},
 	}
 	if _, ok := set[s]; ok {
 		t.value = s
diff --git a/cmd/rekor-cli/app/upload.go b/cmd/rekor-cli/app/upload.go
index 2c49d765e2d181be0394395e642ace4400c97d36..c9cfc6ad50d3af9bfa0fed684117faf253f3a589 100644
--- a/cmd/rekor-cli/app/upload.go
+++ b/cmd/rekor-cli/app/upload.go
@@ -78,6 +78,11 @@ var uploadCmd = &cobra.Command{
 			if err != nil {
 				return nil, err
 			}
+		case "jar":
+			entry, err = CreateJarFromPFlags()
+			if err != nil {
+				return nil, err
+			}
 		default:
 			return nil, errors.New("unknown type specified")
 		}
diff --git a/cmd/rekor-server/app/serve.go b/cmd/rekor-server/app/serve.go
index d92b11d05a1f6f9570f3cff55729a0d8ea74dd9e..2d68d9601f8aea711dc112062088fb5c7a885671 100644
--- a/cmd/rekor-server/app/serve.go
+++ b/cmd/rekor-server/app/serve.go
@@ -28,6 +28,8 @@ import (
 	"github.com/sigstore/rekor/pkg/generated/restapi"
 	"github.com/sigstore/rekor/pkg/generated/restapi/operations"
 	"github.com/sigstore/rekor/pkg/log"
+	"github.com/sigstore/rekor/pkg/types/jar"
+	jar_v001 "github.com/sigstore/rekor/pkg/types/jar/v0.0.1"
 	"github.com/sigstore/rekor/pkg/types/rekord"
 	rekord_v001 "github.com/sigstore/rekor/pkg/types/rekord/v0.0.1"
 	"github.com/sigstore/rekor/pkg/types/rpm"
@@ -63,6 +65,7 @@ var serveCmd = &cobra.Command{
 		pluggableTypeMap := map[string]string{
 			rekord.KIND: rekord_v001.APIVERSION,
 			rpm.KIND:    rpm_v001.APIVERSION,
+			jar.KIND:    jar_v001.APIVERSION,
 		}
 
 		for k, v := range pluggableTypeMap {
diff --git a/go.mod b/go.mod
index c490a5b7371e5d91fc48725cb9b00aca590b77ef..daf05c4edbc4cbe61be5ba9903ddfd23b4e0f4dd 100644
--- a/go.mod
+++ b/go.mod
@@ -17,6 +17,7 @@ require (
 	github.com/go-openapi/validate v0.20.2
 	github.com/google/rpmpack v0.0.0-20210107155803-d6befbf05148
 	github.com/google/trillian v1.3.13
+	github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c // indirect
 	github.com/jedisct1/go-minisign v0.0.0-20210106175330-e54e81d562c7
 	github.com/mediocregopher/radix/v4 v4.0.0-beta.1
 	github.com/mitchellh/go-homedir v1.1.0
@@ -24,11 +25,14 @@ require (
 	github.com/pkg/errors v0.9.1
 	github.com/prometheus/client_golang v1.10.0
 	github.com/rs/cors v1.7.0
-	github.com/sigstore/sigstore v0.0.0-20210414183523-383d6554c35c
+	github.com/sassoftware/relic v7.2.1+incompatible
+	github.com/sigstore/sigstore v0.0.0-20210415112811-cb2061113e4a
 	github.com/spf13/cobra v1.1.3
 	github.com/spf13/pflag v1.0.5
 	github.com/spf13/viper v1.7.1
+	github.com/ulikunitz/xz v0.5.9 // indirect
 	github.com/urfave/negroni v1.0.0
+	github.com/zalando/go-keyring v0.1.1 // indirect
 	go.uber.org/goleak v1.1.10
 	go.uber.org/zap v1.16.0
 	gocloud.dev v0.22.0
diff --git a/go.sum b/go.sum
index 4424062d0a874e227dc1504f17456ec3831e6b50..aa71003b2f4bfa309ad8731738fa78de5bb44ab9 100644
--- a/go.sum
+++ b/go.sum
@@ -194,6 +194,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/danieljoos/wincred v1.1.0 h1:3RNcEpBg4IhIChZdFRSdlQt1QjCp1sMAPIrOnm7Yf8g=
+github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
 github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -417,6 +419,8 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA
 github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
 github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
 github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
+github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
+github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -585,6 +589,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
 github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
 github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
 github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c h1:aY2hhxLhjEAbfXOx2nRJxCXezC6CO2V/yN+OCr1srtk=
+github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
 github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4=
@@ -847,6 +853,8 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
 github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
+github.com/sassoftware/relic v7.2.1+incompatible h1:Pwyh1F3I0r4clFJXkSI8bOyJINGqpgjJU3DYAZeI05A=
+github.com/sassoftware/relic v7.2.1+incompatible/go.mod h1:CWfAxv73/iLZ17rbyhIEq3K9hs5w6FpNMdUT//qR+zk=
 github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
 github.com/segmentio/ksuid v1.0.3/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
@@ -857,8 +865,8 @@ github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxr
 github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sigstore/rekor v0.1.1/go.mod h1:b+T8TvGKWgaFbtPRQgF/gXjbj/R9HdJ5lA93cnGT3Sc=
-github.com/sigstore/sigstore v0.0.0-20210414183523-383d6554c35c h1:YPd6DUue2Vi6cavCGx7cgz3CM2CGS7LHm5FamwbUPic=
-github.com/sigstore/sigstore v0.0.0-20210414183523-383d6554c35c/go.mod h1:EoLIp5JbrCE2VZqdCCIemNEdNYiOcdwF0igIvorqo1o=
+github.com/sigstore/sigstore v0.0.0-20210415112811-cb2061113e4a h1:neuFPsd2BVJvqH5CdoPwKjZI3ZtD2q2PSf+7zrZc5gM=
+github.com/sigstore/sigstore v0.0.0-20210415112811-cb2061113e4a/go.mod h1:EoLIp5JbrCE2VZqdCCIemNEdNYiOcdwF0igIvorqo1o=
 github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@@ -908,6 +916,7 @@ github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3
 github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -932,8 +941,9 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr
 github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
 github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
-github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4=
 github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
+github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I=
+github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
 github.com/ultraware/funlen v0.0.1/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
 github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
 github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
@@ -956,6 +966,8 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/zalando/go-keyring v0.1.1 h1:w2V9lcx/Uj4l+dzAf1m9s+DJ1O8ROkEHnynonHjTcYE=
+github.com/zalando/go-keyring v0.1.1/go.mod h1:OIC+OZ28XbmwFxU/Rp9V7eKzZjamBJwRzC8UFJH9+L8=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
 go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
diff --git a/openapi.yaml b/openapi.yaml
index fe705d8ab5e1ffd4d44536beb31cb6bec43da957..0f9e240214c869626eb3bb39db7245eb6f778ab9 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -269,6 +269,23 @@ definitions:
         - spec
       additionalProperties: false
 
+  jar:
+    type: object
+    description: Java Archive (JAR)
+    allOf:
+    - $ref: '#/definitions/ProposedEntry'
+    - properties:
+        apiVersion:
+          type: string
+          pattern: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
+        spec:
+          type: object
+          $ref: 'pkg/types/jar/jar_schema.json'
+      required:
+        - apiVersion
+        - spec
+      additionalProperties: false
+
   LogEntry:
     type: object
     additionalProperties:
diff --git a/pkg/generated/models/jar.go b/pkg/generated/models/jar.go
new file mode 100644
index 0000000000000000000000000000000000000000..3df3d21b8a4f4b16a2b288bb8097870dea46395c
--- /dev/null
+++ b/pkg/generated/models/jar.go
@@ -0,0 +1,210 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+//
+// Copyright 2021 The Sigstore Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package models
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/go-openapi/validate"
+)
+
+// Jar Java Archive (JAR)
+//
+// swagger:model jar
+type Jar struct {
+
+	// api version
+	// Required: true
+	// Pattern: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
+	APIVersion *string `json:"apiVersion"`
+
+	// spec
+	// Required: true
+	Spec JarSchema `json:"spec"`
+}
+
+// Kind gets the kind of this subtype
+func (m *Jar) Kind() string {
+	return "jar"
+}
+
+// SetKind sets the kind of this subtype
+func (m *Jar) SetKind(val string) {
+}
+
+// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure
+func (m *Jar) UnmarshalJSON(raw []byte) error {
+	var data struct {
+
+		// api version
+		// Required: true
+		// Pattern: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
+		APIVersion *string `json:"apiVersion"`
+
+		// spec
+		// Required: true
+		Spec JarSchema `json:"spec"`
+	}
+	buf := bytes.NewBuffer(raw)
+	dec := json.NewDecoder(buf)
+	dec.UseNumber()
+
+	if err := dec.Decode(&data); err != nil {
+		return err
+	}
+
+	var base struct {
+		/* Just the base type fields. Used for unmashalling polymorphic types.*/
+
+		Kind string `json:"kind"`
+	}
+	buf = bytes.NewBuffer(raw)
+	dec = json.NewDecoder(buf)
+	dec.UseNumber()
+
+	if err := dec.Decode(&base); err != nil {
+		return err
+	}
+
+	var result Jar
+
+	if base.Kind != result.Kind() {
+		/* Not the type we're looking for. */
+		return errors.New(422, "invalid kind value: %q", base.Kind)
+	}
+
+	result.APIVersion = data.APIVersion
+	result.Spec = data.Spec
+
+	*m = result
+
+	return nil
+}
+
+// MarshalJSON marshals this object with a polymorphic type to a JSON structure
+func (m Jar) MarshalJSON() ([]byte, error) {
+	var b1, b2, b3 []byte
+	var err error
+	b1, err = json.Marshal(struct {
+
+		// api version
+		// Required: true
+		// Pattern: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
+		APIVersion *string `json:"apiVersion"`
+
+		// spec
+		// Required: true
+		Spec JarSchema `json:"spec"`
+	}{
+
+		APIVersion: m.APIVersion,
+
+		Spec: m.Spec,
+	})
+	if err != nil {
+		return nil, err
+	}
+	b2, err = json.Marshal(struct {
+		Kind string `json:"kind"`
+	}{
+
+		Kind: m.Kind(),
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	return swag.ConcatJSON(b1, b2, b3), nil
+}
+
+// Validate validates this jar
+func (m *Jar) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateAPIVersion(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateSpec(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *Jar) validateAPIVersion(formats strfmt.Registry) error {
+
+	if err := validate.Required("apiVersion", "body", m.APIVersion); err != nil {
+		return err
+	}
+
+	if err := validate.Pattern("apiVersion", "body", *m.APIVersion, `^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$`); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *Jar) validateSpec(formats strfmt.Registry) error {
+
+	if m.Spec == nil {
+		return errors.Required("spec", "body", nil)
+	}
+
+	return nil
+}
+
+// ContextValidate validate this jar based on the context it is used
+func (m *Jar) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	var res []error
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *Jar) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *Jar) UnmarshalBinary(b []byte) error {
+	var res Jar
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}
diff --git a/pkg/generated/models/jar_schema.go b/pkg/generated/models/jar_schema.go
new file mode 100644
index 0000000000000000000000000000000000000000..d45c53da7c513772cfbc2706d8c6bb33a91c4d46
--- /dev/null
+++ b/pkg/generated/models/jar_schema.go
@@ -0,0 +1,29 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+//
+// Copyright 2021 The Sigstore Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package models
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+// JarSchema JAR Schema
+//
+// Schema for JAR objects
+//
+// swagger:model jarSchema
+type JarSchema interface{}
diff --git a/pkg/generated/models/jar_v001_schema.go b/pkg/generated/models/jar_v001_schema.go
new file mode 100644
index 0000000000000000000000000000000000000000..f2fcab6ebb3990f5746c10bc64294aa09d0878b3
--- /dev/null
+++ b/pkg/generated/models/jar_v001_schema.go
@@ -0,0 +1,545 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+//
+// Copyright 2021 The Sigstore Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package models
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+	"context"
+	"encoding/json"
+
+	"github.com/go-openapi/errors"
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/go-openapi/validate"
+)
+
+// JarV001Schema JAR v0.0.1 Schema
+//
+// Schema for JAR entries
+//
+// swagger:model jarV001Schema
+type JarV001Schema struct {
+
+	// archive
+	// Required: true
+	Archive *JarV001SchemaArchive `json:"archive"`
+
+	// Arbitrary content to be included in the verifiable entry in the transparency log
+	ExtraData interface{} `json:"extraData,omitempty"`
+
+	// signature
+	Signature *JarV001SchemaSignature `json:"signature,omitempty"`
+}
+
+// Validate validates this jar v001 schema
+func (m *JarV001Schema) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateArchive(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateSignature(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *JarV001Schema) validateArchive(formats strfmt.Registry) error {
+
+	if err := validate.Required("archive", "body", m.Archive); err != nil {
+		return err
+	}
+
+	if m.Archive != nil {
+		if err := m.Archive.Validate(formats); err != nil {
+			if ve, ok := err.(*errors.Validation); ok {
+				return ve.ValidateName("archive")
+			}
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (m *JarV001Schema) validateSignature(formats strfmt.Registry) error {
+	if swag.IsZero(m.Signature) { // not required
+		return nil
+	}
+
+	if m.Signature != nil {
+		if err := m.Signature.Validate(formats); err != nil {
+			if ve, ok := err.(*errors.Validation); ok {
+				return ve.ValidateName("signature")
+			}
+			return err
+		}
+	}
+
+	return nil
+}
+
+// ContextValidate validate this jar v001 schema based on the context it is used
+func (m *JarV001Schema) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.contextValidateArchive(ctx, formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.contextValidateSignature(ctx, formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *JarV001Schema) contextValidateArchive(ctx context.Context, formats strfmt.Registry) error {
+
+	if m.Archive != nil {
+		if err := m.Archive.ContextValidate(ctx, formats); err != nil {
+			if ve, ok := err.(*errors.Validation); ok {
+				return ve.ValidateName("archive")
+			}
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (m *JarV001Schema) contextValidateSignature(ctx context.Context, formats strfmt.Registry) error {
+
+	if m.Signature != nil {
+		if err := m.Signature.ContextValidate(ctx, formats); err != nil {
+			if ve, ok := err.(*errors.Validation); ok {
+				return ve.ValidateName("signature")
+			}
+			return err
+		}
+	}
+
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *JarV001Schema) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *JarV001Schema) UnmarshalBinary(b []byte) error {
+	var res JarV001Schema
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}
+
+// JarV001SchemaArchive Information about the archive associated with the entry
+//
+// swagger:model JarV001SchemaArchive
+type JarV001SchemaArchive struct {
+
+	// Specifies the archive inline within the document
+	// Format: byte
+	Content strfmt.Base64 `json:"content,omitempty"`
+
+	// hash
+	Hash *JarV001SchemaArchiveHash `json:"hash,omitempty"`
+
+	// Specifies the location of the archive; if this is specified, a hash value must also be provided
+	// Format: uri
+	URL strfmt.URI `json:"url,omitempty"`
+}
+
+// Validate validates this jar v001 schema archive
+func (m *JarV001SchemaArchive) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateHash(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateURL(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *JarV001SchemaArchive) validateHash(formats strfmt.Registry) error {
+	if swag.IsZero(m.Hash) { // not required
+		return nil
+	}
+
+	if m.Hash != nil {
+		if err := m.Hash.Validate(formats); err != nil {
+			if ve, ok := err.(*errors.Validation); ok {
+				return ve.ValidateName("archive" + "." + "hash")
+			}
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (m *JarV001SchemaArchive) validateURL(formats strfmt.Registry) error {
+	if swag.IsZero(m.URL) { // not required
+		return nil
+	}
+
+	if err := validate.FormatOf("archive"+"."+"url", "body", "uri", m.URL.String(), formats); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validate this jar v001 schema archive based on the context it is used
+func (m *JarV001SchemaArchive) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.contextValidateHash(ctx, formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *JarV001SchemaArchive) contextValidateHash(ctx context.Context, formats strfmt.Registry) error {
+
+	if m.Hash != nil {
+		if err := m.Hash.ContextValidate(ctx, formats); err != nil {
+			if ve, ok := err.(*errors.Validation); ok {
+				return ve.ValidateName("archive" + "." + "hash")
+			}
+			return err
+		}
+	}
+
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *JarV001SchemaArchive) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *JarV001SchemaArchive) UnmarshalBinary(b []byte) error {
+	var res JarV001SchemaArchive
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}
+
+// JarV001SchemaArchiveHash Specifies the hash algorithm and value encompassing the entire signed archive
+//
+// swagger:model JarV001SchemaArchiveHash
+type JarV001SchemaArchiveHash struct {
+
+	// The hashing function used to compute the hash value
+	// Required: true
+	// Enum: [sha256]
+	Algorithm *string `json:"algorithm"`
+
+	// The hash value for the archive
+	// Required: true
+	Value *string `json:"value"`
+}
+
+// Validate validates this jar v001 schema archive hash
+func (m *JarV001SchemaArchiveHash) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateAlgorithm(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validateValue(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+var jarV001SchemaArchiveHashTypeAlgorithmPropEnum []interface{}
+
+func init() {
+	var res []string
+	if err := json.Unmarshal([]byte(`["sha256"]`), &res); err != nil {
+		panic(err)
+	}
+	for _, v := range res {
+		jarV001SchemaArchiveHashTypeAlgorithmPropEnum = append(jarV001SchemaArchiveHashTypeAlgorithmPropEnum, v)
+	}
+}
+
+const (
+
+	// JarV001SchemaArchiveHashAlgorithmSha256 captures enum value "sha256"
+	JarV001SchemaArchiveHashAlgorithmSha256 string = "sha256"
+)
+
+// prop value enum
+func (m *JarV001SchemaArchiveHash) validateAlgorithmEnum(path, location string, value string) error {
+	if err := validate.EnumCase(path, location, value, jarV001SchemaArchiveHashTypeAlgorithmPropEnum, true); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (m *JarV001SchemaArchiveHash) validateAlgorithm(formats strfmt.Registry) error {
+
+	if err := validate.Required("archive"+"."+"hash"+"."+"algorithm", "body", m.Algorithm); err != nil {
+		return err
+	}
+
+	// value enum
+	if err := m.validateAlgorithmEnum("archive"+"."+"hash"+"."+"algorithm", "body", *m.Algorithm); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *JarV001SchemaArchiveHash) validateValue(formats strfmt.Registry) error {
+
+	if err := validate.Required("archive"+"."+"hash"+"."+"value", "body", m.Value); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validates this jar v001 schema archive hash based on context it is used
+func (m *JarV001SchemaArchiveHash) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *JarV001SchemaArchiveHash) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *JarV001SchemaArchiveHash) UnmarshalBinary(b []byte) error {
+	var res JarV001SchemaArchiveHash
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}
+
+// JarV001SchemaSignature Information about the included signature in the JAR file
+//
+// swagger:model JarV001SchemaSignature
+type JarV001SchemaSignature struct {
+
+	// Specifies the PKCS7 signature embedded within the JAR file
+	// Required: true
+	// Format: byte
+	Content *strfmt.Base64 `json:"content"`
+
+	// public key
+	// Required: true
+	PublicKey *JarV001SchemaSignaturePublicKey `json:"publicKey"`
+}
+
+// Validate validates this jar v001 schema signature
+func (m *JarV001SchemaSignature) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateContent(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if err := m.validatePublicKey(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *JarV001SchemaSignature) validateContent(formats strfmt.Registry) error {
+
+	if err := validate.Required("signature"+"."+"content", "body", m.Content); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func (m *JarV001SchemaSignature) validatePublicKey(formats strfmt.Registry) error {
+
+	if err := validate.Required("signature"+"."+"publicKey", "body", m.PublicKey); err != nil {
+		return err
+	}
+
+	if m.PublicKey != nil {
+		if err := m.PublicKey.Validate(formats); err != nil {
+			if ve, ok := err.(*errors.Validation); ok {
+				return ve.ValidateName("signature" + "." + "publicKey")
+			}
+			return err
+		}
+	}
+
+	return nil
+}
+
+// ContextValidate validate this jar v001 schema signature based on the context it is used
+func (m *JarV001SchemaSignature) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.contextValidatePublicKey(ctx, formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *JarV001SchemaSignature) contextValidatePublicKey(ctx context.Context, formats strfmt.Registry) error {
+
+	if m.PublicKey != nil {
+		if err := m.PublicKey.ContextValidate(ctx, formats); err != nil {
+			if ve, ok := err.(*errors.Validation); ok {
+				return ve.ValidateName("signature" + "." + "publicKey")
+			}
+			return err
+		}
+	}
+
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *JarV001SchemaSignature) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *JarV001SchemaSignature) UnmarshalBinary(b []byte) error {
+	var res JarV001SchemaSignature
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}
+
+// JarV001SchemaSignaturePublicKey The X509 certificate containing the public key JAR which verifies the signature of the JAR
+//
+// swagger:model JarV001SchemaSignaturePublicKey
+type JarV001SchemaSignaturePublicKey struct {
+
+	// Specifies the content of the X509 certificate containing the public key used to verify the signature
+	// Required: true
+	// Format: byte
+	Content *strfmt.Base64 `json:"content"`
+}
+
+// Validate validates this jar v001 schema signature public key
+func (m *JarV001SchemaSignaturePublicKey) Validate(formats strfmt.Registry) error {
+	var res []error
+
+	if err := m.validateContent(formats); err != nil {
+		res = append(res, err)
+	}
+
+	if len(res) > 0 {
+		return errors.CompositeValidationError(res...)
+	}
+	return nil
+}
+
+func (m *JarV001SchemaSignaturePublicKey) validateContent(formats strfmt.Registry) error {
+
+	if err := validate.Required("signature"+"."+"publicKey"+"."+"content", "body", m.Content); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// ContextValidate validates this jar v001 schema signature public key based on context it is used
+func (m *JarV001SchemaSignaturePublicKey) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+	return nil
+}
+
+// MarshalBinary interface implementation
+func (m *JarV001SchemaSignaturePublicKey) MarshalBinary() ([]byte, error) {
+	if m == nil {
+		return nil, nil
+	}
+	return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *JarV001SchemaSignaturePublicKey) UnmarshalBinary(b []byte) error {
+	var res JarV001SchemaSignaturePublicKey
+	if err := swag.ReadJSON(b, &res); err != nil {
+		return err
+	}
+	*m = res
+	return nil
+}
diff --git a/pkg/generated/models/proposed_entry.go b/pkg/generated/models/proposed_entry.go
index 511b3f414f88303441ac19a696ce1b3bfe2bdc67..4643b5259e6777e644b834784049d159c605ee8f 100644
--- a/pkg/generated/models/proposed_entry.go
+++ b/pkg/generated/models/proposed_entry.go
@@ -115,6 +115,12 @@ func unmarshalProposedEntry(data []byte, consumer runtime.Consumer) (ProposedEnt
 			return nil, err
 		}
 		return &result, nil
+	case "jar":
+		var result Jar
+		if err := consumer.Consume(buf2, &result); err != nil {
+			return nil, err
+		}
+		return &result, nil
 	case "rekord":
 		var result Rekord
 		if err := consumer.Consume(buf2, &result); err != nil {
diff --git a/pkg/generated/restapi/embedded_spec.go b/pkg/generated/restapi/embedded_spec.go
index b37e67816f5d6fd3a83f8d2f60d41e562d78a4c3..94f16d21679fcd992d0785c680825cc72658f03a 100644
--- a/pkg/generated/restapi/embedded_spec.go
+++ b/pkg/generated/restapi/embedded_spec.go
@@ -539,6 +539,32 @@ func init() {
         }
       }
     },
+    "jar": {
+      "description": "Java Archive (JAR)",
+      "type": "object",
+      "allOf": [
+        {
+          "$ref": "#/definitions/ProposedEntry"
+        },
+        {
+          "required": [
+            "apiVersion",
+            "spec"
+          ],
+          "properties": {
+            "apiVersion": {
+              "type": "string",
+              "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"
+            },
+            "spec": {
+              "type": "object",
+              "$ref": "pkg/types/jar/jar_schema.json"
+            }
+          },
+          "additionalProperties": false
+        }
+      ]
+    },
     "rekord": {
       "description": "Rekord object",
       "type": "object",
@@ -1031,6 +1057,119 @@ func init() {
         }
       }
     },
+    "JarV001SchemaArchive": {
+      "description": "Information about the archive associated with the entry",
+      "type": "object",
+      "oneOf": [
+        {
+          "required": [
+            "url"
+          ]
+        },
+        {
+          "required": [
+            "content"
+          ]
+        }
+      ],
+      "properties": {
+        "content": {
+          "description": "Specifies the archive inline within the document",
+          "type": "string",
+          "format": "byte"
+        },
+        "hash": {
+          "description": "Specifies the hash algorithm and value encompassing the entire signed archive",
+          "type": "object",
+          "required": [
+            "algorithm",
+            "value"
+          ],
+          "properties": {
+            "algorithm": {
+              "description": "The hashing function used to compute the hash value",
+              "type": "string",
+              "enum": [
+                "sha256"
+              ]
+            },
+            "value": {
+              "description": "The hash value for the archive",
+              "type": "string"
+            }
+          }
+        },
+        "url": {
+          "description": "Specifies the location of the archive; if this is specified, a hash value must also be provided",
+          "type": "string",
+          "format": "uri"
+        }
+      }
+    },
+    "JarV001SchemaArchiveHash": {
+      "description": "Specifies the hash algorithm and value encompassing the entire signed archive",
+      "type": "object",
+      "required": [
+        "algorithm",
+        "value"
+      ],
+      "properties": {
+        "algorithm": {
+          "description": "The hashing function used to compute the hash value",
+          "type": "string",
+          "enum": [
+            "sha256"
+          ]
+        },
+        "value": {
+          "description": "The hash value for the archive",
+          "type": "string"
+        }
+      }
+    },
+    "JarV001SchemaSignature": {
+      "description": "Information about the included signature in the JAR file",
+      "type": "object",
+      "required": [
+        "publicKey",
+        "content"
+      ],
+      "properties": {
+        "content": {
+          "description": "Specifies the PKCS7 signature embedded within the JAR file ",
+          "type": "string",
+          "format": "byte"
+        },
+        "publicKey": {
+          "description": "The X509 certificate containing the public key JAR which verifies the signature of the JAR",
+          "type": "object",
+          "required": [
+            "content"
+          ],
+          "properties": {
+            "content": {
+              "description": "Specifies the content of the X509 certificate containing the public key used to verify the signature",
+              "type": "string",
+              "format": "byte"
+            }
+          }
+        }
+      }
+    },
+    "JarV001SchemaSignaturePublicKey": {
+      "description": "The X509 certificate containing the public key JAR which verifies the signature of the JAR",
+      "type": "object",
+      "required": [
+        "content"
+      ],
+      "properties": {
+        "content": {
+          "description": "Specifies the content of the X509 certificate containing the public key used to verify the signature",
+          "type": "string",
+          "format": "byte"
+        }
+      }
+    },
     "LogEntry": {
       "type": "object",
       "additionalProperties": {
@@ -1502,6 +1641,138 @@ func init() {
         }
       }
     },
+    "jar": {
+      "description": "Java Archive (JAR)",
+      "type": "object",
+      "allOf": [
+        {
+          "$ref": "#/definitions/ProposedEntry"
+        },
+        {
+          "required": [
+            "apiVersion",
+            "spec"
+          ],
+          "properties": {
+            "apiVersion": {
+              "type": "string",
+              "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$"
+            },
+            "spec": {
+              "$ref": "#/definitions/jarSchema"
+            }
+          },
+          "additionalProperties": false
+        }
+      ]
+    },
+    "jarSchema": {
+      "description": "Schema for JAR objects",
+      "type": "object",
+      "title": "JAR Schema",
+      "oneOf": [
+        {
+          "$ref": "#/definitions/jarV001Schema"
+        }
+      ],
+      "$schema": "http://json-schema.org/draft-07/schema",
+      "$id": "http://rekor.sigstore.dev/types/jar/jar_schema.json"
+    },
+    "jarV001Schema": {
+      "description": "Schema for JAR entries",
+      "type": "object",
+      "title": "JAR v0.0.1 Schema",
+      "required": [
+        "archive"
+      ],
+      "properties": {
+        "archive": {
+          "description": "Information about the archive associated with the entry",
+          "type": "object",
+          "oneOf": [
+            {
+              "required": [
+                "url"
+              ]
+            },
+            {
+              "required": [
+                "content"
+              ]
+            }
+          ],
+          "properties": {
+            "content": {
+              "description": "Specifies the archive inline within the document",
+              "type": "string",
+              "format": "byte"
+            },
+            "hash": {
+              "description": "Specifies the hash algorithm and value encompassing the entire signed archive",
+              "type": "object",
+              "required": [
+                "algorithm",
+                "value"
+              ],
+              "properties": {
+                "algorithm": {
+                  "description": "The hashing function used to compute the hash value",
+                  "type": "string",
+                  "enum": [
+                    "sha256"
+                  ]
+                },
+                "value": {
+                  "description": "The hash value for the archive",
+                  "type": "string"
+                }
+              }
+            },
+            "url": {
+              "description": "Specifies the location of the archive; if this is specified, a hash value must also be provided",
+              "type": "string",
+              "format": "uri"
+            }
+          }
+        },
+        "extraData": {
+          "description": "Arbitrary content to be included in the verifiable entry in the transparency log",
+          "type": "object",
+          "additionalProperties": true
+        },
+        "signature": {
+          "description": "Information about the included signature in the JAR file",
+          "type": "object",
+          "required": [
+            "publicKey",
+            "content"
+          ],
+          "properties": {
+            "content": {
+              "description": "Specifies the PKCS7 signature embedded within the JAR file ",
+              "type": "string",
+              "format": "byte"
+            },
+            "publicKey": {
+              "description": "The X509 certificate containing the public key JAR which verifies the signature of the JAR",
+              "type": "object",
+              "required": [
+                "content"
+              ],
+              "properties": {
+                "content": {
+                  "description": "Specifies the content of the X509 certificate containing the public key used to verify the signature",
+                  "type": "string",
+                  "format": "byte"
+                }
+              }
+            }
+          }
+        }
+      },
+      "$schema": "http://json-schema.org/draft-07/schema",
+      "$id": "http://rekor.sigstore.dev/types/jar/jar_v0_0_1_schema.json"
+    },
     "rekord": {
       "description": "Rekord object",
       "type": "object",
diff --git a/pkg/pki/pkcs7/pkcs7.go b/pkg/pki/pkcs7/pkcs7.go
new file mode 100644
index 0000000000000000000000000000000000000000..98f90a084b7d6496ec5494d4a2ac35b23525cfae
--- /dev/null
+++ b/pkg/pki/pkcs7/pkcs7.go
@@ -0,0 +1,186 @@
+/*
+Copyright © 2021 The Sigstore Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package pkcs7
+
+import (
+	"bytes"
+	"crypto"
+	"crypto/x509"
+	"encoding/pem"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+
+	"github.com/sassoftware/relic/lib/pkcs7"
+)
+
+type Signature struct {
+	signedData pkcs7.SignedData
+	detached   bool
+	raw        *[]byte
+}
+
+// NewSignature creates and validates an PKCS7 signature object
+func NewSignature(r io.Reader) (*Signature, error) {
+	b, err := ioutil.ReadAll(r)
+	if err != nil {
+		return nil, err
+	}
+	// try PEM decoding first
+	var pkcsBytes *[]byte
+	block, _ := pem.Decode(b)
+	if block != nil {
+		if block.Type != "PKCS7" {
+			return nil, fmt.Errorf("unknown PEM block type %s found during PKCS7 parsing", block.Type)
+		}
+		pkcsBytes = &block.Bytes
+	} else {
+		// PEM decoding failed, it might just be raw ASN.1 data
+		pkcsBytes = &b
+	}
+
+	psd, err := pkcs7.Unmarshal(*pkcsBytes)
+	if err != nil {
+		return nil, err
+	}
+
+	// we store the detached signature as the raw, canonical format
+	if _, err := psd.Detach(); err != nil {
+		return nil, err
+	}
+
+	detached, err := psd.Marshal()
+	if err != nil {
+		return nil, err
+	}
+
+	cb, err := psd.Content.ContentInfo.Bytes()
+	if err != nil {
+		return nil, err
+	}
+
+	return &Signature{
+		signedData: psd.Content,
+		raw:        &detached,
+		detached:   cb == nil,
+	}, nil
+}
+
+// CanonicalValue implements the pki.Signature interface
+func (s Signature) CanonicalValue() ([]byte, error) {
+	if s.raw == nil {
+		return nil, fmt.Errorf("PKCS7 signature has not been initialized")
+	}
+
+	p := pem.Block{
+		Type:  "PKCS7",
+		Bytes: *s.raw,
+	}
+
+	var buf bytes.Buffer
+	if err := pem.Encode(&buf, &p); err != nil {
+		return nil, err
+	}
+	return buf.Bytes(), nil
+}
+
+// Verify implements the pki.Signature interface
+func (s Signature) Verify(r io.Reader, k interface{}) error {
+	if len(*s.raw) == 0 {
+		return fmt.Errorf("PKCS7 signature has not been initialized")
+	}
+
+	// if content was passed to this, verify signature as if it were detached
+	bb := bytes.Buffer{}
+	var extContent []byte
+	if r != nil {
+		n, err := io.Copy(&bb, r)
+		if err != nil {
+			return err
+		}
+		if n > 0 {
+			extContent = bb.Bytes()
+		} else if s.detached {
+			return errors.New("PKCS7 signature is detached and there is no external content to verify against")
+		}
+	}
+
+	if _, err := s.signedData.Verify(extContent, false); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// PublicKey Public Key contained in cert inside PKCS7 bundle
+type PublicKey struct {
+	key     crypto.PublicKey
+	certs   []*x509.Certificate
+	rawCert []byte
+}
+
+// NewPublicKey implements the pki.PublicKey interface
+func NewPublicKey(r io.Reader) (*PublicKey, error) {
+	rawPub, err := ioutil.ReadAll(r)
+	if err != nil {
+		return nil, err
+	}
+
+	// try PEM decoding first
+	var pkcsBytes *[]byte
+	block, _ := pem.Decode(rawPub)
+	if block != nil {
+		if block.Type != "PKCS7" {
+			return nil, fmt.Errorf("unknown PEM block type %s found during PKCS7 parsing", block.Type)
+		}
+		pkcsBytes = &block.Bytes
+	} else {
+		// PEM decoding failed, it might just be raw ASN.1 data
+		pkcsBytes = &rawPub
+	}
+	pkcs7, err := pkcs7.Unmarshal(*pkcsBytes)
+	if err != nil {
+		return nil, err
+	}
+	certs, err := pkcs7.Content.Certificates.Parse()
+	if err != nil {
+		return nil, err
+	}
+	for _, cert := range certs {
+		return &PublicKey{key: cert.PublicKey, certs: certs, rawCert: cert.Raw}, nil
+	}
+	return nil, errors.New("unable to extract public key from certificate inside PKCS7 bundle")
+}
+
+// CanonicalValue implements the pki.PublicKey interface
+func (k PublicKey) CanonicalValue() ([]byte, error) {
+	if k.rawCert == nil {
+		return nil, fmt.Errorf("PKCS7 public key has not been initialized")
+	}
+	//TODO: should we export the entire cert chain, not just the first one?
+	p := pem.Block{
+		Type:  "CERTIFICATE",
+		Bytes: k.rawCert,
+	}
+
+	var buf bytes.Buffer
+	if err := pem.Encode(&buf, &p); err != nil {
+		return nil, err
+	}
+	return buf.Bytes(), nil
+}
diff --git a/pkg/pki/pkcs7/pkcs7_test.go b/pkg/pki/pkcs7/pkcs7_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..21386d727ec4633d5a2f5897358f4531fd41ed8c
--- /dev/null
+++ b/pkg/pki/pkcs7/pkcs7_test.go
@@ -0,0 +1,247 @@
+/*
+Copyright © 2021 The Sigstore Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package pkcs7
+
+import (
+	"bytes"
+	"encoding/base64"
+	"strings"
+	"testing"
+)
+
+const pkcsECDSAPEM = `-----BEGIN PKCS7-----
+MIIW9QYJKoZIhvcNAQcCoIIW5jCCFuICAQExDzANBglghkgBZQMEAgEFADCCBAwG
+CSqGSIb3DQEHAaCCA/0EggP5U2lnbmF0dXJlLVZlcnNpb246IDEuMA0KQ3JlYXRl
+ZC1CeTogMTUgKEFkb3B0T3BlbkpESykNClNIQS0yNTYtRGlnZXN0LU1hbmlmZXN0
+OiB6QzV4S3JxM1pIZS90UnNMMTR6bittM1lReWVaZFltbmxuNWJNdlJaZW5JPQ0K
+U0hBLTI1Ni1EaWdlc3QtTWFuaWZlc3QtTWFpbi1BdHRyaWJ1dGVzOiBBZW00ckh4
+eTYycmx6QzJVU0NVbDcwSEFmYmV2NzhXDQogUkNhUWNKcXEwTE5nPQ0KDQpOYW1l
+OiBzaWdzdG9yZS9wbHVnaW4vU2lnbi5jbGFzcw0KU0hBLTI1Ni1EaWdlc3Q6IEZH
+UVZGbDlROEQ1ZTAzRE1RaGN2aTNtK0orZCtUc3A3TmFxKzBUUXpoSW89DQoNCk5h
+bWU6IE1FVEEtSU5GL21hdmVuL2Rldi5zaWdzdG9yZS9zaWdzdG9yZS1tYXZlbi1w
+bHVnaW4vcG9tLnhtbA0KU0hBLTI1Ni1EaWdlc3Q6IFlWRUFpeXZRMDZOVHRkRFRq
+cVJPYUZZbnQzcDY0QzFFa2NBbWlLNkpOcGM9DQoNCk5hbWU6IE1FVEEtSU5GL21h
+dmVuL2Rldi5zaWdzdG9yZS9zaWdzdG9yZS1tYXZlbi1wbHVnaW4vcG9tLnByb3Bl
+cnRpZXMNClNIQS0yNTYtRGlnZXN0OiA3aU1VWlpLeVI3cjdLelR1K2M2dVlsSWJ5
+c0VuZE1wMVBacUVXR2pHU2lNPQ0KDQpOYW1lOiBNRVRBLUlORi9tYXZlbi9kZXYu
+c2lnc3RvcmUvc2lnc3RvcmUtbWF2ZW4tcGx1Z2luL3BsdWdpbi1oZWxwLnhtbA0K
+U0hBLTI1Ni1EaWdlc3Q6IG4yM1N4ZmlDcU43WW9FSnd5S0k3NUE3N3crRHREUmIr
+dFI0bVl6SnZlWnc9DQoNCk5hbWU6IE1FVEEtSU5GL21hdmVuL3BsdWdpbi54bWwN
+ClNIQS0yNTYtRGlnZXN0OiBTRktBeGVwMlErSzJNVmZVeUV2U1FvMFRBNDhDSitu
+QXNxbmhzRWRJOUVFPQ0KDQpOYW1lOiBzaWdzdG9yZS9wbHVnaW4vU2lnbiQxLmNs
+YXNzDQpTSEEtMjU2LURpZ2VzdDogNlEvQVExZW9QNE9hQVJwbnVSRklRb0tZUC9S
+bmJ0TGxqOGJhUEg3TkdMZz0NCg0KTmFtZTogc2lnc3RvcmUvcGx1Z2luL0hlbHBN
+b2pvLmNsYXNzDQpTSEEtMjU2LURpZ2VzdDogU3ZPNkhibVlBSzBMVEhyVCtYbmRB
+OExJdUptZU5ub1dyYmVHS3dvTE9Pdz0NCg0KoIIEsjCCAfgwggF+oAMCAQICEzVZ
+A2aAoqHzw7Mu3X5uoHi27ocwCgYIKoZIzj0EAwMwKjEVMBMGA1UEChMMc2lnc3Rv
+cmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0yMTAzMDcwMzIwMjlaFw0zMTAy
+MjMwMzIwMjlaMCoxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjERMA8GA1UEAxMIc2ln
+c3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAS0sgOyIuZPqTTvGRFmNMpXplg6
+MDpDWt5C/hmROWeRlnoS/fwPW0TQN0W67GeYtCXGrLWkS+0qeX6f4w+XcanP1HU1
+Z5b0temp/tmH7MHv0Li6JUVAq3DhNvtogOfrc3ejZjBkMA4GA1UdDwEB/wQEAwIB
+BjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBTIxR0AQZokKTJRJOsNrkrt
+SgbT7DAfBgNVHSMEGDAWgBTIxR0AQZokKTJRJOsNrkrtSgbT7DAKBggqhkjOPQQD
+AwNoADBlAjB/JYliXzLour11wYYw4GODMLJZjf0ycVXv/N1qxaJsJjX9OestV+PB
+fXOJt2t6M1wCMQCo0Wsuf2o/47CihiJJkGrYyPLrqR6//gsRb2iVpqWKjZCwxkVP
+vaK84eYSNka3LmkwggKyMIICN6ADAgECAhQA0hq1XjiwESgeFBdACLc/8dxN/jAK
+BggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNp
+Z3N0b3JlMB4XDTIxMDQwNTE3MzA0MVoXDTIxMDQwNTE3NTAzM1owPDEcMBoGA1UE
+CgwTYmNhbGxhd2FAcmVkaGF0LmNvbTEcMBoGA1UEAwwTYmNhbGxhd2FAcmVkaGF0
+LmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOQ44IV3v9zK5zLUoPpqt4Wy
+snDT+OkgZQmPLq6PtNbqXOJnGtdi1crznvmlytJ1rsrNtobtG92Y3XtMSx+2fo6j
+ggEnMIIBIzAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwDAYD
+VR0TAQH/BAIwADAdBgNVHQ4EFgQU89SEXFUXE+hEwlqafHp6CayzjJcwHwYDVR0j
+BBgwFoAUyMUdAEGaJCkyUSTrDa5K7UoG0+wwgY0GCCsGAQUFBwEBBIGAMH4wfAYI
+KwYBBQUHMAKGcGh0dHA6Ly9wcml2YXRlY2EtY29udGVudC02MDNmZTdlNy0wMDAw
+LTIyMjctYmY3NS1mNGY1ZTgwZDI5NTQuc3RvcmFnZS5nb29nbGVhcGlzLmNvbS9j
+YTM2YTFlOTYyNDJiOWZjYjE0Ni9jYS5jcnQwHgYDVR0RBBcwFYETYmNhbGxhd2FA
+cmVkaGF0LmNvbTAKBggqhkjOPQQDAwNpADBmAjEAy3AOFlXTN7pMUyLyzsLk8tn8
+v782Bo5hGSGYJMZn8eRHGktDSlx4bj51Gu+V1c4kAjEA9ISrLl83ZU6j1yP0emR1
+FgAoHceF5dtx4KzSAi4B0Cghz7kBabfljWjCMy36Ce6rMYIOBDCCDgACAQEwQjAq
+MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlAhQA0hq1
+XjiwESgeFBdACLc/8dxN/jANBglghkgBZQMEAgEFADALBgcqhkjOPQIBBQAERzBF
+AiAttO+bYBcMnsMBQlkTdXII2f8CREQVkl9ehakvihSjBgIhAKYic4Ycq3qYLoV9
+4GZWm0NT0EFbzRG5BJaoEZgUL/lyoYINUDCCDUwGCyqGSIb3DQEJEAIOMYINOzCC
+DTcGCSqGSIb3DQEHAqCCDSgwgg0kAgEDMQ8wDQYJYIZIAWUDBAIBBQAwgYEGCyqG
+SIb3DQEJEAEEoHIEcDBuAgEBBglghkgBhv1sBwEwMTANBglghkgBZQMEAgEFAAQg
+DpZGPZm1vvrjhj/lQAoCYWm+9GCixa/ySbShy9tPdjQCEBOsApMET86YdBu0FUV2
+558YDzIwMjEwNDA1MTczMDQxWgIIMX79aENwnPqgggo3MIIE/jCCA+agAwIBAgIQ
+DUJK4L46iP9gQCHOFADw3TANBgkqhkiG9w0BAQsFADByMQswCQYDVQQGEwJVUzEV
+MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t
+MTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgVGltZXN0YW1waW5n
+IENBMB4XDTIxMDEwMTAwMDAwMFoXDTMxMDEwNjAwMDAwMFowSDELMAkGA1UEBhMC
+VVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSAwHgYDVQQDExdEaWdpQ2VydCBU
+aW1lc3RhbXAgMjAyMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMLm
+YYRnxYr1DQikRcpja1HXOhFCvQp1dU2UtAxQtSYQ/h3Ib5FrDJbnGlxI70Tlv5th
+zRWRYlq4/2cLnGP9NmqB+in43Stwhd4CGPN4bbx9+cdtCT2+anaH6Yq9+IRdHnbJ
+5MZ2djpT0dHTWjaPxqPhLxs6t2HWc+xObTOKfF1FLUuxUOZBOjdWhtyTI433UCXo
+ZObd048vV7WHIOsOjizVI9r0TXhG4wODMSlKXAwxikqMiMX3MFr5FK8VX2xDSQn9
+JiNT9o1j6BqrW7EdMMKbaYK02/xWVLwfoYervnpbCiAvSwnJlaeNsvrWY4tOpXIc
+7p96AXP4Gdb+DUmEvQECAwEAAaOCAbgwggG0MA4GA1UdDwEB/wQEAwIHgDAMBgNV
+HRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMEEGA1UdIAQ6MDgwNgYJ
+YIZIAYb9bAcBMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29t
+L0NQUzAfBgNVHSMEGDAWgBT0tuEgHf4prtLkYaWyoiWyyBc1bjAdBgNVHQ4EFgQU
+NkSGjqS6sGa+vCgtHUQ23eNqerwwcQYDVR0fBGowaDAyoDCgLoYsaHR0cDovL2Ny
+bDMuZGlnaWNlcnQuY29tL3NoYTItYXNzdXJlZC10cy5jcmwwMqAwoC6GLGh0dHA6
+Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtdHMuY3JsMIGFBggrBgEF
+BQcBAQR5MHcwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBP
+BggrBgEFBQcwAoZDaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0
+U0hBMkFzc3VyZWRJRFRpbWVzdGFtcGluZ0NBLmNydDANBgkqhkiG9w0BAQsFAAOC
+AQEASBzctemaI7znGucgDo5nRv1CclF0CiNHo6uS0iXEcFm+FKDlJ4GlTRQVGQd5
+8NEEw4bZO73+RAJmTe1ppA/2uHDPYuj1UUp4eTZ6J7fz51Kfk6ftQ55757TdQSKJ
++4eiRgNO/PT+t2R3Y18jUmmDgvoaU+2QzI2hF3MN9PNlOXBL85zWenvaDLw9MtAb
+y/Vh/HUIAHa8gQ74wOFcz8QRcucbZEnYIpp1FUL1LTI4gdr0YKK6tFL7XOBhJCVP
+st/JKahzQ1HavWPWH1ub9y4bTxMd90oNcX6Xt/Q/hOvB46NJofrOp79Wz7pZdmGJ
+X36ntI5nePk2mOHLKNpbh6aKLzCCBTEwggQZoAMCAQICEAqhJdbWMht+QeQF2jaX
+whUwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lD
+ZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGln
+aUNlcnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTE2MDEwNzEyMDAwMFoXDTMxMDEw
+NzEyMDAwMFowcjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ
+MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hB
+MiBBc3N1cmVkIElEIFRpbWVzdGFtcGluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAL3QMu5LzY9/3am6gpnFOVQoV7YjSsQOB0UzURB90Pl9TWh+
+57ag9I2ziOSXv2MhkJi/E7xX08PhfgjWahQAOPcuHjvuzKb2Mln+X2U/4Jvr40ZH
+BhpVfgsnfsCi9aDg3iI/Dv9+lfvzo7oiPhisEeTwmQNtO4V8CdPuXciaC1TjqAlx
+a+DPIhAPdc9xck4Krd9AOly3UeGheRTGTSQjMF287DxgaqwvB8z98OpH2YhQXv1m
+blZhJymJhFHmgudGUP2UKiyn5HU+upgPhH+fMRTWrdXyZMt7HgXQhBlyF/EXBu89
+zdZN7wZC/aJTKk+FHcQdPK/P2qwQ9d2srOlW/5MCAwEAAaOCAc4wggHKMB0GA1Ud
+DgQWBBT0tuEgHf4prtLkYaWyoiWyyBc1bjAfBgNVHSMEGDAWgBRF66Kv9JLLgjEt
+UYunpyGd823IDzASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBhjAT
+BgNVHSUEDDAKBggrBgEFBQcDCDB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGG
+GGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2Nh
+Y2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCB
+gQYDVR0fBHoweDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lD
+ZXJ0QXNzdXJlZElEUm9vdENBLmNybDA6oDigNoY0aHR0cDovL2NybDMuZGlnaWNl
+cnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDBQBgNVHSAESTBHMDgG
+CmCGSAGG/WwAAgQwKjAoBggrBgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQu
+Y29tL0NQUzALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggEBAHGVEulRh1Zp
+ze/d2nyqY3qzeM8GN0CE70uEv8rPAwL9xafDDiBCLK938ysfDCFaKrcFNB1qrpn4
+J6JmvwmqYN92pDqTD/iy0dh8GWLoXoIlHsS6HHssIeLWWywUNUMEaLLbdQLgcseY
+1jxk5R9IEBhfiThhTWJGJIdjjJFSLK8pieV4H9YLFKWA1xJHcLN11ZOFk362kmf7
+U2GJqPVrlsD0WGkNfMgBsbkodbeZY4UijGHKeZR+WfyMD+NvtQEmtmyl7odRIeRY
+YJu6DC0rbaLEfrvEJStHAgh8Sa4TtuF8QkIoxhhWz0E0tmZdtnR79VYzIi8iNrJL
+okqV2PWmjlIxggJNMIICSQIBATCBhjByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMM
+RGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQD
+EyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgVGltZXN0YW1waW5nIENBAhANQkrg
+vjqI/2BAIc4UAPDdMA0GCWCGSAFlAwQCAQUAoIGYMBoGCSqGSIb3DQEJAzENBgsq
+hkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjEwNDA1MTczMDQxWjArBgsqhkiG
+9w0BCRACDDEcMBowGDAWBBTh14Ko4ZG+72vKFpG1qrSUpiSb8zAvBgkqhkiG9w0B
+CQQxIgQggGa+Xld/PXUrauHVUANZD8tGkn6b2ioPrjbYztVzPikwDQYJKoZIhvcN
+AQEBBQAEggEAj7sr/Yqkwqrm21IhIHPLXDDDhxBPfcv0DJhFsAOR77wlDzV52yg6
+JrexTMuLWgPulVN0UyMoCISqMv22R9ELZGGxPjDYBu0jURFKZEryVEOoidA8U07x
+TBSkcGB6Vf4P6mNxzl2whkIg4bgob8ynD8O6eb7aF6sTXFN6GyZHtYhMlMuJw3Tt
+zNwtTy9bCZI4T4IlKscOhJ4hnVz0PO4mi/7C6Y/fLz/KoNXJR1q8LBTlHd5fNN5S
+NCy1JqXRQ/EFawlOicDB5IFL7TFpPTPEXsyTg1x5j1o0tAKErU3FJg30wiblro49
+oNLw5vSDnA3bG/vDsgshFr03RYcLPUVAtA==
+-----END PKCS7-----`
+
+const signedContent = `U2lnbmF0dXJlLVZlcnNpb246IDEuMA0KQ3JlYXRlZC1CeTogMTUgKEFkb3B0T3Bl
+bkpESykNClNIQS0yNTYtRGlnZXN0LU1hbmlmZXN0OiB6QzV4S3JxM1pIZS90UnNM
+MTR6bittM1lReWVaZFltbmxuNWJNdlJaZW5JPQ0KU0hBLTI1Ni1EaWdlc3QtTWFu
+aWZlc3QtTWFpbi1BdHRyaWJ1dGVzOiBBZW00ckh4eTYycmx6QzJVU0NVbDcwSEFm
+YmV2NzhXDQogUkNhUWNKcXEwTE5nPQ0KDQpOYW1lOiBzaWdzdG9yZS9wbHVnaW4v
+U2lnbi5jbGFzcw0KU0hBLTI1Ni1EaWdlc3Q6IEZHUVZGbDlROEQ1ZTAzRE1RaGN2
+aTNtK0orZCtUc3A3TmFxKzBUUXpoSW89DQoNCk5hbWU6IE1FVEEtSU5GL21hdmVu
+L2Rldi5zaWdzdG9yZS9zaWdzdG9yZS1tYXZlbi1wbHVnaW4vcG9tLnhtbA0KU0hB
+LTI1Ni1EaWdlc3Q6IFlWRUFpeXZRMDZOVHRkRFRqcVJPYUZZbnQzcDY0QzFFa2NB
+bWlLNkpOcGM9DQoNCk5hbWU6IE1FVEEtSU5GL21hdmVuL2Rldi5zaWdzdG9yZS9z
+aWdzdG9yZS1tYXZlbi1wbHVnaW4vcG9tLnByb3BlcnRpZXMNClNIQS0yNTYtRGln
+ZXN0OiA3aU1VWlpLeVI3cjdLelR1K2M2dVlsSWJ5c0VuZE1wMVBacUVXR2pHU2lN
+PQ0KDQpOYW1lOiBNRVRBLUlORi9tYXZlbi9kZXYuc2lnc3RvcmUvc2lnc3RvcmUt
+bWF2ZW4tcGx1Z2luL3BsdWdpbi1oZWxwLnhtbA0KU0hBLTI1Ni1EaWdlc3Q6IG4y
+M1N4ZmlDcU43WW9FSnd5S0k3NUE3N3crRHREUmIrdFI0bVl6SnZlWnc9DQoNCk5h
+bWU6IE1FVEEtSU5GL21hdmVuL3BsdWdpbi54bWwNClNIQS0yNTYtRGlnZXN0OiBT
+RktBeGVwMlErSzJNVmZVeUV2U1FvMFRBNDhDSituQXNxbmhzRWRJOUVFPQ0KDQpO
+YW1lOiBzaWdzdG9yZS9wbHVnaW4vU2lnbiQxLmNsYXNzDQpTSEEtMjU2LURpZ2Vz
+dDogNlEvQVExZW9QNE9hQVJwbnVSRklRb0tZUC9SbmJ0TGxqOGJhUEg3TkdMZz0N
+Cg0KTmFtZTogc2lnc3RvcmUvcGx1Z2luL0hlbHBNb2pvLmNsYXNzDQpTSEEtMjU2
+LURpZ2VzdDogU3ZPNkhibVlBSzBMVEhyVCtYbmRBOExJdUptZU5ub1dyYmVHS3dv
+TE9Pdz0NCg0K`
+
+func TestSignature_Verify(t *testing.T) {
+	tests := []struct {
+		name  string
+		pkcs7 string
+	}{
+		{
+			name:  "ec",
+			pkcs7: pkcsECDSAPEM,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s, err := NewSignature(strings.NewReader(tt.pkcs7))
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			pub, err := NewPublicKey(strings.NewReader(tt.pkcs7))
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			data, _ := base64.StdEncoding.DecodeString(signedContent)
+			if err := s.Verify(bytes.NewReader(data), pub); err != nil {
+				t.Fatalf("Signature.Verify() error = %v", err)
+			}
+
+			// Now try with the canonical value (this is a detached signature)
+			cb, err := s.CanonicalValue()
+			if err != nil {
+				t.Fatal(err)
+			}
+			canonicalSig, err := NewSignature(bytes.NewReader(cb))
+			if err != nil {
+				t.Fatal(err)
+			}
+			if err := canonicalSig.Verify(bytes.NewReader(data), pub); err != nil {
+				t.Fatalf("CanonicalSignature.Verify() error = %v", err)
+			}
+		})
+	}
+}
+
+func TestSignature_VerifyFail(t *testing.T) {
+	tests := []struct {
+		name  string
+		pkcs7 string
+	}{
+		{
+			name:  "ec",
+			pkcs7: pkcsECDSAPEM,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			// Make some fake data, and tamper with the signature
+			s, err := NewSignature(strings.NewReader(tt.pkcs7))
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			pub, err := NewPublicKey(strings.NewReader(tt.pkcs7))
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			data := []byte("something that shouldn't verify")
+			if err := s.Verify(bytes.NewReader(data), pub); err == nil {
+				t.Error("Signature.Verify() expected error!")
+			}
+		})
+	}
+}
diff --git a/pkg/pki/pki.go b/pkg/pki/pki.go
index c53116daeb4ae6bd9abf7784650d82ceb5d3f891..43fb9d7b1086c8ddf58dcc00b8302d38b0953e39 100644
--- a/pkg/pki/pki.go
+++ b/pkg/pki/pki.go
@@ -22,6 +22,7 @@ import (
 
 	"github.com/sigstore/rekor/pkg/pki/minisign"
 	"github.com/sigstore/rekor/pkg/pki/pgp"
+	"github.com/sigstore/rekor/pkg/pki/pkcs7"
 	"github.com/sigstore/rekor/pkg/pki/ssh"
 	"github.com/sigstore/rekor/pkg/pki/x509"
 )
@@ -57,6 +58,8 @@ func (a ArtifactFactory) NewPublicKey(r io.Reader) (PublicKey, error) {
 		return x509.NewPublicKey(r)
 	case "ssh":
 		return ssh.NewPublicKey(r)
+	case "pkcs7":
+		return pkcs7.NewPublicKey(r)
 	}
 	return nil, fmt.Errorf("unknown key format '%v'", a.format)
 }
@@ -71,6 +74,8 @@ func (a ArtifactFactory) NewSignature(r io.Reader) (Signature, error) {
 		return x509.NewSignature(r)
 	case "ssh":
 		return ssh.NewSignature(r)
+	case "pkcs7":
+		return pkcs7.NewSignature(r)
 	}
 	return nil, fmt.Errorf("unknown key format '%v'", a.format)
 }
diff --git a/pkg/types/jar/jar.go b/pkg/types/jar/jar.go
new file mode 100644
index 0000000000000000000000000000000000000000..0151a3c9ef4408b114a0bb44e17ebac55fff94d9
--- /dev/null
+++ b/pkg/types/jar/jar.go
@@ -0,0 +1,66 @@
+/*
+Copyright © 2021 The Rekor Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package jar
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/sigstore/rekor/pkg/types"
+	"github.com/sigstore/rekor/pkg/util"
+
+	"github.com/go-openapi/swag"
+	"github.com/sigstore/rekor/pkg/generated/models"
+)
+
+const (
+	KIND = "jar"
+)
+
+type BaseJARType struct{}
+
+func (jt BaseJARType) Kind() string {
+	return KIND
+}
+
+func init() {
+	types.TypeMap.Set(KIND, New)
+}
+
+func New() types.TypeImpl {
+	return &BaseJARType{}
+}
+
+var SemVerToFacFnMap = &util.VersionFactoryMap{VersionFactories: make(map[string]util.VersionFactory)}
+
+func (jt BaseJARType) UnmarshalEntry(pe models.ProposedEntry) (types.EntryImpl, error) {
+	jar, ok := pe.(*models.Jar)
+	if !ok {
+		return nil, errors.New("cannot unmarshal non-JAR types")
+	}
+
+	if genFn, found := SemVerToFacFnMap.Get(swag.StringValue(jar.APIVersion)); found {
+		entry := genFn()
+		if entry == nil {
+			return nil, fmt.Errorf("failure generating JAR object for version '%v'", jar.APIVersion)
+		}
+		if err := entry.Unmarshal(jar); err != nil {
+			return nil, err
+		}
+		return entry, nil
+	}
+	return nil, fmt.Errorf("JARType implementation for version '%v' not found", swag.StringValue(jar.APIVersion))
+}
diff --git a/pkg/types/jar/jar_schema.json b/pkg/types/jar/jar_schema.json
new file mode 100644
index 0000000000000000000000000000000000000000..e8b237d4573c98089b8abd38c6f11788f6d323f8
--- /dev/null
+++ b/pkg/types/jar/jar_schema.json
@@ -0,0 +1,12 @@
+{
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "$id": "http://rekor.sigstore.dev/types/jar/jar_schema.json",
+    "title": "JAR Schema",
+    "description": "Schema for JAR objects",
+    "type": "object",
+    "oneOf": [
+        {
+            "$ref": "v0.0.1/jar_v0_0_1_schema.json"
+        }
+    ]
+}
diff --git a/pkg/types/jar/jar_test.go b/pkg/types/jar/jar_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..fadd5f762544c7d1420eddedbbd0c40d47bcbec3
--- /dev/null
+++ b/pkg/types/jar/jar_test.go
@@ -0,0 +1,124 @@
+/*
+Copyright © 2021 The Rekor Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package jar
+
+import (
+	"context"
+	"errors"
+	"testing"
+
+	"github.com/go-openapi/swag"
+	"github.com/sigstore/rekor/pkg/generated/models"
+	"github.com/sigstore/rekor/pkg/types"
+)
+
+type UnmarshalTester struct {
+	models.Jar
+}
+
+func (u UnmarshalTester) NewEntry() types.EntryImpl {
+	return &UnmarshalTester{}
+}
+
+func (u UnmarshalTester) APIVersion() string {
+	return "2.0.1"
+}
+
+func (u UnmarshalTester) IndexKeys() []string {
+	return []string{}
+}
+
+func (u UnmarshalTester) Canonicalize(ctx context.Context) ([]byte, error) {
+	return nil, nil
+}
+
+func (u UnmarshalTester) HasExternalEntities() bool {
+	return false
+}
+
+func (u *UnmarshalTester) FetchExternalEntities(ctx context.Context) error {
+	return nil
+}
+
+func (u UnmarshalTester) Unmarshal(pe models.ProposedEntry) error {
+	return nil
+}
+
+func (u UnmarshalTester) Validate() error {
+	return nil
+}
+
+type UnmarshalFailsTester struct {
+	UnmarshalTester
+}
+
+func (u UnmarshalFailsTester) NewEntry() types.EntryImpl {
+	return &UnmarshalFailsTester{}
+}
+
+func (u UnmarshalFailsTester) Unmarshal(pe models.ProposedEntry) error {
+	return errors.New("error")
+}
+
+func TestJARType(t *testing.T) {
+	// empty to start
+	if len(SemVerToFacFnMap.VersionFactories) != 0 {
+		t.Error("semver range was not blank at start of test")
+	}
+
+	u := UnmarshalTester{}
+	// ensure semver range parser is working
+	invalidSemVerRange := "not a valid semver range"
+	SemVerToFacFnMap.Set(invalidSemVerRange, u.NewEntry)
+	if len(SemVerToFacFnMap.VersionFactories) > 0 {
+		t.Error("invalid semver range was incorrectly added to SemVerToFacFnMap")
+	}
+
+	// valid semver range can be parsed
+	SemVerToFacFnMap.Set(">= 1.2.3", u.NewEntry)
+	if len(SemVerToFacFnMap.VersionFactories) != 1 {
+		t.Error("valid semver range was not added to SemVerToFacFnMap")
+	}
+
+	u.Jar.APIVersion = swag.String("2.0.1")
+	brt := BaseJARType{}
+
+	// version requested matches implementation in map
+	if _, err := brt.UnmarshalEntry(&u.Jar); err != nil {
+		t.Errorf("unexpected error in Unmarshal: %v", err)
+	}
+
+	// version requested fails to match implementation in map
+	u.Jar.APIVersion = swag.String("1.2.2")
+	if _, err := brt.UnmarshalEntry(&u.Jar); err == nil {
+		t.Error("unexpected success in Unmarshal for non-matching version")
+	}
+
+	// error in Unmarshal call is raised appropriately
+	u.Jar.APIVersion = swag.String("2.2.0")
+	u2 := UnmarshalFailsTester{}
+	SemVerToFacFnMap.Set(">= 1.2.3", u2.NewEntry)
+	if _, err := brt.UnmarshalEntry(&u.Jar); err == nil {
+		t.Error("unexpected success in Unmarshal when error is thrown")
+	}
+
+	// version requested fails to match implementation in map
+	u.Jar.APIVersion = swag.String("not_a_version")
+	if _, err := brt.UnmarshalEntry(&u.Jar); err == nil {
+		t.Error("unexpected success in Unmarshal for invalid version")
+	}
+}
diff --git a/pkg/types/jar/v0.0.1/entry.go b/pkg/types/jar/v0.0.1/entry.go
new file mode 100644
index 0000000000000000000000000000000000000000..3100846954fba5e51385e23d0aabb03a7c4f116d
--- /dev/null
+++ b/pkg/types/jar/v0.0.1/entry.go
@@ -0,0 +1,341 @@
+/*
+Copyright © 2021 The Sigstore Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package jar
+
+import (
+	"archive/zip"
+	"bytes"
+	"context"
+	"crypto/sha256"
+	"encoding/base64"
+	"encoding/hex"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"path"
+	"reflect"
+	"strings"
+
+	"github.com/sigstore/rekor/pkg/log"
+	"github.com/sigstore/rekor/pkg/pki"
+	"github.com/sigstore/rekor/pkg/types"
+	"github.com/sigstore/rekor/pkg/types/jar"
+	"github.com/sigstore/rekor/pkg/util"
+
+	"github.com/asaskevich/govalidator"
+
+	"github.com/go-openapi/strfmt"
+
+	"github.com/go-openapi/swag"
+	"github.com/mitchellh/mapstructure"
+	jarutils "github.com/sassoftware/relic/lib/signjar"
+	"github.com/sigstore/rekor/pkg/generated/models"
+)
+
+const (
+	APIVERSION = "0.0.1"
+)
+
+func init() {
+	jar.SemVerToFacFnMap.Set(APIVERSION, NewEntry)
+}
+
+type V001Entry struct {
+	JARModel                models.JarV001Schema
+	fetchedExternalEntities bool
+	jarObj                  *jarutils.JarSignature
+	keyObj                  pki.PublicKey
+	sigObj                  pki.Signature
+}
+
+func (v V001Entry) APIVersion() string {
+	return APIVERSION
+}
+
+func NewEntry() types.EntryImpl {
+	return &V001Entry{}
+}
+
+func Base64StringtoByteArray() mapstructure.DecodeHookFunc {
+	return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
+		if f.Kind() != reflect.String || t.Kind() != reflect.Slice {
+			return data, nil
+		}
+
+		bytes, err := base64.StdEncoding.DecodeString(data.(string))
+		if err != nil {
+			return []byte{}, fmt.Errorf("failed parsing base64 data: %v", err)
+		}
+		return bytes, nil
+	}
+}
+
+func (v V001Entry) IndexKeys() []string {
+	var result []string
+
+	if v.HasExternalEntities() {
+		if err := v.FetchExternalEntities(context.Background()); err != nil {
+			log.Logger.Error(err)
+			return result
+		}
+	}
+
+	key, err := v.keyObj.CanonicalValue()
+	if err != nil {
+		log.Logger.Error(err)
+	} else {
+		hasher := sha256.New()
+		if _, err := hasher.Write(key); err != nil {
+			log.Logger.Error(err)
+		} else {
+			result = append(result, strings.ToLower(hex.EncodeToString(hasher.Sum(nil))))
+		}
+	}
+
+	if v.JARModel.Archive.Hash != nil {
+		result = append(result, strings.ToLower(swag.StringValue(v.JARModel.Archive.Hash.Value)))
+	}
+
+	return result
+}
+
+func (v *V001Entry) Unmarshal(pe models.ProposedEntry) error {
+	jar, ok := pe.(*models.Jar)
+	if !ok {
+		return errors.New("cannot unmarshal non JAR v0.0.1 type")
+	}
+
+	cfg := mapstructure.DecoderConfig{
+		DecodeHook: Base64StringtoByteArray(),
+		Result:     &v.JARModel,
+	}
+
+	dec, err := mapstructure.NewDecoder(&cfg)
+	if err != nil {
+		return fmt.Errorf("error initializing decoder: %w", err)
+	}
+
+	if err := dec.Decode(jar.Spec); err != nil {
+		return err
+	}
+	// field validation
+	if err := v.JARModel.Validate(strfmt.Default); err != nil {
+		return err
+	}
+	return nil
+
+}
+
+func (v V001Entry) HasExternalEntities() bool {
+	if v.fetchedExternalEntities {
+		return false
+	}
+
+	if v.JARModel.Archive != nil && v.JARModel.Archive.URL.String() != "" {
+		return true
+	}
+	return false
+}
+
+func (v *V001Entry) FetchExternalEntities(ctx context.Context) error {
+	if v.fetchedExternalEntities {
+		return nil
+	}
+
+	if err := v.Validate(); err != nil {
+		return err
+	}
+
+	oldSHA := ""
+	if v.JARModel.Archive.Hash != nil && v.JARModel.Archive.Hash.Value != nil {
+		oldSHA = swag.StringValue(v.JARModel.Archive.Hash.Value)
+	}
+
+	dataReadCloser, err := util.FileOrURLReadCloser(ctx, v.JARModel.Archive.URL.String(), v.JARModel.Archive.Content)
+	if err != nil {
+		return err
+	}
+	defer dataReadCloser.Close()
+
+	hasher := sha256.New()
+	b := &bytes.Buffer{}
+
+	n, err := io.Copy(io.MultiWriter(hasher, b), dataReadCloser)
+	if err != nil {
+		return err
+	}
+
+	computedSHA := hex.EncodeToString(hasher.Sum(nil))
+	if oldSHA != "" && computedSHA != oldSHA {
+		return fmt.Errorf("SHA mismatch: %s != %s", computedSHA, oldSHA)
+	}
+
+	zipReader, err := zip.NewReader(bytes.NewReader(b.Bytes()), n)
+	if err != nil {
+		return err
+	}
+
+	// this ensures that the JAR is signed and the signature verifies, as
+	// well as checks that the hashes in the signed manifest are all valid
+	jarObj, err := jarutils.Verify(zipReader, false)
+	if err != nil {
+		return err
+	}
+	switch len(jarObj) {
+	case 0:
+		return errors.New("no signatures detected in JAR archive")
+	case 1:
+	default:
+		return errors.New("multiple signatures detected in JAR; unable to process")
+	}
+	v.jarObj = jarObj[0]
+
+	af := pki.NewArtifactFactory("pkcs7")
+	// we need to find and extract the PKCS7 bundle from the JAR file manually
+	sigPKCS7, err := extractPKCS7SignatureFromJAR(zipReader)
+	if err != nil {
+		return err
+	}
+
+	v.keyObj, err = af.NewPublicKey(bytes.NewReader(sigPKCS7))
+	if err != nil {
+		return err
+	}
+
+	v.sigObj, err = af.NewSignature(bytes.NewReader(sigPKCS7))
+	if err != nil {
+		return err
+	}
+
+	// if we get here, all goroutines succeeded without error
+	if oldSHA == "" {
+		v.JARModel.Archive.Hash = &models.JarV001SchemaArchiveHash{}
+		v.JARModel.Archive.Hash.Algorithm = swag.String(models.JarV001SchemaArchiveHashAlgorithmSha256)
+		v.JARModel.Archive.Hash.Value = swag.String(computedSHA)
+	}
+
+	v.fetchedExternalEntities = true
+	return nil
+}
+
+func (v *V001Entry) Canonicalize(ctx context.Context) ([]byte, error) {
+	if err := v.FetchExternalEntities(ctx); err != nil {
+		return nil, err
+	}
+	if v.jarObj == nil {
+		return nil, errors.New("JAR object not initialized before canonicalization")
+	}
+	if v.keyObj == nil {
+		return nil, errors.New("public key not initialized before canonicalization")
+	}
+	if v.sigObj == nil {
+		return nil, errors.New("signature not initialized before canonicalization")
+	}
+
+	canonicalEntry := models.JarV001Schema{}
+	canonicalEntry.ExtraData = v.JARModel.ExtraData
+
+	var err error
+	// need to canonicalize key content
+	canonicalEntry.Signature = &models.JarV001SchemaSignature{}
+	canonicalEntry.Signature.PublicKey = &models.JarV001SchemaSignaturePublicKey{}
+	keyContent, err := v.keyObj.CanonicalValue()
+	if err != nil {
+		return nil, err
+	}
+	canonicalEntry.Signature.PublicKey.Content = (*strfmt.Base64)(&keyContent)
+	sigContent, err := v.sigObj.CanonicalValue()
+	if err != nil {
+		return nil, err
+	}
+	canonicalEntry.Signature.Content = (*strfmt.Base64)(&sigContent)
+
+	canonicalEntry.Archive = &models.JarV001SchemaArchive{}
+	canonicalEntry.Archive.Hash = &models.JarV001SchemaArchiveHash{}
+	canonicalEntry.Archive.Hash.Algorithm = v.JARModel.Archive.Hash.Algorithm
+	canonicalEntry.Archive.Hash.Value = v.JARModel.Archive.Hash.Value
+	// archive content is not set deliberately
+
+	// ExtraData is copied through unfiltered
+	canonicalEntry.ExtraData = v.JARModel.ExtraData
+
+	// wrap in valid object with kind and apiVersion set
+	jar := models.Jar{}
+	jar.APIVersion = swag.String(APIVERSION)
+	jar.Spec = &canonicalEntry
+
+	bytes, err := json.Marshal(&jar)
+	if err != nil {
+		return nil, err
+	}
+
+	return bytes, nil
+}
+
+// Validate performs cross-field validation for fields in object
+func (v V001Entry) Validate() error {
+	archive := v.JARModel.Archive
+	if archive == nil {
+		return errors.New("missing package")
+	}
+
+	if len(archive.Content) == 0 && archive.URL.String() == "" {
+		return errors.New("one of 'content' or 'url' must be specified for package")
+	}
+
+	hash := archive.Hash
+	if hash != nil {
+		if !govalidator.IsHash(swag.StringValue(hash.Value), swag.StringValue(hash.Algorithm)) {
+			return errors.New("invalid value for hash")
+		}
+	} else if archive.URL.String() != "" {
+		return errors.New("hash value must be provided if URL is specified")
+	}
+
+	return nil
+}
+
+// extractPKCS7SignatureFromJAR extracts the first signature file from the JAR and returns it
+func extractPKCS7SignatureFromJAR(inz *zip.Reader) ([]byte, error) {
+	for _, f := range inz.File {
+		dir, name := path.Split(strings.ToUpper(f.Name))
+		if dir != "META-INF/" || name == "" {
+			continue
+		}
+		i := strings.LastIndex(name, ".")
+		if i < 0 {
+			continue
+		}
+		fileExt := name[i:]
+		if fileExt == ".RSA" || fileExt == ".DSA" || fileExt == ".EC" || strings.HasPrefix(name, "SIG-") {
+			fileReader, err := f.Open()
+			if err != nil {
+				return nil, err
+			}
+			contents, err := ioutil.ReadAll(fileReader)
+			if err != nil {
+				return nil, err
+			}
+			if err = fileReader.Close(); err != nil {
+				return nil, err
+			}
+			return contents, nil
+		}
+	}
+	return nil, errors.New("unable to locate signature in JAR file")
+}
diff --git a/pkg/types/jar/v0.0.1/entry_test.go b/pkg/types/jar/v0.0.1/entry_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..029c849e06563f06f728ca401c297480d031be3f
--- /dev/null
+++ b/pkg/types/jar/v0.0.1/entry_test.go
@@ -0,0 +1,229 @@
+/*
+Copyright © 2021 The Sigstore Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package jar
+
+import (
+	"context"
+	"crypto/sha256"
+	"encoding/hex"
+	"errors"
+	"io/ioutil"
+	"net/http"
+	"net/http/httptest"
+	"reflect"
+	"testing"
+
+	"github.com/go-openapi/strfmt"
+	"github.com/go-openapi/swag"
+	"github.com/sigstore/rekor/pkg/generated/models"
+	"go.uber.org/goleak"
+)
+
+func TestMain(m *testing.M) {
+	goleak.VerifyTestMain(m)
+}
+
+func TestNewEntryReturnType(t *testing.T) {
+	entry := NewEntry()
+	if reflect.TypeOf(entry) != reflect.ValueOf(&V001Entry{}).Type() {
+		t.Errorf("invalid type returned from NewEntry: %T", entry)
+	}
+}
+
+func TestCrossFieldValidation(t *testing.T) {
+	type TestCase struct {
+		caseDesc                  string
+		entry                     V001Entry
+		hasExtEntities            bool
+		expectUnmarshalSuccess    bool
+		expectCanonicalizeSuccess bool
+	}
+
+	jarBytes, _ := ioutil.ReadFile("../../../../tests/test.jar")
+
+	h := sha256.New()
+	_, _ = h.Write(jarBytes)
+	dataSHA := hex.EncodeToString(h.Sum(nil))
+
+	testServer := httptest.NewServer(http.HandlerFunc(
+		func(w http.ResponseWriter, r *http.Request) {
+			var file *[]byte
+			var err error
+
+			switch r.URL.Path {
+			case "/data":
+				file = &jarBytes
+			default:
+				err = errors.New("unknown URL")
+			}
+			if err != nil {
+				w.WriteHeader(http.StatusNotFound)
+				return
+			}
+			w.WriteHeader(http.StatusOK)
+			_, _ = w.Write(*file)
+		}))
+	defer testServer.Close()
+
+	testCases := []TestCase{
+		{
+			caseDesc:               "empty obj",
+			entry:                  V001Entry{},
+			expectUnmarshalSuccess: false,
+		},
+		{
+			caseDesc: "empty archive",
+			entry: V001Entry{
+				JARModel: models.JarV001Schema{
+					Archive: &models.JarV001SchemaArchive{},
+				},
+			},
+			expectUnmarshalSuccess: false,
+		},
+		{
+			caseDesc: "archive with url but no hash",
+			entry: V001Entry{
+				JARModel: models.JarV001Schema{
+					Archive: &models.JarV001SchemaArchive{
+						URL: strfmt.URI(testServer.URL + "/data"),
+					},
+				},
+			},
+			hasExtEntities:         true,
+			expectUnmarshalSuccess: false,
+		},
+		{
+			caseDesc: "archive with url and empty hash",
+			entry: V001Entry{
+				JARModel: models.JarV001Schema{
+					Archive: &models.JarV001SchemaArchive{
+						Hash: &models.JarV001SchemaArchiveHash{},
+						URL:  strfmt.URI(testServer.URL + "/data"),
+					},
+				},
+			},
+			hasExtEntities:         true,
+			expectUnmarshalSuccess: false,
+		},
+		{
+			caseDesc: "archive with url and hash alg but missing value",
+			entry: V001Entry{
+				JARModel: models.JarV001Schema{
+					Archive: &models.JarV001SchemaArchive{
+						Hash: &models.JarV001SchemaArchiveHash{
+							Algorithm: swag.String(models.JarV001SchemaArchiveHashAlgorithmSha256),
+						},
+						URL: strfmt.URI(testServer.URL + "/data"),
+					},
+				},
+			},
+			hasExtEntities:         true,
+			expectUnmarshalSuccess: false,
+		},
+		{
+			caseDesc: "archive with valid url with matching hash",
+			entry: V001Entry{
+				JARModel: models.JarV001Schema{
+					Archive: &models.JarV001SchemaArchive{
+						Hash: &models.JarV001SchemaArchiveHash{
+							Algorithm: swag.String(models.JarV001SchemaArchiveHashAlgorithmSha256),
+							Value:     swag.String(dataSHA),
+						},
+						URL: strfmt.URI(testServer.URL + "/data"),
+					},
+				},
+			},
+			hasExtEntities:            true,
+			expectUnmarshalSuccess:    true,
+			expectCanonicalizeSuccess: true,
+		},
+		{
+			caseDesc: "archive with inline content",
+			entry: V001Entry{
+				JARModel: models.JarV001Schema{
+					Archive: &models.JarV001SchemaArchive{
+						Content: strfmt.Base64(jarBytes),
+					},
+				},
+			},
+			hasExtEntities:            false,
+			expectUnmarshalSuccess:    true,
+			expectCanonicalizeSuccess: true,
+		},
+		{
+			caseDesc: "archive with url and incorrect hash value",
+			entry: V001Entry{
+				JARModel: models.JarV001Schema{
+					Archive: &models.JarV001SchemaArchive{
+						Hash: &models.JarV001SchemaArchiveHash{
+							Algorithm: swag.String(models.JarV001SchemaArchiveHashAlgorithmSha256),
+							Value:     swag.String("3030303030303030303030303030303030303030303030303030303030303030"),
+						},
+						URL: strfmt.URI(testServer.URL + "/data"),
+					},
+				},
+			},
+			hasExtEntities:            true,
+			expectUnmarshalSuccess:    true,
+			expectCanonicalizeSuccess: false,
+		},
+		{
+			caseDesc: "valid obj with extradata",
+			entry: V001Entry{
+				JARModel: models.JarV001Schema{
+					Archive: &models.JarV001SchemaArchive{
+						Content: strfmt.Base64(jarBytes),
+					},
+					ExtraData: []byte("{\"something\": \"here\""),
+				},
+			},
+			hasExtEntities:            false,
+			expectUnmarshalSuccess:    true,
+			expectCanonicalizeSuccess: true,
+		},
+	}
+
+	for _, tc := range testCases {
+		if err := tc.entry.Validate(); (err == nil) != tc.expectUnmarshalSuccess {
+			t.Errorf("unexpected result in '%v': %v", tc.caseDesc, err)
+		}
+
+		v := &V001Entry{}
+		r := models.Jar{
+			APIVersion: swag.String(tc.entry.APIVersion()),
+			Spec:       tc.entry.JARModel,
+		}
+
+		unmarshalAndValidate := func() error {
+			if err := v.Unmarshal(&r); err != nil {
+				return err
+			}
+			return v.Validate()
+		}
+		if err := unmarshalAndValidate(); (err == nil) != tc.expectUnmarshalSuccess {
+			t.Errorf("unexpected result in '%v': %v", tc.caseDesc, err)
+		}
+
+		if tc.entry.HasExternalEntities() != tc.hasExtEntities {
+			t.Errorf("unexpected result from HasExternalEntities for '%v'", tc.caseDesc)
+		}
+
+		if _, err := tc.entry.Canonicalize(context.TODO()); (err == nil) != tc.expectCanonicalizeSuccess {
+			t.Errorf("unexpected result from Canonicalize for '%v': %v", tc.caseDesc, err)
+		}
+	}
+}
diff --git a/pkg/types/jar/v0.0.1/jar_v0_0_1_schema.json b/pkg/types/jar/v0.0.1/jar_v0_0_1_schema.json
new file mode 100644
index 0000000000000000000000000000000000000000..f42376e7c4e886f3acaadc4a743eb4f4098be515
--- /dev/null
+++ b/pkg/types/jar/v0.0.1/jar_v0_0_1_schema.json
@@ -0,0 +1,79 @@
+{
+    "$schema": "http://json-schema.org/draft-07/schema#",
+    "$id": "http://rekor.sigstore.dev/types/jar/jar_v0_0_1_schema.json",
+    "title": "JAR v0.0.1 Schema",
+    "description": "Schema for JAR entries",
+    "type": "object",
+    "properties": {
+        "signature": {
+            "description": "Information about the included signature in the JAR file",
+            "type": "object",
+            "properties": {
+                "content": {
+                    "description": "Specifies the PKCS7 signature embedded within the JAR file ",
+                    "type": "string",
+                    "format": "byte"
+                },
+                "publicKey" : {
+                    "description": "The X509 certificate containing the public key JAR which verifies the signature of the JAR",
+                    "type": "object",
+                    "properties": {
+                        "content": {
+                            "description": "Specifies the content of the X509 certificate containing the public key used to verify the signature",
+                            "type": "string",
+                            "format": "byte"
+                        }
+                    },
+                    "required": [ "content" ]
+                }
+            },
+            "required": [ "publicKey", "content" ]
+        },
+        "archive": {
+            "description": "Information about the archive associated with the entry",
+            "type": "object",
+            "properties": {
+                "hash": {
+                    "description": "Specifies the hash algorithm and value encompassing the entire signed archive",
+                    "type": "object",
+                    "properties": {
+                        "algorithm": {
+                            "description": "The hashing function used to compute the hash value",
+                            "type": "string",
+                            "enum": [ "sha256" ]
+                        },
+                        "value": {
+                            "description": "The hash value for the archive",
+                            "type": "string"
+                        }
+                    },
+                    "required": [ "algorithm", "value" ]
+                },
+                "url": {
+                    "description": "Specifies the location of the archive; if this is specified, a hash value must also be provided",
+                    "type": "string",
+                    "format": "uri"
+                },
+                "content": {
+                    "description": "Specifies the archive inline within the document",
+                    "type": "string",
+                    "format": "byte"
+                }
+            },
+            "oneOf": [
+                {
+                    "required": [ "url" ]
+                },
+                {
+                    "required": [ "content" ]
+                }
+            ]
+        },
+        "extraData": {
+            "description": "Arbitrary content to be included in the verifiable entry in the transparency log", 
+            "type": "object",
+            "additionalProperties": true
+        }
+    },
+    "required": [ "archive" ]
+}
\ No newline at end of file
diff --git a/tests/e2e_test.go b/tests/e2e_test.go
index ee2c5f96659cfc68da7708ceeaaefa1f27b895c6..dbfe808ef4655042b8e3acbf77c953f85dcfb3f3 100644
--- a/tests/e2e_test.go
+++ b/tests/e2e_test.go
@@ -243,6 +243,20 @@ func TestSSH(t *testing.T) {
 	outputContains(t, out, uuid)
 }
 
+func TestJAR(t *testing.T) {
+	td := t.TempDir()
+	artifactPath := filepath.Join(td, "artifact.jar")
+
+	createSignedJar(t, artifactPath)
+
+	// If we do it twice, it should already exist
+	out := runCli(t, "upload", "--artifact", artifactPath, "--type", "jar")
+	outputContains(t, out, "Created entry at")
+	out = runCli(t, "upload", "--artifact", artifactPath, "--type", "jar")
+	outputContains(t, out, "Entry already exists")
+
+}
+
 func TestX509(t *testing.T) {
 	td := t.TempDir()
 	artifactPath := filepath.Join(td, "artifact")
diff --git a/tests/jar.go b/tests/jar.go
new file mode 100644
index 0000000000000000000000000000000000000000..a1df776d263bd791e806f7ab2ab2886844ab58e7
--- /dev/null
+++ b/tests/jar.go
@@ -0,0 +1,86 @@
+// +build e2e
+
+/*
+Copyright © 2021 The Rekor Authors
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+package e2e
+
+import (
+	"archive/zip"
+	"bytes"
+	"context"
+	"crypto"
+	"os"
+	"testing"
+
+	"github.com/sassoftware/relic/lib/certloader"
+	"github.com/sassoftware/relic/lib/signjar"
+	"github.com/sassoftware/relic/lib/zipslicer"
+)
+
+//note: reuses PKI artifacts from x509 tests
+
+const manifest = `Manifest-Version: 1.0
+
+Name: src/some/java/HelloWorld.class
+SHA-256-Digest: cp40SgHlLIIr397GHijW7aAmWNLn0rgKm5Ap9B4hLd4=
+
+`
+
+func createSignedJar(t *testing.T, artifactPath string) {
+	t.Helper()
+
+	//create a ZIP file with a single file inside
+	f, err := os.Create(artifactPath)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	zw := zip.NewWriter(f)
+	jw, err := zw.Create("src/some/java/HelloWorld.class")
+	if err != nil {
+		t.Fatal(err)
+	}
+	jw.Write([]byte("HelloWorld!"))
+	mf, err := zw.Create("META-INF/MANIFEST.MF")
+	if err != nil {
+		t.Fatal(err)
+	}
+	mf.Write([]byte(manifest))
+	if err := zw.Close(); err != nil {
+		t.Fatal(err)
+	}
+	f.Sync()
+	buf := bytes.Buffer{}
+	zipslicer.ZipToTar(f, &buf)
+
+	jd, err := signjar.DigestJarStream(&buf, crypto.SHA256)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	c := certloader.Certificate{
+		PrivateKey: certPrivateKey,
+		Leaf:       cert,
+	}
+
+	patch, _, err := jd.Sign(context.Background(), &c, "rekor", false, true, false)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := patch.Apply(f, artifactPath); err != nil {
+		t.Fatal(err)
+	}
+}
diff --git a/tests/test.jar b/tests/test.jar
new file mode 100644
index 0000000000000000000000000000000000000000..83935ae040272be3ddfa604189b0cec301784165
Binary files /dev/null and b/tests/test.jar differ