All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.shredzone.acme4j.AccountBuilder Maven / Gradle / Ivy

There is a newer version: 3.4.0
Show newest version
/*
 * acme4j - Java ACME client
 *
 * Copyright (C) 2016 Richard "Shred" Körber
 *   http://acme4j.shredzone.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */
package org.shredzone.acme4j;

import static java.util.Objects.requireNonNull;
import static org.shredzone.acme4j.toolbox.AcmeUtils.macKeyAlgorithm;

import java.net.URI;
import java.net.URL;
import java.security.KeyPair;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.annotation.ParametersAreNonnullByDefault;
import javax.crypto.SecretKey;

import org.jose4j.jwk.PublicJsonWebKey;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.keys.HmacKey;
import org.jose4j.lang.JoseException;
import org.shredzone.acme4j.connector.Connection;
import org.shredzone.acme4j.connector.Resource;
import org.shredzone.acme4j.exception.AcmeException;
import org.shredzone.acme4j.exception.AcmeProtocolException;
import org.shredzone.acme4j.toolbox.AcmeUtils;
import org.shredzone.acme4j.toolbox.JSON;
import org.shredzone.acme4j.toolbox.JSONBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A builder for registering a new account.
 */
@ParametersAreNonnullByDefault
public class AccountBuilder {
    private static final Logger LOG = LoggerFactory.getLogger(AccountBuilder.class);

    private List contacts = new ArrayList<>();
    private Boolean termsOfServiceAgreed;
    private Boolean onlyExisting;
    private String keyIdentifier;
    private KeyPair keyPair;
    private SecretKey macKey;

    /**
     * Add a contact URI to the list of contacts.
     *
     * @param contact
     *            Contact URI
     * @return itself
     */
    public AccountBuilder addContact(URI contact) {
        AcmeUtils.validateContact(contact);
        contacts.add(contact);
        return this;
    }

    /**
     * Add a contact address to the list of contacts.
     * 

* This is a convenience call for {@link #addContact(URI)}. * * @param contact * Contact URI as string * @throws IllegalArgumentException * if there is a syntax error in the URI string * @return itself */ public AccountBuilder addContact(String contact) { addContact(URI.create(contact)); return this; } /** * Add a email address to the list of contacts. *

* This is a convenience call for {@link #addContact(String)} that doesn't * require from you attach "mailto" scheme before email address. * * @param email * Contact email without "mailto" scheme (e.g. [email protected]) * @throws IllegalArgumentException * if there is a syntax error in the URI string * @return itself */ public AccountBuilder addEmail(String email) { addContact("mailto:" + email); return this; } /** * Signals that the user agrees to the terms of service. * * @return itself */ public AccountBuilder agreeToTermsOfService() { this.termsOfServiceAgreed = true; return this; } /** * Signals that only an existing account should be returned. The server will not * create a new account if the key is not known. This is useful if you only have your * account's key pair available, but not your account's location URL. * * @return itself */ public AccountBuilder onlyExisting() { this.onlyExisting = true; return this; } /** * Sets the {@link KeyPair} to be used for this account. * * @param keyPair * Account's {@link KeyPair} * @return itself */ public AccountBuilder useKeyPair(KeyPair keyPair) { this.keyPair = requireNonNull(keyPair, "keyPair"); return this; } /** * Sets a Key Identifier and MAC key provided by the CA. Use this if your CA requires * an individual account identification, e.g. your customer number. * * @param kid * Key Identifier * @param macKey * MAC key * @return itself */ public AccountBuilder withKeyIdentifier(String kid, SecretKey macKey) { if (kid != null && kid.isEmpty()) { throw new IllegalArgumentException("kid must not be empty"); } this.macKey = requireNonNull(macKey, "macKey"); this.keyIdentifier = kid; return this; } /** * Sets a Key Identifier and MAC key provided by the CA. Use this if your CA requires * an individual account identification, e.g. your customer number. * * @param kid * Key Identifier * @param encodedMacKey * Base64url encoded MAC key. It will be decoded for your convenience. * @return itself */ public AccountBuilder withKeyIdentifier(String kid, String encodedMacKey) { byte[] encodedKey = AcmeUtils.base64UrlDecode(requireNonNull(encodedMacKey, "encodedMacKey")); return withKeyIdentifier(kid, new HmacKey(encodedKey)); } /** * Creates a new account. * * @param session * {@link Session} to be used for registration * @return {@link Account} referring to the new account */ public Account create(Session session) throws AcmeException { return createLogin(session).getAccount(); } /** * Creates a new account. *

* This method returns a ready to use {@link Login} for the new {@link Account}. * * @param session * {@link Session} to be used for registration * @return {@link Login} referring to the new account */ public Login createLogin(Session session) throws AcmeException { requireNonNull(session, "session"); if (keyPair == null) { throw new IllegalStateException("Use AccountBuilder.useKeyPair() to set the account's key pair."); } LOG.debug("create"); try (Connection conn = session.connect()) { URL resourceUrl = session.resourceUrl(Resource.NEW_ACCOUNT); JSONBuilder claims = new JSONBuilder(); if (!contacts.isEmpty()) { claims.put("contact", contacts); } if (termsOfServiceAgreed != null) { claims.put("termsOfServiceAgreed", termsOfServiceAgreed); } if (keyIdentifier != null) { claims.put("externalAccountBinding", createExternalAccountBinding( keyIdentifier, keyPair.getPublic(), macKey, resourceUrl)); } if (onlyExisting != null) { claims.put("onlyReturnExisting", onlyExisting); } conn.sendSignedRequest(resourceUrl, claims, session, keyPair); URL location = conn.getLocation(); if (location == null) { throw new AcmeProtocolException("Server did not provide an account location"); } Login login = new Login(location, keyPair, session); JSON json = conn.readJsonResponse(); if (json != null) { login.getAccount().setJSON(json); } return login; } } /** * Creates a JSON structure for external account binding. * * @param kid * Key Identifier provided by the CA * @param accountKey * {@link PublicKey} of the account to register * @param macKey * {@link SecretKey} to sign the key identifier with * @param resource * "newAccount" resource URL * @return Created JSON structure */ private Map createExternalAccountBinding(String kid, PublicKey accountKey, SecretKey macKey, URL resource) throws AcmeException { try { PublicJsonWebKey keyJwk = PublicJsonWebKey.Factory.newPublicJwk(accountKey); JsonWebSignature innerJws = new JsonWebSignature(); innerJws.setPayload(keyJwk.toJson()); innerJws.getHeaders().setObjectHeaderValue("url", resource); innerJws.getHeaders().setObjectHeaderValue("kid", kid); innerJws.setAlgorithmHeaderValue(macKeyAlgorithm(macKey)); innerJws.setKey(macKey); innerJws.setDoKeyValidation(false); innerJws.sign(); JSONBuilder outerClaim = new JSONBuilder(); outerClaim.put("protected", innerJws.getHeaders().getEncodedHeader()); outerClaim.put("signature", innerJws.getEncodedSignature()); outerClaim.put("payload", innerJws.getEncodedPayload()); return outerClaim.toMap(); } catch (JoseException ex) { throw new AcmeException("Could not create external account binding", ex); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy