Skip to content

Commit

Permalink
Snapshot of pre-release
Browse files Browse the repository at this point in the history
  • Loading branch information
martinpaljak committed Oct 4, 2022
1 parent 12bbb04 commit 8985ac7
Show file tree
Hide file tree
Showing 50 changed files with 4,968 additions and 2 deletions.
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
on:
push:
branches:
- main
pull_request:
branches:
- main
name: CI check
jobs:
build:
runs-on: ubuntu-latest
name: Build and deploy
steps:
- name: Check out code
uses: actions/checkout@v3.0.2
with:
submodules: true
- name: Set SSH key
uses: webfactory/ssh-agent@v0.5.4
with:
ssh-private-key: ${{ secrets.SSH_KEY }}
- name: Add known host key
run: ssh-keyscan javacard.pro >> ~/.ssh/known_hosts
- name: Cache local Maven repository
uses: actions/cache@v3.0.8
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Setup java
uses: actions/setup-java@v3.5.0
with:
java-version: 11
distribution: temurin
- name: Compile and verify
run: ./mvnw -U -B -T1C verify
- name: Deploy snapshot
run: ./mvnw -B deploy
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
**/target
*.cap
*.iml
*~
/.idea
/.mvn/wrapper/maven-wrapper.jar
/applet/*.cap
/applet/ant-javacard.jar
pom.xml.versionsBackup
target
117 changes: 117 additions & 0 deletions .mvn/wrapper/MavenWrapperDownloader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright 2007-present the original author or 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.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;

public class MavenWrapperDownloader {

private static final String WRAPPER_VERSION = "0.5.6";
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";

/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";

/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";

/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";

public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());

// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: " + url);

File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}

private static void downloadFileFromURL(String urlString, File destination) throws Exception {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}

}
18 changes: 18 additions & 0 deletions .mvn/wrapper/maven-wrapper.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you 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.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# FIDO2
FIDO2 JavaCard applet; Java utility and library

Head to [Wiki](https://github.com/martinpaljak/FIDO2/wiki) for more information or [start a discussion](https://github.com/martinpaljak/FIDO2/discussions)!
FIDO2 Java toolbox: library, command line tool, JavaCard applet (published later)

Head to [Wiki](https://github.com/martinpaljak/FIDO2/wiki) for more information
or [start a discussion](https://github.com/martinpaljak/FIDO2/discussions)!
29 changes: 29 additions & 0 deletions common/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.martinpaljak</groupId>
<artifactId>fido2-toolbox</artifactId>
<version>22.01.05-SNAPSHOT</version>
</parent>
<artifactId>ctap2-common</artifactId>
<name>FIDO2/U2F/CTAP2 common</name>
<description>FIDO2/U2F/CTAP2 common</description>
<dependencies>
<dependency>
<groupId>com.github.martinpaljak</groupId>
<artifactId>apdu4j-core</artifactId>
<version>2020r3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-cbor</artifactId>
<version>2.13.4</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package pro.javacard.fido2.common;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.security.GeneralSecurityException;
import java.security.Signature;
import java.security.interfaces.ECPublicKey;

public class AssertionVerifier {
private static final Logger logger = LoggerFactory.getLogger(AssertionVerifier.class);

public static boolean verify(AuthenticatorData authenticatorData, byte[] clientDataHash, byte[] signature, ECPublicKey publicKey) {
try {
// Verify assertion, if pubkey given
Signature ecdsa = Signature.getInstance("SHA256withECDSA");
ecdsa.initVerify(publicKey);
ecdsa.update(authenticatorData.getBytes());
ecdsa.update(clientDataHash);
if (ecdsa.verify(signature)) {
logger.info("Verified OK.");
return true;
} else {
logger.warn("Not verified!");
return false;
}
} catch (GeneralSecurityException e) {
logger.error("Failed to verify assertion: " + e.getMessage(), e);
return false;
}
}
}
124 changes: 124 additions & 0 deletions common/src/main/java/pro/javacard/fido2/common/AttestationCA.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package pro.javacard.fido2.common;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.util.encoders.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigInteger;
import java.security.KeyPair;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.Date;

// This is a sample Attestation certificate CA
public class AttestationCA {
private static final Logger logger = LoggerFactory.getLogger(AttestationCA.class);

static final byte[] rootPrivate = Hex.decode("314daf146b500e808360c0c904826c7afd259f401776a0dc7e6be14ce306ba49");
static final byte[] rootPublic = Hex.decode("043e7cf2b8b3685363d6d44ad417071398140547f9fc9cf058e6b834c30c36c02a0fc51e8c438e339dede6e69013eab8f851d2edee8786653fdbc6ac9b60a00fca");

public static final byte[] rootCertificate = Hex.decode("308201793082011fa003020102020401346607300a06082a8648ce3d04030230323130302e06035504030c276a617661636172642e70726f206174746573746174696f6e20726f6f7420233230323131323037301e170d3231303130313030303030305a170d3435303130313030303030305a30323130302e06035504030c276a617661636172642e70726f206174746573746174696f6e20726f6f74202332303231313230373059301306072a8648ce3d020106082a8648ce3d030107034200043e7cf2b8b3685363d6d44ad417071398140547f9fc9cf058e6b834c30c36c02a0fc51e8c438e339dede6e69013eab8f851d2edee8786653fdbc6ac9b60a00fcaa323302130120603551d130101ff040830060101ff020100300b0603551d0f040403020284300a06082a8648ce3d04030203480030450220509d5b1b0d66b3efd632004580965283b800a3b8e1d6ea25ad5ff94ea1d73089022100e941ecb919316ce1b03ecfa81ad293a8f053cd2446b00d1ba146c4e082e7e3ef");

public static final byte[] attestationPrivate = Hex.decode("fa18ca8b592245111416bf023ab28e06d1ea829d276365e2d0e05509d0b04674");
public static final byte[] attestationCertificate = Hex.decode("308201b230820159a003020102020101300a06082a8648ce3d04030230323130302e06035504030c276a617661636172642e70726f206174746573746174696f6e20726f6f7420233230323131323037301e170d3231303130313030303030305a170d3435303130313030303030305a304a310b300906035504061302454531173015060355040a0c0e4fc39c204bc3bc62657270756e6b31223020060355040b0c1941757468656e74696361746f72204174746573746174696f6e3059301306072a8648ce3d020106082a8648ce3d030107034200044c129336dd4989ef135450fec63e20136530e508744500a474dc3a72d6487b01e7e0f96219b675217e7b34c58b522844de3bacac8effcb9b1291a2a740887cc1a3483046300c0603551d130101ff040230003013060b2b0601040182e51c0201010404030204103021060b2b0601040182e51c0101040412041000000000000000000000000000000000300a06082a8648ce3d040302034700304402202b5bed230f194148ed0d439fffeb1a8b2fe95cfbfd032e1af384d7cd0dcc40c602206ea8e226fe7ff0d9a6877240af31c5050b032fc25277cc1cc81162fa4b8cae0a");

// See https://www.w3.org/TR/webauthn/#sctn-packed-attestation-cert-requirements
// And https://fidoalliance.org/specs/fido-v2.0-ps-20150904/fido-key-attestation-v2.0-ps-20150904.html#attestation-statement-certificate-requirements
BigInteger rootSerial = BigInteger.valueOf(20220927);

final X500Name rootSubject = new X500NameBuilder(BCStyle.INSTANCE)
.addRDN(BCStyle.CN, "javacard.pro test attestation root #" + rootSerial)
.build();

final X500Name attestationSubject = new X500NameBuilder(BCStyle.INSTANCE)
.addRDN(BCStyle.C, "EE")
.addRDN(BCStyle.O, "OÜ Küberpunk")
.addRDN(BCStyle.OU, "Authenticator Attestation") // NB! This is important
.build();


X509Certificate makeRootCertificate() throws Exception {
ECPrivateKey privateKey = CryptoUtils.private2privkey(rootPrivate);
ECPublicKey publicKey = CryptoUtils.uncompressed2pubkey(rootPublic);

// start data
Date startDate = Date.from(LocalDate.of(2022, 1, 1).atStartOfDay(ZoneOffset.UTC).toInstant());
Date endDate = Date.from(LocalDate.of(2045, 1, 1).atStartOfDay(ZoneOffset.UTC).toInstant());

// Basic certificate
JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(rootSubject, rootSerial, startDate, endDate, rootSubject, publicKey);

// Extensions. CA true, len = 0
BasicConstraints basicConstraints = new BasicConstraints(0); // True, length == 0
certBuilder.addExtension(new ASN1ObjectIdentifier("2.5.29.19"), true, basicConstraints);// Critical

// Extension: usage cert signing
KeyUsage usage = new KeyUsage(KeyUsage.keyCertSign | KeyUsage.digitalSignature);
certBuilder.addExtension(Extension.keyUsage, false, usage.getEncoded());

ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256withECDSA").build(privateKey);
return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certBuilder.build(contentSigner));
}


X509Certificate makeAttestationCertificate(ECPublicKey attestationPublicKey, byte[] aaguid) throws Exception {
ECPrivateKey privateKey = CryptoUtils.private2privkey(rootPrivate);

// Serial == 1
BigInteger certSerialNumber = BigInteger.ONE;

Date startDate = Date.from(LocalDate.of(2022, 1, 1).atStartOfDay(ZoneOffset.UTC).toInstant());
Date endDate = Date.from(LocalDate.of(2045, 1, 1).atStartOfDay(ZoneOffset.UTC).toInstant());

// Basic certificate
JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(rootSubject, certSerialNumber, startDate, endDate, attestationSubject, attestationPublicKey);

// Extensions - MUST CA FALSE
BasicConstraints basicConstraints = new BasicConstraints(false);
certBuilder.addExtension(new ASN1ObjectIdentifier("2.5.29.19"), true, basicConstraints);

// Extension: transports 1.3.6.1.4.1.45724.2.1.1 NFC = 3 MUST be wrapped in octet string (ref: ?)
certBuilder.addExtension(new ASN1ObjectIdentifier("1.3.6.1.4.1.45724.2.1.1"), false, new DEROctetString(new DERBitString(0x10)));

// Extension: AAGUID
certBuilder.addExtension(new ASN1ObjectIdentifier("1.3.6.1.4.1.45724.1.1.4"), false, new DEROctetString(aaguid));

ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256withECDSA").build(privateKey);
return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certBuilder.build(contentSigner));
}

//@Test
public void makeKey() {
KeyPair keyPair = CryptoUtils.ephemeral();

System.out.println(Hex.toHexString(((ECPrivateKey) keyPair.getPrivate()).getS().toByteArray()));
System.out.println(Hex.toHexString(((ECPublicKey) keyPair.getPublic()).getW().getAffineX().toByteArray()));
System.out.println(Hex.toHexString(((ECPublicKey) keyPair.getPublic()).getW().getAffineY().toByteArray()));
}

//@Test
public void makeCert() throws Exception {
X509Certificate rootCert = makeRootCertificate();
System.out.println(Hex.toHexString(rootCert.getEncoded()));

KeyPair keyPair = CryptoUtils.ephemeral();
System.out.println("Attestation key: " + Hex.toHexString(((ECPrivateKey) keyPair.getPrivate()).getS().toByteArray()));
System.out.println("Attestation cert: " + Hex.toHexString(makeAttestationCertificate((ECPublicKey) keyPair.getPublic(), new byte[16]).getEncoded()));
}
}
Loading

0 comments on commit 8985ac7

Please sign in to comment.